Compare commits

...

109 Commits

Author SHA1 Message Date
Jens Langhammer
026b7d3e73 update template
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-03-09 11:33:48 +01:00
Jens Langhammer
91ac87c934 endpoints/connectors/agent: add browser-backchannel support
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-03-09 11:32:19 +01:00
Jens L.
d880c46d7c enterprise/endpoints/connectors: add google_chrome (#19129)
* init

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

* add icon

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

* actually load

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

* fix serializer

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

* init ui

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

* fix duplicated element name

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

* include chrome url

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

* make it work, some small UI fixes

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

* add tests

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

* format

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

* invisible submit for frame

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

* fix device not set in flow plan, fix other small things, more tests

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

* simplify

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

* add docs

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

* Minor doc changes

* dedupe templates

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

* update docs

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-03-09 11:17:56 +01:00
Oluwatobi Mustapha
d917fef0f9 providers/oauth2: decode percent-encoded basic auth (#20779)
Fixes #20739

Decode percent-encoded client credentials from HTTP Basic authentication before provider lookup while preserving existing behavior for raw plus characters. Add unit and endpoint coverage for encoded client IDs and client secrets.
2026-03-07 18:02:29 +01:00
dependabot[bot]
d8a20afe45 web: bump immutable from 5.1.4 to 5.1.5 in /web (#20720)
Bumps [immutable](https://github.com/immutable-js/immutable-js) from 5.1.4 to 5.1.5.
- [Release notes](https://github.com/immutable-js/immutable-js/releases)
- [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/immutable-js/immutable-js/compare/v5.1.4...v5.1.5)

---
updated-dependencies:
- dependency-name: immutable
  dependency-version: 5.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-07 15:49:26 +01:00
dependabot[bot]
b649dccb86 web: bump the storybook group across 1 directory with 5 updates (#20731)
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.2.13 to 10.2.15
- [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.2.15/code/addons/docs)

Updates `@storybook/addon-links` from 10.2.13 to 10.2.15
- [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.2.15/code/addons/links)

Updates `@storybook/web-components` from 10.2.13 to 10.2.15
- [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.2.15/code/renderers/web-components)

Updates `@storybook/web-components-vite` from 10.2.13 to 10.2.15
- [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.2.15/code/frameworks/web-components-vite)

Updates `storybook` from 10.2.13 to 10.2.15
- [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.2.15/code/core)

---
updated-dependencies:
- dependency-name: "@storybook/addon-docs"
  dependency-version: 10.2.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/addon-links"
  dependency-version: 10.2.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components"
  dependency-version: 10.2.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components-vite"
  dependency-version: 10.2.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: storybook
  dependency-version: 10.2.15
  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>
2026-03-07 15:49:08 +01:00
dependabot[bot]
af1d360a95 web: bump @rollup/plugin-commonjs from 29.0.0 to 29.0.1 in /web in the rollup group across 1 directory (#20732)
web: bump @rollup/plugin-commonjs

Bumps the rollup group with 1 update in the /web directory: [@rollup/plugin-commonjs](https://github.com/rollup/plugins/tree/HEAD/packages/commonjs).


Updates `@rollup/plugin-commonjs` from 29.0.0 to 29.0.1
- [Changelog](https://github.com/rollup/plugins/blob/master/packages/commonjs/CHANGELOG.md)
- [Commits](https://github.com/rollup/plugins/commits/commonjs-v29.0.1/packages/commonjs)

---
updated-dependencies:
- dependency-name: "@rollup/plugin-commonjs"
  dependency-version: 29.0.1
  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>
2026-03-07 15:48:49 +01:00
dependabot[bot]
5b48f779c3 web: bump dompurify from 3.3.1 to 3.3.2 in /web (#20748)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.3.1 to 3.3.2.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.3.1...3.3.2)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-version: 3.3.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-07 15:46:24 +01:00
Ollie Beenham
75ac350d48 providers/scim: fix out-of-scope users and groups not being deleted from destination application (#20742)
* providers/scim: fix out-of-scope users and groups not being deleted from destination application

* provider/scim: add retry mechanism for transient exceptions during cleanup

* fix: fixed google provider http requests following addition of sync_cleanup method

* test: updated unit tests to validate sync behaviour for deletion of out-of-scope users and groups
2026-03-07 15:38:32 +01:00
Dominic R
28717b2bc8 website: override DocSearch button colors in light mode (#20770) 2026-03-06 21:40:01 +00:00
Tana M Berry
22d1c23fbe website/docs: remove potatoes card sigh (#20767) 2026-03-06 16:15:24 +00:00
dependabot[bot]
f15bbd4322 ci: bump docker/build-push-action from 6.19.2 to 7.0.0 (#20757)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-06 13:39:36 +01:00
dependabot[bot]
4bf5fce3a0 core: bump google-api-python-client from 2.191.0 to 2.192.0 (#20752)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-06 13:39:15 +01:00
dependabot[bot]
58957bbeb1 core: bump cachetools from 7.0.2 to 7.0.3 (#20750)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-06 13:38:55 +01:00
dependabot[bot]
b05519d5d1 core: bump ruff from 0.15.4 to 0.15.5 (#20751)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-06 13:38:50 +01:00
dependabot[bot]
c1ffd8f379 core: bump goauthentik/fips-debian from 3781391 to 4966b90 in /lifecycle/container (#20754)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-06 13:38:43 +01:00
dependabot[bot]
5ad4b4b7b2 core: bump library/golang from 1.26.0-trixie to 1.26.1-trixie in /lifecycle/container (#20756)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-06 13:38:34 +01:00
dependabot[bot]
9234c5f839 core: bump goauthentik/fips-python from 6a980e7 to 38c4dd2 in /lifecycle/container (#20755)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-06 13:38:30 +01:00
dependabot[bot]
6c6fee0a9a ci: bump docker/setup-buildx-action from 3.12.0 to 4.0.0 (#20758)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-06 13:38:20 +01:00
dependabot[bot]
1ae0f951c2 core: bump rust-toolchain from 1.93.1 to 1.94.0 (#20760)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-06 13:37:55 +01:00
Severin Schoepke
ea3c56ad80 lifecycle: make gunicorn --max-requests configurable (#20736) 2026-03-05 16:34:18 +01:00
dependabot[bot]
24dd8ee395 lifecycle/aws: bump aws-cdk from 2.1108.0 to 2.1109.0 in /lifecycle/aws (#20725)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 16:01:50 +01:00
Marc 'risson' Schmitt
5d165c4e9f packages/django-channels-postgres: provide sync API for group_send (#20740) 2026-03-05 15:59:43 +01:00
authentik-automation[bot]
efd5b6b874 core, web: update translations (#20722)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-03-05 13:44:35 +01:00
dependabot[bot]
58e4fb004b core: bump goauthentik/selenium from 145.0-ak-0.40.2 to 145.0-ak-0.40.3 in /tests/e2e (#20726)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 13:44:14 +01:00
dependabot[bot]
3bc17b92d4 core: bump goauthentik/fips-python from 43260c0 to 6a980e7 in /lifecycle/container (#20728)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 13:43:56 +01:00
dependabot[bot]
54e0a8229d ci: bump docker/setup-qemu-action from 3.7.0 to 4.0.0 (#20727)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 13:41:52 +01:00
dependabot[bot]
a7966a518f ci: bump docker/login-action from 3.7.0 to 4.0.0 (#20729)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 13:41:38 +01:00
dependabot[bot]
87099194c0 core: bump goauthentik/fips-debian from 487b9f1 to 3781391 in /lifecycle/container (#20730)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 13:41:33 +01:00
dependabot[bot]
6b207ca73a core: bump django from 5.2.11 to 5.2.12 (#20719)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 13:40:37 +01:00
Ken Sternberg
ce1237e03f web/flow: bug: inspector button not hiding when unavailable (#20717)
* Bad check on my part; URLSearchParams returns 'null', which is not undefined (according to Javascript).

* A better check.
2026-03-04 09:06:16 -08:00
Marc 'risson' Schmitt
e8c845d682 ci: pull latest changes before tagging new version (#20413) 2026-03-04 13:53:38 +00:00
authentik-automation[bot]
0334bcdf5a core: bump goauthentik.io/api/v3 to 3.2026.5.0-rc1-1772620985 (#20713)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-03-04 14:21:23 +01:00
Jens L.
6245809eae web/flows: continuous login (#19862)
* wip

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

# Conflicts:
#	authentik/core/signals.py
#	authentik/stages/identification/stage.py
#	web/src/flow/stages/RedirectStage.ts

# Conflicts:
#	web/src/flow/FlowExecutor.ts

* fix race conditions

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

* prevent stale locks

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

* add to feature flag

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

* add separate flag

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

* make it build

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

* revisit

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

* better origin check

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>
2026-03-04 10:37:53 +00:00
Simonyi Gergő
59192d94a0 ci: fix reason change in versions repo bump (#20696)
fix `reason` change in versions repo bump
2026-03-04 10:40:24 +01:00
dependabot[bot]
83c5367c35 web: bump the goauthentik group across 1 directory with 3 updates (#20620)
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/prettier-config](https://github.com/goauthentik/authentik/tree/HEAD/packages/prettier-config) and [@goauthentik/tsconfig](https://github.com/goauthentik/authentik/tree/HEAD/packages/tsconfig).


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

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

Updates `@goauthentik/tsconfig` from 1.0.5 to 1.0.7
- [Release notes](https://github.com/goauthentik/authentik/releases)
- [Commits](https://github.com/goauthentik/authentik/commits/HEAD/packages/tsconfig)

---
updated-dependencies:
- dependency-name: "@goauthentik/esbuild-plugin-live-reload"
  dependency-version: 1.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: goauthentik
- dependency-name: "@goauthentik/prettier-config"
  dependency-version: 3.4.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: goauthentik
- dependency-name: "@goauthentik/tsconfig"
  dependency-version: 1.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: goauthentik
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-04 09:49:20 +01:00
dependabot[bot]
612d8e3df8 core: bump goauthentik/fips-debian from 44cd26c to 487b9f1 in /lifecycle/container (#20702)
core: bump goauthentik/fips-debian in /lifecycle/container

Bumps goauthentik/fips-debian from `44cd26c` to `487b9f1`.

---
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>
2026-03-04 09:48:51 +01:00
dependabot[bot]
7f948ff966 web: bump @codemirror/lang-javascript from 6.2.4 to 6.2.5 in /web (#20681)
Bumps [@codemirror/lang-javascript](https://github.com/codemirror/lang-javascript) from 6.2.4 to 6.2.5.
- [Changelog](https://github.com/codemirror/lang-javascript/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/lang-javascript/compare/6.2.4...6.2.5)

---
updated-dependencies:
- dependency-name: "@codemirror/lang-javascript"
  dependency-version: 6.2.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>
2026-03-04 09:48:17 +01:00
dependabot[bot]
f59d100a89 web: bump @floating-ui/dom from 1.7.5 to 1.7.6 in /web (#20682)
Bumps [@floating-ui/dom](https://github.com/floating-ui/floating-ui/tree/HEAD/packages/dom) from 1.7.5 to 1.7.6.
- [Release notes](https://github.com/floating-ui/floating-ui/releases)
- [Changelog](https://github.com/floating-ui/floating-ui/blob/master/packages/dom/CHANGELOG.md)
- [Commits](https://github.com/floating-ui/floating-ui/commits/@floating-ui/dom@1.7.6/packages/dom)

---
updated-dependencies:
- dependency-name: "@floating-ui/dom"
  dependency-version: 1.7.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>
2026-03-04 09:48:07 +01:00
dependabot[bot]
65add15a7f web: bump globals from 17.3.0 to 17.4.0 in /web (#20683)
Bumps [globals](https://github.com/sindresorhus/globals) from 17.3.0 to 17.4.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v17.3.0...v17.4.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-version: 17.4.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-03-04 09:47:49 +01:00
dependabot[bot]
a47dac9da5 web: bump @sentry/browser from 10.40.0 to 10.42.0 in /web in the sentry group across 1 directory (#20701)
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.40.0 to 10.42.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.40.0...10.42.0)

---
updated-dependencies:
- dependency-name: "@sentry/browser"
  dependency-version: 10.42.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>
2026-03-04 09:47:25 +01:00
dependabot[bot]
baf077beec core: bump goauthentik/fips-python from 7f4c8cb to 43260c0 in /lifecycle/container (#20703)
core: bump goauthentik/fips-python in /lifecycle/container

Bumps goauthentik/fips-python from `7f4c8cb` to `43260c0`.

---
updated-dependencies:
- dependency-name: goauthentik/fips-python
  dependency-version: 3.14.3-slim-trixie-fips
  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-03-04 09:47:16 +01:00
dependabot[bot]
aa01a00165 core: bump astral-sh/uv from 0.10.7 to 0.10.8 in /lifecycle/container (#20704)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.10.7 to 0.10.8.
- [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.10.7...0.10.8)

---
updated-dependencies:
- dependency-name: astral-sh/uv
  dependency-version: 0.10.8
  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-03-04 09:47:02 +01:00
dependabot[bot]
b37d94a6eb core: bump library/golang from 100774d to 4e603da in /lifecycle/container (#20705)
core: bump library/golang in /lifecycle/container

Bumps library/golang from `100774d` to `4e603da`.

---
updated-dependencies:
- dependency-name: library/golang
  dependency-version: 1.26.0-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>
2026-03-04 09:46:50 +01:00
dependabot[bot]
1d78db87bf core: bump library/node from 25.7.0-trixie to 25.8.0-trixie in /website (#20706)
Bumps library/node from 25.7.0-trixie to 25.8.0-trixie.

---
updated-dependencies:
- dependency-name: library/node
  dependency-version: 25.8.0-trixie
  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-03-04 09:46:31 +01:00
dependabot[bot]
6c6c5d5702 ci: bump actions/setup-node from 6.2.0 to 6.3.0 (#20707)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.2.0 to 6.3.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](6044e13b5d...53b83947a5)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.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>
2026-03-04 09:46:16 +01:00
dependabot[bot]
7d4be2624d ci: bump actions/setup-node from 6.2.0 to 6.3.0 in /.github/actions/setup (#20708)
ci: bump actions/setup-node in /.github/actions/setup

Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.2.0 to 6.3.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](6044e13b5d...53b83947a5)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.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>
2026-03-04 09:46:06 +01:00
Ken Sternberg
0581c6ab09 web/admin: bad width on policy test results (#20668)
web/admin/bugfix: bad width on policy test results

## What

1.  Set a 100% width on the container for polcy test log messages.

## Why

A classic bug, made more complex by modern sensibilities. The group to be rendered is in a slot, but its parent doesn’t have a set width by default, and so it’s “projected” into a zero-width container. As a result, the `1fr` (“100/100 width”) doesn’t matter here; we need to go old-skool and force its parent to take up the full width of *its* container with a hard `width` setting, which the gives us some room to be 100/100 in.
2026-03-03 15:17:46 -08:00
Simonyi Gergő
f3b85d88f1 website/docs: add release notes for 2026.2.1 (#20659)
* add release notes for `2026.2.1`

* Update release notes for version 2026.2

Signed-off-by: Connor Peshek <connor@connorpeshek.me>

---------

Signed-off-by: Connor Peshek <connor@connorpeshek.me>
Co-authored-by: Connor Peshek <connor@connorpeshek.me>
2026-03-03 13:08:20 -06:00
Simonyi Gergő
9da72eaa96 web: fix identification stage styling in compatibility mode (#20684)
fix identification stage styling in compatibility mode
2026-03-03 18:59:33 +01:00
Jens L.
ec7efa53cb providers/proxy: move search path to query instead of runtime parameter (#20662)
Co-authored-by: Dominic R <dominic@sdko.org>
2026-03-03 17:49:28 +00:00
Dewi Roberts
8fccf27b38 website/docs: add 2025 pentest (#20626)
* Start

* Add links

* Links

* sidebar

* Update website/docs/security/audits-and-certs/2025-09-includesec.md

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

* Update website/docs/security/audits-and-certs/2025-09-includesec.md

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

* Update website/docs/security/audits-and-certs/2025-09-includesec.md

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

* Update 2025-09-includesec.md

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

* Apply suggestions from code review

Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* Update website/docs/security/audits-and-certs/2025-09-includesec.md

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

* Add link

---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2026-03-03 12:30:15 -05:00
Xabier Napal
35e025b25a outpost/proxyv2: prevent panic in handleSignOut (#20097)
outpost/proxyv2: use safe claims extraction in handleSignOut to prevent panic

Signed-off-by: Xabier Napal <xabier.napal@dvzr.io>
2026-03-03 18:21:25 +01:00
Marc 'risson' Schmitt
3927130233 packages/django-channels-postgres: eagerly delete messages (#20687)
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-03 16:20:37 +01:00
dependabot[bot]
01dd629f02 core: bump sentry-sdk from 2.53.0 to 2.54.0 (#20673)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.53.0 to 2.54.0.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.53.0...2.54.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-version: 2.54.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-03-03 13:18:11 +01:00
dependabot[bot]
559bbd4580 core: bump cachetools from 7.0.1 to 7.0.2 (#20674)
Bumps [cachetools](https://github.com/tkem/cachetools) from 7.0.1 to 7.0.2.
- [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/tkem/cachetools/compare/v7.0.1...v7.0.2)

---
updated-dependencies:
- dependency-name: cachetools
  dependency-version: 7.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>
2026-03-03 13:17:35 +01:00
dependabot[bot]
d2fbf901de core: bump google-api-python-client from 2.190.0 to 2.191.0 (#20671)
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 2.190.0 to 2.191.0.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v2.190.0...v2.191.0)

---
updated-dependencies:
- dependency-name: google-api-python-client
  dependency-version: 2.191.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-03-03 13:17:11 +01:00
dependabot[bot]
b50ee1deff core: bump pytest-github-actions-annotate-failures from 0.3.0 to 0.4.0 (#20675)
Bumps [pytest-github-actions-annotate-failures](https://github.com/pytest-dev/pytest-github-actions-annotate-failures) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/pytest-dev/pytest-github-actions-annotate-failures/releases)
- [Commits](https://github.com/pytest-dev/pytest-github-actions-annotate-failures/compare/v0.3.0...v0.4.0)

---
updated-dependencies:
- dependency-name: pytest-github-actions-annotate-failures
  dependency-version: 0.4.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>
2026-03-03 13:16:49 +01:00
dependabot[bot]
dcf1272561 core: bump aws-cdk-lib from 2.240.0 to 2.241.0 (#20676)
Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk) from 2.240.0 to 2.241.0.
- [Release notes](https://github.com/aws/aws-cdk/releases)
- [Changelog](https://github.com/aws/aws-cdk/blob/main/CHANGELOG.v2.alpha.md)
- [Commits](https://github.com/aws/aws-cdk/compare/v2.240.0...v2.241.0)

---
updated-dependencies:
- dependency-name: aws-cdk-lib
  dependency-version: 2.241.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>
2026-03-03 13:16:16 +01:00
dependabot[bot]
1a88e3c931 ci: bump tj-actions/changed-files from 47.0.4 to 47.0.5 (#20677)
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 47.0.4 to 47.0.5.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](7dee1b0c15...22103cc46b)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-version: 47.0.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>
2026-03-03 13:15:47 +01:00
dependabot[bot]
f11bbb72da core: bump goauthentik/fips-debian from 1b2c47d to 44cd26c in /lifecycle/container (#20678)
core: bump goauthentik/fips-debian in /lifecycle/container

Bumps goauthentik/fips-debian from `1b2c47d` to `44cd26c`.

---
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>
2026-03-03 13:15:28 +01:00
dependabot[bot]
402bfe6a80 core: bump goauthentik/fips-python from 98b4f6a to 7f4c8cb in /lifecycle/container (#20679)
core: bump goauthentik/fips-python in /lifecycle/container

Bumps goauthentik/fips-python from `98b4f6a` to `7f4c8cb`.

---
updated-dependencies:
- dependency-name: goauthentik/fips-python
  dependency-version: 3.14.3-slim-trixie-fips
  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-03-03 13:15:07 +01:00
Dominic R
7f1f3de386 core: fix get_provider returning base Provider instead of subclass (#19064)
Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
2026-03-02 21:27:39 -05:00
Dewi Roberts
ef4d04c29c website/docs: kerberos: add note about caching (#20663)
* Add note about caching

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

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: Dominic R <dominic@sdko.org>
2026-03-02 16:58:05 +00:00
Dominic R
e5a261a0e5 admin/files: allow configuring S3 signature version (#20639) 2026-03-02 15:03:02 +01:00
Dewi Roberts
cd53bc1d1d website/docs: entra id provider: add custom email domain info (#20444)
* WIP

* WIP

* 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: Dominic R <dominic@sdko.org>
2026-03-02 13:29:32 +00:00
dependabot[bot]
f6076d1230 web: bump the storybook group across 1 directory with 5 updates (#20618)
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.2.12 to 10.2.13
- [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.2.13/code/addons/docs)

Updates `@storybook/addon-links` from 10.2.12 to 10.2.13
- [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.2.13/code/addons/links)

Updates `@storybook/web-components` from 10.2.12 to 10.2.13
- [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.2.13/code/renderers/web-components)

Updates `@storybook/web-components-vite` from 10.2.12 to 10.2.13
- [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.2.13/code/frameworks/web-components-vite)

Updates `storybook` from 10.2.12 to 10.2.13
- [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.2.13/code/core)

---
updated-dependencies:
- dependency-name: "@storybook/addon-docs"
  dependency-version: 10.2.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/addon-links"
  dependency-version: 10.2.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components"
  dependency-version: 10.2.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components-vite"
  dependency-version: 10.2.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: storybook
  dependency-version: 10.2.13
  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>
2026-03-02 12:51:46 +01:00
dependabot[bot]
6b05b5d79c web: bump country-flag-icons from 1.6.14 to 1.6.15 in /web (#20623)
Bumps [country-flag-icons](https://gitlab.com/catamphetamine/country-flag-icons) from 1.6.14 to 1.6.15.
- [Changelog](https://gitlab.com/catamphetamine/country-flag-icons/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/catamphetamine/country-flag-icons/compare/v1.6.14...v1.6.15)

---
updated-dependencies:
- dependency-name: country-flag-icons
  dependency-version: 1.6.15
  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-03-02 12:51:11 +01:00
authentik-automation[bot]
d11f33c564 stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#20642)
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-03-02 12:51:00 +01:00
dependabot[bot]
80ca1ab954 core: bump goauthentik/selenium from 145.0-ak-0.40.1 to 145.0-ak-0.40.2 in /tests/e2e (#20650)
core: bump goauthentik/selenium in /tests/e2e

Bumps [goauthentik/selenium](https://github.com/SeleniumHQ/docker-selenium) from 145.0-ak-0.40.1 to 145.0-ak-0.40.2.
- [Release notes](https://github.com/SeleniumHQ/docker-selenium/releases)
- [Commits](https://github.com/SeleniumHQ/docker-selenium/commits)

---
updated-dependencies:
- dependency-name: goauthentik/selenium
  dependency-version: 145.0-ak-0.40.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 12:50:33 +01:00
dependabot[bot]
66f16c8bea core: bump goauthentik/fips-debian from ee57bf8 to 1b2c47d in /lifecycle/container (#20651)
core: bump goauthentik/fips-debian in /lifecycle/container

Bumps goauthentik/fips-debian from `ee57bf8` to `1b2c47d`.

---
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>
2026-03-02 12:50:25 +01:00
dependabot[bot]
a748bccec9 core: bump astral-sh/uv from 0.10.6 to 0.10.7 in /lifecycle/container (#20653)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.10.6 to 0.10.7.
- [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.10.6...0.10.7)

---
updated-dependencies:
- dependency-name: astral-sh/uv
  dependency-version: 0.10.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>
2026-03-02 12:50:18 +01:00
dependabot[bot]
8319f0c45a web: bump the swc group across 1 directory with 11 updates (#20655)
Bumps the swc group with 1 update in the /web directory: [@swc/core](https://github.com/swc-project/swc/tree/HEAD/packages/core).


Updates `@swc/core` from 1.15.13 to 1.15.18
- [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/commits/v1.15.18/packages/core)

Updates `@swc/core-darwin-arm64` from 1.15.13 to 1.15.18
- [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.13...v1.15.18)

Updates `@swc/core-darwin-x64` from 1.15.13 to 1.15.18
- [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.13...v1.15.18)

Updates `@swc/core-linux-arm-gnueabihf` from 1.15.13 to 1.15.18
- [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.13...v1.15.18)

Updates `@swc/core-linux-arm64-gnu` from 1.15.13 to 1.15.18
- [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.13...v1.15.18)

Updates `@swc/core-linux-arm64-musl` from 1.15.13 to 1.15.18
- [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.13...v1.15.18)

Updates `@swc/core-linux-x64-gnu` from 1.15.13 to 1.15.18
- [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.13...v1.15.18)

Updates `@swc/core-linux-x64-musl` from 1.15.13 to 1.15.18
- [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.13...v1.15.18)

Updates `@swc/core-win32-arm64-msvc` from 1.15.13 to 1.15.18
- [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.13...v1.15.18)

Updates `@swc/core-win32-ia32-msvc` from 1.15.13 to 1.15.18
- [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.13...v1.15.18)

Updates `@swc/core-win32-x64-msvc` from 1.15.13 to 1.15.18
- [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.13...v1.15.18)

---
updated-dependencies:
- dependency-name: "@swc/core"
  dependency-version: 1.15.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-arm64"
  dependency-version: 1.15.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-x64"
  dependency-version: 1.15.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm-gnueabihf"
  dependency-version: 1.15.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-gnu"
  dependency-version: 1.15.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-musl"
  dependency-version: 1.15.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-gnu"
  dependency-version: 1.15.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-musl"
  dependency-version: 1.15.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-arm64-msvc"
  dependency-version: 1.15.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-ia32-msvc"
  dependency-version: 1.15.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-x64-msvc"
  dependency-version: 1.15.18
  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>
2026-03-02 12:50:09 +01:00
dependabot[bot]
91b25a8896 web: bump @types/node from 25.3.1 to 25.3.3 in /web (#20656)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.3.1 to 25.3.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.3.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>
2026-03-02 12:49:47 +01:00
dependabot[bot]
46d8c3864e ci: bump astral-sh/setup-uv from 7.3.0 to 7.3.1 in /.github/actions/setup (#20652)
ci: bump astral-sh/setup-uv in /.github/actions/setup

Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 7.3.0 to 7.3.1.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](eac588ad8d...5a095e7a20)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 7.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>
2026-03-02 12:49:19 +01:00
dependabot[bot]
f9006bbddd core: bump goauthentik/fips-python from b83a0cb to 98b4f6a in /lifecycle/container (#20654)
core: bump goauthentik/fips-python in /lifecycle/container

Bumps goauthentik/fips-python from `b83a0cb` to `98b4f6a`.

---
updated-dependencies:
- dependency-name: goauthentik/fips-python
  dependency-version: 3.14.3-slim-trixie-fips
  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-03-02 12:49:09 +01:00
Connor Peshek
7dd36eae9a enterprise/wsfed: Fix metadata export and signing logic (#20643) 2026-03-01 20:15:18 -06:00
Dominic R
3d28439a9e website/docs: fix upgrade link in 2026.5 release notes (#20638) 2026-03-01 02:21:38 +01:00
Simonyi Gergő
9ebf463397 packages/django-dramatiq-postgres: fix worker startup on macos (#20637)
fix worker startup on macos
2026-03-01 02:12:25 +01:00
djagoo
1c05cdaa78 website/integrations: add forgejo (#20635)
* added forgejo to integrations

* Remove screenshot, change some formatting and language

---------

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-02-28 08:51:11 -05:00
Jens L.
9a805759c7 root: fix test runner dropping exit code (#20630)
* root: fix test runner dropping exit code

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

* bump port

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

* fix healthcheck port

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

* check for 1 worker

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

* disable the flaky test

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-02-28 13:48:17 +00:00
Jens L.
7d74bfe201 sources/ldap: add connection logging & downgrade message (#20519)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-02-28 13:46:02 +01:00
dependabot[bot]
f875d5e5d6 ci: bump actions/setup-go from 6.2.0 to 6.3.0 (#20594)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 18:25:52 +00:00
dependabot[bot]
9ac7715682 ci: bump actions/download-artifact from 7.0.0 to 8.0.0 (#20615)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 18:05:59 +00:00
Jens L.
90ff3062ef tasks: fix the occasional DatabaseError for no updated fields (#20629)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-02-27 19:02:42 +01:00
dependabot[bot]
0678c0f4c5 core: bump library/golang from d0a3e4b to 100774d in /lifecycle/container (#20587)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 18:50:00 +01:00
dependabot[bot]
af0fc47939 core: bump goauthentik/selenium from 145.0-ak-0.40.0 to 145.0-ak-0.40.1 in /tests/e2e (#20585)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 18:49:53 +01:00
dependabot[bot]
f34ef54bc3 core: bump goauthentik/fips-python from de8ad64 to b83a0cb in /lifecycle/container (#20589)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 18:49:43 +01:00
dependabot[bot]
d8b9cee276 core: bump goauthentik/fips-debian from 7b82e24 to ee57bf8 in /lifecycle/container (#20590)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 18:49:38 +01:00
dependabot[bot]
ff6b05419f core: bump bandit from 1.9.3 to 1.9.4 (#20586)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 18:49:32 +01:00
dependabot[bot]
071e1f0e4a ci: bump svenstaro/upload-release-action from 2.11.3 to 2.11.4 (#20593)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 18:49:25 +01:00
dependabot[bot]
663a28ac84 core: bump library/node from 25.6.1-trixie to 25.7.0-trixie in /website (#20591)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 18:47:30 +01:00
dependabot[bot]
af5d235afd core: bump library/nginx from 0d1b1f0 to 0236ee0 in /website (#20592)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 18:47:28 +01:00
authentik-automation[bot]
c4aeed3c20 core, web: update translations (#20581)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-02-27 18:47:04 +01:00
dependabot[bot]
d568eb32e1 ci: bump actions/upload-artifact from 6.0.0 to 7.0.0 (#20617)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 18:44:52 +01:00
dependabot[bot]
f69d5a82db ci: bump actions/attest-build-provenance from 3.2.0 to 4.1.0 (#20616)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 18:44:42 +01:00
dependabot[bot]
bba235aa41 core: bump ruff from 0.15.2 to 0.15.4 (#20614)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 18:43:55 +01:00
dependabot[bot]
49ac92348c core: bump duo-client from 5.5.0 to 5.6.1 (#20613)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 18:43:48 +01:00
dependabot[bot]
c5e7e7a333 lifecycle/aws: bump aws-cdk from 2.1107.0 to 2.1108.0 in /lifecycle/aws (#20612)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 18:43:41 +01:00
Simonyi Gergő
473e71e973 root: fix gen-changelog and gen-diff (#20598)
fix `gen-changelog` and `gen-diff`
2026-02-27 17:57:57 +01:00
Jens L.
5183c6caeb tasks: threads instead of forks (#19476)
* tasks: threads instead of forks

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

* fix worker status

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

* only update when needed

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

* dont use status middleware in tests?

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

* types

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

* t

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

* tasks: improved tests

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

# Conflicts:
#	authentik/tasks/test.py

* cleanup

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

* format

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

* replace sleep with threading event

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

* better typing

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

* fix forks override

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

* fix run signature

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-02-27 16:06:31 +01:00
Jens L.
ef51fbba8a crypto: fix kid legacy signal (#20627)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-02-27 15:38:13 +01:00
Jens L.
6c9131eb68 tasks: improved tests (#18978)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-02-27 14:48:04 +01:00
Isaac Freeman
7a8357fedf website/integrations: replace Wiki.js hard-coded application slug with placeholder (#20624)
* Update index.md to replace hard-coded application slug

I named the application `Wiki.js`, and Authentik auto-generated a slug `wiki-js` that doesn't match the one assumed here. Took me a few hours to figure out why it wasn't working.

Signed-off-by: Isaac Freeman <isaac@freeman.org.nz>

* Apply suggestion from @dewi-tik

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

---------

Signed-off-by: Isaac Freeman <isaac@freeman.org.nz>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-02-27 09:41:38 +00:00
Jens L.
2134429479 packages/django-dramatiq-postgres: use fork (#20606)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-02-26 19:57:22 +01:00
dependabot[bot]
e59c380ac5 web: bump @types/node from 25.3.0 to 25.3.1 in /web (#20596)
* web: bump engine configs, paths.

* Fix mounted references.

* web: bump @types/node from 25.3.0 to 25.3.1 in /web

Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.3.0 to 25.3.1.
- [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.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

* Fix package resolution.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-26 19:11:12 +01:00
Jens L.
7c9bc2a23d web/flows: fix source icons being always inverted (#20419)
* web/flows: fix inverted source icons

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

* fix actually

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-02-26 18:40:50 +01:00
dependabot[bot]
f1c02de959 ci: bump actions/setup-go from 6.2.0 to 6.3.0 in /.github/actions/setup (#20595)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6.2.0 to 6.3.0.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](7a3fe6cf4c...4b73464bb3)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: 6.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>
2026-02-26 13:13:21 +01:00
Dominic R
c54011bd8a website/integrations: ak_groups -> groups (#20579) 2026-02-26 08:59:10 +00:00
176 changed files with 4493 additions and 1854 deletions

View File

@@ -22,7 +22,7 @@ runs:
sudo rm -rf /usr/local/lib/android
- name: Install uv
if: ${{ contains(inputs.dependencies, 'python') }}
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v5
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v5
with:
enable-cache: true
- name: Setup python
@@ -36,7 +36,7 @@ runs:
run: uv sync --all-extras --dev --frozen
- name: Setup node
if: ${{ contains(inputs.dependencies, 'node') }}
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v4
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4
with:
node-version-file: web/package.json
cache: "npm"
@@ -44,7 +44,7 @@ runs:
registry-url: 'https://registry.npmjs.org'
- name: Setup go
if: ${{ contains(inputs.dependencies, 'go') }}
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v5
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v5
with:
go-version-file: "go.mod"
- name: Setup docker cache

View File

@@ -43,8 +43,8 @@ jobs:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
@@ -56,23 +56,23 @@ jobs:
release: ${{ inputs.release }}
- name: Login to Docker Hub
if: ${{ inputs.registry_dockerhub }}
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
username: ${{ secrets.DOCKER_CORP_USERNAME }}
password: ${{ secrets.DOCKER_CORP_PASSWORD }}
- name: Login to GitHub Container Registry
if: ${{ inputs.registry_ghcr }}
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
with:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: "go.mod"
- name: Generate API Clients
@@ -80,7 +80,7 @@ jobs:
make gen-client-ts
make gen-client-go
- name: Build Docker Image
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
id: push
with:
context: .
@@ -95,7 +95,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@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v3
id: attest
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
with:

View File

@@ -79,13 +79,13 @@ jobs:
image-name: ${{ inputs.image_name }}
- name: Login to Docker Hub
if: ${{ inputs.registry_dockerhub }}
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
username: ${{ secrets.DOCKER_CORP_USERNAME }}
password: ${{ secrets.DOCKER_CORP_PASSWORD }}
- name: Login to GitHub Container Registry
if: ${{ inputs.registry_ghcr }}
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -97,7 +97,7 @@ jobs:
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@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v3
id: attest
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}

View File

@@ -25,7 +25,7 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
token: ${{ steps.generate_token.outputs.token }}
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
with:
node-version-file: web/package.json
registry-url: "https://registry.npmjs.org"

View File

@@ -33,7 +33,7 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
with:
node-version-file: website/package.json
cache: "npm"
@@ -55,7 +55,7 @@ jobs:
env:
NODE_ENV: production
run: npm run build -w api
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v4
with:
name: api-docs
path: website/api/build
@@ -67,11 +67,11 @@ jobs:
- build
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v5
- uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v5
with:
name: api-docs
path: website/api/build
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
with:
node-version-file: website/package.json
cache: "npm"

View File

@@ -24,7 +24,7 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- name: Setup authentik env
uses: ./.github/actions/setup
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
with:
node-version-file: lifecycle/aws/package.json
cache: "npm"

View File

@@ -36,7 +36,7 @@ jobs:
NODE_ENV: production
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
with:
node-version-file: website/package.json
cache: "npm"
@@ -53,7 +53,7 @@ jobs:
NODE_ENV: production
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
with:
node-version-file: website/package.json
cache: "npm"
@@ -77,9 +77,9 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
@@ -89,14 +89,14 @@ jobs:
image-name: ghcr.io/goauthentik/dev-docs
- name: Login to Container Registry
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker Image
id: push
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
with:
tags: ${{ steps.ev.outputs.imageTags }}
file: website/Dockerfile
@@ -105,7 +105,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@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v3
id: attest
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
with:

View File

@@ -279,7 +279,7 @@ jobs:
with:
flags: conformance
- if: ${{ !cancelled() }}
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: conformance-certification-${{ matrix.job.name }}
path: tests/openid_conformance/exports/

View File

@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: "go.mod"
- name: Prepare and generate API
@@ -43,7 +43,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: "go.mod"
- name: Setup authentik env
@@ -90,9 +90,9 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
@@ -102,7 +102,7 @@ jobs:
image-name: ghcr.io/goauthentik/dev-${{ matrix.type }}
- name: Login to Container Registry
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -111,7 +111,7 @@ jobs:
run: make gen-client-go
- name: Build Docker Image
id: push
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
with:
tags: ${{ steps.ev.outputs.imageTags }}
file: lifecycle/container/${{ matrix.type }}.Dockerfile
@@ -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@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v3
id: attest
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
with:
@@ -148,10 +148,10 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: "go.mod"
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
with:
node-version-file: web/package.json
cache: "npm"

View File

@@ -32,7 +32,7 @@ jobs:
project: web
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
with:
node-version-file: ${{ matrix.project }}/package.json
cache: "npm"
@@ -49,7 +49,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
with:
node-version-file: web/package.json
cache: "npm"
@@ -77,7 +77,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
with:
node-version-file: web/package.json
cache: "npm"

View File

@@ -35,13 +35,13 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
fetch-depth: 2
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
with:
node-version-file: ${{ matrix.package }}/package.json
registry-url: "https://registry.npmjs.org"
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # 24d32ffd492484c1d75e0c0b894501ddb9d30d62
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # 24d32ffd492484c1d75e0c0b894501ddb9d30d62
with:
files: |
${{ matrix.package }}/package.json

View File

@@ -33,9 +33,9 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
@@ -44,21 +44,21 @@ jobs:
with:
image-name: ghcr.io/goauthentik/docs
- name: Login to GitHub Container Registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker Image
id: push
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
with:
tags: ${{ steps.ev.outputs.imageTags }}
file: website/Dockerfile
push: true
platforms: linux/amd64,linux/arm64
context: .
- uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v3
id: attest
if: true
with:
@@ -84,18 +84,18 @@ jobs:
- rac
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: "go.mod"
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
with:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
@@ -108,18 +108,18 @@ jobs:
make gen-client-ts
make gen-client-go
- name: Docker Login Registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
username: ${{ secrets.DOCKER_CORP_USERNAME }}
password: ${{ secrets.DOCKER_CORP_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker Image
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
id: push
with:
push: true
@@ -129,7 +129,7 @@ jobs:
file: lifecycle/container/${{ matrix.type }}.Dockerfile
platforms: linux/amd64,linux/arm64
context: .
- uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v3
id: attest
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
@@ -152,10 +152,10 @@ jobs:
goarch: [amd64, arm64]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: "go.mod"
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
with:
node-version-file: web/package.json
cache: "npm"
@@ -180,7 +180,7 @@ jobs:
export CGO_ENABLED=0
go build -tags=outpost_static_embed -v -o ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }} ./cmd/${{ matrix.type }}
- name: Upload binaries to release
uses: svenstaro/upload-release-action@6b7fa9f267e90b50a19fef07b3596790bb941741 # v2
uses: svenstaro/upload-release-action@b98a3b12e86552593f3e4e577ca8a62aa2f3f22b # v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}

View File

@@ -91,6 +91,7 @@ jobs:
# ID from https://api.github.com/users/authentik-automation[bot]
git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]'
git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com'
git pull
git commit -a -m "release: ${{ inputs.version }}" --allow-empty
git tag "version/${{ inputs.version }}" HEAD -m "version/${{ inputs.version }}"
git push --follow-tags
@@ -174,7 +175,7 @@ jobs:
if: "${{ inputs.release_reason == 'feature' }}"
run: |
changelog_url="https://docs.goauthentik.io/docs/releases/${{ needs.check-inputs.outputs.major_version }}"
reason="{{ inputs.release_reason }}"
reason="${{ inputs.release_reason }}"
jq \
--arg version "${{ inputs.version }}" \
--arg changelog "See ${changelog_url}" \
@@ -186,7 +187,7 @@ jobs:
if: "${{ inputs.release_reason != 'feature' }}"
run: |
changelog_url="https://docs.goauthentik.io/docs/releases/${{ needs.check-inputs.outputs.major_version }}#fixed-in-$(echo -n ${{ inputs.version}} | sed 's/\.//g')"
reason="{{ inputs.release_reason }}"
reason="${{ inputs.release_reason }}"
jq \
--arg version "${{ inputs.version }}" \
--arg changelog "See ${changelog_url}" \

View File

@@ -168,12 +168,22 @@ gen-build: ## Extract the schema from the database
gen-compose:
$(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
gen-changelog: ## (Release) generate the changelog based from the commits since the last version
# These are best-effort guesses based on commit messages
$(eval last_version := $(shell git tag --list 'version/*' --sort 'version:refname' | grep -vE 'rc\d+$$' | tail -1))
$(eval current_commit := $(shell git rev-parse HEAD))
git log --pretty=format:"- %s" $(shell git merge-base ${last_version} ${current_commit})...${current_commit} > merged_to_current
git log --pretty=format:"- %s" $(shell git merge-base ${last_version} ${current_commit})...${last_version} > merged_to_last
grep -Eo 'cherry-pick (#\d+)' merged_to_last | cut -d ' ' -f 2 | sed 's/.*/(&)$$/' > cherry_picked_to_last
grep -vf cherry_picked_to_last merged_to_current | sort > changelog.md
rm merged_to_current
rm merged_to_last
rm cherry_picked_to_last
npx prettier --write changelog.md
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
gen-diff: ## (Release) generate the changelog diff between the current schema and the last version
$(eval last_version := $(shell git tag --list 'version/*' --sort 'version:refname' | grep -vE 'rc\d+$$' | tail -1))
git show ${last_version}:schema.yml > schema-old.yml
docker compose -f scripts/api/compose.yml run --rm --user "${UID}:${GID}" diff \
--markdown \
/local/diff.md \

View File

@@ -100,13 +100,25 @@ class S3Backend(ManageableBackend):
f"storage.{self.usage.value}.{self.name}.addressing_style",
CONFIG.get(f"storage.{self.name}.addressing_style", "auto"),
)
signature_version = CONFIG.get(
f"storage.{self.usage.value}.{self.name}.signature_version",
CONFIG.get(f"storage.{self.name}.signature_version", "s3v4"),
)
# Keep signature_version pass-through and let boto3/botocore handle it.
# In boto3's S3 configuration docs, `s3v4` (default) and deprecated `s3`
# are the documented values:
# https://github.com/boto/boto3/blob/791a3e8f36d83664a47b4281a0586b3546cef3ec/docs/source/guide/configuration.rst?plain=1#L398-L407
# Botocore also supports additional signer names, so we intentionally do
# not enforce a restricted allowlist here.
return self.session.client(
"s3",
endpoint_url=endpoint_url,
use_ssl=use_ssl,
region_name=region_name,
config=Config(signature_version="s3v4", s3={"addressing_style": addressing_style}),
config=Config(
signature_version=signature_version, s3={"addressing_style": addressing_style}
),
)
@property

View File

@@ -1,5 +1,6 @@
from unittest import skipUnless
from botocore.exceptions import UnsupportedSignatureVersionError
from django.test import TestCase
from authentik.admin.files.tests.utils import FileTestS3BackendMixin, s3_test_server_available
@@ -81,6 +82,27 @@ class TestS3Backend(FileTestS3BackendMixin, TestCase):
self.assertIn("X-Amz-Signature=", url)
self.assertIn("test.png", url)
def test_client_signature_version_default_v4(self):
"""Test S3 client defaults to v4 signature when not configured."""
self.assertEqual(self.media_s3_backend.client.meta.config.signature_version, "s3v4")
@CONFIG.patch("storage.s3.signature_version", "s3")
def test_client_signature_version_global_override(self):
"""Test S3 client respects globally configured signature version."""
self.assertEqual(self.media_s3_backend.client.meta.config.signature_version, "s3")
@CONFIG.patch("storage.s3.signature_version", "s3v4")
@CONFIG.patch("storage.media.s3.signature_version", "s3")
def test_client_signature_version_media_override(self):
"""Test usage-specific signature version takes precedence over global."""
self.assertEqual(self.media_s3_backend.client.meta.config.signature_version, "s3")
@CONFIG.patch("storage.media.s3.signature_version", "not-a-real-signature")
def test_client_signature_version_unsupported(self):
"""Test unsupported signature version raises botocore error."""
with self.assertRaises(UnsupportedSignatureVersionError):
self.media_s3_backend.file_url("test.png", use_cache=False)
@CONFIG.patch("storage.s3.bucket_name", "test-bucket")
def test_file_exists_true(self):
"""Test file_exists returns True for existing file"""

View File

@@ -17,7 +17,6 @@ from django.contrib.sessions.base_session import AbstractBaseSession
from django.core.validators import validate_slug
from django.db import models
from django.db.models import Q, QuerySet, options
from django.db.models.constants import LOOKUP_SEP
from django.http import HttpRequest
from django.utils.functional import cached_property
from django.utils.timezone import now
@@ -45,6 +44,7 @@ from authentik.lib.models import (
DomainlessFormattedURLValidator,
SerializerModel,
)
from authentik.lib.utils.inheritance import get_deepest_child
from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.models import PolicyBindingModel
from authentik.rbac.models import Role
@@ -803,25 +803,7 @@ class Application(SerializerModel, PolicyBindingModel):
"""Get casted provider instance. Needs Application queryset with_provider"""
if not self.provider:
return None
candidates = []
base_class = Provider
for subclass in base_class.objects.get_queryset()._get_subclasses_recurse(base_class):
parent = self.provider
for level in subclass.split(LOOKUP_SEP):
try:
parent = getattr(parent, level)
except AttributeError:
break
if parent in candidates:
continue
idx = subclass.count(LOOKUP_SEP)
if type(parent) is not base_class:
idx += 1
candidates.insert(idx, parent)
if not candidates:
return None
return candidates[-1]
return get_deepest_child(self.provider)
def backchannel_provider_for[T: Provider](self, provider_type: type[T], **kwargs) -> T | None:
"""Get Backchannel provider for a specific type"""

View File

@@ -78,7 +78,7 @@ 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
return md5(key_data.encode("utf-8"), usedforsecurity=False).hexdigest() # nosec
class CertificateKeyPair(SerializerModel, ManagedModel, CreatedUpdatedModel):

View File

@@ -10,6 +10,7 @@ class AuthentikEndpointsConnectorAgentAppConfig(ManagedAppConfig):
label = "authentik_endpoints_connectors_agent"
verbose_name = "authentik Endpoints.Connectors.Agent"
default = True
mountpoint = "endpoints/agent/"
def import_related(self):
from authentik.endpoints.connectors.agent.models import AgentConnector

View File

@@ -1,15 +1,24 @@
from datetime import timedelta
from hmac import compare_digest
from plistlib import PlistFormat, dumps
from uuid import uuid4
from xml.etree.ElementTree import Element, SubElement, tostring # nosec
from django.http import HttpRequest
from django.urls import reverse
from django.utils.timezone import now
from jwt import PyJWTError, decode, encode
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField
from authentik.core.api.utils import PassiveSerializer
from authentik.endpoints.connectors.agent.models import AgentConnector, EnrollmentToken
from authentik.crypto.models import CertificateKeyPair
from authentik.endpoints.connectors.agent.models import AgentConnector, DeviceToken, EnrollmentToken
from authentik.endpoints.controller import BaseController
from authentik.endpoints.facts import OSFamily
from authentik.endpoints.models import Device
from authentik.lib.generators import generate_id
from authentik.providers.oauth2.models import JWTAlgorithms
def csp_create_replace_item(loc_uri, data_value) -> Element:
@@ -36,14 +45,12 @@ def csp_create_replace_item(loc_uri, data_value) -> Element:
class MDMConfigResponseSerializer(PassiveSerializer):
config = CharField(required=True)
mime_type = CharField(required=True)
filename = CharField(required=True)
class AgentConnectorController(BaseController[AgentConnector]):
class AgentController(BaseController[AgentConnector]):
@staticmethod
def vendor_identifier() -> str:
return "goauthentik.io/platform"
@@ -51,6 +58,57 @@ class AgentConnectorController(BaseController[AgentConnector]):
def supported_enrollment_methods(self):
return []
def generate_device_challenge(self):
keypair = CertificateKeyPair.objects.get(pk=self.connector.challenge_key_id)
challenge_str = generate_id()
iat = now()
challenge = encode(
{
"atc": challenge_str,
"iss": str(self.connector.pk),
"iat": int(iat.timestamp()),
"exp": int((iat + timedelta(minutes=5)).timestamp()),
"goauthentik.io/device/check_in": self.connector.challenge_trigger_check_in,
},
headers={"kid": keypair.kid},
key=keypair.private_key,
algorithm=JWTAlgorithms.from_private_key(keypair.private_key),
)
return challenge
def validate_device_challenge(self, response: str, challenge: str):
try:
raw = decode(
response,
options={"verify_signature": False},
audience="goauthentik.io/platform/endpoint",
)
except PyJWTError as exc:
self.logger.warning("Could not parse response", exc=exc)
raise ValidationError("Invalid challenge response") from None
device = Device.filter_not_expired(identifier=raw["iss"]).first()
if not device:
self.logger.warning("Could not find device for challenge")
raise ValidationError("Invalid challenge response")
for token in DeviceToken.filter_not_expired(
device__device=device, device__connector=self.connector
).values_list("key", flat=True):
try:
decoded = decode(
response,
key=token,
algorithms="HS512",
issuer=device.identifier,
audience="goauthentik.io/platform/endpoint",
)
if not compare_digest(decoded["atc"], challenge):
self.logger.warning("mismatched challenge")
raise ValidationError("Invalid challenge response")
return device
except PyJWTError as exc:
self.logger.warning("failed to validate device challenge response", exc=exc)
raise ValidationError("Invalid challenge response")
def generate_mdm_config(
self, target_platform: OSFamily, request: HttpRequest, token: EnrollmentToken
) -> MDMConfigResponseSerializer:

View File

@@ -21,7 +21,7 @@ from authentik.lib.models import InternallyManagedMixin, SerializerModel
from authentik.lib.utils.time import timedelta_string_validator
if TYPE_CHECKING:
from authentik.endpoints.connectors.agent.controller import AgentConnectorController
from authentik.endpoints.connectors.agent.controller import AgentController
class AgentConnector(Connector):
@@ -73,10 +73,10 @@ class AgentConnector(Connector):
return AuthenticatorEndpointStageView
@property
def controller(self) -> type[AgentConnectorController]:
from authentik.endpoints.connectors.agent.controller import AgentConnectorController
def controller(self) -> type[AgentController]:
from authentik.endpoints.connectors.agent.controller import AgentController
return AgentConnectorController
return AgentController
@property
def component(self) -> str:

View File

@@ -1,15 +1,18 @@
from datetime import timedelta
from hashlib import sha256
from hmac import compare_digest
from typing import cast
from urllib.parse import urlencode
from django.http import HttpResponse
from django.utils.timezone import now
from jwt import PyJWTError, decode, encode
from rest_framework.exceptions import ValidationError
from django.urls import reverse
from rest_framework.fields import CharField, IntegerField
from authentik.crypto.models import CertificateKeyPair
from authentik.endpoints.connectors.agent.models import DeviceAuthenticationToken, DeviceToken
from authentik.endpoints.connectors.agent.controller import AgentController
from authentik.endpoints.connectors.agent.models import (
AgentConnector,
DeviceAuthenticationToken,
)
from authentik.endpoints.models import Device, EndpointStage, StageMode
from authentik.flows.challenge import (
Challenge,
@@ -17,9 +20,7 @@ from authentik.flows.challenge import (
)
from authentik.flows.planner import PLAN_CONTEXT_DEVICE
from authentik.flows.stage import ChallengeStageView
from authentik.lib.generators import generate_id
from authentik.lib.utils.time import timedelta_from_string
from authentik.providers.oauth2.models import JWTAlgorithms
PLAN_CONTEXT_DEVICE_AUTH_TOKEN = "goauthentik.io/endpoints/device_auth_token" # nosec
PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE = "goauthentik.io/endpoints/connectors/agent/challenge"
@@ -31,8 +32,9 @@ class EndpointAgentChallenge(Challenge):
"""Signed challenge for authentik agent to respond to"""
component = CharField(default="ak-stage-endpoint-agent")
challenge = CharField()
challenge = CharField(required=True)
challenge_idle_timeout = IntegerField()
frame_url = CharField(required=True)
class EndpointAgentChallengeResponse(ChallengeResponse):
@@ -44,47 +46,23 @@ class EndpointAgentChallengeResponse(ChallengeResponse):
def validate_response(self, response: str | None) -> Device | None:
if not response:
return None
try:
raw = decode(
response,
options={"verify_signature": False},
audience="goauthentik.io/platform/endpoint",
)
except PyJWTError as exc:
self.stage.logger.warning("Could not parse response", exc=exc)
raise ValidationError("Invalid challenge response") from None
device = Device.filter_not_expired(identifier=raw["iss"]).first()
if not device:
self.stage.logger.warning("Could not find device for challenge")
raise ValidationError("Invalid challenge response")
for token in DeviceToken.filter_not_expired(
device__device=device,
device__connector=self.stage.executor.current_stage.connector,
).values_list("key", flat=True):
try:
decoded = decode(
response,
key=token,
algorithms="HS512",
issuer=device.identifier,
audience="goauthentik.io/platform/endpoint",
)
if not compare_digest(
decoded["atc"],
self.stage.executor.plan.context[PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE],
):
self.stage.logger.warning("mismatched challenge")
raise ValidationError("Invalid challenge response")
return device
except PyJWTError as exc:
self.stage.logger.warning("failed to validate device challenge response", exc=exc)
raise ValidationError("Invalid challenge response")
return cast(AgentController, self.stage.controller).validate_device_challenge(
response,
self.stage.executor.plan.context[PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE],
)
class AuthenticatorEndpointStageView(ChallengeStageView):
"""Endpoint stage"""
response_class = EndpointAgentChallengeResponse
controller: AgentController
def dispatch(self, request, *args, **kwargs):
stage: EndpointStage = self.executor.current_stage
connector: AgentConnector = stage.connector
self.controller = connector.controller(connector)
return super().dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
# Check if we're in a device interactive auth flow, in which case we use that
@@ -119,21 +97,7 @@ class AuthenticatorEndpointStageView(ChallengeStageView):
def get_challenge(self, *args, **kwargs) -> Challenge:
stage: EndpointStage = self.executor.current_stage
keypair = CertificateKeyPair.objects.get(pk=stage.connector.challenge_key_id)
challenge_str = generate_id()
iat = now()
challenge = encode(
{
"atc": challenge_str,
"iss": str(stage.pk),
"iat": int(iat.timestamp()),
"exp": int((iat + timedelta(minutes=5)).timestamp()),
"goauthentik.io/device/check_in": stage.connector.challenge_trigger_check_in,
},
headers={"kid": keypair.kid},
key=keypair.private_key,
algorithm=JWTAlgorithms.from_private_key(keypair.private_key),
)
challenge = self.controller.generate_device_challenge()
self.executor.plan.context[PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE] = challenge
return EndpointAgentChallenge(
data={
@@ -142,6 +106,11 @@ class AuthenticatorEndpointStageView(ChallengeStageView):
"challenge_idle_timeout": int(
timedelta_from_string(stage.connector.challenge_idle_timeout).total_seconds()
),
"frame_url": self.request.build_absolute_uri(
reverse("authentik_endpoints_connectors_agent:browser-backchannel")
+ "?"
+ urlencode({"xak-agent-challenge": challenge})
),
}
)

View File

@@ -1,5 +1,12 @@
from django.urls import path
from authentik.endpoints.connectors.agent.api.connectors import AgentConnectorViewSet
from authentik.endpoints.connectors.agent.api.enrollment_tokens import EnrollmentTokenViewSet
from authentik.endpoints.connectors.agent.views.browser_backchannel import BrowserBackchannel
urlpatterns = [
path("browser-backchannel/", BrowserBackchannel.as_view(), name="browser-backchannel"),
]
api_urlpatterns = [
("endpoints/agents/connectors", AgentConnectorViewSet),

View File

@@ -0,0 +1,40 @@
from typing import Any
from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest
from django.template.response import TemplateResponse
from django.views import View
from rest_framework.exceptions import ValidationError
from authentik.endpoints.connectors.agent.controller import AgentController
from authentik.endpoints.connectors.agent.models import AgentConnector
from authentik.endpoints.connectors.agent.stage import PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE
from authentik.endpoints.models import EndpointStage
from authentik.flows.planner import PLAN_CONTEXT_DEVICE, FlowPlan
from authentik.flows.views.executor import SESSION_KEY_PLAN
class BrowserBackchannel(View):
def get_flow_plan(self) -> FlowPlan:
flow_plan: FlowPlan = self.request.session[SESSION_KEY_PLAN]
return flow_plan
def setup(self, request: HttpRequest, *args: Any, **kwargs: Any) -> None:
super().setup(request, *args, **kwargs)
stage: EndpointStage = self.get_flow_plan().bindings[0].stage
connector = AgentConnector.objects.filter(pk=stage.connector_id).first()
if not connector:
return HttpResponseBadRequest()
self.controller: AgentController = connector.controller(connector)
def get(self, request: HttpRequest) -> HttpResponse:
response = request.GET.get("xak-agent-response")
flow_plan = self.get_flow_plan()
try:
dev = self.controller.validate_device_challenge(
response, flow_plan.context.get(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE)
)
flow_plan.context[PLAN_CONTEXT_DEVICE] = dev
request.session[SESSION_KEY_PLAN] = flow_plan
except ValidationError:
return HttpResponseBadRequest()
return TemplateResponse(request, "flows/frame-submit.html")

View File

@@ -63,7 +63,7 @@ class OperatingSystemSerializer(Serializer):
"Operating System version, must always be the version number but may contain build name"
),
)
arch = CharField(required=True)
arch = CharField(required=False)
class NetworkInterfaceSerializer(Serializer):

View File

@@ -0,0 +1,42 @@
"""GoogleChromeConnector API Views"""
from django.urls import reverse
from rest_framework.fields import SerializerMethodField
from rest_framework.request import Request
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.endpoints.api.connectors import ConnectorSerializer
from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.enterprise.endpoints.connectors.google_chrome.models import GoogleChromeConnector
class GoogleChromeConnectorSerializer(EnterpriseRequiredMixin, ConnectorSerializer):
"""GoogleChromeConnector Serializer"""
chrome_url = SerializerMethodField()
def get_chrome_url(self, _: GoogleChromeConnector) -> str | None:
"""Full URL to be used in Google Workspace configuration"""
request: Request = self.context.get("request", None)
if not request:
return True
return request.build_absolute_uri(
reverse("authentik_endpoints_connectors_google_chrome:chrome")
)
class Meta:
model = GoogleChromeConnector
fields = ConnectorSerializer.Meta.fields + ["credentials", "chrome_url"]
class GoogleChromeConnectorViewSet(UsedByMixin, ModelViewSet):
"""GoogleChromeConnector Viewset"""
queryset = GoogleChromeConnector.objects.all()
serializer_class = GoogleChromeConnectorSerializer
filterset_fields = [
"name",
]
search_fields = ["name"]
ordering = ["name"]

View File

@@ -0,0 +1,13 @@
"""authentik Endpoint app config"""
from authentik.enterprise.apps import EnterpriseConfig
class AuthentikEndpointsConnectorGoogleChromeAppConfig(EnterpriseConfig):
"""authentik endpoint config"""
name = "authentik.enterprise.endpoints.connectors.google_chrome"
label = "authentik_endpoints_connectors_google_chrome"
verbose_name = "authentik Enterprise.Endpoints.Connectors.Google Chrome"
default = True
mountpoint = "endpoints/google/"

View File

@@ -0,0 +1,116 @@
from json import dumps, loads
from django.http import HttpRequest, HttpResponseRedirect
from django.urls import reverse
from googleapiclient.discovery import build
from authentik.endpoints.controller import BaseController, EnrollmentMethods
from authentik.endpoints.facts import DeviceFacts, OSFamily
from authentik.endpoints.models import Device, DeviceConnection
from authentik.enterprise.endpoints.connectors.google_chrome.google_schema import (
DeviceSignals,
VerifyChallengeResponseResult,
)
from authentik.enterprise.endpoints.connectors.google_chrome.models import GoogleChromeConnector
from authentik.policies.utils import delete_none_values
# Header we get from chrome that initiates verified access
HEADER_DEVICE_TRUST = "X-Device-Trust"
# Header we send to the client with the challenge
HEADER_ACCESS_CHALLENGE = "X-Verified-Access-Challenge"
# Header we get back from the client that we verify with google
HEADER_ACCESS_CHALLENGE_RESPONSE = "X-Verified-Access-Challenge-Response"
# Header value for x-device-trust that initiates the flow
DEVICE_TRUST_VERIFIED_ACCESS = "VerifiedAccess"
class GoogleChromeController(BaseController[GoogleChromeConnector]):
def __init__(self, connector):
super().__init__(connector)
self.google_client = build(
"verifiedaccess",
"v2",
cache_discovery=False,
**connector.google_credentials(),
)
@staticmethod
def vendor_identifier() -> str:
return "chrome.google.com"
def supported_enrollment_methods(self) -> list[EnrollmentMethods]:
return [EnrollmentMethods.AUTOMATIC_USER]
def generate_challenge(self, request: HttpRequest) -> HttpResponseRedirect:
challenge = self.google_client.challenge().generate().execute()
res = HttpResponseRedirect(
request.build_absolute_uri(
reverse("authentik_endpoints_connectors_google_chrome:chrome")
)
)
res[HEADER_ACCESS_CHALLENGE] = dumps(challenge)
return res
def validate_challenge(self, response: str) -> Device:
response = VerifyChallengeResponseResult(
self.google_client.challenge().verify(body=loads(response)).execute()
)
# Remove deprecated string representation of deviceSignals
response.pop("deviceSignal", None)
signals = DeviceSignals(response["deviceSignals"])
device, _ = Device.objects.update_or_create(
identifier=signals["serialNumber"],
defaults={
"name": signals["hostname"],
},
)
conn, _ = DeviceConnection.objects.update_or_create(
device=device,
connector=self.connector,
)
conn.create_snapshot(self.convert_data(signals))
return device
def convert_os_family(self, family) -> OSFamily:
return {
"CHROME_OS": OSFamily.linux,
"CHROMIUM_OS": OSFamily.linux,
"WINDOWS": OSFamily.windows,
"MAC_OS_X": OSFamily.macOS,
"LINUX": OSFamily.linux,
}.get(family, OSFamily.other)
def convert_data(self, raw_signals: DeviceSignals):
data = {
"os": delete_none_values(
{
"family": self.convert_os_family(raw_signals["operatingSystem"]),
"version": raw_signals["osVersion"],
}
),
"disks": [],
"network": delete_none_values(
{
"hostname": raw_signals["hostname"],
"interfaces": [],
"firewall_enabled": raw_signals["osFirewall"] == "OS_FIREWALL_ENABLED",
},
),
"hardware": delete_none_values(
{
"model": raw_signals["deviceModel"],
"manufacturer": raw_signals["deviceManufacturer"],
"serial": raw_signals["serialNumber"],
}
),
"vendor": {
self.vendor_identifier(): {
"agent_version": raw_signals["browserVersion"],
"raw": raw_signals,
},
},
}
facts = DeviceFacts(data=data)
facts.is_valid(raise_exception=True)
return facts.validated_data

View File

@@ -0,0 +1,129 @@
from typing import Literal, TypedDict
# Based on https://github.com/henribru/google-api-python-client-stubs/blob/master/googleapiclient-stubs/_apis/verifiedaccess/v2/schemas.pyi
class Antivirus(TypedDict, total=False):
state: Literal["STATE_UNSPECIFIED", "MISSING", "DISABLED", "ENABLED"]
class Challenge(TypedDict, total=False):
challenge: str
class CrowdStrikeAgent(TypedDict, total=False):
agentId: str
customerId: str
class DeviceSignals(TypedDict, total=False):
allowScreenLock: bool
antivirus: Antivirus
browserVersion: str
builtInDnsClientEnabled: bool
chromeRemoteDesktopAppBlocked: bool
crowdStrikeAgent: CrowdStrikeAgent
deviceAffiliationIds: list[str]
deviceEnrollmentDomain: str
deviceManufacturer: str
deviceModel: str
diskEncryption: Literal[
"DISK_ENCRYPTION_UNSPECIFIED",
"DISK_ENCRYPTION_UNKNOWN",
"DISK_ENCRYPTION_DISABLED",
"DISK_ENCRYPTION_ENCRYPTED",
]
displayName: str
hostname: str
imei: list[str]
macAddresses: list[str]
meid: list[str]
operatingSystem: Literal[
"OPERATING_SYSTEM_UNSPECIFIED",
"CHROME_OS",
"CHROMIUM_OS",
"WINDOWS",
"MAC_OS_X",
"LINUX",
]
osFirewall: Literal[
"OS_FIREWALL_UNSPECIFIED",
"OS_FIREWALL_UNKNOWN",
"OS_FIREWALL_DISABLED",
"OS_FIREWALL_ENABLED",
]
osVersion: str
passwordProtectionWarningTrigger: Literal[
"PASSWORD_PROTECTION_WARNING_TRIGGER_UNSPECIFIED",
"POLICY_UNSET",
"PASSWORD_PROTECTION_OFF",
"PASSWORD_REUSE",
"PHISHING_REUSE",
]
profileAffiliationIds: list[str]
profileEnrollmentDomain: str
realtimeUrlCheckMode: Literal[
"REALTIME_URL_CHECK_MODE_UNSPECIFIED",
"REALTIME_URL_CHECK_MODE_DISABLED",
"REALTIME_URL_CHECK_MODE_ENABLED_MAIN_FRAME",
]
safeBrowsingProtectionLevel: Literal[
"SAFE_BROWSING_PROTECTION_LEVEL_UNSPECIFIED", "INACTIVE", "STANDARD", "ENHANCED"
]
screenLockSecured: Literal[
"SCREEN_LOCK_SECURED_UNSPECIFIED",
"SCREEN_LOCK_SECURED_UNKNOWN",
"SCREEN_LOCK_SECURED_DISABLED",
"SCREEN_LOCK_SECURED_ENABLED",
]
secureBootMode: Literal[
"SECURE_BOOT_MODE_UNSPECIFIED",
"SECURE_BOOT_MODE_UNKNOWN",
"SECURE_BOOT_MODE_DISABLED",
"SECURE_BOOT_MODE_ENABLED",
]
serialNumber: str
siteIsolationEnabled: bool
systemDnsServers: list[str]
thirdPartyBlockingEnabled: bool
trigger: Literal["TRIGGER_UNSPECIFIED", "TRIGGER_BROWSER_NAVIGATION", "TRIGGER_LOGIN_SCREEN"]
windowsMachineDomain: str
windowsUserDomain: str
class Empty(TypedDict, total=False): ...
class VerifyChallengeResponseRequest(TypedDict, total=False):
challengeResponse: str
expectedIdentity: str
class VerifyChallengeResponseResult(TypedDict, total=False):
attestedDeviceId: str
customerId: str
deviceEnrollmentId: str
devicePermanentId: str
deviceSignal: str
deviceSignals: DeviceSignals
keyTrustLevel: Literal[
"KEY_TRUST_LEVEL_UNSPECIFIED",
"CHROME_OS_VERIFIED_MODE",
"CHROME_OS_DEVELOPER_MODE",
"CHROME_BROWSER_HW_KEY",
"CHROME_BROWSER_OS_KEY",
"CHROME_BROWSER_NO_KEY",
]
profileCustomerId: str
profileKeyTrustLevel: Literal[
"KEY_TRUST_LEVEL_UNSPECIFIED",
"CHROME_OS_VERIFIED_MODE",
"CHROME_OS_DEVELOPER_MODE",
"CHROME_BROWSER_HW_KEY",
"CHROME_BROWSER_OS_KEY",
"CHROME_BROWSER_NO_KEY",
]
profilePermanentId: str
signedPublicKeyAndChallenge: str
virtualDeviceId: str
virtualProfileId: str

View File

@@ -0,0 +1,38 @@
# Generated by Django 5.2.11 on 2026-03-01 18:38
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("authentik_endpoints", "0004_deviceaccessgroup_attributes"),
]
operations = [
migrations.CreateModel(
name="GoogleChromeConnector",
fields=[
(
"connector_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_endpoints.connector",
),
),
("credentials", models.JSONField()),
],
options={
"verbose_name": "Google Device Trust Connector",
"verbose_name_plural": "Google Device Trust Connectors",
},
bases=("authentik_endpoints.connector",),
),
]

View File

@@ -0,0 +1,69 @@
"""Endpoint stage"""
from typing import TYPE_CHECKING
from django.db import models
from django.templatetags.static import static
from django.utils.translation import gettext_lazy as _
from google.oauth2.service_account import Credentials
from rest_framework.serializers import BaseSerializer
from authentik.endpoints.models import Connector
from authentik.flows.stage import StageView
if TYPE_CHECKING:
from authentik.enterprise.endpoints.connectors.google_chrome.controller import (
GoogleChromeController,
)
class GoogleChromeConnector(Connector):
"""Verify Google Chrome Device Trust connection for the user's browser."""
credentials = models.JSONField()
def google_credentials(self):
return {
"credentials": Credentials.from_service_account_info(
self.credentials, scopes=["https://www.googleapis.com/auth/verifiedaccess"]
),
}
@property
def icon_url(self):
return static("authentik/sources/google.svg")
@property
def serializer(self) -> type[BaseSerializer]:
from authentik.enterprise.endpoints.connectors.google_chrome.api import (
GoogleChromeConnectorSerializer,
)
return GoogleChromeConnectorSerializer
@property
def stage(self) -> type[StageView] | None:
from authentik.enterprise.endpoints.connectors.google_chrome.stage import (
GoogleChromeStageView,
)
return GoogleChromeStageView
@property
def controller(self) -> type[GoogleChromeController]:
from authentik.enterprise.endpoints.connectors.google_chrome.controller import (
GoogleChromeController,
)
return GoogleChromeController
@property
def component(self) -> str:
return "ak-endpoints-connector-gdtc-form"
def __str__(self) -> str:
return f"Google Device Trust Connector {self.name}"
class Meta:
verbose_name = _("Google Device Trust Connector")
verbose_name_plural = _("Google Device Trust Connectors")

View File

@@ -0,0 +1,32 @@
from django.http import HttpResponse
from django.urls import reverse
from django.utils.translation import gettext as _
from authentik.flows.challenge import (
Challenge,
ChallengeResponse,
FrameChallenge,
FrameChallengeResponse,
)
from authentik.flows.stage import ChallengeStageView
class GoogleChromeStageView(ChallengeStageView):
"""Endpoint stage"""
response_class = FrameChallengeResponse
def get_challenge(self, *args, **kwargs) -> Challenge:
return FrameChallenge(
data={
"component": "xak-flow-frame",
"url": self.request.build_absolute_uri(
reverse("authentik_endpoints_connectors_google_chrome:chrome")
),
"loading_overlay": True,
"loading_text": _("Verifying your browser..."),
}
)
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
return self.executor.stage_ok()

View File

@@ -0,0 +1,36 @@
{
"devicePermanentId": "6f30327d-e436-4f7a-9f89-c37a7b6bf408",
"keyTrustLevel": "CHROME_BROWSER_HW_KEY",
"virtualDeviceId": "Z5DDF07GK6",
"customerId": "qewrqer",
"deviceSignals": {
"deviceManufacturer": "Apple Inc.",
"deviceModel": "MacBookPro18,1",
"operatingSystem": "MAC_OS_X",
"osVersion": "26.2.0",
"displayName": "jens-mac-vm",
"diskEncryption": "DISK_ENCRYPTION_ENCRYPTED",
"serialNumber": "Z5DDF07GK6",
"osFirewall": "OS_FIREWALL_DISABLED",
"systemDnsServers": [
"10.120.20.250:53"
],
"hostname": "jens-mac-vm.lab.beryju.org",
"macAddresses": [
"f4:d4:88:79:07:0e"
],
"screenLockSecured": "SCREEN_LOCK_SECURED_ENABLED",
"deviceEnrollmentDomain": "beryju.org",
"browserVersion": "145.0.7632.76",
"deviceAffiliationIds": [
"qewrqer"
],
"builtInDnsClientEnabled": true,
"chromeRemoteDesktopAppBlocked": false,
"safeBrowsingProtectionLevel": "STANDARD",
"siteIsolationEnabled": true,
"passwordProtectionWarningTrigger": "POLICY_UNSET",
"realtimeUrlCheckMode": "REALTIME_URL_CHECK_MODE_DISABLED",
"trigger": "TRIGGER_BROWSER_NAVIGATION"
}
}

View File

@@ -0,0 +1,67 @@
from json import dumps
from unittest.mock import MagicMock, patch
from django.urls import reverse
from rest_framework.test import APITestCase
from authentik.core.tests.utils import RequestFactory
from authentik.endpoints.facts import OSFamily
from authentik.endpoints.models import Device
from authentik.enterprise.endpoints.connectors.google_chrome.controller import (
HEADER_ACCESS_CHALLENGE,
GoogleChromeController,
)
from authentik.enterprise.endpoints.connectors.google_chrome.models import GoogleChromeConnector
from authentik.enterprise.providers.google_workspace.clients.test_http import MockHTTP
from authentik.lib.generators import generate_id
from authentik.lib.tests.utils import load_fixture
class TestGoogleChromeConnector(APITestCase):
def setUp(self):
self.connector = GoogleChromeConnector.objects.create(
name=generate_id(),
credentials={},
)
self.factory = RequestFactory()
self.api_key = generate_id()
def test_generate_challenge(self):
req = self.factory.get("/")
challenge = generate_id()
http = MockHTTP()
http.add_response(
f"https://verifiedaccess.googleapis.com/v2/challenge:generate?key={self.api_key}&alt=json",
{"challenge": challenge},
method="POST",
)
with patch(
"authentik.enterprise.endpoints.connectors.google_chrome.models.GoogleChromeConnector.google_credentials",
MagicMock(return_value={"developerKey": self.api_key, "http": http}),
):
controller = GoogleChromeController(self.connector)
res = controller.generate_challenge(req)
self.assertEqual(
res["Location"],
req.build_absolute_uri(
reverse("authentik_endpoints_connectors_google_chrome:chrome")
),
)
self.assertEqual(res.headers[HEADER_ACCESS_CHALLENGE], dumps({"challenge": challenge}))
def test_validate_challenge(self):
http = MockHTTP()
http.add_response(
f"https://verifiedaccess.googleapis.com/v2/challenge:verify?key={self.api_key}&alt=json",
load_fixture("fixtures/host_macos.json"),
method="POST",
)
with patch(
"authentik.enterprise.endpoints.connectors.google_chrome.models.GoogleChromeConnector.google_credentials",
MagicMock(return_value={"developerKey": self.api_key, "http": http}),
):
controller = GoogleChromeController(self.connector)
controller.validate_challenge(dumps("{}"))
device = Device.objects.get(identifier="Z5DDF07GK6")
self.assertIsNotNone(device)
self.assertEqual(device.cached_facts.data["os"]["family"], OSFamily.macOS)

View File

@@ -0,0 +1,91 @@
from json import dumps
from unittest.mock import MagicMock, patch
from django.urls import reverse
from authentik.core.tests.utils import RequestFactory, create_test_flow
from authentik.endpoints.models import Device, EndpointStage
from authentik.enterprise.endpoints.connectors.google_chrome.models import GoogleChromeConnector
from authentik.enterprise.providers.google_workspace.clients.test_http import MockHTTP
from authentik.flows.models import FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_DEVICE
from authentik.flows.tests import FlowTestCase
from authentik.lib.generators import generate_id
from authentik.lib.tests.utils import load_fixture
class TestChromeDTCView(FlowTestCase):
def setUp(self):
self.flow = create_test_flow()
self.connector = GoogleChromeConnector.objects.create(
name=generate_id(),
credentials={},
)
self.factory = RequestFactory()
self.api_key = generate_id()
self.stage = EndpointStage.objects.create(
name=generate_id(),
connector=self.connector,
)
FlowStageBinding.objects.create(
target=self.flow,
stage=self.stage,
order=0,
)
def test_dtc_generate_verify(self):
res = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
self.assertStageResponse(
res,
self.flow,
component="xak-flow-frame",
url="http://testserver/endpoints/google/chrome/",
)
challenge = generate_id()
http = MockHTTP()
http.add_response(
f"https://verifiedaccess.googleapis.com/v2/challenge:generate?key={self.api_key}&alt=json",
{"challenge": challenge},
method="POST",
)
http.add_response(
f"https://verifiedaccess.googleapis.com/v2/challenge:verify?key={self.api_key}&alt=json",
load_fixture("fixtures/host_macos.json"),
method="POST",
)
with patch(
"authentik.enterprise.endpoints.connectors.google_chrome.models.GoogleChromeConnector.google_credentials",
MagicMock(return_value={"developerKey": self.api_key, "http": http}),
):
# Generate challenge
res = self.client.get(
reverse("authentik_endpoints_connectors_google_chrome:chrome"),
HTTP_X_DEVICE_TRUST="VerifiedAccess",
)
self.assertEqual(res.status_code, 302)
self.assertEqual(
res.headers["X-Verified-Access-Challenge"],
dumps({"challenge": challenge}),
)
# Validate challenge
res = self.client.get(
reverse("authentik_endpoints_connectors_google_chrome:chrome"),
HTTP_X_VERIFIED_ACCESS_CHALLENGE_RESPONSE=dumps({}),
)
self.assertEqual(res.status_code, 200)
device = Device.objects.get(identifier="Z5DDF07GK6")
self.assertIsNotNone(device)
# Continue flow
with self.assertFlowFinishes() as plan:
res = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
self.assertStageRedirects(res, "/")
plan = plan()
plan_device = plan.context[PLAN_CONTEXT_DEVICE]
self.assertEqual(device.pk, plan_device.pk)

View File

@@ -0,0 +1,16 @@
"""API URLs"""
from django.urls import path
from authentik.enterprise.endpoints.connectors.google_chrome.api import GoogleChromeConnectorViewSet
from authentik.enterprise.endpoints.connectors.google_chrome.views.dtc import (
GoogleChromeDeviceTrustConnector,
)
urlpatterns = [
path("chrome/", GoogleChromeDeviceTrustConnector.as_view(), name="chrome"),
]
api_urlpatterns = [
("endpoints/google_chrome/connectors", GoogleChromeConnectorViewSet),
]

View File

@@ -0,0 +1,46 @@
from typing import Any
from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest
from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.clickjacking import xframe_options_sameorigin
from authentik.endpoints.models import EndpointStage
from authentik.enterprise.endpoints.connectors.google_chrome.controller import (
HEADER_ACCESS_CHALLENGE_RESPONSE,
HEADER_DEVICE_TRUST,
GoogleChromeController,
)
from authentik.enterprise.endpoints.connectors.google_chrome.models import GoogleChromeConnector
from authentik.flows.planner import PLAN_CONTEXT_DEVICE, FlowPlan
from authentik.flows.views.executor import SESSION_KEY_PLAN
@method_decorator(xframe_options_sameorigin, name="dispatch")
class GoogleChromeDeviceTrustConnector(View):
"""Google Chrome Device-trust connector based endpoint authenticator"""
def get_flow_plan(self) -> FlowPlan:
flow_plan: FlowPlan = self.request.session[SESSION_KEY_PLAN]
return flow_plan
def setup(self, request: HttpRequest, *args: Any, **kwargs: Any) -> None:
super().setup(request, *args, **kwargs)
stage: EndpointStage = self.get_flow_plan().bindings[0].stage
connector = GoogleChromeConnector.objects.filter(pk=stage.connector_id).first()
if not connector:
return HttpResponseBadRequest()
self.controller: GoogleChromeController = connector.controller(connector)
def get(self, request: HttpRequest) -> HttpResponse:
x_device_trust = request.headers.get(HEADER_DEVICE_TRUST)
x_access_challenge_response = request.headers.get(HEADER_ACCESS_CHALLENGE_RESPONSE)
if x_device_trust == "VerifiedAccess" and x_access_challenge_response is None:
return self.controller.generate_challenge(request)
if x_access_challenge_response:
device = self.controller.validate_challenge(x_access_challenge_response)
flow_plan = self.get_flow_plan()
flow_plan.context[PLAN_CONTEXT_DEVICE] = device
self.request.session[SESSION_KEY_PLAN] = flow_plan
return TemplateResponse(request, "flows/frame-submit.html")

View File

@@ -331,7 +331,7 @@ class GoogleWorkspaceGroupTests(TestCase):
).exists()
)
self.assertFalse(Event.objects.filter(action=EventAction.SYSTEM_EXCEPTION).exists())
self.assertEqual(len(http.requests()), 5)
self.assertEqual(len(http.requests()), 7)
def test_sync_discover_multiple(self):
"""Test group discovery"""
@@ -372,7 +372,7 @@ class GoogleWorkspaceGroupTests(TestCase):
).exists()
)
self.assertFalse(Event.objects.filter(action=EventAction.SYSTEM_EXCEPTION).exists())
self.assertEqual(len(http.requests()), 5)
self.assertEqual(len(http.requests()), 7)
# Change response to trigger update
http.add_response(
f"https://admin.googleapis.com/admin/directory/v1/groups?customer=my_customer&maxResults=500&orderBy=email&key={self.api_key}&alt=json",

View File

@@ -309,7 +309,7 @@ class GoogleWorkspaceUserTests(TestCase):
).exists()
)
self.assertFalse(Event.objects.filter(action=EventAction.SYSTEM_EXCEPTION).exists())
self.assertEqual(len(http.requests()), 5)
self.assertEqual(len(http.requests()), 7)
def test_sync_discover_multiple(self):
"""Test user discovery, running multiple times"""
@@ -352,7 +352,7 @@ class GoogleWorkspaceUserTests(TestCase):
).exists()
)
self.assertFalse(Event.objects.filter(action=EventAction.SYSTEM_EXCEPTION).exists())
self.assertEqual(len(http.requests()), 5)
self.assertEqual(len(http.requests()), 7)
# Change response, which will trigger a discovery update
http.add_response(
f"https://admin.googleapis.com/admin/directory/v1/users?customer=my_customer&maxResults=500&orderBy=email&key={self.api_key}&alt=json",

View File

@@ -81,6 +81,8 @@ class SignInProcessor:
self.sign_in_request = sign_in_request
self.saml_processor = AssertionProcessor(self.provider, self.request, AuthNRequest())
self.saml_processor.provider.audience = self.sign_in_request.wtrealm
if self.provider.signing_kp:
self.saml_processor.provider.sign_assertion = True
def create_response_token(self):
root = Element(f"{{{NS_WS_FED_TRUST}}}RequestSecurityTokenResponse", nsmap=NS_MAP)
@@ -148,7 +150,8 @@ class SignInProcessor:
def response(self) -> dict[str, str]:
root = self.create_response_token()
assertion = root.xpath("//saml:Assertion", namespaces=NS_MAP)[0]
self.saml_processor._sign(assertion)
if self.provider.signing_kp:
self.saml_processor._sign(assertion)
str_token = etree.tostring(root).decode("utf-8") # nosec
return delete_none_values(
{

View File

@@ -3,8 +3,7 @@
from django.urls import path
from authentik.enterprise.providers.ws_federation.api.providers import WSFederationProviderViewSet
from authentik.enterprise.providers.ws_federation.views import WSFedEntryView
from authentik.providers.saml.views.metadata import MetadataDownload
from authentik.enterprise.providers.ws_federation.views import MetadataDownload, WSFedEntryView
urlpatterns = [
path(

View File

@@ -4,6 +4,7 @@ TENANT_APPS = [
"authentik.enterprise.audit",
"authentik.enterprise.endpoints.connectors.agent",
"authentik.enterprise.endpoints.connectors.fleet",
"authentik.enterprise.endpoints.connectors.google_chrome",
"authentik.enterprise.lifecycle",
"authentik.enterprise.policies.unique_password",
"authentik.enterprise.providers.google_workspace",

View File

@@ -9,6 +9,11 @@ from django.views import View
from django.views.decorators.clickjacking import xframe_options_sameorigin
from googleapiclient.discovery import build
from authentik.enterprise.endpoints.connectors.google_chrome.controller import (
HEADER_ACCESS_CHALLENGE,
HEADER_ACCESS_CHALLENGE_RESPONSE,
HEADER_DEVICE_TRUST,
)
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import (
AuthenticatorEndpointGDTCStage,
EndpointDevice,
@@ -19,15 +24,6 @@ from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
from authentik.stages.user_login.stage import PLAN_CONTEXT_METHOD_ARGS_KNOWN_DEVICE
# Header we get from chrome that initiates verified access
HEADER_DEVICE_TRUST = "X-Device-Trust"
# Header we send to the client with the challenge
HEADER_ACCESS_CHALLENGE = "X-Verified-Access-Challenge"
# Header we get back from the client that we verify with google
HEADER_ACCESS_CHALLENGE_RESPONSE = "X-Verified-Access-Challenge-Response"
# Header value for x-device-trust that initiates the flow
DEVICE_TRUST_VERIFIED_ACCESS = "VerifiedAccess"
PLAN_CONTEXT_METHOD_ARGS_ENDPOINTS = "endpoints"
@@ -94,4 +90,4 @@ class GoogleChromeDeviceTrustConnector(View):
PLAN_CONTEXT_METHOD_ARGS_KNOWN_DEVICE, True
)
request.session[SESSION_KEY_PLAN] = flow_plan
return TemplateResponse(request, "stages/authenticator_endpoint/google_chrome_dtc.html")
return TemplateResponse(request, "flows/frame-submit.html")

View File

@@ -29,6 +29,12 @@ class RefreshOtherFlowsAfterAuthentication(Flag[bool], key="flows_refresh_others
visibility = "public"
class ContinuousLogin(Flag[bool], key="flows_continuous_login"):
default = False
visibility = "public"
class AuthentikFlowsConfig(ManagedAppConfig):
"""authentik flows app config"""

View File

@@ -166,6 +166,7 @@ storage:
# region: "us-east-1"
# use_ssl: True
# endpoint: "https://s3.us-east-1.amazonaws.com"
# signature_version: "s3v4"
# access_key: ""
# secret_key: ""
# bucket_name: "authentik-data"

View File

@@ -103,6 +103,7 @@ class SyncTasks:
)
users_tasks.run().wait(timeout=provider.get_object_sync_time_limit_ms(User))
group_tasks.run().wait(timeout=provider.get_object_sync_time_limit_ms(Group))
self._sync_cleanup(provider, task)
except TransientSyncException as exc:
self.logger.warning("transient sync exception", exc=exc)
task.warning("Sync encountered a transient exception. Retrying", exc=exc)
@@ -111,6 +112,35 @@ class SyncTasks:
task.error(exc)
return
def _sync_cleanup(self, provider: OutgoingSyncProvider, task: Task):
"""Delete remote objects that are no longer in scope"""
for object_type in (User, Group):
try:
client = provider.client_for_model(object_type)
except TransientSyncException:
continue
in_scope_pks = set(provider.get_object_qs(object_type).values_list("pk", flat=True))
stale = client.connection_type.objects.filter(provider=provider).exclude(
**{f"{client.connection_type_query}__pk__in": in_scope_pks}
)
for connection in stale:
try:
client.delete(connection.scim_id)
task.info(
f"Deleted out-of-scope {object_type._meta.verbose_name}",
scim_id=connection.scim_id,
)
except NotFoundSyncException:
pass
except TransientSyncException as exc:
self.logger.warning("transient error during cleanup", exc=exc)
self.logger.warning(
"Cleanup encountered a transient exception. Retrying", exc=exc
)
raise Retry() from exc
except DryRunRejected as exc:
self.logger.info("Rejected dry-run cleanup event", exc=exc)
def sync_objects(
self,
object_type: str,

View File

@@ -0,0 +1,119 @@
"""Tests for inheritance helpers."""
from contextlib import contextmanager
from django.db import connection, models
from django.test import TransactionTestCase
from django.test.utils import isolate_apps
from authentik.lib.utils.inheritance import get_deepest_child
@contextmanager
def temporary_inheritance_models():
"""Create a temporary multi-table inheritance graph for testing."""
with isolate_apps("authentik.lib.tests"):
class GrandParent(models.Model):
class Meta:
app_label = "tests"
def __str__(self) -> str:
return f"GrandParent({self.pk})"
class Parent(GrandParent):
class Meta:
app_label = "tests"
def __str__(self) -> str:
return f"Parent({self.pk})"
class Child(Parent):
class Meta:
app_label = "tests"
def __str__(self) -> str:
return f"Child({self.pk})"
class GrandChild(Child):
class Meta:
app_label = "tests"
def __str__(self) -> str:
return f"GrandChild({self.pk})"
with connection.schema_editor() as schema_editor:
schema_editor.create_model(GrandParent)
schema_editor.create_model(Parent)
schema_editor.create_model(Child)
schema_editor.create_model(GrandChild)
try:
yield GrandParent, Parent, Child, GrandChild
finally:
with connection.schema_editor() as schema_editor:
schema_editor.delete_model(GrandChild)
schema_editor.delete_model(Child)
schema_editor.delete_model(Parent)
schema_editor.delete_model(GrandParent)
class TestInheritanceUtils(TransactionTestCase):
"""Tests for helper functions in authentik.lib.utils.inheritance."""
def test_get_deepest_child_grandparent_to_parent(self):
"""GrandParent -> Parent."""
with temporary_inheritance_models() as (GrandParent, Parent, _Child, _GrandChild):
parent = Parent.objects.create()
grandparent = GrandParent.objects.get(pk=parent.pk)
resolved = get_deepest_child(grandparent)
self.assertIsInstance(resolved, Parent)
self.assertEqual(resolved.pk, parent.pk)
def test_get_deepest_child_grandparent_to_child(self):
"""GrandParent -> Child."""
with temporary_inheritance_models() as (GrandParent, _Parent, Child, _GrandChild):
child = Child.objects.create()
grandparent = GrandParent.objects.get(pk=child.pk)
resolved = get_deepest_child(grandparent)
self.assertIsInstance(resolved, Child)
self.assertEqual(resolved.pk, child.pk)
def test_get_deepest_child_grandparent_to_grandchild(self):
"""GrandParent -> GrandChild."""
with temporary_inheritance_models() as (GrandParent, _Parent, _Child, GrandChild):
grandchild = GrandChild.objects.create()
grandparent = GrandParent.objects.get(pk=grandchild.pk)
resolved = get_deepest_child(grandparent)
self.assertIsInstance(resolved, GrandChild)
self.assertEqual(resolved.pk, grandchild.pk)
def test_get_deepest_child_parent_to_child(self):
"""Parent -> Child (start from non-root)."""
with temporary_inheritance_models() as (_GrandParent, Parent, Child, _GrandChild):
child = Child.objects.create()
parent = Parent.objects.get(pk=child.pk)
resolved = get_deepest_child(parent)
self.assertIsInstance(resolved, Child)
self.assertEqual(resolved.pk, child.pk)
def test_get_deepest_child_no_queries_with_preloaded_relations(self):
"""No extra queries when the inheritance chain is fully select_related."""
with temporary_inheritance_models() as (GrandParent, _Parent, _Child, GrandChild):
grandchild = GrandChild.objects.create()
grandparent = GrandParent.objects.select_related("parent__child__grandchild").get(
pk=grandchild.pk
)
with self.assertNumQueries(0):
resolved = get_deepest_child(grandparent)
self.assertIsInstance(resolved, GrandChild)

View File

@@ -0,0 +1,41 @@
from django.db.models import Model, OneToOneField, OneToOneRel
def get_deepest_child(parent: Model) -> Model:
"""
In multiple table inheritance, given any ancestor object, get the deepest child object.
See https://docs.djangoproject.com/en/dev/topics/db/models/#multi-table-inheritance
This function does not query the database if `select_related` has been performed on all
subclasses of `parent`'s model.
"""
# Almost verbatim copy from django-model-utils, see
# https://github.com/jazzband/django-model-utils/blob/5.0.0/model_utils/managers.py#L132
one_to_one_rels = [
field for field in parent._meta.get_fields() if isinstance(field, OneToOneRel)
]
submodel_fields = [
rel
for rel in one_to_one_rels
if isinstance(rel.field, OneToOneField)
and issubclass(rel.field.model, parent._meta.model)
and parent._meta.model is not rel.field.model
and rel.parent_link
]
submodel_accessors = [submodel_field.get_accessor_name() for submodel_field in submodel_fields]
# End Copy
child = None
for submodel in submodel_accessors:
try:
child = getattr(parent, submodel)
break
except AttributeError:
continue
if not child:
return parent
return get_deepest_child(child)

View File

@@ -7,7 +7,6 @@ from socket import gethostname
from typing import Any
from urllib.parse import urlparse
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.core.cache import cache
from django.utils.translation import gettext_lazy as _
@@ -159,7 +158,7 @@ def outpost_send_update(pk: Any):
layer = get_channel_layer()
group = build_outpost_group(outpost.pk)
LOGGER.debug("sending update", channel=group, outpost=outpost)
async_to_sync(layer.group_send)(group, {"type": "event.update"})
layer.group_send_blocking(group, {"type": "event.update"})
@actor(description=_("Checks the local environment and create Service connections."))
@@ -210,7 +209,7 @@ def outpost_session_end(session_id: str):
for outpost in Outpost.objects.all():
LOGGER.info("Sending session end signal to outpost", outpost=outpost)
group = build_outpost_group(outpost.pk)
async_to_sync(layer.group_send)(
layer.group_send_blocking(
group,
{
"type": "event.session.end",

View File

@@ -2,6 +2,7 @@
from base64 import b64encode
from json import loads
from urllib.parse import quote
from django.urls import reverse
@@ -96,3 +97,16 @@ class TesOAuth2DeviceBackchannel(OAuthTestCase):
self.assertEqual(res.status_code, 200)
body = loads(res.content.decode())
self.assertEqual(body["expires_in"], 60)
def test_backchannel_client_id_via_auth_header_urlencoded(self):
"""Test URL-encoded client IDs in Basic auth"""
self.provider.client_id = "test/client+id"
self.provider.save()
creds = b64encode(f"{quote(self.provider.client_id, safe='')}:".encode()).decode()
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
HTTP_AUTHORIZATION=f"Basic {creds}",
)
self.assertEqual(res.status_code, 200)
body = loads(res.content.decode())
self.assertEqual(body["expires_in"], 60)

View File

@@ -2,6 +2,7 @@
from base64 import b64encode
from json import dumps
from urllib.parse import quote
from django.test import RequestFactory
from django.urls import reverse
@@ -28,6 +29,7 @@ from authentik.providers.oauth2.models import (
ScopeMapping,
)
from authentik.providers.oauth2.tests.utils import OAuthTestCase
from authentik.providers.oauth2.utils import extract_client_auth
from authentik.providers.oauth2.views.token import TokenParams
@@ -115,6 +117,20 @@ class TestToken(OAuthTestCase):
params = TokenParams.parse(request, provider, provider.client_id, provider.client_secret)
self.assertEqual(params.provider, provider)
def test_extract_client_auth_basic_auth_percent_decodes(self):
"""test percent-decoding of client credentials in Basic auth"""
header = b64encode(
f"{quote('client/id', safe='')}:{quote('secret+/==', safe='')}".encode()
).decode()
request = self.factory.post("/", HTTP_AUTHORIZATION=f"Basic {header}")
self.assertEqual(extract_client_auth(request), ("client/id", "secret+/=="))
def test_extract_client_auth_basic_auth_preserves_raw_plus(self):
"""test compatibility with clients that still send raw plus characters"""
header = b64encode(b"client:secret+plus").decode()
request = self.factory.post("/", HTTP_AUTHORIZATION=f"Basic {header}")
self.assertEqual(extract_client_auth(request), ("client", "secret+plus"))
def test_auth_code_view(self):
"""test request param"""
provider = OAuth2Provider.objects.create(

View File

@@ -2,6 +2,7 @@
from base64 import b64encode
from json import loads
from urllib.parse import quote
from django.test import RequestFactory
from django.urls import reverse
@@ -178,6 +179,41 @@ class TestTokenClientCredentialsStandardCompat(OAuthTestCase):
self.assertEqual(jwt["given_name"], self.user.name)
self.assertEqual(jwt["preferred_username"], self.user.username)
def test_successful_basic_auth_urlencoded_client_secret(self):
"""test successful with URL-encoded Basic auth credentials"""
client_secret = b64encode(f"sa:{self.token.key}".encode()).decode()
header = b64encode(
f"{quote(self.provider.client_id, safe='')}:{quote(client_secret, safe='')}".encode()
).decode()
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
{
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
},
HTTP_AUTHORIZATION=f"Basic {header}",
)
self.assertEqual(response.status_code, 200)
body = loads(response.content.decode())
self.assertEqual(body["token_type"], TOKEN_TYPE)
_, alg = self.provider.jwt_key
jwt = decode(
body["access_token"],
key=self.provider.signing_key.public_key,
algorithms=[alg],
audience=self.provider.client_id,
)
self.assertEqual(jwt["given_name"], self.user.name)
self.assertEqual(jwt["preferred_username"], self.user.username)
jwt = decode(
body["id_token"],
key=self.provider.signing_key.public_key,
algorithms=[alg],
audience=self.provider.client_id,
)
self.assertEqual(jwt["given_name"], self.user.name)
self.assertEqual(jwt["preferred_username"], self.user.username)
def test_successful_password(self):
"""test successful (password grant)"""
response = self.client.post(

View File

@@ -7,7 +7,7 @@ from binascii import Error
from hashlib import sha256
from hmac import compare_digest
from typing import Any
from urllib.parse import urlparse
from urllib.parse import unquote, urlparse
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.http.response import HttpResponseRedirect
@@ -122,6 +122,10 @@ def extract_client_auth(request: HttpRequest) -> tuple[str, str]:
try:
user_pass = b64decode(b64_user_pass).decode("utf-8").partition(":")
client_id, _, client_secret = user_pass
# RFC 6749 requires client credentials in Basic auth to be form-encoded first.
# We only percent-decode here so raw `+` characters keep their previous meaning.
client_id = unquote(client_id)
client_secret = unquote(client_secret)
except ValueError, Error:
client_id = client_secret = "" # nosec
else:

View File

@@ -1,6 +1,5 @@
"""proxy provider tasks"""
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.utils.translation import gettext_lazy as _
from dramatiq.actor import actor
@@ -16,7 +15,7 @@ def proxy_on_logout(session_id: str):
hashed_session_id = hash_session_key(session_id)
for outpost in Outpost.objects.filter(type=OutpostType.PROXY):
group = build_outpost_group(outpost.pk)
async_to_sync(layer.group_send)(
layer.group_send_blocking(
group,
{
"type": "event.provider.specific",

View File

@@ -10,6 +10,7 @@ from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application, Group, User
from authentik.lib.generators import generate_id
from authentik.providers.scim.models import SCIMMapping, SCIMProvider, SCIMProviderGroup
from authentik.providers.scim.tasks import scim_sync
class SCIMGroupTests(TestCase):
@@ -205,3 +206,80 @@ class SCIMGroupTests(TestCase):
self.assertEqual(mock.request_history[1].method, "POST")
self.assertEqual(mock.request_history[2].method, "GET")
self.assertNotIn("PUT", [req.method for req in mock.request_history])
def _create_stale_provider_group(self, scim_id: str) -> Group:
"""Create a group that is outside the provider's scope (via group_filters) with an
existing SCIMProviderGroup, simulating a previously synced group now out of scope."""
self.app.backchannel_providers.remove(self.provider)
anchor = Group.objects.create(name=generate_id())
stale = Group.objects.create(name=generate_id())
self.app.backchannel_providers.add(self.provider)
self.provider.group_filters.set([anchor])
SCIMProviderGroup.objects.create(provider=self.provider, group=stale, scim_id=scim_id)
return stale
@Mocker()
def test_sync_cleanup_stale_group_delete(self, mock: Mocker):
"""Stale (out-of-scope) groups are deleted during full sync cleanup"""
scim_id = generate_id()
mock.get("https://localhost/ServiceProviderConfig", json={})
mock.post("https://localhost/Groups", json={"id": generate_id()})
mock.delete(f"https://localhost/Groups/{scim_id}", status_code=204)
self._create_stale_provider_group(scim_id)
scim_sync.send(self.provider.pk).get_result()
delete_reqs = [r for r in mock.request_history if r.method == "DELETE"]
self.assertEqual(len(delete_reqs), 1)
self.assertEqual(delete_reqs[0].url, f"https://localhost/Groups/{scim_id}")
self.assertFalse(
SCIMProviderGroup.objects.filter(provider=self.provider, scim_id=scim_id).exists()
)
@Mocker()
def test_sync_cleanup_stale_group_not_found(self, mock: Mocker):
"""Stale group cleanup handles 404 from the remote gracefully"""
scim_id = generate_id()
mock.get("https://localhost/ServiceProviderConfig", json={})
mock.post("https://localhost/Groups", json={"id": generate_id()})
mock.delete(f"https://localhost/Groups/{scim_id}", status_code=404)
self._create_stale_provider_group(scim_id)
scim_sync.send(self.provider.pk).get_result()
delete_reqs = [r for r in mock.request_history if r.method == "DELETE"]
self.assertEqual(len(delete_reqs), 1)
self.assertFalse(
SCIMProviderGroup.objects.filter(provider=self.provider, scim_id=scim_id).exists()
)
@Mocker()
def test_sync_cleanup_stale_group_transient_error(self, mock: Mocker):
"""Stale group cleanup logs and retries on transient HTTP errors"""
scim_id = generate_id()
mock.get("https://localhost/ServiceProviderConfig", json={})
mock.post("https://localhost/Groups", json={"id": generate_id()})
mock.delete(f"https://localhost/Groups/{scim_id}", status_code=429)
self._create_stale_provider_group(scim_id)
scim_sync.send(self.provider.pk)
delete_reqs = [r for r in mock.request_history if r.method == "DELETE"]
self.assertEqual(len(delete_reqs), 1)
@Mocker()
def test_sync_cleanup_stale_group_dry_run(self, mock: Mocker):
"""Stale group cleanup skips HTTP DELETE in dry_run mode"""
self.provider.dry_run = True
self.provider.save()
scim_id = generate_id()
mock.get("https://localhost/ServiceProviderConfig", json={})
self._create_stale_provider_group(scim_id)
scim_sync.send(self.provider.pk)
delete_reqs = [r for r in mock.request_history if r.method == "DELETE"]
self.assertEqual(len(delete_reqs), 0)

View File

@@ -1,17 +1,19 @@
"""SCIM User tests"""
from json import loads
from unittest.mock import patch
from django.test import TestCase
from jsonschema import validate
from requests_mock import Mocker
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application, Group, User
from authentik.core.models import Application, Group, User, UserTypes
from authentik.lib.generators import generate_id
from authentik.lib.sync.outgoing.base import SAFE_METHODS
from authentik.lib.sync.outgoing.exceptions import TransientSyncException
from authentik.providers.scim.models import SCIMMapping, SCIMProvider, SCIMProviderUser
from authentik.providers.scim.tasks import scim_sync, scim_sync_objects
from authentik.providers.scim.tasks import scim_sync, scim_sync_objects, sync_tasks
from authentik.tasks.models import Task
from authentik.tenants.models import Tenant
@@ -537,3 +539,104 @@ class SCIMUserTests(TestCase):
self.assertEqual(mock.call_count, 2)
self.assertEqual(mock.request_history[0].method, "GET")
self.assertEqual(mock.request_history[1].method, "POST")
def _create_stale_provider_user(self, scim_id: str, uid: str) -> User:
"""Create a service-account user (excluded from provider scope) with an existing
SCIMProviderUser, simulating a previously synced user that is now out of scope."""
user = User.objects.create(
username=uid,
name=f"{uid} {uid}",
email=f"{uid}@goauthentik.io",
type=UserTypes.SERVICE_ACCOUNT,
)
SCIMProviderUser.objects.create(provider=self.provider, user=user, scim_id=scim_id)
return user
@Mocker()
def test_sync_cleanup_stale_user_delete(self, mock: Mocker):
"""Stale (out-of-scope) users are deleted during full sync cleanup"""
scim_id = generate_id()
uid = generate_id()
mock.get("https://localhost/ServiceProviderConfig", json={})
mock.delete(f"https://localhost/Users/{scim_id}", status_code=204)
self._create_stale_provider_user(scim_id, uid)
scim_sync.send(self.provider.pk).get_result()
delete_reqs = [r for r in mock.request_history if r.method == "DELETE"]
self.assertEqual(len(delete_reqs), 1)
self.assertEqual(delete_reqs[0].url, f"https://localhost/Users/{scim_id}")
self.assertFalse(
SCIMProviderUser.objects.filter(provider=self.provider, scim_id=scim_id).exists()
)
@Mocker()
def test_sync_cleanup_stale_user_not_found(self, mock: Mocker):
"""Stale user cleanup handles 404 from the remote gracefully"""
scim_id = generate_id()
uid = generate_id()
mock.get("https://localhost/ServiceProviderConfig", json={})
mock.delete(f"https://localhost/Users/{scim_id}", status_code=404)
self._create_stale_provider_user(scim_id, uid)
scim_sync.send(self.provider.pk).get_result()
delete_reqs = [r for r in mock.request_history if r.method == "DELETE"]
self.assertEqual(len(delete_reqs), 1)
self.assertFalse(
SCIMProviderUser.objects.filter(provider=self.provider, scim_id=scim_id).exists()
)
@Mocker()
def test_sync_cleanup_stale_user_transient_error(self, mock: Mocker):
"""Stale user cleanup logs and retries on transient HTTP errors"""
scim_id = generate_id()
uid = generate_id()
mock.get("https://localhost/ServiceProviderConfig", json={})
mock.delete(f"https://localhost/Users/{scim_id}", status_code=429)
self._create_stale_provider_user(scim_id, uid)
scim_sync.send(self.provider.pk)
delete_reqs = [r for r in mock.request_history if r.method == "DELETE"]
self.assertEqual(len(delete_reqs), 1)
@Mocker()
def test_sync_cleanup_stale_user_dry_run(self, mock: Mocker):
"""Stale user cleanup skips HTTP DELETE in dry_run mode"""
self.provider.dry_run = True
self.provider.save()
scim_id = generate_id()
uid = generate_id()
mock.get("https://localhost/ServiceProviderConfig", json={})
self._create_stale_provider_user(scim_id, uid)
scim_sync.send(self.provider.pk)
delete_reqs = [r for r in mock.request_history if r.method == "DELETE"]
self.assertEqual(len(delete_reqs), 0)
def test_sync_cleanup_client_for_model_transient(self):
"""Cleanup silently skips an object type when client_for_model raises
TransientSyncException"""
with Mocker() as mock:
mock.get("https://localhost/ServiceProviderConfig", json={})
with patch.object(
SCIMProvider,
"client_for_model",
side_effect=TransientSyncException("connection failed"),
):
scim_sync.send(self.provider.pk).get_result()
def test_sync_transient_exception(self):
"""TransientSyncException in _sync_cleanup is caught by sync() which then
schedules a retry"""
with Mocker() as mock:
mock.get("https://localhost/ServiceProviderConfig", json={})
with patch.object(
sync_tasks,
"_sync_cleanup",
side_effect=TransientSyncException("connection failed"),
):
scim_sync.send(self.provider.pk)

View File

@@ -89,7 +89,7 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover
sentry_init()
self.logger.debug("Test environment configured")
use_test_broker()
self.task_broker = use_test_broker()
# Send startup signals
pre_startup.send(sender=self, mode="test")
@@ -185,7 +185,9 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover
self.logger.info("Running tests", test_files=self.args)
with patch("guardian.shortcuts._get_ct_cached", patched__get_ct_cached):
try:
return pytest.main(self.args)
except Exception as e: # noqa
self.logger.error("Error running tests", error=str(e), test_files=self.args)
ret = pytest.main(self.args)
self.task_broker.close()
return ret
except Exception as exc: # noqa
self.logger.error("Error running tests", exc=exc, test_files=self.args)
return 1

View File

@@ -14,6 +14,7 @@ from django.utils.translation import gettext_lazy as _
from ldap3 import ALL, NONE, RANDOM, Connection, Server, ServerPool, Tls
from ldap3.core.exceptions import LDAPException, LDAPInsufficientAccessRightsResult, LDAPSchemaError
from rest_framework.serializers import Serializer
from structlog.stdlib import get_logger
from authentik.core.models import (
Group,
@@ -31,6 +32,7 @@ from authentik.tasks.schedules.common import ScheduleSpec
LDAP_TIMEOUT = 15
LDAP_UNIQUENESS = "ldap_uniq"
LDAP_DISTINGUISHED_NAME = "distinguishedName"
LOGGER = get_logger()
def flatten(value: Any) -> Any:
@@ -268,6 +270,7 @@ class LDAPSource(IncomingSyncSource):
)
if self.start_tls:
LOGGER.debug("Connection StartTLS", source=self)
conn.start_tls(read_server_info=False)
try:
successful = conn.bind()
@@ -278,7 +281,9 @@ class LDAPSource(IncomingSyncSource):
# See https://github.com/goauthentik/authentik/issues/4590
# See also https://github.com/goauthentik/authentik/issues/3399
if server_kwargs.get("get_info", ALL) == NONE:
LOGGER.warning("Failed to connect after schema downgrade", source=self, exc=exc)
raise exc
LOGGER.warning("Downgrading connection to no schema info", source=self, exc=exc)
server_kwargs["get_info"] = NONE
return self.connection(server, server_kwargs, connection_kwargs)
finally:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -99,6 +99,7 @@ class IdentificationChallenge(Challenge):
password_fields = BooleanField()
allow_show_password = BooleanField(default=False)
application_pre = CharField(required=False)
application_pre_launch = CharField(required=False)
flow_designation = ChoiceField(FlowDesignation.choices)
captcha_stage = CaptchaChallenge(required=False, allow_null=True)
@@ -348,9 +349,12 @@ class IdentificationStageView(ChallengeStageView):
# If the user has been redirected to us whilst trying to access an
# application, PLAN_CONTEXT_APPLICATION is set in the flow plan
if PLAN_CONTEXT_APPLICATION in self.executor.plan.context:
challenge.initial_data["application_pre"] = self.executor.plan.context.get(
app: Application = self.executor.plan.context.get(
PLAN_CONTEXT_APPLICATION, Application()
).name
)
challenge.initial_data["application_pre"] = app.name
if launch_url := app.get_launch_url():
challenge.initial_data["application_pre_launch"] = launch_url
if (
PLAN_CONTEXT_DEVICE in self.executor.plan.context
and PLAN_CONTEXT_DEVICE_AUTH_TOKEN in self.executor.plan.context

View File

@@ -1,44 +0,0 @@
from signal import pause
from structlog.stdlib import get_logger
from authentik.lib.config import CONFIG
LOGGER = get_logger()
def worker_healthcheck():
import authentik.tasks.setup # noqa
from authentik.tasks.middleware import WorkerHealthcheckMiddleware
host, _, port = CONFIG.get("listen.http").rpartition(":")
try:
port = int(port)
except ValueError:
LOGGER.error(f"Invalid port entered: {port}")
WorkerHealthcheckMiddleware.run(host, port)
pause()
def worker_status():
import authentik.tasks.setup # noqa
from authentik.tasks.middleware import WorkerStatusMiddleware
WorkerStatusMiddleware.run()
def worker_metrics():
import authentik.tasks.setup # noqa
from authentik.tasks.middleware import MetricsMiddleware
addr, _, port = CONFIG.get("listen.metrics").rpartition(":")
try:
port = int(port)
except ValueError:
LOGGER.error(f"Invalid port entered: {port}")
MetricsMiddleware.run(addr, port)
pause()

View File

@@ -1,29 +1,37 @@
import socket
from collections.abc import Callable
from http.server import BaseHTTPRequestHandler
from time import sleep
from threading import Event as TEvent
from threading import Thread, current_thread
from typing import Any, cast
import pglock
from django.db import OperationalError, connections
from django.db import OperationalError, connections, transaction
from django.utils.timezone import now
from django_dramatiq_postgres.middleware import (
CurrentTask as BaseCurrentTask,
)
from django_dramatiq_postgres.middleware import HTTPServer
from django_dramatiq_postgres.middleware import (
HTTPServer,
HTTPServerThread,
)
from django_dramatiq_postgres.middleware import (
MetricsMiddleware as BaseMetricsMiddleware,
)
from django_dramatiq_postgres.middleware import (
_MetricsHandler as BaseMetricsHandler,
)
from dramatiq import Worker
from dramatiq.broker import Broker
from dramatiq.message import Message
from dramatiq.middleware import Middleware
from psycopg.errors import Error
from setproctitle import setthreadtitle
from structlog.stdlib import get_logger
from authentik import authentik_full_version
from authentik.events.models import Event, EventAction
from authentik.lib.config import CONFIG
from authentik.lib.sentry import should_ignore_exception
from authentik.lib.utils.reflection import class_to_path
from authentik.root.monitoring import monitoring_set
@@ -213,17 +221,39 @@ class _healthcheck_handler(BaseHTTPRequestHandler):
class WorkerHealthcheckMiddleware(Middleware):
@property
def forks(self):
from authentik.tasks.forks import worker_healthcheck
thread: HTTPServerThread | None
return [worker_healthcheck]
def __init__(self):
host, _, port = CONFIG.get("listen.http").rpartition(":")
try:
port = int(port)
except ValueError:
LOGGER.error(f"Invalid port entered: {port}")
self.host, self.port = host, port
def after_worker_boot(self, broker: Broker, worker: Worker):
self.thread = HTTPServerThread(
target=WorkerHealthcheckMiddleware.run, args=(self.host, self.port)
)
self.thread.start()
def before_worker_shutdown(self, broker: Broker, worker: Worker):
server = self.thread.server
if server:
server.shutdown()
LOGGER.debug("Stopping WorkerHealthcheckMiddleware")
self.thread.join()
@staticmethod
def run(addr: str, port: int):
setthreadtitle("authentik Worker Healthcheck server")
try:
httpd = HTTPServer((addr, port), _healthcheck_handler)
httpd.serve_forever()
server = HTTPServer((addr, port), _healthcheck_handler)
thread = cast(HTTPServerThread, current_thread())
thread.server = server
server.serve_forever()
except OSError as exc:
get_logger(__name__, type(WorkerHealthcheckMiddleware)).warning(
"Port is already in use, not starting healthcheck server",
@@ -232,36 +262,50 @@ class WorkerHealthcheckMiddleware(Middleware):
class WorkerStatusMiddleware(Middleware):
@property
def forks(self):
from authentik.tasks.forks import worker_status
thread: Thread | None
thread_event: TEvent | None
return [worker_status]
def after_worker_boot(self, broker: Broker, worker: Worker):
self.thread_event = TEvent()
self.thread = Thread(target=WorkerStatusMiddleware.run, args=(self.thread_event,))
self.thread.start()
def before_worker_shutdown(self, broker: Broker, worker: Worker):
self.thread_event.set()
LOGGER.debug("Stopping WorkerStatusMiddleware")
self.thread.join()
@staticmethod
def run():
status = WorkerStatus.objects.create(
hostname=socket.gethostname(),
version=authentik_full_version(),
)
while True:
def run(event: TEvent):
setthreadtitle("authentik Worker status")
with transaction.atomic():
hostname = socket.gethostname()
WorkerStatus.objects.filter(hostname=hostname).delete()
status, _ = WorkerStatus.objects.update_or_create(
hostname=hostname,
version=authentik_full_version(),
)
while not event.is_set():
try:
WorkerStatusMiddleware.keep(status)
WorkerStatusMiddleware.keep(event, status)
except DB_ERRORS: # pragma: no cover
sleep(10)
event.wait(10)
try:
connections.close_all()
except DB_ERRORS:
pass
@staticmethod
def keep(status: WorkerStatus):
def keep(event: TEvent, status: WorkerStatus):
lock_id = f"goauthentik.io/worker/status/{status.pk}"
with pglock.advisory(lock_id, side_effect=pglock.Raise):
while True:
while not event.is_set():
status.refresh_from_db()
old_last_seen = status.last_seen
status.last_seen = now()
status.save(update_fields=("last_seen",))
sleep(30)
if old_last_seen != status.last_seen:
status.save(update_fields=("last_seen",))
event.wait(30)
class _MetricsHandler(BaseMetricsHandler):
@@ -271,10 +315,26 @@ class _MetricsHandler(BaseMetricsHandler):
class MetricsMiddleware(BaseMetricsMiddleware):
thread: HTTPServerThread | None
handler_class = _MetricsHandler
@property
def forks(self):
from authentik.tasks.forks import worker_metrics
def forks(self) -> list[Callable[[], None]]:
return []
return [worker_metrics]
def after_worker_boot(self, broker: Broker, worker: Worker):
addr, _, port = CONFIG.get("listen.metrics").rpartition(":")
try:
port = int(port)
except ValueError:
LOGGER.error(f"Invalid port entered: {port}")
self.thread = HTTPServerThread(target=MetricsMiddleware.run, args=(addr, port))
self.thread.start()
def before_worker_shutdown(self, broker: Broker, worker: Worker):
server = self.thread.server
if server:
server.shutdown()
LOGGER.debug("Stopping MetricsMiddleware")
self.thread.join()

View File

@@ -10,24 +10,26 @@ from dramatiq.results.middleware import Results
from dramatiq.worker import Worker, _ConsumerThread, _WorkerThread
from authentik.tasks.broker import PostgresBroker
from authentik.tasks.middleware import MetricsMiddleware
from authentik.tasks.middleware import WorkerHealthcheckMiddleware
TESTING_QUEUE = "testing"
class TestWorker(Worker):
def __init__(self, queue_name: str, broker: Broker):
def __init__(self, broker: Broker):
super().__init__(broker=broker)
self.work_queue = PriorityQueue()
self.consumers = {
queue_name: _ConsumerThread(
TESTING_QUEUE: _ConsumerThread(
broker=self.broker,
queue_name=queue_name,
queue_name=TESTING_QUEUE,
prefetch=2,
work_queue=self.work_queue,
worker_timeout=1,
),
}
self.consumers[queue_name].consumer = self.broker.consume(
queue_name=queue_name,
self.consumers[TESTING_QUEUE].consumer = self.broker.consume(
queue_name=TESTING_QUEUE,
prefetch=2,
timeout=1,
)
@@ -40,18 +42,29 @@ class TestWorker(Worker):
self.broker.emit_before("worker_boot", self)
self.broker.emit_after("worker_boot", self)
self.broker.emit_after("process_boot")
def process_message(self, message: MessageProxy):
self.work_queue.put(message)
self.consumers[message.queue_name].consumer.in_processing.add(message.message_id)
self.work_queue.put((0, message))
self.consumers[TESTING_QUEUE].consumer.in_processing.add(message.message_id)
self._worker.process_message(message)
class TestBroker(PostgresBroker):
worker: TestWorker | None = None
def start(self):
self.worker = TestWorker(broker=self)
def close(self):
self.emit_before("worker_shutdown", self)
return super().close()
def enqueue(self, *args, **kwargs):
message = super().enqueue(*args, **kwargs)
worker = TestWorker(message.queue_name, broker=self)
worker.process_message(MessageProxy(message))
message = super().enqueue(*args, **kwargs).copy(queue_name=TESTING_QUEUE)
if not self.worker:
return message
self.worker.process_message(MessageProxy(message))
return message
@@ -69,8 +82,8 @@ def use_test_broker():
middleware: Middleware = import_string(middleware_class)(
**middleware_kwargs,
)
if isinstance(middleware, MetricsMiddleware):
continue
if isinstance(middleware, WorkerHealthcheckMiddleware):
middleware.port = 9102
if isinstance(middleware, Retries):
middleware.max_retries = 0
if isinstance(middleware, Results):
@@ -80,4 +93,6 @@ def use_test_broker():
)
broker.add_middleware(middleware)
broker.start()
set_broker(broker)
return broker

View File

@@ -1,10 +1,7 @@
from json import loads
from django.test import TestCase
from django.urls import reverse
from authentik.core.models import Group, User
from authentik.lib.generators import generate_id
from authentik.core.tests.utils import create_test_admin_user
class TestAdminAPI(TestCase):
@@ -12,15 +9,13 @@ class TestAdminAPI(TestCase):
def setUp(self) -> None:
super().setUp()
self.user = User.objects.create(username=generate_id())
self.group = Group.objects.create(name=generate_id(), is_superuser=True)
self.group.users.add(self.user)
self.group.save()
self.user = create_test_admin_user()
self.client.force_login(self.user)
def test_workers(self):
"""Test Workers API"""
response = self.client.get(reverse("authentik_api:tasks_workers"))
self.assertEqual(response.status_code, 200)
body = loads(response.content)
self.assertEqual(len(body), 0)
# Disabled for flakiness
# body = loads(response.content)
# self.assertEqual(len(body), 1)

View File

@@ -0,0 +1,52 @@
from django.test import TestCase
from dramatiq import actor, get_broker
from authentik.tasks.middleware import CurrentTask
from authentik.tasks.models import Task, TaskLog
class TestWorkerMiddleware(TestCase):
def test_task_log(self):
@actor
def test_task():
self = CurrentTask.get_task()
self.info("foo")
test_task.send()
task = Task.objects.filter(actor_name=test_task.actor_name).first()
logs = list(
TaskLog.objects.filter(task=task).order_by("timestamp").values_list("event", flat=True)
)
self.assertEqual(
logs,
[
"Task has been queued",
"Task is being processed",
"foo",
"Task finished processing without errors",
],
)
broker = get_broker()
del broker.actors[test_task.actor_name]
def test_task_exceptions(self):
@actor
def test_task():
raise ValueError("foo")
test_task.send()
task = Task.objects.filter(actor_name=test_task.actor_name).first()
logs = list(
TaskLog.objects.filter(task=task).order_by("timestamp").values_list("event", flat=True)
)
self.assertEqual(
logs,
[
"Task has been queued",
"Task is being processed",
"foo",
],
)
broker = get_broker()
del broker.actors[test_task.actor_name]

View File

@@ -696,6 +696,46 @@
}
}
},
{
"type": "object",
"required": [
"model",
"identifiers"
],
"properties": {
"model": {
"const": "authentik_endpoints_connectors_google_chrome.googlechromeconnector"
},
"id": {
"type": "string"
},
"state": {
"type": "string",
"enum": [
"absent",
"created",
"must_created",
"present"
],
"default": "present"
},
"conditions": {
"type": "array",
"items": {
"type": "boolean"
}
},
"permissions": {
"$ref": "#/$defs/model_authentik_endpoints_connectors_google_chrome.googlechromeconnector_permissions"
},
"attrs": {
"$ref": "#/$defs/model_authentik_endpoints_connectors_google_chrome.googlechromeconnector"
},
"identifiers": {
"$ref": "#/$defs/model_authentik_endpoints_connectors_google_chrome.googlechromeconnector"
}
}
},
{
"type": "object",
"required": [
@@ -5634,6 +5674,10 @@
"authentik_endpoints_connectors_fleet.change_fleetconnector",
"authentik_endpoints_connectors_fleet.delete_fleetconnector",
"authentik_endpoints_connectors_fleet.view_fleetconnector",
"authentik_endpoints_connectors_google_chrome.add_googlechromeconnector",
"authentik_endpoints_connectors_google_chrome.change_googlechromeconnector",
"authentik_endpoints_connectors_google_chrome.delete_googlechromeconnector",
"authentik_endpoints_connectors_google_chrome.view_googlechromeconnector",
"authentik_enterprise.add_license",
"authentik_enterprise.add_licenseusage",
"authentik_enterprise.change_license",
@@ -6770,6 +6814,57 @@
}
}
},
"model_authentik_endpoints_connectors_google_chrome.googlechromeconnector": {
"type": "object",
"properties": {
"connector_uuid": {
"type": "string",
"format": "uuid",
"title": "Connector uuid"
},
"name": {
"type": "string",
"minLength": 1,
"title": "Name"
},
"enabled": {
"type": "boolean",
"title": "Enabled"
},
"credentials": {
"type": "object",
"additionalProperties": true,
"title": "Credentials"
}
},
"required": []
},
"model_authentik_endpoints_connectors_google_chrome.googlechromeconnector_permissions": {
"type": "array",
"items": {
"type": "object",
"required": [
"permission"
],
"properties": {
"permission": {
"type": "string",
"enum": [
"add_googlechromeconnector",
"change_googlechromeconnector",
"delete_googlechromeconnector",
"view_googlechromeconnector"
]
},
"user": {
"type": "integer"
},
"role": {
"type": "string"
}
}
}
},
"model_authentik_lifecycle.lifecycleiteration": {
"type": "object",
"properties": {
@@ -8819,6 +8914,7 @@
"authentik.enterprise.audit",
"authentik.enterprise.endpoints.connectors.agent",
"authentik.enterprise.endpoints.connectors.fleet",
"authentik.enterprise.endpoints.connectors.google_chrome",
"authentik.enterprise.lifecycle",
"authentik.enterprise.policies.unique_password",
"authentik.enterprise.providers.google_workspace",
@@ -8949,6 +9045,7 @@
"authentik_brands.brand",
"authentik_blueprints.blueprintinstance",
"authentik_endpoints_connectors_fleet.fleetconnector",
"authentik_endpoints_connectors_google_chrome.googlechromeconnector",
"authentik_lifecycle.lifecyclerule",
"authentik_lifecycle.lifecycleiteration",
"authentik_lifecycle.review",
@@ -11226,6 +11323,10 @@
"authentik_endpoints_connectors_fleet.change_fleetconnector",
"authentik_endpoints_connectors_fleet.delete_fleetconnector",
"authentik_endpoints_connectors_fleet.view_fleetconnector",
"authentik_endpoints_connectors_google_chrome.add_googlechromeconnector",
"authentik_endpoints_connectors_google_chrome.change_googlechromeconnector",
"authentik_endpoints_connectors_google_chrome.delete_googlechromeconnector",
"authentik_endpoints_connectors_google_chrome.view_googlechromeconnector",
"authentik_enterprise.add_license",
"authentik_enterprise.add_licenseusage",
"authentik_enterprise.change_license",

2
go.mod
View File

@@ -30,7 +30,7 @@ require (
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2026020.17-0.20260223141659-4c1444ee54d9
goauthentik.io/api/v3 v3.2026020.17-0.20260304104333-840924fe52c4
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.35.0
golang.org/x/sync v0.19.0

2
go.sum
View File

@@ -218,6 +218,8 @@ goauthentik.io/api/v3 v3.2026020.17-0.20260217173516-3a500f6eed7d h1:Gb26L41O+Q7
goauthentik.io/api/v3 v3.2026020.17-0.20260217173516-3a500f6eed7d/go.mod h1:uYa+yGMglhJy8ymyUQ8KQiJjOb3UZTuPQ24Ot2s9BCo=
goauthentik.io/api/v3 v3.2026020.17-0.20260223141659-4c1444ee54d9 h1:tuvgm4e1nV0ZPZy24wOeJcuAbMnhbJA09BuI2fzBHRk=
goauthentik.io/api/v3 v3.2026020.17-0.20260223141659-4c1444ee54d9/go.mod h1:uYa+yGMglhJy8ymyUQ8KQiJjOb3UZTuPQ24Ot2s9BCo=
goauthentik.io/api/v3 v3.2026020.17-0.20260304104333-840924fe52c4 h1:zjmi1QNVQPABt0Yx5hws1lXR3tuTI23Ae7MwXffbP/s=
goauthentik.io/api/v3 v3.2026020.17-0.20260304104333-840924fe52c4/go.mod h1:uYa+yGMglhJy8ymyUQ8KQiJjOb3UZTuPQ24Ot2s9BCo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=

View File

@@ -26,7 +26,6 @@ import (
"goauthentik.io/api/v3"
"goauthentik.io/internal/config"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/proxyv2/constants"
"goauthentik.io/internal/outpost/proxyv2/hs256"
"goauthentik.io/internal/outpost/proxyv2/metrics"
"goauthentik.io/internal/outpost/proxyv2/templates"
@@ -294,22 +293,16 @@ func (a *Application) Stop() {
func (a *Application) handleSignOut(rw http.ResponseWriter, r *http.Request) {
redirect := a.endpoint.EndSessionEndpoint
s, err := a.sessions.Get(r, a.SessionName())
if err != nil {
cc := a.getClaimsFromSession(rw, r)
if cc == nil {
a.redirectToStart(rw, r)
return
}
c, exists := s.Values[constants.SessionClaims]
if c == nil && !exists {
a.redirectToStart(rw, r)
return
}
cc := c.(types.Claims)
uv := url.Values{
"id_token_hint": []string{cc.RawToken},
}
redirect += "?" + uv.Encode()
err = a.Logout(r.Context(), func(c types.Claims) bool {
err := a.Logout(r.Context(), func(c types.Claims) bool {
return c.Sub == cc.Sub
})
if err != nil {

View File

@@ -187,10 +187,7 @@ func BuildConnConfig(cfg config.PostgreSQLConfig) (*pgx.ConnConfig, error) {
if connConfig.RuntimeParams == nil {
connConfig.RuntimeParams = make(map[string]string)
}
if cfg.DefaultSchema != "" {
connConfig.RuntimeParams["search_path"] = cfg.DefaultSchema
}
effectiveSearchPath := cfg.DefaultSchema
// Parse and apply connection options if specified
if cfg.ConnOptions != "" {
@@ -198,12 +195,39 @@ func BuildConnConfig(cfg config.PostgreSQLConfig) (*pgx.ConnConfig, error) {
if err != nil {
return nil, fmt.Errorf("failed to parse connection options: %w", err)
}
// search_path from ConnOptions is not supported here; Django controls schema selection.
// Always remove it so it cannot end up in startup RuntimeParams via applyConnOptions.
delete(connOpts, "search_path")
if err := applyConnOptions(connConfig, connOpts); err != nil {
return nil, fmt.Errorf("failed to apply connection options: %w", err)
}
}
// search_path may already be present via pgx/libpq inherited defaults (e.g. service files).
// Always remove it from startup RuntimeParams; apply it via AfterConnect instead.
if inheritedSearchPath, hasInheritedSearchPath := connConfig.RuntimeParams["search_path"]; hasInheritedSearchPath {
if effectiveSearchPath == "" {
effectiveSearchPath = inheritedSearchPath
}
delete(connConfig.RuntimeParams, "search_path")
}
// Set search_path after connection startup to avoid startup-parameter issues with PgBouncer.
if effectiveSearchPath != "" {
connConfig.AfterConnect = func(ctx context.Context, pgConn *pgconn.PgConn) error {
result := pgConn.ExecParams(
ctx,
"select pg_catalog.set_config('search_path', $1, false)",
[][]byte{[]byte(effectiveSearchPath)},
nil,
nil,
nil,
).Read()
return result.Err
}
}
return connConfig, nil
}

View File

@@ -700,7 +700,7 @@ func TestBuildConnConfig(t *testing.T) {
DefaultSchema: "custom_schema",
},
validate: func(t *testing.T, cc *pgx.ConnConfig) {
assert.Equal(t, "custom_schema", cc.RuntimeParams["search_path"])
assert.NotNil(t, cc.AfterConnect)
},
},
{
@@ -756,7 +756,7 @@ func TestBuildConnConfig(t *testing.T) {
assert.Equal(t, "admin", cc.User)
assert.Equal(t, "my super secret password!@#", cc.Password)
assert.Equal(t, "production", cc.Database)
assert.Equal(t, "app_schema", cc.RuntimeParams["search_path"])
assert.NotNil(t, cc.AfterConnect)
assert.Equal(t, "authentik", cc.RuntimeParams["application_name"])
},
},
@@ -863,7 +863,7 @@ func TestBuildConnConfig_WithSSLCertificates(t *testing.T) {
assert.Equal(t, "db.example.com", cc.TLSConfig.ServerName)
assert.NotNil(t, cc.TLSConfig.RootCAs)
assert.Len(t, cc.TLSConfig.Certificates, 1)
assert.Equal(t, "app_schema", cc.RuntimeParams["search_path"])
assert.NotNil(t, cc.AfterConnect)
assert.Equal(t, "authentik", cc.RuntimeParams["application_name"])
},
},
@@ -1357,6 +1357,83 @@ func TestBuildConnConfig_WithBase64EncodedConnOptions(t *testing.T) {
}
}
// Verifies DefaultSchema is applied via AfterConnect and never via startup RuntimeParams.
func TestBuildConnConfig_SearchPath_DefaultSchema(t *testing.T) {
cfg := config.PostgreSQLConfig{
Host: "localhost",
Port: "5432",
User: "authentik",
Name: "authentik",
DefaultSchema: "default_schema",
}
connConfig, err := BuildConnConfig(cfg)
require.NoError(t, err)
require.NotNil(t, connConfig.AfterConnect)
_, hasSearchPath := connConfig.RuntimeParams["search_path"]
assert.False(t, hasSearchPath, "search_path should not appear in RuntimeParams")
}
// Verifies ConnOptions search_path is ignored and excluded from startup RuntimeParams.
func TestBuildConnConfig_SearchPath_ConnOptions(t *testing.T) {
cfg := config.PostgreSQLConfig{
Host: "localhost",
Port: "5432",
User: "authentik",
Name: "authentik",
ConnOptions: base64.StdEncoding.EncodeToString([]byte(`{"search_path":"connopt_schema"}`)),
}
connConfig, err := BuildConnConfig(cfg)
require.NoError(t, err)
assert.Nil(t, connConfig.AfterConnect)
_, hasSearchPath := connConfig.RuntimeParams["search_path"]
assert.False(t, hasSearchPath, "search_path should not appear in RuntimeParams")
}
// Verifies ConnOptions search_path does not override DefaultSchema and other conn options still apply.
func TestBuildConnConfig_SearchPath_ConnOptionsIgnoredWhenDefaultSchemaSet(t *testing.T) {
cfg := config.PostgreSQLConfig{
Host: "localhost",
Port: "5432",
User: "authentik",
Name: "authentik",
DefaultSchema: "default_schema",
ConnOptions: base64.StdEncoding.EncodeToString([]byte(`{"search_path":"override_schema","application_name":"authentik-proxy"}`)),
}
connConfig, err := BuildConnConfig(cfg)
require.NoError(t, err)
require.NotNil(t, connConfig.AfterConnect)
assert.Equal(t, "authentik-proxy", connConfig.RuntimeParams["application_name"])
_, hasSearchPath := connConfig.RuntimeParams["search_path"]
assert.False(t, hasSearchPath, "search_path should not appear in RuntimeParams")
}
// Verifies inherited search_path from pgx/libpq defaults is removed from startup RuntimeParams.
func TestBuildConnConfig_SearchPath_InheritedServiceSetting(t *testing.T) {
serviceFile := filepath.Join(t.TempDir(), "pg_service.conf")
err := os.WriteFile(serviceFile, []byte("[authentik-test]\nsearch_path=service_schema\n"), 0o600)
require.NoError(t, err)
t.Setenv("PGSERVICE", "authentik-test")
t.Setenv("PGSERVICEFILE", serviceFile)
cfg := config.PostgreSQLConfig{
Host: "localhost",
Port: "5432",
User: "authentik",
Name: "authentik",
}
connConfig, err := BuildConnConfig(cfg)
require.NoError(t, err)
require.NotNil(t, connConfig.AfterConnect)
_, hasSearchPath := connConfig.RuntimeParams["search_path"]
assert.False(t, hasSearchPath, "search_path should not appear in RuntimeParams")
}
// TestBuildConnConfig_TargetSessionAttrs demonstrates how target_session_attrs
// should be properly handled using pgx's ValidateConnect callback
func TestBuildConnConfig_TargetSessionAttrs(t *testing.T) {

View File

@@ -9,7 +9,7 @@
"version": "0.0.0",
"license": "MIT",
"devDependencies": {
"aws-cdk": "^2.1107.0",
"aws-cdk": "^2.1109.0",
"cross-env": "^10.1.0"
},
"engines": {
@@ -25,9 +25,9 @@
"license": "MIT"
},
"node_modules/aws-cdk": {
"version": "2.1107.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1107.0.tgz",
"integrity": "sha512-7GKCq7p/33Jw+C+Ohwl4LnnKjvI/MzemeNZlTu/Kg8IwuZx5WEXEi32YLOlxbE1JOvleDslCWK5AIkBZ0omx/Q==",
"version": "2.1109.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1109.0.tgz",
"integrity": "sha512-K0jvr5ne9kvDrFfdzbPee/s2rH/iXdGoMHTp/0jaj1qFMOh49RkLWTnURa0sBpJJ0uB2sMzIx7YRmAn55wAy1Q==",
"dev": true,
"license": "Apache-2.0",
"bin": {

View File

@@ -7,7 +7,7 @@
"aws-cfn": "cross-env CI=false cdk synth --version-reporting=false > template.yaml"
},
"devDependencies": {
"aws-cdk": "^2.1107.0",
"aws-cdk": "^2.1109.0",
"cross-env": "^10.1.0"
},
"engines": {

View File

@@ -26,7 +26,7 @@ RUN npm run build && \
npm run build:sfe
# Stage 2: Build go proxy
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.0-trixie@sha256:d0a3e4b733ecc47e92a7e7f0fa141392e5a2349e288470aad1ffd82552da5139 AS go-builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.1-trixie@sha256:ab8c4944b04c6f97c2b5bffce471b7f3d55f2228badc55eae6cce87596d5710b AS go-builder
ARG TARGETOS
ARG TARGETARCH
@@ -78,9 +78,9 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
# Stage 4: Download uv
FROM ghcr.io/astral-sh/uv:0.10.6@sha256:2f2ccd27bbf953ec7a9e3153a4563705e41c852a5e1912b438fc44d88d6cb52c AS uv
FROM ghcr.io/astral-sh/uv:0.10.8@sha256:88234bc9e09c2b2f6d176a3daf411419eb0370d450a08129257410de9cfafd2a AS uv
# Stage 5: Base python image
FROM ghcr.io/goauthentik/fips-python:3.14.3-slim-trixie-fips@sha256:de8ad649ed77baa64c07deb0dba2151e18dcb0408fe6ff37bdef236aabb9a576 AS python-base
FROM ghcr.io/goauthentik/fips-python:3.14.3-slim-trixie-fips@sha256:38c4dd2432580d6337f3d478550e5b50ecb9b40b3d351cb622eb8f7729a8f78d AS python-base
ENV VENV_PATH="/ak-root/.venv" \
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \

View File

@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
# Stage 1: Build
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.0-trixie@sha256:d0a3e4b733ecc47e92a7e7f0fa141392e5a2349e288470aad1ffd82552da5139 AS builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.1-trixie@sha256:ab8c4944b04c6f97c2b5bffce471b7f3d55f2228badc55eae6cce87596d5710b AS builder
ARG TARGETOS
ARG TARGETARCH
@@ -31,7 +31,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
go build -o /go/ldap ./cmd/ldap
# Stage 2: Run
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:7b82e2433395fed1e400120bcd1686de2faba9f59251e19b60dd7dd1ed9efe42
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:4966b9032a123db8b61dc2603eaa6d6290a612273a30190a6250ba0761e10cde
ARG VERSION
ARG GIT_BUILD_HASH

View File

@@ -17,7 +17,7 @@ COPY web .
RUN npm run build-proxy
# Stage 2: Build
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.0-trixie@sha256:d0a3e4b733ecc47e92a7e7f0fa141392e5a2349e288470aad1ffd82552da5139 AS builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.1-trixie@sha256:ab8c4944b04c6f97c2b5bffce471b7f3d55f2228badc55eae6cce87596d5710b AS builder
ARG TARGETOS
ARG TARGETARCH
@@ -47,7 +47,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
go build -o /go/proxy ./cmd/proxy
# Stage 3: Run
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:7b82e2433395fed1e400120bcd1686de2faba9f59251e19b60dd7dd1ed9efe42
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:4966b9032a123db8b61dc2603eaa6d6290a612273a30190a6250ba0761e10cde
ARG VERSION
ARG GIT_BUILD_HASH

View File

@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
# Stage 1: Build
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.0-trixie@sha256:d0a3e4b733ecc47e92a7e7f0fa141392e5a2349e288470aad1ffd82552da5139 AS builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.1-trixie@sha256:ab8c4944b04c6f97c2b5bffce471b7f3d55f2228badc55eae6cce87596d5710b AS builder
ARG TARGETOS
ARG TARGETARCH

View File

@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
# Stage 1: Build
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.0-trixie@sha256:d0a3e4b733ecc47e92a7e7f0fa141392e5a2349e288470aad1ffd82552da5139 AS builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.1-trixie@sha256:ab8c4944b04c6f97c2b5bffce471b7f3d55f2228badc55eae6cce87596d5710b AS builder
ARG TARGETOS
ARG TARGETARCH
@@ -31,7 +31,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
go build -o /go/radius ./cmd/radius
# Stage 2: Run
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:7b82e2433395fed1e400120bcd1686de2faba9f59251e19b60dd7dd1ed9efe42
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:4966b9032a123db8b61dc2603eaa6d6290a612273a30190a6250ba0761e10cde
ARG VERSION
ARG GIT_BUILD_HASH

View File

@@ -42,8 +42,8 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings")
preload_app = True
max_requests = 1000
max_requests_jitter = 50
max_requests = CONFIG.get_int("web.max_requests", 1000)
max_requests_jitter = CONFIG.get_int("web.max_requests_jitter", 50)
logconfig_dict = get_logger_config()

694
package-lock.json generated
View File

@@ -10,11 +10,15 @@
"dependencies": {
"@eslint/js": "^9.39.3",
"@goauthentik/eslint-config": "./packages/eslint-config",
"@goauthentik/logger-js": "./packages/logger-js",
"@goauthentik/prettier-config": "./packages/prettier-config",
"@goauthentik/tsconfig": "./packages/tsconfig",
"@typescript-eslint/eslint-plugin": "^8.56.1",
"@typescript-eslint/parser": "^8.56.1",
"eslint": "^9.39.3",
"npm-run-all": "^4.1.5",
"pino": "^10.3.1",
"pino-pretty": "^13.1.2",
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0",
"typescript": "^5.9.3",
@@ -504,6 +508,10 @@
"resolved": "packages/eslint-config",
"link": true
},
"node_modules/@goauthentik/logger-js": {
"resolved": "packages/logger-js",
"link": true
},
"node_modules/@goauthentik/prettier-config": {
"resolved": "packages/prettier-config",
"link": true
@@ -690,6 +698,12 @@
"node": ">= 8"
}
},
"node_modules/@pinojs/redact": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
"integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==",
"license": "MIT"
},
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -1260,6 +1274,15 @@
"node": ">= 0.4"
}
},
"node_modules/atomic-sleep": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
"integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
"license": "MIT",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@@ -1472,6 +1495,12 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/colorette": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"license": "MIT"
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -1558,6 +1587,15 @@
"node": ">=4.0"
}
},
"node_modules/dateformat": {
"version": "4.6.3",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
"integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -1671,6 +1709,24 @@
"integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==",
"license": "ISC"
},
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/error-ex": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
"integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.2.1"
}
},
"node_modules/es-abstract": {
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz",
@@ -2339,6 +2395,12 @@
"node": ">=0.10.0"
}
},
"node_modules/fast-copy": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.2.tgz",
"integrity": "sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==",
"license": "MIT"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -2357,6 +2419,12 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"license": "MIT"
},
"node_modules/fast-safe-stringify": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
"license": "MIT"
},
"node_modules/fastq": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
@@ -3042,6 +3110,12 @@
"node": ">= 0.4"
}
},
"node_modules/help-me": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
"integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
"license": "MIT"
},
"node_modules/hermes-estree": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
@@ -3057,6 +3131,12 @@
"hermes-estree": "0.25.1"
}
},
"node_modules/hosted-git-info": {
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
"license": "ISC"
},
"node_modules/ignore": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
@@ -3145,6 +3225,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
"license": "MIT"
},
"node_modules/is-async-function": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
@@ -3566,6 +3652,15 @@
"node": ">= 0.4"
}
},
"node_modules/joycon": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
"integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/js-levenshtein-esm": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-2.0.0.tgz",
@@ -3608,6 +3703,12 @@
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"license": "MIT"
},
"node_modules/json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
"license": "MIT"
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -3681,6 +3782,21 @@
"node": ">= 0.8.0"
}
},
"node_modules/load-json-file": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
"integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.1.2",
"parse-json": "^4.0.0",
"pify": "^3.0.0",
"strip-bom": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -3757,6 +3873,14 @@
"node": ">= 0.4"
}
},
"node_modules/memorystream": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
"integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==",
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/minimatch": {
"version": "10.2.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz",
@@ -3811,6 +3935,12 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"license": "MIT"
},
"node_modules/nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
"license": "MIT"
},
"node_modules/node-cache": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz",
@@ -3856,6 +3986,218 @@
"integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
"license": "MIT"
},
"node_modules/normalize-package-data": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
"integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
"license": "BSD-2-Clause",
"dependencies": {
"hosted-git-info": "^2.1.4",
"resolve": "^1.10.0",
"semver": "2 || 3 || 4 || 5",
"validate-npm-package-license": "^3.0.1"
}
},
"node_modules/normalize-package-data/node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"license": "ISC",
"bin": {
"semver": "bin/semver"
}
},
"node_modules/npm-run-all": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz",
"integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^3.2.1",
"chalk": "^2.4.1",
"cross-spawn": "^6.0.5",
"memorystream": "^0.3.1",
"minimatch": "^3.0.4",
"pidtree": "^0.3.0",
"read-pkg": "^3.0.0",
"shell-quote": "^1.6.1",
"string.prototype.padend": "^3.0.0"
},
"bin": {
"npm-run-all": "bin/npm-run-all/index.js",
"run-p": "bin/run-p/index.js",
"run-s": "bin/run-s/index.js"
},
"engines": {
"node": ">= 4"
}
},
"node_modules/npm-run-all/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"license": "MIT",
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/npm-run-all/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/npm-run-all/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/npm-run-all/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/npm-run-all/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"license": "MIT",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/npm-run-all/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"license": "MIT"
},
"node_modules/npm-run-all/node_modules/cross-spawn": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz",
"integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==",
"license": "MIT",
"dependencies": {
"nice-try": "^1.0.4",
"path-key": "^2.0.1",
"semver": "^5.5.0",
"shebang-command": "^1.2.0",
"which": "^1.2.9"
},
"engines": {
"node": ">=4.8"
}
},
"node_modules/npm-run-all/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"license": "MIT",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/npm-run-all/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/npm-run-all/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/npm-run-all/node_modules/path-key": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
"integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/npm-run-all/node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"license": "ISC",
"bin": {
"semver": "bin/semver"
}
},
"node_modules/npm-run-all/node_modules/shebang-command": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
"integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==",
"license": "MIT",
"dependencies": {
"shebang-regex": "^1.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/npm-run-all/node_modules/shebang-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
"integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/npm-run-all/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/npm-run-all/node_modules/which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"which": "bin/which"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -3971,6 +4313,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-exit-leak-free": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
"integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -4056,6 +4407,19 @@
"node": ">=6"
}
},
"node_modules/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==",
"license": "MIT",
"dependencies": {
"error-ex": "^1.3.1",
"json-parse-better-errors": "^1.0.1"
},
"engines": {
"node": ">=4"
}
},
"node_modules/parse5": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
@@ -4104,6 +4468,18 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"license": "MIT"
},
"node_modules/path-type": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
"integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
"license": "MIT",
"dependencies": {
"pify": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -4122,6 +4498,100 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pidtree": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz",
"integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==",
"license": "MIT",
"bin": {
"pidtree": "bin/pidtree.js"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/pino": {
"version": "10.3.1",
"resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz",
"integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==",
"license": "MIT",
"dependencies": {
"@pinojs/redact": "^0.4.0",
"atomic-sleep": "^1.0.0",
"on-exit-leak-free": "^2.1.0",
"pino-abstract-transport": "^3.0.0",
"pino-std-serializers": "^7.0.0",
"process-warning": "^5.0.0",
"quick-format-unescaped": "^4.0.3",
"real-require": "^0.2.0",
"safe-stable-stringify": "^2.3.1",
"sonic-boom": "^4.0.1",
"thread-stream": "^4.0.0"
},
"bin": {
"pino": "bin.js"
}
},
"node_modules/pino-abstract-transport": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz",
"integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==",
"license": "MIT",
"dependencies": {
"split2": "^4.0.0"
}
},
"node_modules/pino-pretty": {
"version": "13.1.3",
"resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz",
"integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==",
"license": "MIT",
"dependencies": {
"colorette": "^2.0.7",
"dateformat": "^4.6.3",
"fast-copy": "^4.0.0",
"fast-safe-stringify": "^2.1.1",
"help-me": "^5.0.0",
"joycon": "^3.1.1",
"minimist": "^1.2.6",
"on-exit-leak-free": "^2.1.0",
"pino-abstract-transport": "^3.0.0",
"pump": "^3.0.0",
"secure-json-parse": "^4.0.0",
"sonic-boom": "^4.0.1",
"strip-json-comments": "^5.0.2"
},
"bin": {
"pino-pretty": "bin.js"
}
},
"node_modules/pino-pretty/node_modules/strip-json-comments": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz",
"integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==",
"license": "MIT",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pino-std-serializers": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz",
"integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==",
"license": "MIT"
},
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
@@ -4200,6 +4670,22 @@
}
}
},
"node_modules/process-warning": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz",
"integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "MIT"
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -4211,6 +4697,16 @@
"react-is": "^16.13.1"
}
},
"node_modules/pump": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -4240,12 +4736,41 @@
],
"license": "MIT"
},
"node_modules/quick-format-unescaped": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
"license": "MIT"
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
"integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==",
"license": "MIT",
"dependencies": {
"load-json-file": "^4.0.0",
"normalize-package-data": "^2.3.2",
"path-type": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/real-require": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
"integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
"license": "MIT",
"engines": {
"node": ">= 12.13.0"
}
},
"node_modules/reflect.getprototypeof": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -4424,6 +4949,31 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/safe-stable-stringify": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/secure-json-parse": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz",
"integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "BSD-3-Clause"
},
"node_modules/segment-sort": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/segment-sort/-/segment-sort-1.0.9.tgz",
@@ -4509,6 +5059,18 @@
"node": ">=8"
}
},
"node_modules/shell-quote": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
@@ -4581,6 +5143,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sonic-boom": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz",
"integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==",
"license": "MIT",
"dependencies": {
"atomic-sleep": "^1.0.0"
}
},
"node_modules/sort-object-keys": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-2.1.0.tgz",
@@ -4617,6 +5188,47 @@
"node": ">=0.10.0"
}
},
"node_modules/spdx-correct": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
"integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
"license": "Apache-2.0",
"dependencies": {
"spdx-expression-parse": "^3.0.0",
"spdx-license-ids": "^3.0.0"
}
},
"node_modules/spdx-exceptions": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
"integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
"license": "CC-BY-3.0"
},
"node_modules/spdx-expression-parse": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
"integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
"license": "MIT",
"dependencies": {
"spdx-exceptions": "^2.1.0",
"spdx-license-ids": "^3.0.0"
}
},
"node_modules/spdx-license-ids": {
"version": "3.0.23",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz",
"integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==",
"license": "CC0-1.0"
},
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"license": "ISC",
"engines": {
"node": ">= 10.x"
}
},
"node_modules/stop-iteration-iterator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
@@ -4703,6 +5315,24 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/string.prototype.padend": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz",
"integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1",
"es-abstract": "^1.23.2",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/string.prototype.repeat": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz",
@@ -4832,6 +5462,18 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"license": "MIT"
},
"node_modules/thread-stream": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz",
"integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==",
"license": "MIT",
"dependencies": {
"real-require": "^0.2.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -5109,6 +5751,16 @@
"node": ">= 4"
}
},
"node_modules/validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
"integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
"license": "Apache-2.0",
"dependencies": {
"spdx-correct": "^3.0.0",
"spdx-expression-parse": "^3.0.0"
}
},
"node_modules/validator": {
"version": "13.15.26",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz",
@@ -5311,9 +5963,45 @@
}
}
},
"packages/logger-js": {
"name": "@goauthentik/logger-js",
"version": "1.0.1",
"license": "MIT",
"devDependencies": {
"@eslint/js": "^9.39.3",
"@goauthentik/prettier-config": "../prettier-config",
"@goauthentik/tsconfig": "../tsconfig",
"@types/node": "^25.3.0",
"@typescript-eslint/eslint-plugin": "^8.56.1",
"@typescript-eslint/parser": "^8.56.1",
"eslint": "^9.39.3",
"pino": "^10.3.1",
"pino-pretty": "^13.1.2",
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.56.1"
},
"engines": {
"node": ">=24",
"npm": ">=11.10.1"
},
"peerDependencies": {
"pino": "^10.3.1",
"pino-pretty": "^13.1.2"
},
"peerDependenciesMeta": {
"pino": {
"optional": true
},
"pino-pretty": {
"optional": true
}
}
},
"packages/prettier-config": {
"name": "@goauthentik/prettier-config",
"version": "3.4.1",
"version": "3.4.3",
"license": "MIT",
"dependencies": {
"format-imports": "^4.0.8"
@@ -5342,11 +6030,11 @@
},
"packages/tsconfig": {
"name": "@goauthentik/tsconfig",
"version": "1.0.5",
"version": "1.0.7",
"license": "MIT",
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
}
}
}

View File

@@ -9,14 +9,21 @@
"lint:spellcheck": "echo 'Skipping spellcheck linting'"
},
"type": "module",
"imports": {
"#logger": "./packages/logger-js/lib/node.js"
},
"dependencies": {
"@eslint/js": "^9.39.3",
"@goauthentik/eslint-config": "./packages/eslint-config",
"@goauthentik/logger-js": "./packages/logger-js",
"@goauthentik/prettier-config": "./packages/prettier-config",
"@goauthentik/tsconfig": "./packages/tsconfig",
"@typescript-eslint/eslint-plugin": "^8.56.1",
"@typescript-eslint/parser": "^8.56.1",
"eslint": "^9.39.3",
"npm-run-all": "^4.1.5",
"pino": "^10.3.1",
"pino-pretty": "^13.1.2",
"prettier": "^3.8.1",
"prettier-plugin-packagejson": "^3.0.0",
"typescript": "^5.9.3",
@@ -30,15 +37,16 @@
"devEngines": {
"runtime": {
"name": "node",
"onFail": "ignore",
"version": "24"
"onFail": "warn",
"version": ">=24"
},
"packageManager": {
"name": "npm",
"version": "11.10.1",
"onFail": "ignore"
"version": ">=11.10.1",
"onFail": "warn"
}
},
"packageManager": "npm@11.11.0+sha512.f36811c4aae1fde639527368ae44c571d050006a608d67a191f195a801a52637a312d259186254aa3a3799b05335b7390539cf28656d18f0591a1125ba35f973",
"prettier": "@goauthentik/prettier-config",
"overrides": {
"format-imports": {

View File

@@ -36,7 +36,7 @@ async def _async_proxy(
) -> Any:
# Must be defined as a function and not a method due to
# https://bugs.python.org/issue38364
layer = obj._get_layer()
layer = obj._get_layer(allow_sync=False)
return await getattr(layer, name)(*args, **kwargs)
@@ -63,7 +63,7 @@ class PostgresChannelLayerLoopProxy:
self._args = args
self._kwargs = kwargs
self._kwargs["channel_layer"] = self
self._layers: dict[asyncio.AbstractEventLoop, PostgresChannelLoopLayer] = {}
self._layers: dict[asyncio.AbstractEventLoop | None, PostgresChannelLoopLayer] = {}
def __getattr__(self, name: str) -> Any:
if name in (
@@ -77,7 +77,7 @@ class PostgresChannelLayerLoopProxy:
):
return functools.partial(_async_proxy, self, name)
else:
return getattr(self._get_layer(), name)
return getattr(self._get_layer(allow_sync=True), name)
def serialize(self, message: dict[str, Any]) -> bytes:
"""Serializes message to a byte string."""
@@ -90,15 +90,23 @@ class PostgresChannelLayerLoopProxy:
m = zlib.decompress(message)
return cast(dict[str, Any], msgpack.unpackb(m, raw=False))
def _get_layer(self) -> PostgresChannelLoopLayer:
loop = asyncio.get_running_loop()
def _get_layer(self, allow_sync: bool) -> PostgresChannelLoopLayer:
try:
loop = asyncio.get_running_loop()
except RuntimeError as exc:
if allow_sync:
# No loop configured, we will only allow sync APIs
loop = None
else:
raise exc
try:
layer = self._layers[loop]
except KeyError:
layer = PostgresChannelLoopLayer(*self._args, **self._kwargs)
self._layers[loop] = layer
_wrap_close(self, loop)
if loop is not None:
_wrap_close(self, loop)
return layer
@@ -235,15 +243,16 @@ class PostgresChannelLoopLayer(BaseChannelLayer):
try:
while True:
message_id, message = await q.get()
if message is None:
async with await self.connection() as conn:
async with conn.cursor() as cursor:
async with await self.connection() as conn:
async with conn.cursor() as cursor:
if message is None:
await cursor.execute(
sql.SQL("""
SELECT {table}.{message}
DELETE
FROM {table}
WHERE {table}.{id} = %s
""").format(
RETURNING {table}.{message}
""").format(
table=sql.Identifier(MESSAGE_TABLE),
id=sql.Identifier("id"),
message=sql.Identifier("message"),
@@ -254,6 +263,18 @@ class PostgresChannelLoopLayer(BaseChannelLayer):
if row is None:
continue
message = row[0]
else:
await cursor.execute(
sql.SQL("""
DELETE
FROM {table}
WHERE {table}.{id} = %s
""").format(
table=sql.Identifier(MESSAGE_TABLE),
id=sql.Identifier("id"),
),
(message_id,),
)
break
except asyncio.CancelledError, TimeoutError, GeneratorExit:
# We assume here that the reason we are cancelled is because the consumer
@@ -383,6 +404,50 @@ class PostgresChannelLoopLayer(BaseChannelLayer):
messages,
)
def group_send_blocking(self, group: str, message: dict[str, Any]) -> None:
"""
Sends a message to the entire group, blocking version.
"""
assert self.require_valid_group_name(group), "Group name not valid" # nosec
group_key = self._group_key(group)
serialized_message = self.channel_layer.serialize(message)
with connections[self.using].cursor() as cursor:
cursor.execute(
sql.SQL("""
SELECT DISTINCT {table}.{channel}
FROM {table}
WHERE {table}.{group_key} = %s
""").format(
table=sql.Identifier(GROUP_CHANNEL_TABLE),
channel=sql.Identifier("channel"),
group_key=sql.Identifier("group_key"),
),
(group_key,),
)
channels = [row[0] for row in cursor.fetchall()]
messages = [
(uuid4(), channel, serialized_message, now() + timedelta(seconds=self.expiry))
for channel in channels
]
with connections[self.using].cursor() as cursor:
cursor.executemany(
sql.SQL("""
INSERT INTO {table}
({id}, {channel}, {message}, {expires})
VALUES (%s, %s, %s, %s)
""").format(
table=sql.Identifier(MESSAGE_TABLE),
id=sql.Identifier("id"),
channel=sql.Identifier("channel"),
message=sql.Identifier("message"),
expires=sql.Identifier("expires"),
),
messages,
)
def _group_key(self, group: str) -> str:
"""
Common function to make the storage key for the group.

View File

@@ -1,5 +1,7 @@
import platform
import sys
from argparse import Namespace
from multiprocessing import set_start_method
from typing import Any
from django.apps.registry import apps
@@ -69,7 +71,10 @@ class Command(BaseCommand):
args.pid_file = pid_file
args.verbose = verbosity - 1
# > On macOS [...] the fork start method should be considered unsafe
# https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods
if not platform.system() == "Darwin":
set_start_method("fork")
connections.close_all()
sys.exit(main(args)) # type: ignore[no-untyped-call]

View File

@@ -5,6 +5,7 @@ from collections.abc import Callable
from http.server import BaseHTTPRequestHandler
from http.server import HTTPServer as BaseHTTPServer
from ipaddress import IPv6Address, ip_address
from threading import Thread, current_thread
from typing import TYPE_CHECKING, Any, cast
from django.db import DatabaseError, close_old_connections, connections
@@ -22,6 +23,13 @@ if TYPE_CHECKING:
from django_dramatiq_postgres.broker import PostgresBroker
class HTTPServerThread(Thread):
"""Base class for a thread which runs an HTTP Server. Mainly used for typing
the `server` instance variable."""
server: HTTPServer | None
class HTTPServer(BaseHTTPServer):
def server_bind(self) -> None:
self.socket.close()
@@ -234,9 +242,6 @@ class MetricsMiddleware(Middleware):
return [worker_metrics]
def before_worker_boot(self, broker: Broker, worker: Any) -> None:
if Conf().test:
return
from prometheus_client import Counter, Gauge, Histogram
self.total_messages = Counter(
@@ -353,6 +358,8 @@ class MetricsMiddleware(Middleware):
def run(cls, addr: str, port: int) -> None:
try:
server = HTTPServer((addr, port), cls.handler_class)
thread = cast(HTTPServerThread, current_thread())
thread.server = server
server.serve_forever()
except OSError:
get_logger(__name__, type(MetricsMiddleware)).warning(

View File

@@ -1,12 +1,12 @@
{
"name": "@goauthentik/docusaurus-config",
"version": "2.5.0",
"version": "2.5.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@goauthentik/docusaurus-config",
"version": "2.5.0",
"version": "2.5.1",
"license": "MIT",
"dependencies": {
"deepmerge-ts": "^7.1.5",
@@ -34,7 +34,7 @@
},
"engines": {
"node": ">=24",
"npm": ">=11.10.0"
"npm": ">=11.10.1"
},
"optionalDependencies": {
"react": ">=18",
@@ -101,7 +101,7 @@
},
"../prettier-config": {
"name": "@goauthentik/prettier-config",
"version": "3.4.1",
"version": "3.4.3",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -131,12 +131,12 @@
},
"../tsconfig": {
"name": "@goauthentik/tsconfig",
"version": "1.0.5",
"version": "1.0.7",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
}
},
"node_modules/@algolia/abtesting": {

View File

@@ -1,6 +1,6 @@
{
"name": "@goauthentik/docusaurus-config",
"version": "2.5.0",
"version": "2.5.1",
"description": "authentik's Docusaurus config",
"license": "MIT",
"repository": {
@@ -68,7 +68,7 @@
],
"engines": {
"node": ">=24",
"npm": ">=11.10.0"
"npm": ">=11.10.1"
},
"devEngines": {
"runtime": {
@@ -78,7 +78,7 @@
},
"packageManager": {
"name": "npm",
"version": "11.10.1",
"version": ">=11.10.1",
"onFail": "warn"
}
},

View File

@@ -1,12 +1,12 @@
{
"name": "@goauthentik/esbuild-plugin-live-reload",
"version": "1.6.0",
"version": "1.6.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@goauthentik/esbuild-plugin-live-reload",
"version": "1.6.0",
"version": "1.6.1",
"license": "MIT",
"dependencies": {
"find-free-ports": "^3.1.1"
@@ -31,7 +31,7 @@
},
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
},
"peerDependencies": {
"esbuild": "^0.27.3"
@@ -79,7 +79,7 @@
},
"../prettier-config": {
"name": "@goauthentik/prettier-config",
"version": "3.4.1",
"version": "3.4.3",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -109,12 +109,12 @@
},
"../tsconfig": {
"name": "@goauthentik/tsconfig",
"version": "1.0.5",
"version": "1.0.7",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
}
},
"node_modules/@esbuild/aix-ppc64": {

View File

@@ -1,6 +1,6 @@
{
"name": "@goauthentik/esbuild-plugin-live-reload",
"version": "1.6.0",
"version": "1.6.1",
"description": "ESBuild + browser refresh. Build completes, page reloads.",
"license": "MIT",
"repository": {
@@ -72,7 +72,7 @@
],
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
},
"devEngines": {
"runtime": {
@@ -82,7 +82,7 @@
},
"packageManager": {
"name": "npm",
"version": "11.10.1",
"version": ">=11.10.1",
"onFail": "warn"
}
},

View File

@@ -45,7 +45,7 @@
},
"../prettier-config": {
"name": "@goauthentik/prettier-config",
"version": "3.4.1",
"version": "3.4.3",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -75,12 +75,12 @@
},
"../tsconfig": {
"name": "@goauthentik/tsconfig",
"version": "1.0.5",
"version": "1.0.7",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
}
},
"node_modules/@babel/code-frame": {

View File

@@ -70,13 +70,13 @@
"devEngines": {
"runtime": {
"name": "node",
"version": "24",
"onFail": "ignore"
"version": ">=24",
"onFail": "warn"
},
"packageManager": {
"name": "npm",
"version": "11.10.1",
"onFail": "ignore"
"version": ">=11.10.1",
"onFail": "warn"
}
},
"prettier": "@goauthentik/prettier-config",

View File

@@ -1,12 +1,12 @@
{
"name": "@goauthentik/prettier-config",
"version": "3.4.1",
"version": "3.4.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@goauthentik/prettier-config",
"version": "3.4.1",
"version": "3.4.2",
"license": "MIT",
"dependencies": {
"format-imports": "^4.0.8"
@@ -75,12 +75,12 @@
},
"../tsconfig": {
"name": "@goauthentik/tsconfig",
"version": "1.0.5",
"version": "1.0.7",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
}
},
"node_modules/@babel/helper-string-parser": {

View File

@@ -1,6 +1,6 @@
{
"name": "@goauthentik/prettier-config",
"version": "3.4.1",
"version": "3.4.3",
"description": "authentik's Prettier config",
"license": "MIT",
"repository": {
@@ -64,13 +64,13 @@
"devEngines": {
"runtime": {
"name": "node",
"version": "24",
"onFail": "ignore"
"version": ">=24",
"onFail": "warn"
},
"packageManager": {
"name": "npm",
"version": "11.10.1",
"onFail": "ignore"
"version": ">=11.10.1",
"onFail": "warn"
}
},
"prettier": "./index.js",

View File

@@ -1,16 +1,16 @@
{
"name": "@goauthentik/tsconfig",
"version": "1.0.5",
"version": "1.0.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@goauthentik/tsconfig",
"version": "1.0.5",
"version": "1.0.7",
"license": "MIT",
"engines": {
"node": ">=24",
"npm": ">=11.6.2"
"npm": ">=11.10.1"
}
}
}

Some files were not shown because too many files have changed in this diff Show More