Compare commits

..

278 Commits

Author SHA1 Message Date
Marc 'risson' Schmitt
04c066d8b0 lint
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-19 19:20:34 +01:00
Marc 'risson' Schmitt
f3341a4b83 Merge branch 'main' into rust-server
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-19 18:46:47 +01:00
Marc 'risson' Schmitt
4dfdf9afa3 root: allow listening on multiple IPs (#20930) 2026-03-19 15:46:47 +00:00
Dominic R
545b1e8f19 website: switch docs analytics to gtag (#20993) 2026-03-19 16:39:58 +01:00
Marc 'risson' Schmitt
27f652dcf3 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-19 16:22:45 +01:00
Marc 'risson' Schmitt
dca2c2f536 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-19 16:10:43 +01:00
Marc 'risson' Schmitt
5d426411dd wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-19 15:59:31 +01:00
Dominic R
763f7f9e64 web: link file picker to docs (#20995) 2026-03-19 10:58:37 -04:00
Marc 'risson' Schmitt
35ec2ea930 remove useless change
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-19 15:58:09 +01:00
Marc 'risson' Schmitt
b7c4d04c16 Merge remote-tracking branch 'origin/multiple-listeners' into rust-server
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-19 15:56:52 +01:00
Oluwatobi Mustapha
a10ec34aec web/flow: reset stale authenticator selection between consecutive validate stages (#20802)
* fix(web): reset stale MFA challenge selection across stages

* Surface API errors in plucked details.

* Clean up error messages, lifecycle, cancel states.

* Address review feedback on base host property and tag resolver

Fix lint and typing for authenticator component resolver

Format authenticator resolver signature

chore: trigger CI rerun after transient npm network failure

* Tidy return value.

---------

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2026-03-19 15:49:49 +01:00
dependabot[bot]
03b23b87e0 ci: bump actions/cache from 5.0.3 to 5.0.4 (#21002)
Bumps [actions/cache](https://github.com/actions/cache) from 5.0.3 to 5.0.4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](cdf6c1fa76...668228422a)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 5.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-19 15:49:09 +01:00
Marc 'risson' Schmitt
8ef1b945e8 lint
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-19 15:47:44 +01:00
Marc 'risson' Schmitt
7fab5b6e93 Merge branch 'main' into multiple-listeners 2026-03-19 14:23:47 +00:00
Marc 'risson' Schmitt
7468a7271c Update website/docs/releases/2026/v2026.5.md
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-19 15:23:30 +01:00
Marc 'risson' Schmitt
1a270f9c6e Apply suggestions from code review
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-19 15:23:01 +01:00
Marc 'risson' Schmitt
3ae126cd99 Update website/docs/install-config/configuration/configuration.mdx
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-19 15:22:42 +01:00
Marc 'risson' Schmitt
894f134893 root: init rust workspace (#20983) 2026-03-19 14:12:00 +00:00
Marc 'risson' Schmitt
6db2fbc8aa wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-19 14:38:19 +01:00
Marc 'risson' Schmitt
32f6738a40 errgroup
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-19 14:33:42 +01:00
Marc 'risson' Schmitt
1ddc596362 better unix listener
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-19 14:22:41 +01:00
Jens L.
25d3d5751e website/docs: fix swapped sidebar label (#21011)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-03-19 13:17:53 +01:00
dependabot[bot]
22d6f91bbc core: bump goauthentik/fips-python from ec5c4cd to 859ad57 in /lifecycle/container (#21003)
core: bump goauthentik/fips-python in /lifecycle/container

Bumps goauthentik/fips-python from `ec5c4cd` to `859ad57`.

---
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-19 13:08:17 +01:00
dependabot[bot]
c49bc9e5a9 core: bump goauthentik/fips-debian from a613b75 to 7baeeaa in /lifecycle/container (#21001)
core: bump goauthentik/fips-debian in /lifecycle/container

Bumps goauthentik/fips-debian from `a613b75` to `7baeeaa`.

---
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-19 13:00:38 +01:00
dependabot[bot]
ba00882385 core: bump djangorestframework from 3.16.1 to 3.17.0 (#21000)
Bumps [djangorestframework](https://github.com/encode/django-rest-framework) from 3.16.1 to 3.17.0.
- [Release notes](https://github.com/encode/django-rest-framework/releases)
- [Commits](https://github.com/encode/django-rest-framework/compare/3.16.1...3.17.0)

---
updated-dependencies:
- dependency-name: djangorestframework
  dependency-version: 3.17.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-19 13:00:27 +01:00
dependabot[bot]
43941a5aba lifecycle/aws: bump aws-cdk from 2.1111.0 to 2.1112.0 in /lifecycle/aws (#20999)
Bumps [aws-cdk](https://github.com/aws/aws-cdk-cli/tree/HEAD/packages/aws-cdk) from 2.1111.0 to 2.1112.0.
- [Release notes](https://github.com/aws/aws-cdk-cli/releases)
- [Commits](https://github.com/aws/aws-cdk-cli/commits/aws-cdk@v2.1112.0/packages/aws-cdk)

---
updated-dependencies:
- dependency-name: aws-cdk
  dependency-version: 2.1112.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-19 12:58:44 +01:00
dependabot[bot]
49c80ee9e6 ci: bump codecov/codecov-action from 5.5.2 to 5.5.3 in /.github/actions/test-results (#21004)
ci: bump codecov/codecov-action in /.github/actions/test-results

Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.5.2 to 5.5.3.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](671740ac38...1af58845a9)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: 5.5.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-19 12:58:24 +01:00
dependabot[bot]
810a479242 web: bump the storybook group across 1 directory with 5 updates (#21005)
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.19 to 10.3.0
- [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.3.0/code/addons/docs)

Updates `@storybook/addon-links` from 10.2.19 to 10.3.0
- [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.3.0/code/addons/links)

Updates `@storybook/web-components` from 10.2.19 to 10.3.0
- [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.3.0/code/renderers/web-components)

Updates `@storybook/web-components-vite` from 10.2.19 to 10.3.0
- [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.3.0/code/frameworks/web-components-vite)

Updates `storybook` from 10.2.19 to 10.3.0
- [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.3.0/code/core)

---
updated-dependencies:
- dependency-name: "@storybook/addon-docs"
  dependency-version: 10.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: storybook
- dependency-name: "@storybook/addon-links"
  dependency-version: 10.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: storybook
- dependency-name: "@storybook/web-components"
  dependency-version: 10.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: storybook
- dependency-name: "@storybook/web-components-vite"
  dependency-version: 10.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: storybook
- dependency-name: storybook
  dependency-version: 10.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  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-19 12:58:11 +01:00
dependabot[bot]
94cd66dd24 web: bump knip from 5.87.0 to 5.88.0 in /web (#21006)
Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 5.87.0 to 5.88.0.
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@5.88.0/packages/knip)

---
updated-dependencies:
- dependency-name: knip
  dependency-version: 5.88.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-19 12:57:48 +01:00
dependabot[bot]
0e60d0a235 web: bump @formatjs/intl-listformat from 8.2.3 to 8.3.1 in /web (#21007)
Bumps [@formatjs/intl-listformat](https://github.com/formatjs/formatjs) from 8.2.3 to 8.3.1.
- [Release notes](https://github.com/formatjs/formatjs/releases)
- [Commits](https://github.com/formatjs/formatjs/compare/@formatjs/intl-listformat@8.2.3...@formatjs/intl-listformat@8.3.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-19 12:57:37 +01:00
Dominic R
31261e12f8 website/docs: update kubernetes install guide for Gateway API (#20961) 2026-03-19 01:46:32 +00:00
Jens L.
b5cfe14606 website/docs: fix notification transport etc (#20982)
* fix mismatched caps

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

* transport rules??

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

* structure

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

* less redundant title

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

* fix label

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-03-19 00:05:44 +01:00
Jens L.
046bc8ac98 web/admin: fix missing OSM referrerPolicy header (#20984)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-03-18 23:09:22 +01:00
Marc 'risson' Schmitt
0c8d07da26 ci: avoid installing unnecessary dependencies for lint (#20981) 2026-03-18 18:11:53 +00:00
Rishabh Dewangan
e6c625a97b providers/oauth2: evaluate property mappings in client credentials JWT flow (#20979)
* fix(providers/oauth2): evaluate property mappings in client credentials JWT flow

* always top level input

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

* clamp username at max length

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

* keep original test

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2026-03-18 15:43:04 +01:00
Marc 'risson' Schmitt
1281371077 Merge branch 'main' into rust-server 2026-03-18 15:29:43 +01:00
Marc 'risson' Schmitt
58508ebc4e Merge branch 'main' into rust-server 2026-03-18 15:29:25 +01:00
Marc 'risson' Schmitt
aa614ad31c lint
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-18 15:22:47 +01:00
Marc 'risson' Schmitt
b9b1c7ccf6 metrics socket and healthchecks for all outposts
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-18 15:17:33 +01:00
Marc 'risson' Schmitt
f8209680fa server healthcheck and unix socket
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-18 14:58:55 +01:00
dependabot[bot]
fa17d66bdd core: bump ujson from 5.11.0 to 5.12.0 (#20980)
Bumps [ujson](https://github.com/ultrajson/ultrajson) from 5.11.0 to 5.12.0.
- [Release notes](https://github.com/ultrajson/ultrajson/releases)
- [Commits](https://github.com/ultrajson/ultrajson/compare/5.11.0...5.12.0)

---
updated-dependencies:
- dependency-name: ujson
  dependency-version: 5.12.0
  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-18 14:49:36 +01:00
Marc 'risson' Schmitt
2b2c6a3b9b Merge branch 'main' into multiple-listeners 2026-03-18 13:43:09 +01:00
dependabot[bot]
9584ceeea2 website: bump the build group in /website with 3 updates (#20963)
* website: bump the build group in /website with 3 updates

Bumps the build group in /website with 3 updates: [@rspack/binding-darwin-arm64](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack), [@rspack/binding-linux-arm64-gnu](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack) and [@rspack/binding-linux-x64-gnu](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack).


Updates `@rspack/binding-darwin-arm64` from 1.7.8 to 1.7.9
- [Release notes](https://github.com/web-infra-dev/rspack/releases)
- [Commits](https://github.com/web-infra-dev/rspack/commits/v1.7.9/packages/rspack)

Updates `@rspack/binding-linux-arm64-gnu` from 1.7.8 to 1.7.9
- [Release notes](https://github.com/web-infra-dev/rspack/releases)
- [Commits](https://github.com/web-infra-dev/rspack/commits/v1.7.9/packages/rspack)

Updates `@rspack/binding-linux-x64-gnu` from 1.7.8 to 1.7.9
- [Release notes](https://github.com/web-infra-dev/rspack/releases)
- [Commits](https://github.com/web-infra-dev/rspack/commits/v1.7.9/packages/rspack)

---
updated-dependencies:
- dependency-name: "@rspack/binding-darwin-arm64"
  dependency-version: 1.7.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@rspack/binding-linux-arm64-gnu"
  dependency-version: 1.7.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@rspack/binding-linux-x64-gnu"
  dependency-version: 1.7.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
...

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

* ts ts ts

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2026-03-18 10:50:23 +01:00
authentik-automation[bot]
989cfe1f88 core: bump goauthentik.io/api/v3 to 3.2026.5.0-rc1-1773774443 (#20955)
* core: bump goauthentik.io/api/v3 to 3.2026.5.0-rc1-1773774443

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

* tidy

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

* tidy

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

---------

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2026-03-18 10:14:48 +01:00
dependabot[bot]
84a1429cf6 ci: bump calibreapp/image-actions from d9c8ee5c3dc52ae4622c82ead88d658f4b16b65f to 03c976c29803442fc4040a9de5509669e7759b81 (#20970)
ci: bump calibreapp/image-actions

Bumps [calibreapp/image-actions](https://github.com/calibreapp/image-actions) from d9c8ee5c3dc52ae4622c82ead88d658f4b16b65f to 03c976c29803442fc4040a9de5509669e7759b81.
- [Release notes](https://github.com/calibreapp/image-actions/releases)
- [Commits](d9c8ee5c3d...03c976c298)

---
updated-dependencies:
- dependency-name: calibreapp/image-actions
  dependency-version: 03c976c29803442fc4040a9de5509669e7759b81
  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-18 10:06:08 +01:00
dependabot[bot]
69b7acbb7a core: bump library/golang from 9c51d8b to 96b2878 in /lifecycle/container (#20972)
core: bump library/golang in /lifecycle/container

Bumps library/golang from `9c51d8b` to `96b2878`.

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-18 10:04:46 +01:00
dependabot[bot]
acaf3d09a8 core: bump library/node from 407d745 to 394048f in /website (#20973)
Bumps library/node from `407d745` to `394048f`.

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-18 10:04:36 +01:00
dependabot[bot]
d60aa804f6 core: bump library/nginx from bc45d24 to dec7a90 in /website (#20974)
Bumps library/nginx from `bc45d24` to `dec7a90`.

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-18 10:04:25 +01:00
dependabot[bot]
1453e327a9 core: bump github.com/go-ldap/ldap/v3 from 3.4.12 to 3.4.13 (#20962)
Bumps [github.com/go-ldap/ldap/v3](https://github.com/go-ldap/ldap) from 3.4.12 to 3.4.13.
- [Release notes](https://github.com/go-ldap/ldap/releases)
- [Commits](https://github.com/go-ldap/ldap/compare/v3.4.12...v3.4.13)

---
updated-dependencies:
- dependency-name: github.com/go-ldap/ldap/v3
  dependency-version: 3.4.13
  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-18 10:02:10 +01:00
dependabot[bot]
51d749eb21 core: bump google-api-python-client from 2.192.0 to 2.193.0 (#20964)
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 2.192.0 to 2.193.0.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v2.192.0...v2.193.0)

---
updated-dependencies:
- dependency-name: google-api-python-client
  dependency-version: 2.193.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-18 10:02:06 +01:00
dependabot[bot]
585266b551 web: bump @sentry/browser from 10.43.0 to 10.44.0 in /web in the sentry group across 1 directory (#20965)
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.43.0 to 10.44.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.43.0...10.44.0)

---
updated-dependencies:
- dependency-name: "@sentry/browser"
  dependency-version: 10.44.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-18 10:02:02 +01:00
dependabot[bot]
1ed8b21191 core: bump django-tenants from 3.10.0 to 3.10.1 (#20966)
Bumps [django-tenants](https://github.com/django-tenants/django-tenants) from 3.10.0 to 3.10.1.
- [Release notes](https://github.com/django-tenants/django-tenants/releases)
- [Changelog](https://github.com/django-tenants/django-tenants/blob/master/CHANGES.rst)
- [Commits](https://github.com/django-tenants/django-tenants/commits)

---
updated-dependencies:
- dependency-name: django-tenants
  dependency-version: 3.10.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-18 10:01:59 +01:00
dependabot[bot]
bad031445d core: bump coverage[toml] from 7.13.4 to 7.13.5 (#20967)
Bumps [coverage[toml]](https://github.com/coveragepy/coveragepy) from 7.13.4 to 7.13.5.
- [Release notes](https://github.com/coveragepy/coveragepy/releases)
- [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst)
- [Commits](https://github.com/coveragepy/coveragepy/compare/7.13.4...7.13.5)

---
updated-dependencies:
- dependency-name: coverage[toml]
  dependency-version: 7.13.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-18 10:01:55 +01:00
dependabot[bot]
13e14f1429 core: bump goauthentik/fips-debian from e06f0fe to a613b75 in /lifecycle/container (#20968)
core: bump goauthentik/fips-debian in /lifecycle/container

Bumps goauthentik/fips-debian from `e06f0fe` to `a613b75`.

---
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-18 10:01:52 +01:00
dependabot[bot]
9225895ced core: bump sentry-sdk from 2.54.0 to 2.55.0 (#20969)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.54.0 to 2.55.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.54.0...2.55.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-version: 2.55.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-18 10:01:48 +01:00
dependabot[bot]
ad2218611f core: bump goauthentik/fips-python from 08bc05d to ec5c4cd in /lifecycle/container (#20971)
core: bump goauthentik/fips-python in /lifecycle/container

Bumps goauthentik/fips-python from `08bc05d` to `ec5c4cd`.

---
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-18 10:01:44 +01:00
Dominic R
056119f901 web: Fix admin table horizontal scrolling (#20960) 2026-03-17 23:40:04 +00:00
dependabot[bot]
ee391b9a76 core: bump pyasn1 from 0.6.2 to 0.6.3 (#20956)
Bumps [pyasn1](https://github.com/pyasn1/pyasn1) from 0.6.2 to 0.6.3.
- [Release notes](https://github.com/pyasn1/pyasn1/releases)
- [Changelog](https://github.com/pyasn1/pyasn1/blob/main/CHANGES.rst)
- [Commits](https://github.com/pyasn1/pyasn1/compare/v0.6.2...v0.6.3)

---
updated-dependencies:
- dependency-name: pyasn1
  dependency-version: 0.6.3
  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-17 21:39:10 +01:00
Marc 'risson' Schmitt
48e1edfaa2 tasks: fix workers API URL missing trailing / (#20954) 2026-03-17 18:55:43 +00:00
Ken Sternberg
a897535998 web: Supply our font and color choices to rapidoc. (#20775)
* Supply our font and color choices to rapidoc.

* Of course prettier has opinions, but one extra linefeed?
2026-03-17 17:26:36 +01:00
Fletcher Heisler
5831a24423 core: redirect service accounts away from main UI like external users (#20900)
* core: redirect service accounts away from main UI like external users

Service account and internal service account users are now redirected
to the brand's default application (or shown access denied) when
accessing the admin/user interfaces, consistent with external user
behavior. Adds interface view tests covering all user types.

* core: fix black formatting in test_interface_views
2026-03-17 12:17:34 -04:00
Jens L.
4a46f6f0c7 website/docs: use full path for sysd on windows (#20951)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-03-17 15:29:11 +01:00
Ken Sternberg
5fae44ff5b web/flow: separate out independent behavior tracks from IdentificationStage (autoredirect, webauthn, captcha, rememberme) (#20578)
* Another attempt.

* Reconstruction the separation-of-concerns build using the newest version of 'main', since the merge was getting weird.

* Added the Flow Executor event handling; stages can now send events to trigger challenge updates or submissions, rather than Demeter violations.

* Captcha Controller is in.  Autoredirect is in and passing.

* Add webauthn; modernize RememberMe

* Webauthn hooked up.

* Prettier has 🚽 opinions sometimes.

* Don't look at me like that, prettier.

* Added comments describing the controllers.

* ## What

At a reviewer’s request, analyzed variable use and moved as many as possible into the JavaScript private (`#`) space. The analysis also showed that the rememberMe function `isValidChallenge` was no longer being used.

Also, for consistency, and to eliminate the confusion that an IdentificationChallenge might also be a PasskeyChallenge, the only place where that abstraction “leaked” was in `IdentificationStage.renderInput()`; by adding an `live` flag to the Webauthn controller I was able to:

    -        const autocomplete: AutoFill = passkeyChallenge ? "username webauthn" : "username";
    +        const autocomplete: AutoFill = this.#webauthn.live ? "username webauthn" : "username";

… and eliminate any PasskeyChallenge typecasting entirely from the IdentificationStage. While this only loosens the coupling a little bit, it emphasizes that the IdentificationStage treats PasskeyChallenge as a peripheral system with its own responsibilities and business logic.

The actual logic is unchanged.

* A ONE LETTER fix

* Typos in comments are embarrassing.
2026-03-17 07:24:44 -07:00
dependabot[bot]
3cd982750f web: bump the storybook group across 1 directory with 5 updates (#20944)
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.17 to 10.2.19
- [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.19/code/addons/docs)

Updates `@storybook/addon-links` from 10.2.17 to 10.2.19
- [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.19/code/addons/links)

Updates `@storybook/web-components` from 10.2.17 to 10.2.19
- [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.19/code/renderers/web-components)

Updates `@storybook/web-components-vite` from 10.2.17 to 10.2.19
- [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.19/code/frameworks/web-components-vite)

Updates `storybook` from 10.2.17 to 10.2.19
- [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.19/code/core)

---
updated-dependencies:
- dependency-name: "@storybook/addon-docs"
  dependency-version: 10.2.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/addon-links"
  dependency-version: 10.2.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components"
  dependency-version: 10.2.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components-vite"
  dependency-version: 10.2.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: storybook
  dependency-version: 10.2.19
  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-17 14:36:15 +01:00
dependabot[bot]
9497f503f8 web: bump chromedriver from 146.0.3 to 146.0.4 in /web (#20945)
* web: bump chromedriver from 146.0.3 to 146.0.4 in /web

Bumps [chromedriver](https://github.com/giggio/node-chromedriver) from 146.0.3 to 146.0.4.
- [Commits](https://github.com/giggio/node-chromedriver/compare/146.0.3...146.0.4)

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

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

* sigh

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2026-03-17 13:37:29 +01:00
Marc 'risson' Schmitt
9f2047e679 outposts: only dispatch logout task if any outpost exists (#20920) 2026-03-17 13:11:28 +01:00
dependabot[bot]
0e528cbcf0 core: bump goauthentik/fips-debian from 2517845 to e06f0fe in /lifecycle/container (#20852)
core: bump goauthentik/fips-debian in /lifecycle/container

Bumps goauthentik/fips-debian from `2517845` to `e06f0fe`.

---
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-17 12:55:00 +01:00
dependabot[bot]
e6c482150a website: bump @goauthentik/docusaurus-config from 2.4.0 to 2.5.1 in /website in the docusaurus group (#20907)
website: bump @goauthentik/docusaurus-config

Bumps the docusaurus group in /website with 1 update: [@goauthentik/docusaurus-config](https://github.com/goauthentik/authentik/tree/HEAD/packages/docusaurus-config).


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

---
updated-dependencies:
- dependency-name: "@goauthentik/docusaurus-config"
  dependency-version: 2.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docusaurus
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-17 12:51:21 +01:00
dependabot[bot]
da5f5419e5 web: bump knip from 5.86.0 to 5.87.0 in /web (#20948)
Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 5.86.0 to 5.87.0.
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@5.87.0/packages/knip)

---
updated-dependencies:
- dependency-name: knip
  dependency-version: 5.87.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-17 12:34:47 +01:00
dependabot[bot]
ac4a3884c1 core: bump library/nginx from d0913a1 to bc45d24 in /website (#20853)
Bumps library/nginx from `d0913a1` to `bc45d24`.

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-17 12:22:04 +01:00
dependabot[bot]
cce84dcf9d website: bump openapi-to-postmanv2 from 5.8.0 to 6.0.0 in /website (#20910)
Bumps [openapi-to-postmanv2](https://github.com/postmanlabs/openapi-to-postman) from 5.8.0 to 6.0.0.
- [Release notes](https://github.com/postmanlabs/openapi-to-postman/releases)
- [Changelog](https://github.com/postmanlabs/openapi-to-postman/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/postmanlabs/openapi-to-postman/compare/v5.8.0...v6.0.0)

---
updated-dependencies:
- dependency-name: openapi-to-postmanv2
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-17 12:19:49 +01:00
dependabot[bot]
7bca2255a8 core: bump library/node from d025db2 to 407d745 in /website (#20943)
Bumps library/node from `d025db2` to `407d745`.

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-17 12:16:45 +01:00
dependabot[bot]
9376dd45c1 web: bump @formatjs/intl-listformat from 8.2.2 to 8.2.3 in /web (#20946)
Bumps [@formatjs/intl-listformat](https://github.com/formatjs/formatjs) from 8.2.2 to 8.2.3.
- [Release notes](https://github.com/formatjs/formatjs/releases)
- [Commits](https://github.com/formatjs/formatjs/compare/@formatjs/intl-listformat@8.2.2...@formatjs/intl-listformat@8.2.3)

---
updated-dependencies:
- dependency-name: "@formatjs/intl-listformat"
  dependency-version: 8.2.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-17 12:15:41 +01:00
dependabot[bot]
1c7094b723 web: bump core-js from 3.48.0 to 3.49.0 in /web (#20947)
Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.48.0 to 3.49.0.
- [Release notes](https://github.com/zloirock/core-js/releases)
- [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zloirock/core-js/commits/v3.49.0/packages/core-js)

---
updated-dependencies:
- dependency-name: core-js
  dependency-version: 3.49.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-17 12:15:14 +01:00
Marc 'risson' Schmitt
57b2984f74 packages/django-dramatiq-postgres: scheduler: only dispatch tasks if they're not running yet (#20921)
* packages/django-dramatiq-postgres: scheduler: only dispatch tasks if they're not running yet

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

* lint

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

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-17 12:14:16 +01:00
authentik-automation[bot]
c2445d6f9b core, web: update translations (#20935)
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-17 12:14:07 +01:00
dependabot[bot]
9f93a08244 core: bump uvicorn[standard] from 0.41.0 to 0.42.0 (#20936)
Bumps [uvicorn[standard]](https://github.com/Kludex/uvicorn) from 0.41.0 to 0.42.0.
- [Release notes](https://github.com/Kludex/uvicorn/releases)
- [Changelog](https://github.com/Kludex/uvicorn/blob/main/docs/release-notes.md)
- [Commits](https://github.com/Kludex/uvicorn/compare/0.41.0...0.42.0)

---
updated-dependencies:
- dependency-name: uvicorn[standard]
  dependency-version: 0.42.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-17 12:14:03 +01:00
dependabot[bot]
c7cd24cf94 core: bump library/golang from ab8c494 to 9c51d8b in /lifecycle/container (#20937)
core: bump library/golang in /lifecycle/container

Bumps library/golang from `ab8c494` to `9c51d8b`.

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-17 12:13:59 +01:00
dependabot[bot]
b404c8af8b core: bump astral-sh/uv from 0.10.10 to 0.10.11 in /lifecycle/container (#20938)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.10.10 to 0.10.11.
- [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.10...0.10.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-17 12:13:56 +01:00
dependabot[bot]
ff24034edb ci: bump svenstaro/upload-release-action from 2.11.4 to 2.11.5 (#20939)
Bumps [svenstaro/upload-release-action](https://github.com/svenstaro/upload-release-action) from 2.11.4 to 2.11.5.
- [Release notes](https://github.com/svenstaro/upload-release-action/releases)
- [Changelog](https://github.com/svenstaro/upload-release-action/blob/master/CHANGELOG.md)
- [Commits](b98a3b12e8...29e53e9178)

---
updated-dependencies:
- dependency-name: svenstaro/upload-release-action
  dependency-version: 2.11.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-17 12:13:52 +01:00
dependabot[bot]
470a16de24 ci: bump astral-sh/setup-uv from 7.5.0 to 7.6.0 in /.github/actions/setup (#20941)
ci: bump astral-sh/setup-uv in /.github/actions/setup

Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 7.5.0 to 7.6.0.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](e06108dd0a...37802adc94)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 7.6.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-17 12:13:48 +01:00
dependabot[bot]
d52eea9c5f core: bump goauthentik/fips-python from 9d550e1 to 08bc05d in /lifecycle/container (#20942)
core: bump goauthentik/fips-python in /lifecycle/container

Bumps goauthentik/fips-python from `9d550e1` to `08bc05d`.

---
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-17 12:13:44 +01:00
Marc 'risson' Schmitt
fe020ed413 root: makefile: remove spellcheck from lint-fix (#20924) 2026-03-16 17:28:54 -03:00
Marc 'risson' Schmitt
db6ca79e37 lifecycle/migrate: add flag to skip migrations (#20863) 2026-03-16 17:27:00 -03:00
authentik-automation[bot]
b3dda80166 core: bump goauthentik.io/api/v3 to 3.2026.5.0-rc1-1773681602 (#20927)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-03-16 17:58:22 +00:00
Marc 'risson' Schmitt
62644a79fd root: allow listening on multiple IPs
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-16 18:40:45 +01:00
Connor Peshek
15613c3eff web: rename SCIM provider "User filtering" section to "Filtering" (#20879)
The section contains both user and group filter controls, so a generic
label is more accurate.
2026-03-16 18:29:42 +01:00
Pavel Pavel
270cf0b1d8 web/admin: Fix SCIM 'page_size' UI issue (#20890)
Fix SCIM page size UI issue

Co-authored-by: Pavel Sinkevych <pavelsinkevych@gmail.com>
2026-03-16 18:28:29 +01:00
dependabot[bot]
02e695e6a0 ci: bump astral-sh/setup-uv from 7.4.0 to 7.5.0 in /.github/actions/setup (#20875)
ci: bump astral-sh/setup-uv in /.github/actions/setup

Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 7.4.0 to 7.5.0.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](6ee6290f1c...e06108dd0a)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 18:27:11 +01:00
dependabot[bot]
6b955cf607 website: bump flatted from 3.3.3 to 3.4.1 in /website (#20892)
Bumps [flatted](https://github.com/WebReflection/flatted) from 3.3.3 to 3.4.1.
- [Commits](https://github.com/WebReflection/flatted/compare/v3.3.3...v3.4.1)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.1
  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-16 18:25:09 +01:00
dependabot[bot]
b19c61ecdf website: bump the build group in /website with 12 updates (#20908)
Bumps the build group in /website with 12 updates:

| Package | From | To |
| --- | --- | --- |
| [@rspack/binding-darwin-arm64](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack) | `1.6.6` | `1.7.8` |
| [@rspack/binding-linux-arm64-gnu](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack) | `1.6.6` | `1.7.8` |
| [@rspack/binding-linux-x64-gnu](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack) | `1.6.6` | `1.7.8` |
| [@swc/core-darwin-arm64](https://github.com/swc-project/swc) | `1.15.3` | `1.15.18` |
| [@swc/core-linux-arm64-gnu](https://github.com/swc-project/swc) | `1.15.3` | `1.15.18` |
| [@swc/core-linux-x64-gnu](https://github.com/swc-project/swc) | `1.15.3` | `1.15.18` |
| [@swc/html-darwin-arm64](https://github.com/swc-project/swc) | `1.15.3` | `1.15.18` |
| [@swc/html-linux-arm64-gnu](https://github.com/swc-project/swc) | `1.15.3` | `1.15.18` |
| [@swc/html-linux-x64-gnu](https://github.com/swc-project/swc) | `1.15.3` | `1.15.18` |
| [lightningcss-darwin-arm64](https://github.com/parcel-bundler/lightningcss) | `1.30.2` | `1.32.0` |
| [lightningcss-linux-arm64-gnu](https://github.com/parcel-bundler/lightningcss) | `1.30.2` | `1.32.0` |
| [lightningcss-linux-x64-gnu](https://github.com/parcel-bundler/lightningcss) | `1.30.2` | `1.32.0` |


Updates `@rspack/binding-darwin-arm64` from 1.6.6 to 1.7.8
- [Release notes](https://github.com/web-infra-dev/rspack/releases)
- [Commits](https://github.com/web-infra-dev/rspack/commits/v1.7.8/packages/rspack)

Updates `@rspack/binding-linux-arm64-gnu` from 1.6.6 to 1.7.8
- [Release notes](https://github.com/web-infra-dev/rspack/releases)
- [Commits](https://github.com/web-infra-dev/rspack/commits/v1.7.8/packages/rspack)

Updates `@rspack/binding-linux-x64-gnu` from 1.6.6 to 1.7.8
- [Release notes](https://github.com/web-infra-dev/rspack/releases)
- [Commits](https://github.com/web-infra-dev/rspack/commits/v1.7.8/packages/rspack)

Updates `@swc/core-darwin-arm64` from 1.15.3 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.3...v1.15.18)

Updates `@swc/core-linux-arm64-gnu` from 1.15.3 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.3...v1.15.18)

Updates `@swc/core-linux-x64-gnu` from 1.15.3 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.3...v1.15.18)

Updates `@swc/html-darwin-arm64` from 1.15.3 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.3...v1.15.18)

Updates `@swc/html-linux-arm64-gnu` from 1.15.3 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.3...v1.15.18)

Updates `@swc/html-linux-x64-gnu` from 1.15.3 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.3...v1.15.18)

Updates `lightningcss-darwin-arm64` from 1.30.2 to 1.32.0
- [Release notes](https://github.com/parcel-bundler/lightningcss/releases)
- [Commits](https://github.com/parcel-bundler/lightningcss/compare/v1.30.2...v1.32.0)

Updates `lightningcss-linux-arm64-gnu` from 1.30.2 to 1.32.0
- [Release notes](https://github.com/parcel-bundler/lightningcss/releases)
- [Commits](https://github.com/parcel-bundler/lightningcss/compare/v1.30.2...v1.32.0)

Updates `lightningcss-linux-x64-gnu` from 1.30.2 to 1.32.0
- [Release notes](https://github.com/parcel-bundler/lightningcss/releases)
- [Commits](https://github.com/parcel-bundler/lightningcss/compare/v1.30.2...v1.32.0)

---
updated-dependencies:
- dependency-name: "@rspack/binding-darwin-arm64"
  dependency-version: 1.7.8
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: build
- dependency-name: "@rspack/binding-linux-arm64-gnu"
  dependency-version: 1.7.8
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: build
- dependency-name: "@rspack/binding-linux-x64-gnu"
  dependency-version: 1.7.8
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: build
- dependency-name: "@swc/core-darwin-arm64"
  dependency-version: 1.15.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@swc/core-linux-arm64-gnu"
  dependency-version: 1.15.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@swc/core-linux-x64-gnu"
  dependency-version: 1.15.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@swc/html-darwin-arm64"
  dependency-version: 1.15.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@swc/html-linux-arm64-gnu"
  dependency-version: 1.15.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@swc/html-linux-x64-gnu"
  dependency-version: 1.15.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: lightningcss-darwin-arm64
  dependency-version: 1.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: build
- dependency-name: lightningcss-linux-arm64-gnu
  dependency-version: 1.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: build
- dependency-name: lightningcss-linux-x64-gnu
  dependency-version: 1.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: build
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 18:24:23 +01:00
dependabot[bot]
378f7c67a5 web: bump prettier-plugin-packagejson from 3.0.0 to 3.0.2 in /web (#20759)
Bumps [prettier-plugin-packagejson](https://github.com/matzkoh/prettier-plugin-packagejson) from 3.0.0 to 3.0.2.
- [Release notes](https://github.com/matzkoh/prettier-plugin-packagejson/releases)
- [Commits](https://github.com/matzkoh/prettier-plugin-packagejson/compare/v3.0.0...v3.0.2)

---
updated-dependencies:
- dependency-name: prettier-plugin-packagejson
  dependency-version: 3.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-16 18:20:58 +01:00
dependabot[bot]
6268da3007 web: bump @sentry/browser from 10.42.0 to 10.43.0 in /web in the sentry group across 1 directory (#20839)
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.42.0 to 10.43.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.42.0...10.43.0)

---
updated-dependencies:
- dependency-name: "@sentry/browser"
  dependency-version: 10.43.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-16 18:20:36 +01:00
Jens L.
db9081e7dc policies: remove BufferedPolicyAccessView (#20521)
* policies: remove BufferedPolicyAccessView

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

# Conflicts:
#	authentik/policies/views.py
#	authentik/providers/oauth2/views/authorize.py
#	schema.yml
#	tests/e2e/test_provider_saml.py

* format

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-03-16 18:19:15 +01:00
dependabot[bot]
060766f16e web: bump dompurify from 3.3.2 to 3.3.3 in /web (#20856)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.3.2 to 3.3.3.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.3.2...3.3.3)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-version: 3.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-16 18:18:33 +01:00
dependabot[bot]
669d54a768 web: bump chromedriver from 145.0.6 to 146.0.3 in /web (#20916)
* web: bump chromedriver from 145.0.6 to 146.0.3 in /web

Bumps [chromedriver](https://github.com/giggio/node-chromedriver) from 145.0.6 to 146.0.3.
- [Commits](https://github.com/giggio/node-chromedriver/compare/145.0.6...146.0.3)

---
updated-dependencies:
- dependency-name: chromedriver
  dependency-version: 146.0.3
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* ts ts ts dependabot

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2026-03-16 18:15:18 +01:00
dependabot[bot]
60a90c0bd4 core: bump pyopenssl from 25.3.0 to 26.0.0 (#20926)
Bumps [pyopenssl](https://github.com/pyca/pyopenssl) from 25.3.0 to 26.0.0.
- [Changelog](https://github.com/pyca/pyopenssl/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/pyopenssl/compare/25.3.0...26.0.0)

---
updated-dependencies:
- dependency-name: pyopenssl
  dependency-version: 26.0.0
  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-16 17:14:22 +00:00
dependabot[bot]
dd19a33b68 lifecycle/aws: bump aws-cdk from 2.1110.0 to 2.1111.0 in /lifecycle/aws (#20847)
Bumps [aws-cdk](https://github.com/aws/aws-cdk-cli/tree/HEAD/packages/aws-cdk) from 2.1110.0 to 2.1111.0.
- [Release notes](https://github.com/aws/aws-cdk-cli/releases)
- [Commits](https://github.com/aws/aws-cdk-cli/commits/aws-cdk@v2.1111.0/packages/aws-cdk)

---
updated-dependencies:
- dependency-name: aws-cdk
  dependency-version: 2.1111.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-16 17:35:40 +01:00
dependabot[bot]
3e23e4f58b web: bump the bundler group across 1 directory with 7 updates (#20876)
Bumps the bundler group with 2 updates in the /web directory: [@vitest/browser](https://github.com/vitest-dev/vitest/tree/HEAD/packages/browser) and [esbuild](https://github.com/evanw/esbuild).


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

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

Updates `esbuild` from 0.27.3 to 0.27.4
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.3...v0.27.4)

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

Updates `@esbuild/darwin-arm64` from 0.27.3 to 0.27.4
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.3...v0.27.4)

Updates `@esbuild/linux-arm64` from 0.27.3 to 0.27.4
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.3...v0.27.4)

Updates `@esbuild/linux-x64` from 0.27.3 to 0.27.4
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.3...v0.27.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 17:34:37 +01:00
dependabot[bot]
a2e99d4030 web: bump @types/node from 25.4.0 to 25.5.0 in /web (#20878)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.4.0 to 25.5.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 17:34:18 +01:00
dependabot[bot]
a9254715d1 web: bump flatted from 3.3.3 to 3.4.1 (#20891)
Bumps [flatted](https://github.com/WebReflection/flatted) from 3.3.3 to 3.4.1.
- [Commits](https://github.com/WebReflection/flatted/compare/v3.3.3...v3.4.1)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.1
  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-16 17:34:09 +01:00
dependabot[bot]
c78c8e4fd5 web: bump flatted from 3.3.3 to 3.4.1 in /web (#20923)
Bumps [flatted](https://github.com/WebReflection/flatted) from 3.3.3 to 3.4.1.
- [Commits](https://github.com/WebReflection/flatted/compare/v3.3.3...v3.4.1)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.1
  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-16 17:32:56 +01:00
Marc 'risson' Schmitt
d9ae4837b5 core: expiring model: ignore DoesNotExist error (#20922)
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-16 17:28:11 +01:00
dependabot[bot]
84d700f79c core: bump astral-sh/uv from 0.10.9 to 0.10.10 in /lifecycle/container (#20913)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 14:27:05 +00:00
authentik-automation[bot]
45dcef8e9d core, web: update translations (#20899)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-03-16 13:51:23 +00:00
authentik-automation[bot]
ced62a9332 core: bump goauthentik.io/api/v3 to 3.2026.5.0-rc1-1773518508 (#20897)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-03-16 13:51:06 +00:00
authentik-automation[bot]
aaac14a7c7 stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#20905)
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-16 13:50:51 +00:00
dependabot[bot]
bc45ef6c9a ci: bump softprops/action-gh-release from 2.5.0 to 2.6.1 (#20912)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 13:50:36 +00:00
dependabot[bot]
1ae6051f8c ci: bump actions/create-github-app-token from 2.2.1 to 3.0.0 (#20914)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 13:49:30 +00:00
dependabot[bot]
c1445f6828 core: bump goauthentik/fips-python from 1ef7bd9 to 9d550e1 in /lifecycle/container (#20915)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 14:49:23 +01:00
Marc 'risson' Schmitt
c426c94a25 Merge branch 'main' into rust-server 2026-03-16 14:09:37 +01:00
Jens L.
3d964ddd2e endpoints: fix tasks failing (#20904)
* endpoints: fix tasks failing

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-15 01:47:02 +01:00
Jens L.
24a817cce8 providers/scim: use modified GroupMember class to support extra attributes on it (#20827)
* providers/scim: use modified GroupMember class to support extra attributes on it

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

* exclude unset

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-03-14 21:04:41 +01:00
Jens L.
59263ae678 events: add option to configure webhook CA (#20823)
* events: add option to configure webhook CA

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

* add tests

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

* add docs

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

* Update website/docs/sys-mgmt/events/transports.md

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Jens L. <jens@beryju.org>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens L. <jens@beryju.org>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-03-14 21:01:01 +01:00
Marcelo Elizeche Landó
e9b33be694 stages/authenticator_webauthn: Add WebAuthn client hints support (#20700)
* Add webauthn_hints to models

* Add migrations

* Add webauthn_hints to the API

* Add enum to settings.py

* Add webauthn client hints to configuration forms in authenticator_webauthn and authenticator_validate

* Add compatability for older user agents auto infering authenticatorAttachment

* Rewording

* Fix capitalization

* Add tests

* Use ak-dual-select instead of checkboxes for hints

* Add preserve-order, no-search and no-status properties to ak-dual-select

* add no-search and no-status to ak-dual-select in AuthenticatorValidateStageForm.ts
2026-03-13 20:36:28 -03:00
Marcelo Elizeche Landó
0ff3869ea3 web/elements: Add preserve-order, no-search and no-status attributes to ak-dual-select (#20749)
* Add preserve-order, no-search and no-status properties to ak-dual-select

* fix linting
2026-03-13 17:11:28 -03:00
Connor Peshek
219a110339 docs: Add note on skipping object syncing (#20882) 2026-03-13 12:41:30 -05:00
Marc 'risson' Schmitt
2e04738306 use waitgroups for multiple servers, TODO: fix healthchecks
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-13 16:13:56 +01:00
Marc 'risson' Schmitt
297e8db6eb use waitgroups for multiple servers
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-13 16:03:45 +01:00
Marc 'risson' Schmitt
5b9a30be4b Merge branch 'main' into rust-server 2026-03-13 15:59:30 +01:00
dependabot[bot]
ef202f0a26 core: bump orjson from 3.11.5 to 3.11.6 (#20870)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-13 15:46:54 +01:00
authentik-automation[bot]
e80b1bfc2b core, web: update translations (#20871)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-03-13 15:45:39 +01:00
dependabot[bot]
fbd3008a0c core: bump goauthentik/fips-python from f9f8a26 to 1ef7bd9 in /lifecycle/container (#20874)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-13 15:45:07 +01:00
dependabot[bot]
77f8ed6c43 core: bump ruff from 0.15.5 to 0.15.6 (#20873)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-13 15:44:52 +01:00
CryptoManiac
7d3aca97bb root: fix log function to redirect output to stderr (#20858) 2026-03-12 17:19:02 +00:00
Marc 'risson' Schmitt
4ca3bfa3e4 providers/proxy: remove redundant logout event (#20860) 2026-03-12 13:25:40 -03:00
Marc 'risson' Schmitt
457429f261 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-12 14:45:48 +01:00
Marc 'risson' Schmitt
a0bac73c59 go ak api controller: add support for unix urls
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-12 14:44:15 +01:00
Marc 'risson' Schmitt
b82abaf230 fixup
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-12 13:45:21 +01:00
Marc 'risson' Schmitt
c4b1e4bd44 http timeouts
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-12 13:41:50 +01:00
Marc 'risson' Schmitt
5592c4769a Merge branch 'main' into rust-server 2026-03-12 13:26:15 +01:00
Marc 'risson' Schmitt
f71f5b7278 fixup
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-11 19:12:03 +01:00
Marc 'risson' Schmitt
d7159cfce2 fixup
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-11 19:02:21 +01:00
Marc 'risson' Schmitt
30dc4e120b fixup
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-11 19:01:15 +01:00
Marc 'risson' Schmitt
619023be75 fixup
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-11 19:00:17 +01:00
Marc 'risson' Schmitt
de63473cd2 more ci
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-11 18:59:23 +01:00
Marc 'risson' Schmitt
6aa50b962c rustfmt in ci
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-11 18:50:01 +01:00
Marc 'risson' Schmitt
f240ca1708 more ci
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-11 18:48:21 +01:00
Marc 'risson' Schmitt
550da2005e start ci
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-11 18:46:28 +01:00
Marc 'risson' Schmitt
8818a0b06c revert
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-11 17:54:18 +01:00
Marc 'risson' Schmitt
013190ddd0 fix tests?
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-11 17:50:43 +01:00
Marc 'risson' Schmitt
6fb777ae5b make server listen on unix socket
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-11 17:45:55 +01:00
Marc 'risson' Schmitt
41f13d8805 fix sentry tracing
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-11 16:26:12 +01:00
Marc 'risson' Schmitt
fc5f0e7dc5 spellcheck
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-11 15:20:51 +01:00
Marc 'risson' Schmitt
9b9379ac8f lint
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-11 15:09:28 +01:00
Marc 'risson' Schmitt
c4b0825dad tasks/test: remove worker health check
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-11 15:06:39 +01:00
Marc 'risson' Schmitt
946ace14c1 fix makefile
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-11 15:05:50 +01:00
Marc 'risson' Schmitt
6a9eb8e9c7 Merge branch 'main' into rust-server 2026-03-11 14:28:31 +01:00
Marc 'risson' Schmitt
4f0d0e72d5 disable sentry span handling, it's broken
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-10 18:55:59 +01:00
Marc 'risson' Schmitt
411648672e config: separate initial loading and starting the reloader
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-10 18:47:51 +01:00
Marc 'risson' Schmitt
d5f6d30aeb update deps
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-10 17:26:24 +01:00
Marc 'risson' Schmitt
1508ad0ab8 subpath
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-10 17:10:56 +01:00
Marc 'risson' Schmitt
892e8fd856 config fallback values
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-10 17:04:58 +01:00
Marc 'risson' Schmitt
d4b0ac7c14 more todos
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-10 16:14:19 +01:00
Marc 'risson' Schmitt
fe4857abbb db: use connection callbacks for search path and application name
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-10 16:13:02 +01:00
Marc 'risson' Schmitt
8b73872c0d refine sentry setup
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-10 16:00:19 +01:00
Marc 'risson' Schmitt
d22597377a finally finish tracing
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-10 15:40:34 +01:00
Marc 'risson' Schmitt
58d198d60a Merge branch 'main' into rust-server
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-10 13:31:55 +01:00
Marc 'risson' Schmitt
1de19546d7 tracing almost done
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-09 18:00:38 +01:00
Marc 'risson' Schmitt
8ad054ce65 Merge branch 'main' into rust-server 2026-03-09 16:22:12 +01:00
Marc 'risson' Schmitt
df95fc89eb wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-06 19:53:19 +01:00
Marc 'risson' Schmitt
75898710f1 update, some logging
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-06 15:30:46 +01:00
Marc 'risson' Schmitt
3a5a0c2e4f fmt
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-06 13:45:37 +01:00
Marc 'risson' Schmitt
b806e14a00 Merge branch 'main' into rust-server
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-06 13:44:28 +01:00
Marc 'risson' Schmitt
c2d02cd807 fix deny
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-05 17:47:54 +01:00
Marc 'risson' Schmitt
1212402231 remove deprecated rustls-pemfile
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-05 17:43:43 +01:00
Marc 'risson' Schmitt
2927f414c5 extract tracelayer
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-05 17:33:37 +01:00
Marc 'risson' Schmitt
5ba18fbd55 fmt
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-05 13:46:35 +01:00
Marc 'risson' Schmitt
1b108e40d6 Merge branch 'main' into rust-server 2026-03-05 13:46:07 +01:00
Marc 'risson' Schmitt
982ae7b261 nit
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-05 13:35:24 +01:00
Marc 'risson' Schmitt
294a656ad2 worker status
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-05 13:34:35 +01:00
Marc 'risson' Schmitt
dab8bab916 better handling of socket path for future testing, healthcheck
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-04 16:54:57 +01:00
Marc 'risson' Schmitt
ee1803a0ae pedantic
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-03 18:33:13 +01:00
Marc 'risson' Schmitt
99c9894a04 extract brands
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-03 16:48:25 +01:00
Marc 'risson' Schmitt
2352ce72c9 Merge branch 'main' into rust-server 2026-03-03 14:03:27 +01:00
Marc 'risson' Schmitt
bb28e6425d small fixes
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-02 18:08:57 +01:00
Marc 'risson' Schmitt
f2149dfd90 write mode to authentik-mode file
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-02 15:40:30 +01:00
Marc 'risson' Schmitt
2ff0f09db1 spawn_blocking for tls computations
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-02 15:24:46 +01:00
Marc 'risson' Schmitt
40a91fd4fb fix minor stuff
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-02 15:08:55 +01:00
Marc 'risson' Schmitt
2e3f76441c Merge branch 'main' into rust-server
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-02 15:05:46 +01:00
Marc 'risson' Schmitt
f91474dd91 Merge branch 'main' into rust-server
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-27 18:19:54 +01:00
Marc 'risson' Schmitt
61dbd5976f some todos
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-27 18:16:25 +01:00
Marc 'risson' Schmitt
8099ac6508 some cleanup
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-27 18:15:50 +01:00
Marc 'risson' Schmitt
61ed26e3f6 worker started from rust
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-27 16:54:14 +01:00
Marc 'risson' Schmitt
ea17d4cbf1 finish tls
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-27 13:58:38 +01:00
Marc 'risson' Schmitt
ac388667d0 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-26 17:14:08 +01:00
Marc 'risson' Schmitt
cdc42de5b5 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-24 18:50:08 +01:00
Marc 'risson' Schmitt
2770c3a7e0 Merge branch 'main' into rust-server 2026-02-24 15:20:32 +01:00
Marc 'risson' Schmitt
f41f501702 finish metrics
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-24 15:20:09 +01:00
Marc 'risson' Schmitt
08685a574a better metrics
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-23 18:40:32 +01:00
Marc 'risson' Schmitt
15377f5154 better arbiter again
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-23 15:30:33 +01:00
Marc 'risson' Schmitt
52da505aab Merge branch 'main' into rust-server 2026-02-23 13:47:34 +01:00
Marc 'risson' Schmitt
d8a2a069aa add tests for extractors
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-20 18:38:09 +01:00
Marc 'risson' Schmitt
fec9dcc2e7 better logging
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-20 16:54:32 +01:00
Marc 'risson' Schmitt
b644fa5a2c revert unintended change
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-20 13:30:00 +01:00
Marc 'risson' Schmitt
9a5d59533e remove rust worker, fixup main
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-20 13:28:02 +01:00
Marc 'risson' Schmitt
3c64570398 use arcswap instead of lock for config
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-20 13:19:54 +01:00
Marc 'risson' Schmitt
a735f6dcf3 support proxying websockets to core
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-19 18:44:16 +01:00
Marc 'risson' Schmitt
f33e7f13eb autoreload
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-19 17:47:21 +01:00
Marc 'risson' Schmitt
eee00fa29b fix returned headers
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-19 16:45:42 +01:00
Marc 'risson' Schmitt
5a95a14a8f wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-19 16:38:21 +01:00
Marc 'risson' Schmitt
7b46fac608 fixup
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-19 15:31:35 +01:00
Marc 'risson' Schmitt
bb488e1c2c some better lints
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-19 15:29:02 +01:00
Marc 'risson' Schmitt
138aa0e4e9 Merge branch 'main' into rust-server
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-19 14:33:17 +01:00
Marc 'risson' Schmitt
e65cd2999f tls headers
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-18 19:36:53 +01:00
Marc 'risson' Schmitt
490790c272 cleanup forwarding
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-18 19:03:30 +01:00
Marc 'risson' Schmitt
b640b42dbb wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-18 18:16:42 +01:00
Marc 'risson' Schmitt
1371465ebe fixup
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-18 18:13:48 +01:00
Marc 'risson' Schmitt
c623b96dc2 some more cleanup
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-18 18:13:18 +01:00
Marc 'risson' Schmitt
43fe1918db cleanup
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-18 17:38:59 +01:00
Marc 'risson' Schmitt
3e2489834d remove cargo.lock for docsmg
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-18 15:56:28 +01:00
Marc 'risson' Schmitt
7ba86b7de3 introduce the arbiter, treewide fmt
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-18 15:55:45 +01:00
Marc 'risson' Schmitt
85ef3cda04 bring docsmg up to standards
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-18 15:18:48 +01:00
Marc 'risson' Schmitt
62911536bf fix tests
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-18 14:14:29 +01:00
Marc 'risson' Schmitt
1a27971399 move everything to a single crate
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-18 14:13:51 +01:00
Marc 'risson' Schmitt
7a0e946bb5 start moving to a single crate
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-17 18:45:49 +01:00
Marc 'risson' Schmitt
428ccc2c14 fix metrics endpoint
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-17 18:27:17 +01:00
Marc 'risson' Schmitt
0b706d5830 Merge branch 'main' into rust-server
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-17 13:44:03 +01:00
Marc 'risson' Schmitt
b9f4a1aed7 Merge branch 'main' into rust-server 2026-02-16 14:07:56 +01:00
Marc 'risson' Schmitt
d2cb45aadf start cleanup
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-11 15:15:43 +01:00
Marc 'risson' Schmitt
de12748f25 Merge branch 'main' into rust-server 2026-02-11 14:41:18 +01:00
Marc 'risson' Schmitt
f8f39b8edc start on metrics
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-09 19:25:06 +01:00
Marc 'risson' Schmitt
986385a951 initialize python globally when needed
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-09 18:45:20 +01:00
Marc 'risson' Schmitt
129ed95cf0 Merge branch 'main' into rust-server 2026-02-09 18:39:57 +01:00
Marc 'risson' Schmitt
dc0d535fcc small improvements to storage token checks
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-09 18:39:40 +01:00
Marc 'risson' Schmitt
5c0e23a78f features for which process to build
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-09 18:07:39 +01:00
Marc 'risson' Schmitt
b4bf082864 Merge branch 'main' into rust-server 2026-02-09 14:37:11 +01:00
Dominic R
2f00983c29 static file handling 2026-02-08 11:41:30 -05:00
Dominic R
af93a1e230 rev tls_state to what it was before 2026-02-08 09:47:32 -05:00
Dominic R
dbb3898621 fix client error 2026-02-08 09:44:47 -05:00
Dominic R
a668ddcaf5 make it work on macos 2026-02-08 09:32:38 -05:00
Marc 'risson' Schmitt
051aea6f99 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-06 14:47:51 +01:00
Marc 'risson' Schmitt
b8104ec156 Merge branch 'main' into rust-server
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-05 17:30:12 +01:00
Marc 'risson' Schmitt
e59970e6ab fmt
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-04 19:04:18 +01:00
Marc 'risson' Schmitt
0b50b0aa13 actually use the proxy protocol acceptor
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-04 19:03:45 +01:00
Marc 'risson' Schmitt
7b9b1c2c70 proxy protocol and tls extractors finally
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-04 18:55:41 +01:00
Marc 'risson' Schmitt
1e1cdffb33 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-04 14:29:20 +01:00
Marc 'risson' Schmitt
8ad572ba35 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-02 19:14:14 +01:00
Marc 'risson' Schmitt
8a5b8ad047 custom tls acceptor, wip proxy protocol acceptor
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-02 18:43:27 +01:00
Marc 'risson' Schmitt
907a4ce478 Merge branch 'main' into rust-server
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-02 17:07:24 +01:00
Marc 'risson' Schmitt
a26254df02 compression and loading page
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-30 17:58:59 +01:00
Marc 'risson' Schmitt
bf9679dcb5 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-30 17:01:10 +01:00
Marc 'risson' Schmitt
71ee2f6c66 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-30 17:00:49 +01:00
Marc 'risson' Schmitt
90fb12a804 proxying works correctly now
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-30 16:09:41 +01:00
Marc 'risson' Schmitt
e271a8a0af static files
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-29 15:43:36 +01:00
Marc 'risson' Schmitt
6100fd7800 remove sea-orm
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-29 13:59:11 +01:00
Marc 'risson' Schmitt
b78d62f550 add console-subscriber for tokio-console debugging
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-28 15:56:14 +01:00
Marc 'risson' Schmitt
21eb1bb7d0 fix gunicorn shutdown detection
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-28 15:56:01 +01:00
Marc 'risson' Schmitt
e4445a44c4 db
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-28 15:22:41 +01:00
Marc 'risson' Schmitt
6fecbb41ca start on db
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-28 01:53:37 +01:00
Marc 'risson' Schmitt
4a840796bf Merge branch 'main' into rust-server
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-28 01:36:55 +01:00
Marc 'risson' Schmitt
cc7f190735 config reloading
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-28 01:33:25 +01:00
Marc 'risson' Schmitt
c4962f86dd wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-27 20:17:32 +01:00
Marc 'risson' Schmitt
ad672338e0 Merge branch 'main' into rust-server 2026-01-27 15:13:32 +01:00
Marc 'risson' Schmitt
fadf344955 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-26 14:25:40 +01:00
Marc 'risson' Schmitt
8c58873a3a wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-22 17:19:21 +01:00
Marc 'risson' Schmitt
ac7dd69be2 fixup
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-21 17:57:24 +01:00
Marc 'risson' Schmitt
f01ab7ccb2 we proxying
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-21 17:53:56 +01:00
Marc 'risson' Schmitt
13f7ac6eca wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-20 17:57:09 +01:00
Marc 'risson' Schmitt
24202f9a3f wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-20 15:26:56 +01:00
Marc 'risson' Schmitt
5a72130576 cleanup
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-19 17:52:35 +01:00
Marc 'risson' Schmitt
fe5d24004e Merge branch 'main' into rust-server
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-19 16:30:12 +01:00
Marc 'risson' Schmitt
dd7c13c5bd wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-12 18:12:52 +01:00
Marc 'risson' Schmitt
32de1ab6c6 Merge branch 'main' into rust-server
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-12 14:07:32 +01:00
Marc 'risson' Schmitt
6e4384d672 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-11-17 14:26:40 +01:00
Marc 'risson' Schmitt
79f7759d4b Merge branch 'main' into rust-server 2025-11-14 14:48:26 +01:00
Marc 'risson' Schmitt
0ca41cb184 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-11-13 19:01:40 +01:00
Marc 'risson' Schmitt
f8e5c895d6 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-11-13 18:58:30 +01:00
Marc 'risson' Schmitt
2ba8991a3b wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-11-13 18:30:57 +01:00
Marc 'risson' Schmitt
19b36d2e0d wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-11-13 14:08:54 +01:00
Marc 'risson' Schmitt
fb802a53bc Merge branch 'main' into rust-server 2025-11-13 03:28:17 +01:00
Marc 'risson' Schmitt
2f6465d5a0 Merge branch 'main' into rust-server 2025-11-12 15:16:35 +01:00
Marc 'risson' Schmitt
c5437d2b0b wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-11-07 18:46:21 +01:00
Marc 'risson' Schmitt
8e2e90a87f wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-11-06 19:14:07 +01:00
Marc 'risson' Schmitt
4deb3d45cf wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-11-06 18:43:33 +01:00
Marc 'risson' Schmitt
b61bb3cc17 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-11-05 16:07:11 +01:00
Marc 'risson' Schmitt
af3332df9f wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-11-04 19:30:43 +01:00
Marc 'risson' Schmitt
0849df7478 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-11-04 17:53:54 +01:00
248 changed files with 15383 additions and 3307 deletions

2
.cargo/config.toml Normal file
View File

@@ -0,0 +1,2 @@
[build]
rustflags = ["--cfg", "tokio_unstable"]

47
.cargo/deny.toml Normal file
View File

@@ -0,0 +1,47 @@
[licenses]
allow = [
"Apache-2.0 WITH LLVM-exception",
"Apache-2.0",
"BSD-3-Clause",
"CC0-1.0",
"CDLA-Permissive-2.0",
"ISC",
"MIT",
"MPL-2.0",
"OpenSSL",
"Unicode-3.0",
"Zlib",
]
[licenses.private]
ignore = true
[bans]
multiple-versions = "allow"
wildcards = "deny"
[bans.workspace-dependencies]
duplicates = "deny"
include-path-dependencies = true
unused = "deny"
# No non-FIPS compliant dependencies
[[bans.deny]]
name = "native-tls"
[[bans.deny]]
name = "openssl"
[[bans.deny]]
name = "openssl-sys"
[[bans.deny]]
name = "ring"
[[bans.features]]
allow = [
"alloc",
"aws-lc-sys",
"default",
"fips",
"prebuilt-nasm",
"ring-io",
"ring-sig-verify",
]
name = "aws-lc-rs"
exact = true

16
.cargo/rustfmt.toml Normal file
View File

@@ -0,0 +1,16 @@
comment_width = 100
format_code_in_doc_comments = true
format_strings = true
group_imports = "StdExternalCrate"
hex_literal_case = "Lower"
imports_granularity = "Crate"
max_width = 100
newline_style = "Unix"
normalize_comments = true
normalize_doc_attributes = true
reorder_impl_items = true
style_edition = "2024"
use_field_init_shorthand = true
use_try_shorthand = true
where_single_line = true
wrap_comments = true

View File

@@ -4,7 +4,7 @@ description: "Setup authentik testing environment"
inputs:
dependencies:
description: "List of dependencies to setup"
default: "system,python,node,go,runtime"
default: "system,python,rust,node,go,runtime"
postgresql_version:
description: "Optional postgresql image tag"
default: "16"
@@ -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@6ee6290f1cbc4156c0bdd66691b2c144ef8df19a # v5
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v5
with:
enable-cache: true
- name: Setup python
@@ -34,6 +34,20 @@ runs:
if: ${{ contains(inputs.dependencies, 'python') }}
shell: bash
run: uv sync --all-extras --dev --frozen
- name: Setup rust (stable)
if: ${{ contains(inputs.dependencies, 'rust') && !contains(inputs.dependencies, 'rust-nightly') }}
uses: actions-rust-lang/setup-rust-toolchain@a0b538fa0b742a6aa35d6e2c169b4bd06d225a98 # v1
- name: Setup rust (nightly)
if: ${{ contains(inputs.dependencies, 'rust-nightly') }}
uses: actions-rust-lang/setup-rust-toolchain@a0b538fa0b742a6aa35d6e2c169b4bd06d225a98 # v1
with:
toolchain: nightly
components: rustfmt
- name: Setup rust dependencies
if: ${{ contains(inputs.dependencies, 'rust') }}
uses: taiki-e/install-action@64c5c20c872907b6f7cd50994ac189e7274160f2 # v2
with:
tool: cargo-deny cargo-machete cargo-llvm-cov nextest
- name: Setup node (web)
if: ${{ contains(inputs.dependencies, 'node') }}
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4

View File

@@ -2,18 +2,22 @@ name: "Process test results"
description: Convert test results to JUnit, add them to GitHub Actions and codecov
inputs:
files:
description: Comma-separated explicit list of files to upload
flags:
description: Codecov flags
runs:
using: "composite"
steps:
- uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
- uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
with:
files: ${{ inputs.files }}
flags: ${{ inputs.flags }}
use_oidc: true
- uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
- uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
with:
files: ${{ inputs.files }}
flags: ${{ inputs.flags }}
use_oidc: true
report_type: test_results

View File

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

View File

@@ -41,7 +41,7 @@ jobs:
- working-directory: website/
name: Install Dependencies
run: npm ci
- uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v4
with:
path: |
${{ github.workspace }}/website/api/.docusaurus

View File

@@ -16,6 +16,7 @@ env:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
RUSTFLAGS: "-Dwarnings"
permissions:
# Needed for checkout
@@ -28,20 +29,36 @@ jobs:
strategy:
fail-fast: false
matrix:
job:
- bandit
- black
- spellcheck
- pending-migrations
- ruff
- mypy
include:
- job: bandit
deps: python
- job: black
deps: python
- job: spellcheck
deps: node
- job: pending-migrations
deps: python,runtime
- job: ruff
deps: python
- job: mypy
deps: python
- job: cargo-deny
deps: rust
- job: cargo-machete
deps: rust
- job: clippy
deps: rust
- job: rustfmt
deps: rust-nightly
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- name: Setup authentik env
uses: ./.github/actions/setup
with:
dependencies: ${{ matrix.deps }}
- name: run job
run: uv run make ci-${{ matrix.job }}
run: make ci-lint-${{ matrix.job }}
test-gen-build:
runs-on: ubuntu-latest
steps:
@@ -127,6 +144,7 @@ jobs:
CI_TEST_SEED: ${{ needs.test-make-seed.outputs.seed }}
CI_RUN_ID: ${{ matrix.run_id }}
CI_TOTAL_RUNS: "5"
PROMETHEUS_MULTIPROC_DIR: /tmp
run: |
uv run make ci-test
- uses: ./.github/actions/test-results
@@ -156,6 +174,7 @@ jobs:
CI_TEST_SEED: ${{ needs.test-make-seed.outputs.seed }}
CI_RUN_ID: ${{ matrix.run_id }}
CI_TOTAL_RUNS: "5"
PROMETHEUS_MULTIPROC_DIR: /tmp
run: |
uv run make ci-test
- uses: ./.github/actions/test-results
@@ -172,6 +191,8 @@ jobs:
- name: Create k8s Kind Cluster
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
- name: run integration
env:
PROMETHEUS_MULTIPROC_DIR: /tmp
run: |
uv run coverage run manage.py test tests/integration
uv run coverage xml
@@ -215,7 +236,7 @@ jobs:
run: |
docker compose -f tests/e2e/compose.yml up -d --quiet-pull
- id: cache-web
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v4
with:
path: web/dist
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
@@ -228,6 +249,8 @@ jobs:
npm run build
npm run build:sfe
- name: run e2e
env:
PROMETHEUS_MULTIPROC_DIR: /tmp
run: |
uv run coverage run manage.py test ${{ matrix.job.glob }}
uv run coverage xml
@@ -258,7 +281,7 @@ jobs:
run: |
docker compose -f tests/openid_conformance/compose.yml up -d --quiet-pull
- id: cache-web
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v4
with:
path: web/dist
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
@@ -271,6 +294,8 @@ jobs:
npm run build
npm run build:sfe
- name: run conformance
env:
PROMETHEUS_MULTIPROC_DIR: /tmp
run: |
uv run coverage run manage.py test ${{ matrix.job.glob }}
uv run coverage xml
@@ -283,6 +308,29 @@ jobs:
with:
name: conformance-certification-${{ matrix.job.name }}
path: tests/openid_conformance/exports/
test-rust:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- name: Setup authentik env
uses: ./.github/actions/setup
with:
dependencies: rust
- name: run tests
run: |
cargo llvm-cov --no-report nextest --workspace
cargo llvm-cov report --codecov --output-path target/llvm-cov-target/rust.json
- uses: ./.github/actions/test-results
if: ${{ always() }}
with:
files: target/llvm-cov-target/rust.json
flags: rust
- if: ${{ !cancelled() }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: test-rust
path: target/llvm-cov-target/rust.json
ci-core-mark:
if: always()
needs:

View File

@@ -29,7 +29,7 @@ jobs:
github.event.pull_request.head.repo.full_name == github.repository)
steps:
- id: generate_token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
@@ -38,7 +38,7 @@ jobs:
token: ${{ steps.generate_token.outputs.token }}
- name: Compress images
id: compress
uses: calibreapp/image-actions@d9c8ee5c3dc52ae4622c82ead88d658f4b16b65f # main
uses: calibreapp/image-actions@03c976c29803442fc4040a9de5509669e7759b81 # main
with:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
compressOnly: ${{ github.event_name != 'pull_request' }}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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@b98a3b12e86552593f3e4e577ca8a62aa2f3f22b # v2
uses: svenstaro/upload-release-action@29e53e917877a24fad85510ded594ab3c9ca12de # v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}

View File

@@ -67,7 +67,7 @@ jobs:
steps:
- id: app-token
name: Generate app token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
@@ -96,7 +96,7 @@ jobs:
git tag "version/${{ inputs.version }}" HEAD -m "version/${{ inputs.version }}"
git push --follow-tags
- name: Create Release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
with:
token: "${{ steps.app-token.outputs.token }}"
tag_name: "version/${{ inputs.version }}"
@@ -115,7 +115,7 @@ jobs:
steps:
- id: app-token
name: Generate app token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
@@ -157,7 +157,7 @@ jobs:
steps:
- id: app-token
name: Generate app token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}

View File

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

View File

@@ -21,7 +21,7 @@ jobs:
steps:
- id: generate_token
if: ${{ github.event_name != 'pull_request' }}
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}

18
.gitignore vendored
View File

@@ -195,6 +195,24 @@ pyvenv.cfg
pip-selfcheck.json
# End of https://www.gitignore.io/api/python,django
# Created by https://www.toptal.com/developers/gitignore/api/rust
# Edit at https://www.toptal.com/developers/gitignore?templates=rust
### Rust ###
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# End of https://www.toptal.com/developers/gitignore/api/rust
/static/
local.env.yml

View File

@@ -3,6 +3,7 @@
# Backend
authentik/ @goauthentik/backend
blueprints/ @goauthentik/backend
src/ @goauthentik/backend
cmd/ @goauthentik/backend
internal/ @goauthentik/backend
lifecycle/ @goauthentik/backend
@@ -11,8 +12,12 @@ scripts/ @goauthentik/backend
tests/ @goauthentik/backend
pyproject.toml @goauthentik/backend
uv.lock @goauthentik/backend
Cargo.toml @goauthentik/backend
Cargo.lock @goauthentik/backend
go.mod @goauthentik/backend
go.sum @goauthentik/backend
.config/ @goauthentik/backend
rust-toolchain.toml @goauthentik/backend
# Infrastructure
.github/ @goauthentik/infrastructure
lifecycle/aws/ @goauthentik/infrastructure

5181
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

296
Cargo.toml Normal file
View File

@@ -0,0 +1,296 @@
[workspace]
members = [".", "website/scripts/docsmg"]
resolver = "3"
[workspace.package]
authors = ["authentik Team <hello@goauthentik.io>"]
edition = "2024"
readme = "README.md"
homepage = "https://goauthentik.io"
repository = "https://github.com/goauthentik/authentik.git"
license-file = "LICENSE"
publish = false
[workspace.dependencies]
arc-swap = "1.8.2"
argh = "0.1.17"
async-trait = "0.1.89"
aws-lc-rs = { version = "1.16.1", features = ["fips"] }
axum = { version = "0.8.8", features = ["http2", "macros", "ws"] }
axum-server = { version = "0.8.0", features = ["tls-rustls-no-provider"] }
bytes = "1.11.1"
chrono = "0.4.44"
clap = { version = "4.5.59", features = ["derive", "env"] }
client-ip = { version = "0.2.1", features = ["forwarded-header"] }
color-eyre = "0.6.5"
colored = "3.1.1"
config = { version = "0.15.19", default-features = false, features = [
"yaml",
"async",
] }
console-subscriber = "0.5.0"
dotenvy = "0.15.7"
durstr = "0.4.0"
eyre = "0.6.12"
forwarded-header-value = "0.1.1"
futures = "0.3.32"
glob = "0.3.3"
http-body-util = "0.1.3"
hyper = "1.8.1"
hyper-unix-socket = "0.3.0"
hyper-util = "0.1.20"
ipnet = { version = "2.12.0", features = ["serde"] }
# See https://github.com/mladedav/json-subscriber/pull/23
json-subscriber = { git = "https://github.com/rissson/json-subscriber.git", rev = "950ad7cb887a0a14fd5cb8afb8e76db1f456c032" }
jsonwebtoken = { version = "10.3.0", default-features = false, features = [
"aws_lc_rs",
] }
metrics = "0.24.3"
metrics-exporter-prometheus = { version = "0.18.1", default-features = false }
nix = { version = "0.31.2", features = ["hostname", "signal"] }
notify = "8.2.0"
pem = "3.0.6"
pin-project-lite = "0.2.17"
pyo3 = "0.28.2"
percent-encoding = "2.3.2"
rcgen = { version = "0.14.7", default-features = false, features = [
"aws_lc_rs",
"fips",
] }
regex = "1.12.3"
rustls = { version = "0.23.37", features = ["fips"] }
sentry = { version = "0.47.0", default-features = false, features = [
"backtrace",
"contexts",
"debug-images",
"panic",
"rustls",
"reqwest",
"tower",
"tracing",
] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
sqlx = { version = "0.8.6", default-features = false, features = [
"runtime-tokio",
"tls-rustls-aws-lc-rs",
"postgres",
"derive",
"macros",
"uuid",
"chrono",
"ipnet",
"json",
] }
time = "0.3.47"
thiserror = "2.0.18"
tokio = { version = "1.50.0", features = ["full"] }
tokio-rustls = "0.26.4"
tokio-tungstenite = "0.28.0"
tokio-util = "0.7.18"
tower = "0.5.3"
tower-http = { version = "0.6.8", features = [
"compression-br",
"compression-deflate",
"compression-gzip",
"compression-zstd",
"fs",
"timeout",
] }
tower-service = "0.3.3"
tracing = "0.1.44"
tracing-error = "0.2.1"
tracing-subscriber = { version = "0.3.22", features = [
"env-filter",
"json",
"tracing-log",
] }
url = "2.5.8"
uuid = { version = "1.22.0", features = ["v4"] }
[profile.dev.package.backtrace]
opt-level = 3
[profile.release]
lto = true
debug = 2
[workspace.lints.rust]
ambiguous_negative_literals = "warn"
closure_returning_async_block = "warn"
macro_use_extern_crate = "deny"
# must_not_suspend = "deny", unstable see https://github.com/rust-lang/rust/issues/83310
non_ascii_idents = "deny"
redundant_imports = "warn"
semicolon_in_expressions_from_macros = "warn"
trivial_casts = "warn"
trivial_numeric_casts = "warn"
unit_bindings = "warn"
unreachable_pub = "warn"
unsafe_code = "deny"
unused_extern_crates = "warn"
unused_import_braces = "warn"
unused_lifetimes = "warn"
unused_macro_rules = "warn"
unused_qualifications = "warn"
[workspace.lints.rustdoc]
unescaped_backticks = "warn"
[workspace.lints.clippy]
### enable all lints
cargo = { priority = -1, level = "warn" }
complexity = { priority = -1, level = "warn" }
correctness = { priority = -1, level = "warn" }
nursery = { priority = -1, level = "warn" }
pedantic = { priority = -1, level = "warn" }
perf = { priority = -1, level = "warn" }
# Those are too restrictive and disabled by default, however we enable some below
# restriction = { priority = -1, level = "warn" }
style = { priority = -1, level = "warn" }
suspicious = { priority = -1, level = "warn" }
### and disable the ones we don't want
### cargo group
multiple_crate_versions = "allow"
### pedantic group
redundant_closure_for_method_calls = "allow"
struct_field_names = "allow"
too_many_lines = "allow"
### nursery
missing_const_for_fn = "allow"
redundant_pub_crate = "allow"
option_if_let_else = "allow"
### restriction group
allow_attributes = "warn"
allow_attributes_without_reason = "warn"
as_conversions = "warn"
as_pointer_underscore = "warn"
as_underscore = "warn"
assertions_on_result_states = "warn"
clone_on_ref_ptr = "warn"
create_dir = "warn"
dbg_macro = "warn"
default_numeric_fallback = "warn"
disallowed_script_idents = "warn"
empty_drop = "warn"
empty_enum_variants_with_brackets = "warn"
empty_structs_with_brackets = "warn"
error_impl_error = "warn"
exit = "warn"
filetype_is_file = "warn"
float_cmp_const = "warn"
fn_to_numeric_cast_any = "warn"
get_unwrap = "warn"
if_then_some_else_none = "warn"
impl_trait_in_params = "warn"
infinite_loop = "warn"
lossy_float_literal = "warn"
map_with_unused_argument_over_ranges = "warn"
mem_forget = "warn"
missing_asserts_for_indexing = "warn"
missing_trait_methods = "warn"
mixed_read_write_in_expression = "warn"
mutex_atomic = "warn"
mutex_integer = "warn"
needless_raw_strings = "warn"
non_zero_suggestions = "warn"
panic_in_result_fn = "warn"
pathbuf_init_then_push = "warn"
print_stdout = "warn"
rc_buffer = "warn"
redundant_test_prefix = "warn"
redundant_type_annotations = "warn"
ref_patterns = "warn"
renamed_function_params = "warn"
rest_pat_in_fully_bound_structs = "warn"
return_and_then = "warn"
same_name_method = "warn"
semicolon_inside_block = "warn"
str_to_string = "warn"
string_add = "warn"
suspicious_xor_used_as_pow = "warn"
tests_outside_test_module = "warn"
todo = "warn"
try_err = "warn"
undocumented_unsafe_blocks = "warn"
unimplemented = "warn"
unnecessary_safety_comment = "warn"
unnecessary_safety_doc = "warn"
unnecessary_self_imports = "warn"
unneeded_field_pattern = "warn"
unseparated_literal_suffix = "warn"
unused_result_ok = "warn"
unused_trait_names = "warn"
unwrap_in_result = "warn"
unwrap_used = "warn"
verbose_file_reads = "warn"
[package]
name = "authentik"
version = "2026.5.0-rc1"
authors.workspace = true
edition.workspace = true
readme.workspace = true
homepage.workspace = true
repository.workspace = true
license-file.workspace = true
publish.workspace = true
[features]
default = ["core", "proxy"]
proxy = []
core = ["proxy", "dep:sqlx", "dep:pyo3"]
[dependencies]
arc-swap.workspace = true
argh.workspace = true
async-trait.workspace = true
aws-lc-rs.workspace = true
axum-server.workspace = true
axum.workspace = true
client-ip.workspace = true
color-eyre.workspace = true
config.workspace = true
console-subscriber.workspace = true
durstr.workspace = true
eyre.workspace = true
forwarded-header-value.workspace = true
futures.workspace = true
glob.workspace = true
http-body-util.workspace = true
hyper-unix-socket.workspace = true
hyper-util.workspace = true
hyper.workspace = true
ipnet.workspace = true
json-subscriber.workspace = true
jsonwebtoken.workspace = true
metrics.workspace = true
metrics-exporter-prometheus.workspace = true
nix.workspace = true
notify.workspace = true
pem.workspace = true
percent-encoding.workspace = true
pin-project-lite.workspace = true
pyo3 = { workspace = true, optional = true }
rcgen.workspace = true
rustls.workspace = true
sentry.workspace = true
serde.workspace = true
serde_json.workspace = true
sqlx = { workspace = true, optional = true }
thiserror.workspace = true
time.workspace = true
tokio-rustls.workspace = true
tokio-tungstenite.workspace = true
tokio-util.workspace = true
tokio.workspace = true
tower-http.workspace = true
tower.workspace = true
tracing-error.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
url.workspace = true
uuid.workspace = true
[lints]
workspace = true

View File

@@ -23,6 +23,7 @@ BREW_LDFLAGS :=
BREW_CPPFLAGS :=
BREW_PKG_CONFIG_PATH :=
CARGO := cargo
UV := uv
# For macOS users, add the libxml2 installed from brew libxmlsec1 to the build path
@@ -69,22 +70,26 @@ help: ## Show this help
sort
@echo ""
go-test:
go-test: ## Run the golang tests
go test -timeout 0 -v -race -cover ./...
rust-test: ## Run the Rust tests
$(CARGO) nextest run --workspace
test: ## Run the server tests and produce a coverage report (locally)
$(UV) run coverage run manage.py test --keepdb $(or $(filter-out $@,$(MAKECMDGOALS)),authentik)
$(UV) run coverage html
$(UV) run coverage report
lint-fix: lint-spellcheck ## Lint and automatically fix errors in the python source code. Reports spelling errors.
lint-fix: ## Lint and automatically fix errors in the python source code. Reports spelling errors.
$(UV) run black $(PY_SOURCES)
$(UV) run ruff check --fix $(PY_SOURCES)
$(CARGO) +nightly fmt --all -- --config-path .cargo/rustfmt.toml
lint-spellcheck: ## Reports spelling errors.
npm run lint:spellcheck
lint: ci-bandit ci-mypy ## Lint the python and golang sources
lint: ci-lint-bandit ci-lint-mypy ci-lint-cargo-deny ci-lint-cargo-machete ## Lint the python and golang sources
golangci-lint run -v
core-install:
@@ -105,12 +110,24 @@ i18n-extract: core-i18n-extract web-i18n-extract ## Extract strings that requir
aws-cfn:
cd lifecycle/aws && npm i && $(UV) run npm run aws-cfn
run-server: ## Run the main authentik server process
run: ## Run the authentik server and worker, without auto reloading
$(UV) run ak allinone
run-watch: ## Run the authentik server and worker, with auto reloading
$(UV) run watchexec --on-busy-update=restart --stop-signal=SIGINT --exts py,rs --no-meta --notify -- ak allinone
run-server: ## Run the authentik server, without auto reloading
$(UV) run ak server
run-worker: ## Run the main authentik worker process
run-server-watch: ## Run the authentik server, with auto reloading
$(UV) run watchexec --on-busy-update=restart --stop-signal=SIGINT --exts py,rs --no-meta --notify -- ak server
run-worker: ## Run the authentik worker, without auto reloading
$(UV) run ak worker
run-worker-watch: ## Run the authentik worker, with auto reloading
$(UV) run watchexec --on-busy-update=restart --stop-signal=SIGINT --exts py,rs --no-meta --notify -- ak worker
core-i18n-extract:
$(UV) run ak makemessages \
--add-location file \
@@ -149,7 +166,7 @@ ifndef version
$(error Usage: make bump version=20xx.xx.xx )
endif
$(eval current_version := $(shell cat ${PWD}/internal/constants/VERSION))
$(SED_INPLACE) 's/^version = ".*"/version = "$(version)"/' ${PWD}/pyproject.toml
$(SED_INPLACE) 's/^version = ".*"/version = "$(version)"/' ${PWD}/pyproject.toml ${PWD}/Cargo.toml
$(SED_INPLACE) 's/^VERSION = ".*"/VERSION = "$(version)"/' ${PWD}/authentik/__init__.py
$(MAKE) gen-build gen-compose aws-cfn
$(SED_INPLACE) "s/\"${current_version}\"/\"$(version)\"/" ${PWD}/package.json ${PWD}/package-lock.json ${PWD}/web/package.json ${PWD}/web/package-lock.json
@@ -331,27 +348,40 @@ test-docker:
# which makes the YAML File a lot smaller
ci--meta-debug:
$(UV) run python -V
node --version
$(UV) run python -V || echo "No python installed"
$(CARGO) --version || echo "No rust installed"
node --version || echo "No node installed"
ci-mypy: ci--meta-debug
ci-lint-mypy: ci--meta-debug
$(UV) run mypy --strict $(PY_SOURCES)
ci-black: ci--meta-debug
ci-lint-black: ci--meta-debug
$(UV) run black --check $(PY_SOURCES)
ci-ruff: ci--meta-debug
ci-lint-ruff: ci--meta-debug
$(UV) run ruff check $(PY_SOURCES)
ci-spellcheck: ci--meta-debug
ci-lint-spellcheck: ci--meta-debug
npm run lint:spellcheck
ci-bandit: ci--meta-debug
ci-lint-bandit: ci--meta-debug
$(UV) run bandit -c pyproject.toml -r $(PY_SOURCES) -iii
ci-pending-migrations: ci--meta-debug
ci-lint-pending-migrations: ci--meta-debug
$(UV) run ak makemigrations --check
ci-lint-cargo-deny: ci--meta-debug
$(CARGO) deny --locked --workspace check --config .cargo/deny.toml
ci-lint-cargo-machete: ci--meta-debug
$(CARGO) machete
ci-lint-rustfmt: ci--meta-debug
$(CARGO) +nightly fmt --all --check -- --config-path .cargo/rustfmt.toml
ci-lint-clippy: ci--meta-debug
$(CARGO) clippy -- -D warnings
ci-test: ci--meta-debug
$(UV) run coverage run manage.py test --keepdb authentik
$(UV) run coverage report

View File

@@ -92,6 +92,7 @@ class FileBackend(ManageableBackend):
"nbf": now() - timedelta(seconds=15),
},
key=sha256(f"{settings.SECRET_KEY}:{self.usage}".encode()).hexdigest(),
# Must match crates/authentik-server/src/static.rs
algorithm="HS256",
)
url = f"{prefix}/files/{path}?token={token}"

View File

@@ -1,7 +1,5 @@
"""Apply blueprint from commandline"""
from sys import exit as sys_exit
from django.core.management.base import BaseCommand, no_translations
from structlog.stdlib import get_logger
@@ -28,7 +26,7 @@ class Command(BaseCommand):
self.stderr.write("Blueprint invalid")
for log in logs:
self.stderr.write(f"\t{log.logger}: {log.event}: {log.attributes}")
sys_exit(1)
raise RuntimeError("Blueprint invalid")
importer.apply()
def add_arguments(self, parser):

View File

@@ -1115,7 +1115,11 @@ class ExpiringModel(models.Model):
default the object is deleted. This is less efficient compared
to bulk deleting objects, but classes like Token() need to change
values instead of being deleted."""
return self.delete(*args, **kwargs)
try:
return self.delete(*args, **kwargs)
except self.DoesNotExist:
# Object has already been deleted, so this should be fine
return None
@classmethod
def filter_not_expired(cls, **kwargs) -> QuerySet[Self]:

View File

@@ -0,0 +1,101 @@
"""Test interface view redirect behavior by user type"""
from django.test import TestCase
from django.urls import reverse
from authentik.brands.models import Brand
from authentik.core.models import Application, UserTypes
from authentik.core.tests.utils import create_test_brand, create_test_user
class TestInterfaceRedirects(TestCase):
"""Test RootRedirectView and BrandDefaultRedirectView redirect logic by user type"""
def setUp(self):
self.app = Application.objects.create(name="test-app", slug="test-app")
self.brand: Brand = create_test_brand(default_application=self.app)
def _assert_redirects_to_app(self, url_name: str, user_type: UserTypes):
user = create_test_user(type=user_type)
self.client.force_login(user)
response = self.client.get(reverse(f"authentik_core:{url_name}"))
self.assertRedirects(
response,
reverse(
"authentik_core:application-launch", kwargs={"application_slug": self.app.slug}
),
fetch_redirect_response=False,
)
def _assert_no_redirect(self, url_name: str, user_type: UserTypes):
"""Internal users should not be redirected away."""
user = create_test_user(type=user_type)
self.client.force_login(user)
response = self.client.get(reverse(f"authentik_core:{url_name}"))
# Internal users get a 200 (rendered template) or redirect to if-user, not to the app
app_url = reverse(
"authentik_core:application-launch", kwargs={"application_slug": self.app.slug}
)
self.assertNotEqual(response.get("Location"), app_url)
# --- RootRedirectView ---
def test_root_redirect_external_user(self):
"""External users are redirected to the default app from root"""
self._assert_redirects_to_app("root-redirect", UserTypes.EXTERNAL)
def test_root_redirect_service_account(self):
"""Service accounts are redirected to the default app from root"""
self._assert_redirects_to_app("root-redirect", UserTypes.SERVICE_ACCOUNT)
def test_root_redirect_internal_service_account(self):
"""Internal service accounts are redirected to the default app from root"""
self._assert_redirects_to_app("root-redirect", UserTypes.INTERNAL_SERVICE_ACCOUNT)
def test_root_redirect_internal_user(self):
"""Internal users are NOT redirected to the app from root"""
self._assert_no_redirect("root-redirect", UserTypes.INTERNAL)
# --- BrandDefaultRedirectView (if/user/) ---
def test_if_user_external_user(self):
"""External users are redirected to the default app from if/user/"""
self._assert_redirects_to_app("if-user", UserTypes.EXTERNAL)
def test_if_user_service_account(self):
"""Service accounts are redirected to the default app from if/user/"""
self._assert_redirects_to_app("if-user", UserTypes.SERVICE_ACCOUNT)
def test_if_user_internal_service_account(self):
"""Internal service accounts are redirected to the default app from if/user/"""
self._assert_redirects_to_app("if-user", UserTypes.INTERNAL_SERVICE_ACCOUNT)
def test_if_user_internal_user(self):
"""Internal users are NOT redirected to the app from if/user/"""
self._assert_no_redirect("if-user", UserTypes.INTERNAL)
# --- BrandDefaultRedirectView (if/admin/) ---
def test_if_admin_service_account(self):
"""Service accounts are redirected to the default app from if/admin/"""
self._assert_redirects_to_app("if-admin", UserTypes.SERVICE_ACCOUNT)
def test_if_admin_internal_service_account(self):
"""Internal service accounts are redirected to the default app from if/admin/"""
self._assert_redirects_to_app("if-admin", UserTypes.INTERNAL_SERVICE_ACCOUNT)
def test_if_admin_internal_user(self):
"""Internal users are NOT redirected to the app from if/admin/"""
self._assert_no_redirect("if-admin", UserTypes.INTERNAL)
# --- No default app set ---
def test_service_account_no_default_app_access_denied(self):
"""Service accounts get access denied when no default app is configured"""
self.brand.default_application = None
self.brand.save()
user = create_test_user(type=UserTypes.SERVICE_ACCOUNT)
self.client.force_login(user)
response = self.client.get(reverse("authentik_core:if-user"))
self.assertEqual(response.status_code, 200)
self.assertIn(b"Interface can only be accessed by internal users", response.content)

View File

@@ -26,7 +26,11 @@ class RootRedirectView(RedirectView):
query_string = True
def redirect_to_app(self, request: HttpRequest):
if request.user.is_authenticated and request.user.type == UserTypes.EXTERNAL:
if request.user.is_authenticated and request.user.type in (
UserTypes.EXTERNAL,
UserTypes.SERVICE_ACCOUNT,
UserTypes.INTERNAL_SERVICE_ACCOUNT,
):
brand: Brand = request.brand
if brand.default_application:
return redirect(
@@ -62,7 +66,11 @@ class BrandDefaultRedirectView(InterfaceView):
"""By default redirect to default app"""
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
if request.user.is_authenticated and request.user.type == UserTypes.EXTERNAL:
if request.user.is_authenticated and request.user.type in (
UserTypes.EXTERNAL,
UserTypes.SERVICE_ACCOUNT,
UserTypes.INTERNAL_SERVICE_ACCOUNT,
):
brand: Brand = request.brand
if brand.default_application:
return redirect(

View File

@@ -44,3 +44,6 @@ class BaseController[T: "Connector"]:
def stage_view_authentication(self) -> StageView | None:
return None
def sync_endpoints(self):
raise NotImplementedError

View File

@@ -162,8 +162,11 @@ class Connector(ScheduledModel, SerializerModel):
@property
def schedule_specs(self) -> list[ScheduleSpec]:
from authentik.endpoints.controller import Capabilities
from authentik.endpoints.tasks import endpoints_sync
if Capabilities.ENROLL_AUTOMATIC_API not in self.controller(self).capabilities():
return []
return [
ScheduleSpec(
actor=endpoints_sync,

View File

@@ -21,7 +21,7 @@ def endpoints_sync(connector_pk: Any):
return
controller = connector.controller
ctrl = controller(connector)
if Capabilities.AUTOMATIC_API not in ctrl.capabilities():
if Capabilities.ENROLL_AUTOMATIC_API not in ctrl.capabilities():
return
LOGGER.info("Syncing connector", connector=connector.name)
ctrl.sync_endpoints()

View File

@@ -0,0 +1,35 @@
from unittest.mock import PropertyMock, patch
from rest_framework.test import APITestCase
from authentik.endpoints.controller import BaseController, Capabilities
from authentik.endpoints.models import Connector
from authentik.endpoints.tasks import endpoints_sync
from authentik.lib.generators import generate_id
class TestEndpointTasks(APITestCase):
def test_agent_sync(self):
class controller(BaseController):
def capabilities(self):
return [Capabilities.ENROLL_AUTOMATIC_API]
def sync_endpoints(self):
pass
with patch.object(Connector, "controller", PropertyMock(return_value=controller)):
connector = Connector.objects.create(name=generate_id())
self.assertEqual(len(connector.schedule_specs), 1)
endpoints_sync.send(connector.pk).get_result(block=True)
def test_agent_no_sync(self):
class controller(BaseController):
def capabilities(self):
return []
with patch.object(Connector, "controller", PropertyMock(return_value=controller)):
connector = Connector.objects.create(name=generate_id())
self.assertEqual(len(connector.schedule_specs), 0)
endpoints_sync.send(connector.pk).get_result(block=True)

View File

@@ -63,6 +63,7 @@ class NotificationTransportSerializer(ModelSerializer):
"mode",
"mode_verbose",
"webhook_url",
"webhook_ca",
"webhook_mapping_body",
"webhook_mapping_headers",
"email_subject_prefix",

View File

@@ -0,0 +1,26 @@
# Generated by Django 5.2.12 on 2026-03-10 10:40
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_crypto", "0006_certificatekeypair_cert_expiry_and_more"),
("authentik_events", "0016_alter_event_action"),
]
operations = [
migrations.AddField(
model_name="notificationtransport",
name="webhook_ca",
field=models.ForeignKey(
default=None,
help_text="When set, the selected ceritifcate is used to validate the certificate of the webhook server.",
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="authentik_crypto.certificatekeypair",
),
),
]

View File

@@ -28,6 +28,7 @@ from authentik.core.middleware import (
SESSION_KEY_IMPERSONATE_USER,
)
from authentik.core.models import ExpiringModel, Group, PropertyMapping, User
from authentik.crypto.models import CertificateKeyPair
from authentik.events.context_processors.base import get_context_processors
from authentik.events.utils import (
cleanse_dict,
@@ -41,6 +42,7 @@ from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.errors import exception_to_dict
from authentik.lib.utils.http import get_http_session
from authentik.lib.utils.time import timedelta_from_string
from authentik.outposts.docker_tls import DockerInlineTLS
from authentik.policies.models import PolicyBindingModel
from authentik.root.middleware import ClientIPMiddleware
from authentik.root.ws.consumer import build_user_group
@@ -326,6 +328,16 @@ class NotificationTransport(TasksModel, SerializerModel):
email_template = models.TextField(default=EmailTemplates.EVENT_NOTIFICATION)
webhook_url = models.TextField(blank=True, validators=[DomainlessURLValidator()])
webhook_ca = models.ForeignKey(
CertificateKeyPair,
null=True,
default=None,
on_delete=models.SET_DEFAULT,
help_text=_(
"When set, the selected ceritifcate is used to "
"validate the certificate of the webhook server."
),
)
webhook_mapping_body = models.ForeignKey(
"NotificationWebhookMapping",
on_delete=models.SET_DEFAULT,
@@ -409,21 +421,29 @@ class NotificationTransport(TasksModel, SerializerModel):
notification=notification,
)
)
try:
response = get_http_session().post(
self.webhook_url,
json=default_body,
headers=headers,
)
response.raise_for_status()
except RequestException as exc:
raise NotificationTransportError(
exc.response.text if exc.response else str(exc)
) from exc
return [
response.status_code,
response.text,
]
def send(**kwargs):
try:
response = get_http_session().post(
self.webhook_url,
json=default_body,
headers=headers,
**kwargs,
)
response.raise_for_status()
except RequestException as exc:
raise NotificationTransportError(
exc.response.text if exc.response else str(exc)
) from exc
return [
response.status_code,
response.text,
]
if self.webhook_ca:
with DockerInlineTLS(self.webhook_ca, authentication_kp=None) as tls:
return send(verify=tls.ca_cert)
return send()
def send_webhook_slack(self, notification: Notification) -> list[str]:
"""Send notification to slack or slack-compatible endpoints"""

View File

@@ -10,6 +10,7 @@ from requests_mock import Mocker
from authentik import authentik_full_version
from authentik.core.tests.utils import create_test_admin_user
from authentik.crypto.models import CertificateKeyPair
from authentik.events.api.notification_transports import NotificationTransportSerializer
from authentik.events.models import (
Event,
@@ -61,6 +62,37 @@ class TestEventTransports(TestCase):
},
)
def test_transport_webhook_ca_invalid_unset(self):
"""Test webhook transport"""
transport: NotificationTransport = NotificationTransport.objects.create(
name=generate_id(),
mode=TransportMode.WEBHOOK,
webhook_url="https://localhost:1234/test",
)
with Mocker() as mocker:
mocker.post("https://localhost:1234/test")
transport.send(self.notification)
self.assertEqual(mocker.call_count, 1)
self.assertTrue(mocker.request_history[0].verify)
def test_transport_webhook_ca(self):
"""Test webhook transport"""
kp = CertificateKeyPair.objects.create(
name=generate_id(),
certificate_data="foo",
)
transport: NotificationTransport = NotificationTransport.objects.create(
name=generate_id(),
mode=TransportMode.WEBHOOK,
webhook_url="https://localhost:1234/test",
webhook_ca=kp,
)
with Mocker() as mocker:
mocker.post("https://localhost:1234/test")
transport.send(self.notification)
self.assertEqual(mocker.call_count, 1)
self.assertIsNotNone(mocker.request_history[0].verify)
def test_transport_webhook_mapping(self):
"""Test webhook transport with custom mapping"""
mapping_body = NotificationWebhookMapping.objects.create(

View File

@@ -35,14 +35,43 @@
{% block head %}
<script src="{% versioned_script 'dist/flow/FlowInterface-%v.js' %}" type="module"></script>
<style data-id="flow-css">
:root {
--ak-global--background-image: url("{{ flow_background_url }}");
}
</style>
{% endblock %}
{% block body %}
<ak-skip-to-content></ak-skip-to-content>
<ak-message-container></ak-message-container>
<ak-flow
slug="{{ flow.slug }}"
layout={{ flow.layout|default:'stacked' }}"
style='--ak-global--background-image: url("{{ flow_background_url }}")'
></ak-flow>
<div class="pf-c-page__drawer">
<div class="pf-c-drawer pf-m-collapsed" id="flow-drawer">
<div class="pf-c-drawer__main">
<div class="pf-c-drawer__content">
<div class="pf-c-drawer__body">
<ak-flow-executor
slug="{{ flow.slug }}"
class="pf-c-login"
data-layout="{{ flow.layout|default:'stacked' }}"
loading
>
{% include "base/placeholder.html" %}
<ak-brand-links name="flow-links" slot="footer"></ak-brand-links>
</ak-flow-executor>
</div>
</div>
<ak-flow-inspector
id="flow-inspector"
data-registration="lazy"
class="pf-c-drawer__panel pf-m-width-33"
slug="{{ flow.slug }}"
></ak-flow-inspector>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -342,10 +342,10 @@ def django_db_config(config: ConfigLoader | None = None) -> dict:
"default": {
"ENGINE": "psqlextra.backend",
"HOST": config.get("postgresql.host"),
"NAME": config.get("postgresql.name"),
"PORT": config.get("postgresql.port"),
"USER": config.get("postgresql.user"),
"PASSWORD": config.get("postgresql.password"),
"PORT": config.get("postgresql.port"),
"NAME": config.get("postgresql.name"),
"OPTIONS": {
"sslmode": config.get("postgresql.sslmode"),
"sslrootcert": config.get("postgresql.sslrootcert"),
@@ -423,4 +423,5 @@ if __name__ == "__main__":
if len(argv) < 2: # noqa: PLR2004
print(dumps(CONFIG.raw, indent=4, cls=AttrEncoder))
else:
print(CONFIG.get(argv[-1]))
for arg in argv[1:]:
print(CONFIG.get(arg))

View File

@@ -17,11 +17,13 @@
postgresql:
host: localhost
name: authentik
user: authentik
port: 5432
user: authentik
password: "env://POSTGRES_PASSWORD"
name: authentik
sslmode: disable
conn_max_age: 60
conn_health_checks: false
use_pool: False
test:
name: test_authentik
@@ -32,12 +34,18 @@ postgresql:
# host: replica1.example.com
listen:
http: 0.0.0.0:9000
https: 0.0.0.0:9443
ldap: 0.0.0.0:3389
ldaps: 0.0.0.0:6636
radius: 0.0.0.0:1812
metrics: 0.0.0.0:9300
http:
- "[::]:9000"
https:
- "[::]:9443"
ldap:
- "[::]:3389"
ldaps:
- "[::]:6636"
radius:
- "[::]:1812"
metrics:
- "[::]:9300"
debug: 0.0.0.0:9900
debug_py: 0.0.0.0:9901
trusted_proxy_cidrs:
@@ -66,6 +74,19 @@ log_level: info
log:
http_headers:
- User-Agent
rust_log:
"console_subscriber": info
"h2": info
"hyper_util": warn
"mio": info
"notify": info
"reqwest": info
"runtime": info
"rustls": info
"sqlx": info
"sqlx_postgres": info
"tokio": info
"tungstenite": info
sessions:
unauthenticated_age: days=1
@@ -137,8 +158,7 @@ tenants:
blueprints_dir: /blueprints
web:
# No default here as it's set dynamically
# workers: 2
workers: 2
threads: 4
path: /
timeout_http_read_header: 5s
@@ -183,3 +203,5 @@ storage:
# backend: file # or s3
# file: {}
# s3: {}
skip_migrations: false

View File

@@ -41,7 +41,7 @@ def structlog_configure():
add_process_id,
add_tenant_information,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.TimeStamper(fmt="iso", utc=False),
structlog.processors.TimeStamper(fmt="iso", utc=True),
structlog.processors.StackInfoRenderer(),
structlog.processors.ExceptionRenderer(
structlog.tracebacks.ExceptionDictTransformer(show_locals=CONFIG.get_bool("debug"))

View File

@@ -27,6 +27,12 @@ class DockerInlineTLS:
self.authentication_kp = authentication_kp
self._paths = []
def __enter__(self):
return self.write()
def __exit__(self, exc_type, exc, tb):
self.cleanup()
def write_file(self, name: str, contents: str) -> str:
"""Wrapper for mkstemp that uses fdopen"""
path = Path(gettempdir(), name)

View File

@@ -163,4 +163,5 @@ def outpost_pre_delete_cleanup(sender, instance: Outpost, **_):
@receiver(pre_delete, sender=AuthenticatedSession)
def outpost_logout_revoke(sender: type[AuthenticatedSession], instance: AuthenticatedSession, **_):
"""Catch logout by expiring sessions being deleted"""
outpost_session_end.send(instance.session.session_key)
if Outpost.objects.exists():
outpost_session_end.send(instance.session.session_key)

View File

@@ -7,7 +7,6 @@ For example: The 'dummy' policy is available at `authentik.policies.dummy`.
from prometheus_client import Gauge, Histogram
from authentik.blueprints.apps import ManagedAppConfig
from authentik.tenants.flags import Flag
GAUGE_POLICIES_CACHED = Gauge(
"authentik_policies_cached",
@@ -32,12 +31,6 @@ HIST_POLICIES_EXECUTION_TIME = Histogram(
)
class BufferedPolicyAccessViewFlag(Flag[bool], key="policies_buffered_access_view"):
default = False
visibility = "public"
class AuthentikPoliciesConfig(ManagedAppConfig):
"""authentik policies app config"""

View File

@@ -1,29 +1,19 @@
from django.http import Http404, HttpResponse
from django.test import TestCase
from django.urls import reverse
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application, Group, Provider
from authentik.core.tests.utils import (
RequestFactory,
create_test_brand,
create_test_flow,
create_test_user,
)
from authentik.flows.models import Flow, FlowDesignation
from authentik.flows.planner import FlowPlan
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.generators import generate_id
from authentik.policies.apps import BufferedPolicyAccessViewFlag
from authentik.policies.models import PolicyBinding
from authentik.policies.views import (
QS_BUFFER_ID,
SESSION_KEY_BUFFER,
BufferedPolicyAccessView,
BufferView,
PolicyAccessView,
)
from authentik.tenants.flags import patch_flag
class TestPolicyViews(TestCase):
@@ -124,71 +114,3 @@ class TestPolicyViews(TestCase):
res = TestView.as_view()(req)
self.assertEqual(res.status_code, 302)
self.assertEqual(res.url, "/if/flow/default-authentication-flow/?next=%2F")
@patch_flag(BufferedPolicyAccessViewFlag, True)
def test_pav_buffer(self):
"""Test simple policy access view"""
provider = Provider.objects.create(
name=generate_id(),
)
app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider)
flow = create_test_flow(FlowDesignation.AUTHENTICATION)
class TestView(BufferedPolicyAccessView):
def resolve_provider_application(self):
self.provider = provider
self.application = app
def get(self, *args, **kwargs):
return HttpResponse("foo")
req = self.factory.get("/")
req.session[SESSION_KEY_PLAN] = FlowPlan(flow.pk)
req.session.save()
res = TestView.as_view()(req)
self.assertEqual(res.status_code, 302)
self.assertTrue(res.url.startswith(reverse("authentik_policies:buffer")))
@patch_flag(BufferedPolicyAccessViewFlag, True)
@apply_blueprint("default/flow-default-authentication-flow.yaml")
def test_pav_buffer_skip(self):
"""Test simple policy access view (skip buffer)"""
provider = Provider.objects.create(
name=generate_id(),
)
app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider)
flow = Flow.objects.get(slug="default-authentication-flow")
class TestView(BufferedPolicyAccessView):
def resolve_provider_application(self):
self.provider = provider
self.application = app
def get(self, *args, **kwargs):
return HttpResponse("foo")
req = self.factory.get("/?skip_buffer=true")
req.brand = create_test_brand(flow_authentication=flow)
req.session[SESSION_KEY_PLAN] = FlowPlan(flow.pk)
req.session.save()
res = TestView.as_view()(req)
self.assertEqual(res.status_code, 302)
self.assertTrue(
res.url.startswith(reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug}))
)
def test_buffer(self):
"""Test buffer view"""
uid = generate_id()
req = self.factory.get(f"/?{QS_BUFFER_ID}={uid}")
ts = generate_id()
req.session[SESSION_KEY_BUFFER % uid] = {
"method": "get",
"body": {},
"url": f"/{ts}",
}
req.session.save()
res = BufferView.as_view()(req)
self.assertEqual(res.status_code, 200)
self.assertIn(ts, res.render().content.decode())

View File

@@ -1,12 +1,10 @@
"""authentik access helper classes"""
from typing import Any
from uuid import uuid4
from django.contrib import messages
from django.contrib.auth.mixins import AccessMixin
from django.http import Http404, HttpRequest, HttpResponse, QueryDict
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.http import urlencode
from django.utils.translation import gettext as _
@@ -19,16 +17,13 @@ from authentik.flows.models import Flow, FlowDesignation
from authentik.flows.planner import (
PLAN_CONTEXT_APPLICATION,
PLAN_CONTEXT_POST,
FlowPlan,
FlowPlanner,
)
from authentik.flows.views.executor import (
SESSION_KEY_PLAN,
SESSION_KEY_POST,
ToDefaultFlow,
)
from authentik.lib.sentry import SentryIgnoredException
from authentik.policies.apps import BufferedPolicyAccessViewFlag
from authentik.policies.denied import AccessDeniedResponse
from authentik.policies.engine import PolicyEngine
from authentik.policies.models import PolicyBindingModel
@@ -194,39 +189,3 @@ class BufferView(TemplateView):
kwargs["check_auth_url"] = reverse("authentik_api:user-me")
kwargs["continue_url"] = url_with_qs(buffer["url"], **{QS_BUFFER_ID: buf_id})
return super().get_context_data(**kwargs)
class BufferedPolicyAccessView(PolicyAccessView):
"""PolicyAccessView which buffers access requests in case the user is not logged in"""
def handle_no_permission(self):
plan: FlowPlan | None = self.request.session.get(SESSION_KEY_PLAN)
if plan:
flow = Flow.objects.filter(pk=plan.flow_pk).first()
if not flow or flow.designation != FlowDesignation.AUTHENTICATION:
LOGGER.debug("Not buffering request, no flow or flow not for authentication")
return super().handle_no_permission()
if not plan:
LOGGER.debug("Not buffering request, no flow plan active")
return super().handle_no_permission()
if not BufferedPolicyAccessViewFlag.get():
return super().handle_no_permission()
if self.request.GET.get(QS_SKIP_BUFFER):
LOGGER.debug("Not buffering request, explicit skip")
return super().handle_no_permission()
buffer_id = str(uuid4())
LOGGER.debug("Buffering access request", bf_id=buffer_id)
self.request.session[SESSION_KEY_BUFFER % buffer_id] = {
"body": self.request.POST,
"url": self.request.build_absolute_uri(self.request.get_full_path()),
"method": self.request.method.lower(),
}
return redirect(
url_with_qs(reverse("authentik_policies:buffer"), **{QS_BUFFER_ID: buffer_id})
)
def dispatch(self, request, *args, **kwargs):
response = super().dispatch(request, *args, **kwargs)
if QS_BUFFER_ID in self.request.GET:
self.request.session.pop(SESSION_KEY_BUFFER % self.request.GET[QS_BUFFER_ID], None)
return response

View File

@@ -15,7 +15,7 @@ from authentik.common.oauth.constants import (
SCOPE_OPENID_PROFILE,
TOKEN_TYPE,
)
from authentik.core.models import Application, Group
from authentik.core.models import USERNAME_MAX_LENGTH, Application, Group, User
from authentik.core.tests.utils import create_test_cert, create_test_flow
from authentik.lib.generators import generate_id
from authentik.policies.models import PolicyBinding
@@ -27,7 +27,7 @@ from authentik.providers.oauth2.models import (
)
from authentik.providers.oauth2.tests.utils import OAuthTestCase
from authentik.providers.oauth2.views.jwks import JWKSView
from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.models import OAuthSource, OAuthSourcePropertyMapping
class TestTokenClientCredentialsJWTSource(OAuthTestCase):
@@ -220,6 +220,10 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
},
)
self.assertEqual(response.status_code, 200)
user = User.objects.filter(username=f"{self.provider.name}-foo").first()
self.assertIsNotNone(user)
body = loads(response.content.decode())
self.assertEqual(body["token_type"], TOKEN_TYPE)
_, alg = self.provider.jwt_key
@@ -233,3 +237,54 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
jwt["given_name"], "Autogenerated user from application test (client credentials JWT)"
)
self.assertEqual(jwt["preferred_username"], "test-foo")
def test_successful_mapping(self):
"""test successful"""
test_username = ("mapped-foo" + ("a" * 150))[:USERNAME_MAX_LENGTH]
mapping = OAuthSourcePropertyMapping.objects.create(
name="test-mapping",
expression="""return {
"email": oauth_userinfo.get("email"),
"name": oauth_userinfo.get("name"),
"username": oauth_userinfo.get("username"),
}""",
)
self.source.user_property_mappings.add(mapping)
token = self.helper_provider.encode(
{
"sub": "foo",
"email": "test-user@example.com",
"name": "Mapped Test User",
"username": "mapped-foo" + ("a" * 150),
"exp": datetime.now() + timedelta(hours=2),
}
)
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}",
"client_id": self.provider.client_id,
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": token,
},
)
self.assertEqual(response.status_code, 200)
user = User.objects.filter(username=test_username).first()
self.assertIsNotNone(user)
body = loads(response.content.decode())
self.assertEqual(body["token_type"], TOKEN_TYPE)
key_obj, alg = self.provider.jwt_key
jwt = decode(
body["access_token"],
key=key_obj.public_key(),
algorithms=[alg],
audience=self.provider.client_id,
)
self.assertEqual(jwt["email"], "test-user@example.com")
self.assertEqual(jwt["given_name"], "Mapped Test User")
self.assertEqual(jwt["preferred_username"], test_username)

View File

@@ -45,7 +45,7 @@ from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, StageVie
from authentik.lib.utils.time import timedelta_from_string
from authentik.lib.views import bad_request_message
from authentik.policies.types import PolicyRequest
from authentik.policies.views import BufferedPolicyAccessView, RequestValidationError
from authentik.policies.views import PolicyAccessView, RequestValidationError
from authentik.providers.oauth2.errors import (
AuthorizeError,
ClientIdError,
@@ -338,7 +338,7 @@ class OAuthAuthorizationParams:
return code
class AuthorizationFlowInitView(BufferedPolicyAccessView):
class AuthorizationFlowInitView(PolicyAccessView):
"""OAuth2 Flow initializer, checks access to application and starts flow"""
params: OAuthAuthorizationParams

View File

@@ -45,6 +45,7 @@ from authentik.core.models import (
User,
UserTypes,
)
from authentik.core.sources.mapper import SourceMapper
from authentik.events.middleware import audit_ignore
from authentik.events.models import Event, EventAction
from authentik.events.signals import get_login_event
@@ -476,7 +477,7 @@ class TokenParams:
self.__check_policy_access(app, request, oauth_jwt=token)
if not provider:
self.__create_user_from_jwt(token, app, source)
self.__create_user_from_jwt(token, app, source, request)
method_args = {
"jwt": token,
@@ -530,18 +531,30 @@ class TokenParams:
raise TokenError("invalid_grant")
self.device_code = code
def __create_user_from_jwt(self, token: dict[str, Any], app: Application, source: OAuthSource):
def __create_user_from_jwt(
self, token: dict[str, Any], app: Application, source: OAuthSource, request: HttpRequest
):
"""Create user from JWT"""
with audit_ignore():
# Run the JWT payload through the core mapping engine
mapped = SourceMapper(source).build_object_properties(
User, request=request, info=token, oauth_userinfo=token
)
self.user, created = User.objects.update_or_create(
username=f"{self.provider.name}-{token.get('sub')}",
username=mapped.get("username", f"{self.provider.name}-{token.get('sub')}")[
:USERNAME_MAX_LENGTH
],
defaults={
"last_login": timezone.now(),
"name": (
f"Autogenerated user from application {app.name} (client credentials JWT)"
"name": mapped.get(
"name",
f"Autogenerated user from application {app.name} (client credentials JWT)",
),
"email": mapped.get("email", ""),
"path": source.get_user_path(),
"type": UserTypes.SERVICE_ACCOUNT,
"attributes": mapped.get("attributes", {}),
},
)
self.user.attributes[USER_ATTRIBUTE_GENERATED] = True

View File

@@ -1,13 +0,0 @@
"""Proxy provider signals"""
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from authentik.core.models import AuthenticatedSession
from authentik.providers.proxy.tasks import proxy_on_logout
@receiver(pre_delete, sender=AuthenticatedSession)
def logout_proxy_revoke(sender: type[AuthenticatedSession], instance: AuthenticatedSession, **_):
"""Catch logout by expiring sessions being deleted"""
proxy_on_logout.send(instance.session.session_key)

View File

@@ -1,25 +0,0 @@
"""proxy provider tasks"""
from channels.layers import get_channel_layer
from django.utils.translation import gettext_lazy as _
from dramatiq.actor import actor
from authentik.outposts.consumer import build_outpost_group
from authentik.outposts.models import Outpost, OutpostType
from authentik.providers.oauth2.id_token import hash_session_key
@actor(description=_("Terminate session on Proxy outpost."))
def proxy_on_logout(session_id: str):
layer = get_channel_layer()
hashed_session_id = hash_session_key(session_id)
for outpost in Outpost.objects.filter(type=OutpostType.PROXY):
group = build_outpost_group(outpost.pk)
layer.group_send_blocking(
group,
{
"type": "event.provider.specific",
"sub_type": "logout",
"session_id": hashed_session_id,
},
)

View File

@@ -18,14 +18,14 @@ from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, FlowPlanner
from authentik.flows.stage import RedirectStage
from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.engine import PolicyEngine
from authentik.policies.views import BufferedPolicyAccessView
from authentik.policies.views import PolicyAccessView
from authentik.providers.rac.models import ConnectionToken, Endpoint, RACProvider
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
PLAN_CONNECTION_SETTINGS = "connection_settings"
class RACStartView(BufferedPolicyAccessView):
class RACStartView(PolicyAccessView):
"""Start a RAC connection by checking access and creating a connection token"""
endpoint: Endpoint

View File

@@ -15,7 +15,7 @@ from authentik.flows.models import in_memory_stage
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_SSO, FlowPlanner
from authentik.flows.views.executor import SESSION_KEY_POST
from authentik.lib.views import bad_request_message
from authentik.policies.views import BufferedPolicyAccessView
from authentik.policies.views import PolicyAccessView
from authentik.providers.saml.exceptions import CannotHandleAssertion
from authentik.providers.saml.models import SAMLBindings, SAMLProvider
from authentik.providers.saml.processors.authn_request_parser import AuthNRequestParser
@@ -35,7 +35,7 @@ from authentik.stages.consent.stage import (
LOGGER = get_logger()
class SAMLSSOView(BufferedPolicyAccessView):
class SAMLSSOView(PolicyAccessView):
"""SAML SSO Base View, which plans a flow and injects our final stage.
Calls get/post handler."""
@@ -88,7 +88,7 @@ class SAMLSSOView(BufferedPolicyAccessView):
def post(self, request: HttpRequest, application_slug: str) -> HttpResponse:
"""GET and POST use the same handler, but we can't
override .dispatch easily because BufferedPolicyAccessView's dispatch"""
override .dispatch easily because PolicyAccessView's dispatch"""
return self.get(request, application_slug)

View File

@@ -7,7 +7,6 @@ from django.db import transaction
from django.utils.http import urlencode
from orjson import dumps
from pydantic import ValidationError
from pydanticscim.group import GroupMember
from authentik.core.models import Group
from authentik.lib.merge import MERGE_LIST_UNIQUE
@@ -25,6 +24,7 @@ from authentik.providers.scim.clients.exceptions import (
)
from authentik.providers.scim.clients.schema import (
SCIM_GROUP_SCHEMA,
GroupMember,
PatchOp,
PatchOperation,
PatchRequest,
@@ -111,7 +111,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
raise exc
groups = self._request(
"GET",
f"/Groups?{urlencode({'filter': f'displayName eq \"{group.name}\"'})}",
f"/Groups?{urlencode({'filter': f'displayName eq "{group.name}"'})}",
)
groups_res = groups.get("Resources", [])
if len(groups_res) < 1:
@@ -321,7 +321,12 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
PatchOperation(
op=PatchOp.add,
path="members",
value=[{"value": x}],
value=[
GroupMember(value=x).model_dump(
mode="json",
exclude_unset=True,
)
],
)
for x in users_to_add
],
@@ -329,7 +334,12 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
PatchOperation(
op=PatchOp.remove,
path="members",
value=[{"value": x}],
value=[
GroupMember(value=x).model_dump(
mode="json",
exclude_unset=True,
)
],
)
for x in users_to_remove
],
@@ -352,7 +362,12 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
PatchOperation(
op=PatchOp.add,
path="members",
value=[{"value": x}],
value=[
GroupMember(value=x).model_dump(
mode="json",
exclude_unset=True,
)
],
)
for x in user_ids
],
@@ -375,7 +390,12 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
PatchOperation(
op=PatchOp.remove,
path="members",
value=[{"value": x}],
value=[
GroupMember(value=x).model_dump(
mode="json",
exclude_unset=True,
)
],
)
for x in user_ids
],

View File

@@ -7,6 +7,7 @@ from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from pydantic import AnyUrl, BaseModel, ConfigDict, Field, model_validator
from pydanticscim.group import Group as BaseGroup
from pydanticscim.group import GroupMember as BaseGroupMember
from pydanticscim.responses import PatchOperation as BasePatchOperation
from pydanticscim.responses import PatchRequest as BasePatchRequest
from pydanticscim.responses import SCIMError as BaseSCIMError
@@ -160,6 +161,13 @@ class Group(BaseGroup):
schemas: list[str] = [SCIM_GROUP_SCHEMA]
externalId: str | None = None
meta: dict | None = None
members: list[GroupMember] | None = Field(None, description="A list of members of the Group.")
class GroupMember(BaseGroupMember):
"""Modified GroupMember that allows extra fields"""
model_config = ConfigDict(extra="allow")
class Bulk(BaseBulk):

View File

@@ -339,6 +339,9 @@ class LoggingMiddleware:
def log(self, request: HttpRequest, status_code: int, runtime: int, **kwargs):
"""Log request"""
# Those are logged by the server above
if request.path in ("/-/metrics/", "/-/health/ready/"):
return
for header in self.headers_to_log:
header_value = request.headers.get(header)
if not header_value:

View File

@@ -1,37 +1,21 @@
"""Metrics view"""
from hmac import compare_digest
from pathlib import Path
from tempfile import gettempdir
from django.conf import settings
from django.db import connections
from django.db.utils import OperationalError
from django.dispatch import Signal
from django.http import HttpRequest, HttpResponse
from django.views import View
from django_prometheus.exports import ExportToDjangoView
monitoring_set = Signal()
class MetricsView(View):
"""Wrapper around ExportToDjangoView with authentication, accessed by the authentik router"""
def __init__(self, **kwargs):
_tmp = Path(gettempdir())
with open(_tmp / "authentik-core-metrics.key") as _f:
self.monitoring_key = _f.read()
"""View for metrics monitoring_set signal, accessed by the authentik router"""
def get(self, request: HttpRequest) -> HttpResponse:
"""Check for HTTP-Basic auth"""
auth_header = request.META.get("HTTP_AUTHORIZATION", "")
auth_type, _, given_credentials = auth_header.partition(" ")
authed = auth_type == "Bearer" and compare_digest(given_credentials, self.monitoring_key)
if not authed and not settings.DEBUG:
return HttpResponse(status=401)
monitoring_set.send_robust(self)
return ExportToDjangoView(request)
return HttpResponse(status=204)
class LiveView(View):

View File

@@ -186,6 +186,7 @@ SPECTACULAR_SETTINGS = {
"SAMLBindingsEnum": "authentik.providers.saml.models.SAMLBindings",
"UserTypeEnum": "authentik.core.models.UserTypes",
"UserVerificationEnum": "authentik.stages.authenticator_webauthn.models.UserVerification",
"WebAuthnHintEnum": "authentik.stages.authenticator_webauthn.models.WebAuthnHint",
"SCIMAuthenticationModeEnum": "authentik.providers.scim.models.SCIMAuthenticationMode",
"PKCEMethodEnum": "authentik.sources.oauth.models.PKCEMethod",
"DeviceFactsOSFamily": "authentik.endpoints.facts.OSFamily",
@@ -439,8 +440,6 @@ DRAMATIQ = {
("authentik.tasks.middleware.TaskLogMiddleware", {}),
("authentik.tasks.middleware.LoggingMiddleware", {}),
("authentik.tasks.middleware.DescriptionMiddleware", {}),
("authentik.tasks.middleware.WorkerHealthcheckMiddleware", {}),
("authentik.tasks.middleware.WorkerStatusMiddleware", {}),
(
"authentik.tasks.middleware.MetricsMiddleware",
{

View File

@@ -14,12 +14,12 @@ class TestRoot(TransactionTestCase):
def setUp(self):
_tmp = Path(gettempdir())
self.token = token_urlsafe(32)
with open(_tmp / "authentik-core-metrics.key", "w") as _f:
with open(_tmp / "authentik-metrics-gunicorn.key", "w") as _f:
_f.write(self.token)
def tearDown(self):
_tmp = Path(gettempdir())
(_tmp / "authentik-core-metrics.key").unlink()
(_tmp / "authentik-metrics-gunicorn.key").unlink()
def test_monitoring_error(self):
"""Test monitoring without any credentials"""

View File

@@ -36,6 +36,7 @@ class AuthenticatorValidateStageSerializer(StageSerializer):
"configuration_stages",
"last_auth_threshold",
"webauthn_user_verification",
"webauthn_hints",
"webauthn_allowed_device_types",
"webauthn_allowed_device_types_obj",
]

View File

@@ -80,7 +80,10 @@ def get_webauthn_challenge_without_user(
authentication_options.challenge
)
return options_to_json_dict(authentication_options)
options_dict = options_to_json_dict(authentication_options)
if stage.webauthn_hints:
options_dict["hints"] = list(stage.webauthn_hints)
return options_dict
def get_webauthn_challenge(
@@ -109,7 +112,10 @@ def get_webauthn_challenge(
authentication_options.challenge
)
return options_to_json_dict(authentication_options)
options_dict = options_to_json_dict(authentication_options)
if stage.webauthn_hints:
options_dict["hints"] = list(stage.webauthn_hints)
return options_dict
def select_challenge(request: HttpRequest, device: Device):

View File

@@ -0,0 +1,33 @@
# Generated by Django 5.2.11 on 2026-03-04 02:30
import django.contrib.postgres.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"authentik_stages_authenticator_validate",
"0014_alter_authenticatorvalidatestage_device_classes",
),
]
operations = [
migrations.AddField(
model_name="authenticatorvalidatestage",
name="webauthn_hints",
field=django.contrib.postgres.fields.ArrayField(
base_field=models.TextField(
choices=[
("security-key", "Security Key"),
("client-device", "Client Device"),
("hybrid", "Hybrid"),
]
),
blank=True,
default=list,
size=None,
),
),
]

View File

@@ -8,7 +8,7 @@ from rest_framework.serializers import BaseSerializer
from authentik.flows.models import NotConfiguredAction, Stage
from authentik.lib.utils.time import timedelta_string_validator
from authentik.stages.authenticator_webauthn.models import UserVerification
from authentik.stages.authenticator_webauthn.models import UserVerification, WebAuthnHint
class DeviceClasses(models.TextChoices):
@@ -73,6 +73,11 @@ class AuthenticatorValidateStage(Stage):
choices=UserVerification.choices,
default=UserVerification.PREFERRED,
)
webauthn_hints = ArrayField(
models.TextField(choices=WebAuthnHint.choices),
default=list,
blank=True,
)
webauthn_allowed_device_types = models.ManyToManyField(
"authentik_stages_authenticator_webauthn.WebAuthnDeviceType", blank=True
)

View File

@@ -28,6 +28,7 @@ from authentik.stages.authenticator_webauthn.models import (
UserVerification,
WebAuthnDevice,
WebAuthnDeviceType,
WebAuthnHint,
)
from authentik.stages.authenticator_webauthn.stage import PLAN_CONTEXT_WEBAUTHN_CHALLENGE
from authentik.stages.authenticator_webauthn.tasks import webauthn_mds_import
@@ -256,6 +257,105 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
self.assertEqual(challenge["timeout"], 60000)
self.assertEqual(challenge["userVerification"], "preferred")
def test_device_challenge_webauthn_with_hints(self):
"""Test that webauthn hints are included in authentication challenge"""
request = self.request_factory.get("/")
request.user = self.user
webauthn_device = WebAuthnDevice.objects.create(
user=self.user,
public_key=bytes_to_base64url(b"qwerqwerqre"),
credential_id=bytes_to_base64url(b"foobarbaz"),
sign_count=0,
rp_id=generate_id(),
)
stage = AuthenticatorValidateStage.objects.create(
name=generate_id(),
last_auth_threshold="milliseconds=0",
not_configured_action=NotConfiguredAction.CONFIGURE,
device_classes=[DeviceClasses.WEBAUTHN],
webauthn_user_verification=UserVerification.PREFERRED,
webauthn_hints=[WebAuthnHint.CLIENT_DEVICE, WebAuthnHint.HYBRID],
)
plan = FlowPlan("")
stage_view = AuthenticatorValidateStageView(
FlowExecutorView(flow=None, current_stage=stage, plan=plan), request=request
)
challenge = get_challenge_for_device(stage_view, stage, webauthn_device)
self.assertEqual(challenge["hints"], ["client-device", "hybrid"])
def test_device_challenge_webauthn_no_hints(self):
"""Test that hints key is absent when no hints configured"""
request = self.request_factory.get("/")
request.user = self.user
webauthn_device = WebAuthnDevice.objects.create(
user=self.user,
public_key=bytes_to_base64url(b"qwerqwerqre"),
credential_id=bytes_to_base64url(b"foobarbaz"),
sign_count=0,
rp_id=generate_id(),
)
stage = AuthenticatorValidateStage.objects.create(
name=generate_id(),
last_auth_threshold="milliseconds=0",
not_configured_action=NotConfiguredAction.CONFIGURE,
device_classes=[DeviceClasses.WEBAUTHN],
webauthn_user_verification=UserVerification.PREFERRED,
)
plan = FlowPlan("")
stage_view = AuthenticatorValidateStageView(
FlowExecutorView(flow=None, current_stage=stage, plan=plan), request=request
)
challenge = get_challenge_for_device(stage_view, stage, webauthn_device)
self.assertNotIn("hints", challenge)
def test_get_challenge_userless_with_hints(self):
"""Test that hints are included in userless/passwordless challenge"""
request = self.request_factory.get("/")
stage = AuthenticatorValidateStage.objects.create(
name=generate_id(),
webauthn_user_verification=UserVerification.PREFERRED,
webauthn_hints=[WebAuthnHint.SECURITY_KEY, WebAuthnHint.CLIENT_DEVICE],
)
plan = FlowPlan("")
stage_view = AuthenticatorValidateStageView(
FlowExecutorView(flow=None, current_stage=stage, plan=plan), request=request
)
challenge = get_webauthn_challenge_without_user(stage_view, stage)
self.assertEqual(challenge["hints"], ["security-key", "client-device"])
def test_device_challenge_webauthn_hints_order_preserved(self):
"""Test that hint order is preserved in authentication challenge"""
request = self.request_factory.get("/")
request.user = self.user
webauthn_device = WebAuthnDevice.objects.create(
user=self.user,
public_key=bytes_to_base64url(b"qwerqwerqre"),
credential_id=bytes_to_base64url(b"foobarbaz"),
sign_count=0,
rp_id=generate_id(),
)
stage = AuthenticatorValidateStage.objects.create(
name=generate_id(),
last_auth_threshold="milliseconds=0",
not_configured_action=NotConfiguredAction.CONFIGURE,
device_classes=[DeviceClasses.WEBAUTHN],
webauthn_user_verification=UserVerification.PREFERRED,
webauthn_hints=[
WebAuthnHint.HYBRID,
WebAuthnHint.SECURITY_KEY,
WebAuthnHint.CLIENT_DEVICE,
],
)
plan = FlowPlan("")
stage_view = AuthenticatorValidateStageView(
FlowExecutorView(flow=None, current_stage=stage, plan=plan), request=request
)
challenge = get_challenge_for_device(stage_view, stage, webauthn_device)
self.assertEqual(challenge["hints"], ["hybrid", "security-key", "client-device"])
def test_validate_challenge_unrestricted(self):
"""Test webauthn authentication (unrestricted webauthn device)"""
webauthn_mds_import.send(force=True).get_result()

View File

@@ -23,6 +23,7 @@ class AuthenticatorWebAuthnStageSerializer(StageSerializer):
"user_verification",
"authenticator_attachment",
"resident_key_requirement",
"hints",
"device_type_restrictions",
"device_type_restrictions_obj",
"max_attempts",
@@ -34,6 +35,14 @@ class AuthenticatorWebAuthnStageViewSet(UsedByMixin, ModelViewSet):
queryset = AuthenticatorWebAuthnStage.objects.all()
serializer_class = AuthenticatorWebAuthnStageSerializer
filterset_fields = "__all__"
filterset_fields = [
"name",
"configure_flow",
"user_verification",
"authenticator_attachment",
"resident_key_requirement",
"device_type_restrictions",
"max_attempts",
]
ordering = ["name"]
search_fields = ["name"]

View File

@@ -191,5 +191,8 @@
"name": "Sticky Password Manager",
"icon_dark": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgdmlld0JveD0iMCAwIDEwOCAxMDgiCiAgIHdpZHRoPSIxMDgiCiAgIGhlaWdodD0iMTA4IgogICB2ZXJzaW9uPSIxLjEiCiAgIGlkPSJzdmc1IgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDx0aXRsZT5TdGlja3kgUGFzc3dvcmQgTWFuYWdlcjwvdGl0bGU+CiAgPGRlZnMKICAgICBpZD0iZGVmczUiIC8+CiAgPGNpcmNsZQogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7c3Ryb2tlLXdpZHRoOjEuNzYyMDEiCiAgICAgaWQ9InBhdGg2IgogICAgIGN4PSI1NCIKICAgICBjeT0iNTQiCiAgICAgcj0iNTQiIC8+CiAgPGcKICAgICBpZD0iZzYiCiAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMS4zNDMzMjQzLDAsMCwxLjM0MzMyNDMsLTE4LjU0MDYwNywtMTguNTI3NTk0KSI+CiAgICA8cGF0aAogICAgICAgZD0ibSA2NC4zNSw1My4xMSAtOS41LC05LjUgYyAtMC40OCwtMC40OCAtMS4yNiwtMC40OCAtMS43NSwwIGwgLTkuNSw5LjUgYyAtMC40OCwwLjQ4IC0wLjQ4LDEuMjYgMCwxLjc0IGwgOS41LDkuNSBjIDAuMjQsMC4yNCAwLjU2LDAuMzYgMC44NywwLjM2IDAuMzEsMCAwLjYzLC0wLjEyIDAuODcsLTAuMzYgbCA5LjUsLTkuNSBjIDAuMjMsLTAuMjMgMC4zNiwtMC41NCAwLjM2LC0wLjg3IDAsLTAuMzMgLTAuMTMsLTAuNjQgLTAuMzYsLTAuODcgeiIKICAgICAgIGZpbGw9IiMwMDAwMDAiCiAgICAgICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgICAgICBzdHJva2U9IiMwMDAwMDAiCiAgICAgICBzdHJva2Utd2lkdGg9IjMuNjIiCiAgICAgICBzdHJva2Utb3BhY2l0eT0iMCIKICAgICAgIGlkPSJwYXRoMSIgLz4KICAgIDxwYXRoCiAgICAgICBkPSJtIDUwLjExLDY3LjM0IC05LjUsLTkuNSBjIC0wLjQ4LC0wLjQ4IC0xLjI2LC0wLjQ4IC0xLjc0LDAgbCAtOS40OSw5LjUgYyAtMC40OCwwLjQ4IC0wLjQ4LDEuMjYgMCwxLjc1IGwgOS40OSw5LjUgYyAwLjI0LDAuMjQgMC41NiwwLjM2IDAuODcsMC4zNiAwLjMxLDAgMC42MywtMC4xMiAwLjg3LC0wLjM2IGwgOS41LC05LjUgYyAwLjIzLC0wLjI0IDAuMzYsLTAuNTQgMC4zNiwtMC44NyAwLC0wLjMzIC0wLjEzLC0wLjY0IC0wLjM2LC0wLjg3IgogICAgICAgZmlsbD0iIzAwYTllMCIKICAgICAgIGZpbGwtcnVsZT0iZXZlbm9kZCIKICAgICAgIHN0cm9rZT0iIzAwMDAwMCIKICAgICAgIHN0cm9rZS13aWR0aD0iMy42MiIKICAgICAgIHN0cm9rZS1vcGFjaXR5PSIwIgogICAgICAgaWQ9InBhdGgyIiAvPgogICAgPHBhdGgKICAgICAgIGQ9Im0gNzguNTcsMzguODggLTkuNSwtOS40OSBjIC0wLjQ4LC0wLjQ4IC0xLjI3LC0wLjQ4IC0xLjc0LDAgbCAtOS41LDkuNSBjIC0wLjQ4LDAuNDggLTAuNDgsMS4yNiAwLDEuNzQgbCA5LjUsOS41IGMgMC4yNCwwLjI0IDAuNTUsMC4zNiAwLjg3LDAuMzYgMC4zMSwwIDAuNjMsLTAuMTIgMC44NywtMC4zNiBsIDkuNSwtOS41IGMgMC4yMywtMC4yNCAwLjM2LC0wLjU0IDAuMzYsLTAuODcgMCwtMC4zMyAtMC4xMywtMC42NCAtMC4zNiwtMC44NyIKICAgICAgIGZpbGw9IiNkNjE4MTgiCiAgICAgICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgICAgICBzdHJva2U9IiMwMDAwMDAiCiAgICAgICBzdHJva2Utd2lkdGg9IjMuNjIiCiAgICAgICBzdHJva2Utb3BhY2l0eT0iMCIKICAgICAgIGlkPSJwYXRoMyIgLz4KICAgIDxwYXRoCiAgICAgICBkPSJtIDUwLjEsMzguODYgLTkuNSwtOS41IGMgLTAuNDgsLTAuNDggLTEuMjcsLTAuNDggLTEuNzQsMCBsIC05LjUsOS41IGMgLTAuNDgsMC40OCAtMC40OCwxLjI2IDAsMS43NSBsIDkuNSw5LjUgYyAwLjI0LDAuMjQgMC41NSwwLjM2IDAuODcsMC4zNiAwLjMyLDAgMC42MywtMC4xMiAwLjg3LC0wLjM2IGwgOS40OSwtOS41IGMgMC4yMywtMC4yMyAwLjM3LC0wLjU0IDAuMzcsLTAuODggMCwtMC4zMyAtMC4xMywtMC42NCAtMC4zNywtMC44NyIKICAgICAgIGZpbGw9IiM3YWI4MDAiCiAgICAgICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgICAgICBzdHJva2U9IiMwMDAwMDAiCiAgICAgICBzdHJva2Utd2lkdGg9IjMuNjIiCiAgICAgICBzdHJva2Utb3BhY2l0eT0iMCIKICAgICAgIGlkPSJwYXRoNCIgLz4KICAgIDxwYXRoCiAgICAgICBkPSJtIDc4LjY0LDY3LjM5IC05LjUsLTkuNDkgYyAtMC40OCwtMC40OCAtMS4yNywtMC40OCAtMS43NSwwIGwgLTkuNDksOS40OSBjIC0wLjQ4LDAuNDggLTAuNDgsMS4yNiAwLDEuNzUgbCA5LjQ5LDkuNSBjIDAuMjQsMC4yNCAwLjU1LDAuMzYgMC44NywwLjM2IDAuMzIsMCAwLjYzLC0wLjEyIDAuODcsLTAuMzYgbCA5LjUsLTkuNSBjIDAuMjMsLTAuMjMgMC4zNiwtMC41NCAwLjM2LC0wLjg3IDAsLTAuMzMgLTAuMTMsLTAuNjQgLTAuMzYsLTAuODgiCiAgICAgICBmaWxsPSIjMDA0NmFkIgogICAgICAgZmlsbC1ydWxlPSJldmVub2RkIgogICAgICAgc3Ryb2tlPSIjMDAwMDAwIgogICAgICAgc3Ryb2tlLXdpZHRoPSIzLjYyIgogICAgICAgc3Ryb2tlLW9wYWNpdHk9IjAiCiAgICAgICBpZD0icGF0aDUiIC8+CiAgPC9nPgo8L3N2Zz4K",
"icon_light": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgdmlld0JveD0iMCAwIDEwOCAxMDgiCiAgIHdpZHRoPSIxMDgiCiAgIGhlaWdodD0iMTA4IgogICB2ZXJzaW9uPSIxLjEiCiAgIGlkPSJzdmc1IgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDx0aXRsZT5TdGlja3kgUGFzc3dvcmQgTWFuYWdlcjwvdGl0bGU+CiAgPGRlZnMKICAgICBpZD0iZGVmczUiIC8+CiAgPGNpcmNsZQogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7c3Ryb2tlLXdpZHRoOjEuNzYyMDEiCiAgICAgaWQ9InBhdGg2IgogICAgIGN4PSI1NCIKICAgICBjeT0iNTQiCiAgICAgcj0iNTQiIC8+CiAgPGcKICAgICBpZD0iZzYiCiAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMS4zNDMzMjQzLDAsMCwxLjM0MzMyNDMsLTE4LjU0MDYwNywtMTguNTI3NTk0KSI+CiAgICA8cGF0aAogICAgICAgZD0ibSA2NC4zNSw1My4xMSAtOS41LC05LjUgYyAtMC40OCwtMC40OCAtMS4yNiwtMC40OCAtMS43NSwwIGwgLTkuNSw5LjUgYyAtMC40OCwwLjQ4IC0wLjQ4LDEuMjYgMCwxLjc0IGwgOS41LDkuNSBjIDAuMjQsMC4yNCAwLjU2LDAuMzYgMC44NywwLjM2IDAuMzEsMCAwLjYzLC0wLjEyIDAuODcsLTAuMzYgbCA5LjUsLTkuNSBjIDAuMjMsLTAuMjMgMC4zNiwtMC41NCAwLjM2LC0wLjg3IDAsLTAuMzMgLTAuMTMsLTAuNjQgLTAuMzYsLTAuODcgeiIKICAgICAgIGZpbGw9IiMwMDAwMDAiCiAgICAgICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgICAgICBzdHJva2U9IiMwMDAwMDAiCiAgICAgICBzdHJva2Utd2lkdGg9IjMuNjIiCiAgICAgICBzdHJva2Utb3BhY2l0eT0iMCIKICAgICAgIGlkPSJwYXRoMSIgLz4KICAgIDxwYXRoCiAgICAgICBkPSJtIDUwLjExLDY3LjM0IC05LjUsLTkuNSBjIC0wLjQ4LC0wLjQ4IC0xLjI2LC0wLjQ4IC0xLjc0LDAgbCAtOS40OSw5LjUgYyAtMC40OCwwLjQ4IC0wLjQ4LDEuMjYgMCwxLjc1IGwgOS40OSw5LjUgYyAwLjI0LDAuMjQgMC41NiwwLjM2IDAuODcsMC4zNiAwLjMxLDAgMC42MywtMC4xMiAwLjg3LC0wLjM2IGwgOS41LC05LjUgYyAwLjIzLC0wLjI0IDAuMzYsLTAuNTQgMC4zNiwtMC44NyAwLC0wLjMzIC0wLjEzLC0wLjY0IC0wLjM2LC0wLjg3IgogICAgICAgZmlsbD0iIzAwYTllMCIKICAgICAgIGZpbGwtcnVsZT0iZXZlbm9kZCIKICAgICAgIHN0cm9rZT0iIzAwMDAwMCIKICAgICAgIHN0cm9rZS13aWR0aD0iMy42MiIKICAgICAgIHN0cm9rZS1vcGFjaXR5PSIwIgogICAgICAgaWQ9InBhdGgyIiAvPgogICAgPHBhdGgKICAgICAgIGQ9Im0gNzguNTcsMzguODggLTkuNSwtOS40OSBjIC0wLjQ4LC0wLjQ4IC0xLjI3LC0wLjQ4IC0xLjc0LDAgbCAtOS41LDkuNSBjIC0wLjQ4LDAuNDggLTAuNDgsMS4yNiAwLDEuNzQgbCA5LjUsOS41IGMgMC4yNCwwLjI0IDAuNTUsMC4zNiAwLjg3LDAuMzYgMC4zMSwwIDAuNjMsLTAuMTIgMC44NywtMC4zNiBsIDkuNSwtOS41IGMgMC4yMywtMC4yNCAwLjM2LC0wLjU0IDAuMzYsLTAuODcgMCwtMC4zMyAtMC4xMywtMC42NCAtMC4zNiwtMC44NyIKICAgICAgIGZpbGw9IiNkNjE4MTgiCiAgICAgICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgICAgICBzdHJva2U9IiMwMDAwMDAiCiAgICAgICBzdHJva2Utd2lkdGg9IjMuNjIiCiAgICAgICBzdHJva2Utb3BhY2l0eT0iMCIKICAgICAgIGlkPSJwYXRoMyIgLz4KICAgIDxwYXRoCiAgICAgICBkPSJtIDUwLjEsMzguODYgLTkuNSwtOS41IGMgLTAuNDgsLTAuNDggLTEuMjcsLTAuNDggLTEuNzQsMCBsIC05LjUsOS41IGMgLTAuNDgsMC40OCAtMC40OCwxLjI2IDAsMS43NSBsIDkuNSw5LjUgYyAwLjI0LDAuMjQgMC41NSwwLjM2IDAuODcsMC4zNiAwLjMyLDAgMC42MywtMC4xMiAwLjg3LC0wLjM2IGwgOS40OSwtOS41IGMgMC4yMywtMC4yMyAwLjM3LC0wLjU0IDAuMzcsLTAuODggMCwtMC4zMyAtMC4xMywtMC42NCAtMC4zNywtMC44NyIKICAgICAgIGZpbGw9IiM3YWI4MDAiCiAgICAgICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgICAgICBzdHJva2U9IiMwMDAwMDAiCiAgICAgICBzdHJva2Utd2lkdGg9IjMuNjIiCiAgICAgICBzdHJva2Utb3BhY2l0eT0iMCIKICAgICAgIGlkPSJwYXRoNCIgLz4KICAgIDxwYXRoCiAgICAgICBkPSJtIDc4LjY0LDY3LjM5IC05LjUsLTkuNDkgYyAtMC40OCwtMC40OCAtMS4yNywtMC40OCAtMS43NSwwIGwgLTkuNDksOS40OSBjIC0wLjQ4LDAuNDggLTAuNDgsMS4yNiAwLDEuNzUgbCA5LjQ5LDkuNSBjIDAuMjQsMC4yNCAwLjU1LDAuMzYgMC44NywwLjM2IDAuMzIsMCAwLjYzLC0wLjEyIDAuODcsLTAuMzYgbCA5LjUsLTkuNSBjIDAuMjMsLTAuMjMgMC4zNiwtMC41NCAwLjM2LC0wLjg3IDAsLTAuMzMgLTAuMTMsLTAuNjQgLTAuMzYsLTAuODgiCiAgICAgICBmaWxsPSIjMDA0NmFkIgogICAgICAgZmlsbC1ydWxlPSJldmVub2RkIgogICAgICAgc3Ryb2tlPSIjMDAwMDAwIgogICAgICAgc3Ryb2tlLXdpZHRoPSIzLjYyIgogICAgICAgc3Ryb2tlLW9wYWNpdHk9IjAiCiAgICAgICBpZD0icGF0aDUiIC8+CiAgPC9nPgo8L3N2Zz4K"
},
"70617373-7761-6c6c-6669-646f32303236": {
"name": "Passwall"
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,33 @@
# Generated by Django 5.2.11 on 2026-03-04 02:30
import django.contrib.postgres.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"authentik_stages_authenticator_webauthn",
"0014_alter_authenticatorwebauthnstage_friendly_name",
),
]
operations = [
migrations.AddField(
model_name="authenticatorwebauthnstage",
name="hints",
field=django.contrib.postgres.fields.ArrayField(
base_field=models.TextField(
choices=[
("security-key", "Security Key"),
("client-device", "Client Device"),
("hybrid", "Hybrid"),
]
),
blank=True,
default=list,
size=None,
),
),
]

View File

@@ -1,6 +1,7 @@
"""WebAuthn stage"""
from django.contrib.auth import get_user_model
from django.contrib.postgres.fields.array import ArrayField
from django.db import models
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
@@ -67,6 +68,24 @@ class AuthenticatorAttachment(models.TextChoices):
CROSS_PLATFORM = "cross-platform"
class WebAuthnHint(models.TextChoices):
"""Hints to guide the browser in prioritizing the preferred authenticator during
WebAuthn registration and authentication. Unlike authenticatorAttachment, hints are
advisory and browsers may ignore them.
Members:
`SECURITY_KEY`: A portable FIDO2 authenticator, like a YubiKey
`CLIENT_DEVICE`: The device WebAuthn is being called on, like TouchID or Windows Hello
`HYBRID`: A platform authenticator on a mobile device, accessed via QR code
https://w3c.github.io/webauthn/#enumdef-publickeycredentialhint
"""
SECURITY_KEY = "security-key"
CLIENT_DEVICE = "client-device"
HYBRID = "hybrid"
class AuthenticatorWebAuthnStage(ConfigurableStage, FriendlyNamedStage, Stage):
"""Setup WebAuthn-based authentication for the user."""
@@ -82,6 +101,12 @@ class AuthenticatorWebAuthnStage(ConfigurableStage, FriendlyNamedStage, Stage):
choices=AuthenticatorAttachment.choices, default=None, null=True
)
hints = ArrayField(
models.TextField(choices=WebAuthnHint.choices),
default=list,
blank=True,
)
device_type_restrictions = models.ManyToManyField("WebAuthnDeviceType", blank=True)
max_attempts = models.PositiveIntegerField(default=0)

View File

@@ -16,6 +16,7 @@ from webauthn.helpers.structs import (
AuthenticatorAttachment,
AuthenticatorSelectionCriteria,
PublicKeyCredentialCreationOptions,
PublicKeyCredentialHint,
ResidentKeyRequirement,
UserVerificationRequirement,
)
@@ -127,6 +128,20 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
if authenticator_attachment:
authenticator_attachment = AuthenticatorAttachment(str(authenticator_attachment))
hints = [PublicKeyCredentialHint(h) for h in stage.hints] or None
# For compatibility with older user agents that don't support hints,
# auto-infer authenticatorAttachment from hints when not explicitly set.
# https://w3c.github.io/webauthn/#enum-hints
if hints and not authenticator_attachment:
hint_values = set(stage.hints)
cross_platform = {"security-key", "hybrid"}
platform = {"client-device"}
if hint_values <= cross_platform:
authenticator_attachment = AuthenticatorAttachment.CROSS_PLATFORM
elif hint_values <= platform:
authenticator_attachment = AuthenticatorAttachment.PLATFORM
registration_options: PublicKeyCredentialCreationOptions = generate_registration_options(
rp_id=get_rp_id(self.request),
rp_name=self.request.brand.branding_title,
@@ -139,6 +154,7 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
authenticator_attachment=authenticator_attachment,
),
attestation=AttestationConveyancePreference.DIRECT,
hints=hints,
)
self.executor.plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = registration_options.challenge

View File

@@ -17,6 +17,7 @@ from authentik.stages.authenticator_webauthn.models import (
AuthenticatorWebAuthnStage,
WebAuthnDevice,
WebAuthnDeviceType,
WebAuthnHint,
)
from authentik.stages.authenticator_webauthn.stage import PLAN_CONTEXT_WEBAUTHN_CHALLENGE
from authentik.stages.authenticator_webauthn.tasks import webauthn_mds_import
@@ -302,6 +303,145 @@ class TestAuthenticatorWebAuthnStage(FlowTestCase):
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
self.assertTrue(WebAuthnDevice.objects.filter(user=self.user).exists())
def test_registration_options_with_hints(self):
"""Test that hints are included in registration options"""
self.stage.hints = [WebAuthnHint.CLIENT_DEVICE, WebAuthnHint.SECURITY_KEY]
self.stage.save()
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
self.assertEqual(response.status_code, 200)
registration = response.json()["registration"]
self.assertEqual(registration["hints"], ["client-device", "security-key"])
def test_registration_options_hints_empty(self):
"""Test that no hints key is present when hints are empty"""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
self.assertEqual(response.status_code, 200)
registration = response.json()["registration"]
self.assertNotIn("hints", registration)
def test_registration_options_hints_infer_attachment_cross_platform(self):
"""Test that authenticatorAttachment is auto-inferred as cross-platform
from security-key/hybrid hints for backwards compatibility"""
self.stage.hints = [WebAuthnHint.SECURITY_KEY]
self.stage.authenticator_attachment = None
self.stage.save()
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
self.assertEqual(response.status_code, 200)
registration = response.json()["registration"]
self.assertEqual(
registration["authenticatorSelection"]["authenticatorAttachment"], "cross-platform"
)
def test_registration_options_hints_infer_attachment_platform(self):
"""Test that authenticatorAttachment is auto-inferred as platform
from client-device hint for backwards compatibility"""
self.stage.hints = [WebAuthnHint.CLIENT_DEVICE]
self.stage.authenticator_attachment = None
self.stage.save()
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
self.assertEqual(response.status_code, 200)
registration = response.json()["registration"]
self.assertEqual(
registration["authenticatorSelection"]["authenticatorAttachment"], "platform"
)
def test_registration_options_hints_no_infer_when_attachment_set(self):
"""Test that authenticatorAttachment is NOT overridden when explicitly set"""
self.stage.hints = [WebAuthnHint.SECURITY_KEY]
self.stage.authenticator_attachment = "platform"
self.stage.save()
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
self.assertEqual(response.status_code, 200)
registration = response.json()["registration"]
self.assertEqual(
registration["authenticatorSelection"]["authenticatorAttachment"], "platform"
)
def test_registration_options_hints_no_infer_mixed(self):
"""Test that authenticatorAttachment is NOT inferred when hints are mixed"""
self.stage.hints = [WebAuthnHint.SECURITY_KEY, WebAuthnHint.CLIENT_DEVICE]
self.stage.authenticator_attachment = None
self.stage.save()
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
self.assertEqual(response.status_code, 200)
registration = response.json()["registration"]
self.assertNotIn("authenticatorAttachment", registration["authenticatorSelection"])
def test_registration_options_hints_order_preserved(self):
"""Test that hint order is preserved (first hint = highest priority)"""
self.stage.hints = [
WebAuthnHint.HYBRID,
WebAuthnHint.CLIENT_DEVICE,
WebAuthnHint.SECURITY_KEY,
]
self.stage.save()
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
self.assertEqual(response.status_code, 200)
registration = response.json()["registration"]
self.assertEqual(registration["hints"], ["hybrid", "client-device", "security-key"])
def test_register_max_retries(self):
"""Test registration (exceeding max retries)"""
self.stage.max_attempts = 2

View File

@@ -1,5 +1,6 @@
import pglock
from django.utils.timezone import now, timedelta
from datetime import timedelta
from django.utils.timezone import now
from drf_spectacular.utils import extend_schema, inline_serializer
from packaging.version import parse
from rest_framework.fields import BooleanField, CharField
@@ -31,18 +32,13 @@ class WorkerView(APIView):
def get(self, request: Request) -> Response:
response = []
our_version = parse(authentik_full_version())
for status in WorkerStatus.objects.filter(last_seen__gt=now() - timedelta(minutes=2)):
lock_id = f"goauthentik.io/worker/status/{status.pk}"
with pglock.advisory(lock_id, timeout=0, side_effect=pglock.Return) as acquired:
# The worker doesn't hold the lock, it isn't running
if acquired:
continue
version_matching = parse(status.version) == our_version
response.append(
{
"worker_id": f"{status.pk}@{status.hostname}",
"version": status.version,
"version_matching": version_matching,
}
)
for status in WorkerStatus.objects.filter(last_seen__gt=now() - timedelta(seconds=45)):
version_matching = parse(status.version) == our_version
response.append(
{
"worker_id": f"{status.pk}@{status.hostname}",
"version": status.version,
"version_matching": version_matching,
}
)
return Response(response)

View File

@@ -1,42 +1,23 @@
import socket
from collections.abc import Callable
from http.server import BaseHTTPRequestHandler
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, transaction
from django.utils.timezone import now
from django.db import OperationalError
from django_dramatiq_postgres.middleware import (
CurrentTask as BaseCurrentTask,
)
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
from authentik.root.signals import post_startup, pre_startup, startup
from authentik.tasks.models import Task, TaskLog, TaskStatus, WorkerStatus
from authentik.tasks.models import Task, TaskLog, TaskStatus
from authentik.tenants.models import Tenant
from authentik.tenants.utils import get_current_tenant
@@ -193,148 +174,15 @@ class DescriptionMiddleware(Middleware):
return {"description"}
class _healthcheck_handler(BaseHTTPRequestHandler):
def log_request(self, code="-", size="-"):
HEALTHCHECK_LOGGER.info(
self.path,
method=self.command,
status=code,
)
def log_error(self, format, *args):
HEALTHCHECK_LOGGER.warning(format, *args)
def do_HEAD(self):
try:
for db_conn in connections.all():
# Force connection reload
db_conn.connect()
_ = db_conn.cursor()
self.send_response(200)
except DB_ERRORS: # pragma: no cover
self.send_response(503)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.send_header("Content-Length", "0")
self.end_headers()
do_GET = do_HEAD
class WorkerHealthcheckMiddleware(Middleware):
thread: HTTPServerThread | None
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:
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",
exc=exc,
)
class WorkerStatusMiddleware(Middleware):
thread: Thread | None
thread_event: TEvent | None
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(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(event, status)
except DB_ERRORS: # pragma: no cover
event.wait(10)
try:
connections.close_all()
except DB_ERRORS:
pass
@staticmethod
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 not event.is_set():
status.refresh_from_db()
old_last_seen = status.last_seen
status.last_seen = now()
if old_last_seen != status.last_seen:
status.save(update_fields=("last_seen",))
event.wait(30)
class _MetricsHandler(BaseMetricsHandler):
def do_GET(self) -> None:
monitoring_set.send_robust(self)
return super().do_GET()
class MetricsMiddleware(BaseMetricsMiddleware):
thread: HTTPServerThread | None
handler_class = _MetricsHandler
@property
def forks(self) -> list[Callable[[], None]]:
def forks(self):
return []
def after_worker_boot(self, broker: Broker, worker: Worker):
addr, _, port = CONFIG.get("listen.metrics").rpartition(":")
def before_worker_boot(self, broker: Broker, worker: Any) -> None:
from prometheus_client import values
from prometheus_client.values import MultiProcessValue
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()
values.ValueClass = MultiProcessValue(lambda: worker.worker_id)
def before_worker_shutdown(self, broker: Broker, worker: Worker):
server = self.thread.server
if server:
server.shutdown()
LOGGER.debug("Stopping MetricsMiddleware")
self.thread.join()
return super().before_worker_boot(broker, worker)

View File

@@ -1,4 +1,6 @@
from django.utils.timezone import now, timedelta
from datetime import timedelta
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from dramatiq import actor

View File

@@ -10,7 +10,6 @@ from dramatiq.results.middleware import Results
from dramatiq.worker import Worker, _ConsumerThread, _WorkerThread
from authentik.tasks.broker import PostgresBroker
from authentik.tasks.middleware import WorkerHealthcheckMiddleware
TESTING_QUEUE = "testing"
@@ -18,6 +17,7 @@ TESTING_QUEUE = "testing"
class TestWorker(Worker):
def __init__(self, broker: Broker):
super().__init__(broker=broker)
self.worker_id = 1000
self.work_queue = PriorityQueue()
self.consumers = {
TESTING_QUEUE: _ConsumerThread(
@@ -82,8 +82,6 @@ def use_test_broker():
middleware: Middleware = import_string(middleware_class)(
**middleware_kwargs,
)
if isinstance(middleware, WorkerHealthcheckMiddleware):
middleware.port = 9102
if isinstance(middleware, Retries):
middleware.max_retries = 0
if isinstance(middleware, Results):

View File

@@ -5,5 +5,5 @@ from authentik.tasks.api.workers import WorkerView
api_urlpatterns = [
("tasks/tasks", TaskViewSet),
path("tasks/workers", WorkerView.as_view(), name="tasks_workers"),
path("tasks/workers/", WorkerView.as_view(), name="tasks_workers"),
]

View File

@@ -8236,6 +8236,12 @@
"type": "string",
"title": "Webhook url"
},
"webhook_ca": {
"type": "string",
"format": "uuid",
"title": "Webhook ca",
"description": "When set, the selected ceritifcate is used to validate the certificate of the webhook server."
},
"webhook_mapping_body": {
"type": "string",
"format": "uuid",
@@ -14728,6 +14734,19 @@
"title": "Webauthn user verification",
"description": "Enforce user verification for WebAuthn devices."
},
"webauthn_hints": {
"type": "array",
"items": {
"type": "string",
"enum": [
"security-key",
"client-device",
"hybrid"
],
"title": "Webauthn hints"
},
"title": "Webauthn hints"
},
"webauthn_allowed_device_types": {
"type": "array",
"items": {
@@ -14813,6 +14832,19 @@
],
"title": "Resident key requirement"
},
"hints": {
"type": "array",
"items": {
"type": "string",
"enum": [
"security-key",
"client-device",
"hybrid"
],
"title": "Hints"
},
"title": "Hints"
},
"device_type_restrictions": {
"type": "array",
"items": {

View File

@@ -1,7 +1,9 @@
package main
import (
"context"
"fmt"
"net"
"net/http"
"os"
"path"
@@ -12,7 +14,8 @@ import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"goauthentik.io/internal/config"
"goauthentik.io/internal/utils/web"
utils "goauthentik.io/internal/utils/web"
"goauthentik.io/internal/web"
)
var workerPidFile = path.Join(os.TempDir(), "authentik-worker.pid")
@@ -44,9 +47,15 @@ func init() {
func checkServer() int {
h := &http.Client{
Transport: web.NewUserAgentTransport("goauthentik.io/healthcheck", http.DefaultTransport),
Transport: utils.NewUserAgentTransport("goauthentik.io/healthcheck",
&http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", path.Join(os.TempDir(), web.SocketName))
},
},
),
}
url := fmt.Sprintf("http://%s%s-/health/live/", config.Get().Listen.HTTP, config.Get().Web.Path)
url := fmt.Sprintf("http://localhost%s-/health/live/", config.Get().Web.Path)
res, err := h.Head(url)
if err != nil {
log.WithError(err).Warning("failed to send healthcheck request")

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"net/http"
"net/url"
"os"
"time"
"github.com/getsentry/sentry-go"
@@ -51,9 +52,10 @@ var rootCmd = &cobra.Command{
ex := common.Init()
defer common.Defer()
u, err := url.Parse(fmt.Sprintf("http://%s%s", config.Get().Listen.HTTP, config.Get().Web.Path))
if err != nil {
panic(err)
u := url.URL{
Scheme: "unix",
Host: fmt.Sprintf("%s/%s", os.TempDir(), web.SocketName),
Path: config.Get().Web.Path,
}
ws := web.NewWebServer()
@@ -70,13 +72,13 @@ var rootCmd = &cobra.Command{
},
}
func attemptProxyStart(ws *web.WebServer, u *url.URL) {
func attemptProxyStart(ws *web.WebServer, u url.URL) {
maxTries := 100
attempt := 0
l := log.WithField("logger", "authentik.server")
for {
l.Debug("attempting to init outpost")
ac := ak.NewAPIController(*u, config.Get().SecretKey)
ac := ak.NewAPIController(u, config.Get().SecretKey)
if ac == nil {
attempt += 1
time.Sleep(1 * time.Second)

View File

@@ -12,7 +12,12 @@
},
"reporters": [
"default",
["@cspell/cspell-json-reporter", { "outFile": "./cspell-report.json" }]
[
"@cspell/cspell-json-reporter",
{
"outFile": "./cspell-report.json"
}
]
],
"dictionaryDefinitions": [
{
@@ -32,12 +37,16 @@
"path": "./locale/en/dictionaries/python.txt",
"addWords": true
},
{
"name": "en-x-authentik-rust",
"path": "./locale/en/dictionaries/rust.txt",
"addWords": true
},
{
"name": "en-x-authentik-golang",
"path": "./locale/en/dictionaries/golang.txt",
"addWords": true
},
{
"name": "en-x-authentik-people",
"path": "./locale/en/dictionaries/people.txt",
@@ -81,7 +90,10 @@
{
"name": "ConfSuffix",
"description": "Variables with `conf` or `config` suffix",
"pattern": ["\\w+(conf|config)\\b", "\\b(conf|config)\\w+"]
"pattern": [
"\\w+(conf|config)\\b",
"\\b(conf|config)\\w+"
]
}
],
"ignoreRegExpList": [
@@ -119,7 +131,6 @@
"\\w+l?ified\\b",
// "ifying" suffix, e.g. "stringifying", "classifying".
"\\w+l?ifying\\b",
"SpellCheckerIgnoreInDocSetting",
"EncodedURI",
"Urls",
@@ -135,7 +146,11 @@
"languageSettings": [
{
"languageId": "markdown,mdx",
"dictionaries": ["en-x-authentik-python", "en-x-authentik-golang"],
"dictionaries": [
"en-x-authentik-python",
"en-x-authentik-rust",
"en-x-authentik-golang"
],
"ignoreRegExpList": [
// Fenced code blocks
"/^\\s*```[\\s\\S]*?^\\s*```/gm",
@@ -146,7 +161,6 @@
},
{
"languageId": "typescript,javascript,typescriptreact,javascriptreact,mdx,astro",
"ignoreRegExpList": [
// Event handlers e.g. onClick, onmouseover
"\\bon\\w+\\b",
@@ -166,18 +180,33 @@
},
{
"languageId": "python",
"dictionaries": ["en-x-authentik-python"],
"includeRegExpList": ["comments"]
"dictionaries": [
"en-x-authentik-python"
],
"includeRegExpList": [
"comments"
]
},
{
"languageId": "rust",
"dictionaries": [
"en-x-authentik-rust"
]
},
{
"languageId": "go",
"dictionaries": ["en-x-authentik-golang"]
"dictionaries": [
"en-x-authentik-golang"
]
},
{
"languageId": "makefile",
"dictionaries": ["en-x-authentik-python", "en-x-authentik-golang"]
"languageId": "makefile,toml,yaml",
"dictionaries": [
"en-x-authentik-python",
"en-x-authentik-rust",
"en-x-authentik-golang"
]
},
{
"languageId": "css,scss",
"ignoreRegExpList": [
@@ -188,20 +217,15 @@
],
"ignorePaths": [
//#region i18n
"{cspell.*,cSpell.*,.cspell.*,cspell.config.*}", // CSpell configuration files
"cspell-report.{json,html,txt}", // CSpell report files
"dictionaries", // Custom dictionary files
"ignore.txt", // Custom ignore list files
"./locale", // Locale files (Django, CSpell)
"web/xliff", // XLIFF translation files
"web/src/locales", // Generated TypeScript locale
//#endregion
//#region Monorepo
"CODEOWNERS", // GitHub code owners file
"LICENSE", // License file
".gitignore", // Git ignore file
@@ -224,9 +248,7 @@
"fixtures", // Test fixtures
"tests/e2e/**/*.php", // PHP fixtures
"compose.yml", // Docker Compose files
//#region JavaScript/TypeScript
".eslintignore", // ESLint ignore file
".prettierignore", // Prettier ignore file
".yarn", // Yarn cache and configuration
@@ -239,7 +261,6 @@
"*.min.{js,css}", // Minified JS and CSS files
"*.min.{js,css}.map", // Source maps for minified files
//#region Python
"pyproject.toml",
"unittest.xml", // Pytest output
".venv", // Python virtual environment
@@ -248,37 +269,25 @@
"blueprints",
"mds",
//#endregion
//#region Rust
"./target", // Rust compilation artifacts
//#endregion
//#region Docusaurus
"*.api.mdx", // Generated API docs
".docusaurus/**", // Cache
"./{docs,website}/build", // Topic docs build output
"./{docs,website}/**/build", // Workspaces output
//#endregion
//#region Golang
"go.mod", // Go module file
"go.sum", // Go module file
"htmlcov", // Coverage HTML output
"coverage.txt", // Coverage text output
//#endregion
//#region Media
"./data", // Media files
"./media", // Legacy media files
"*.{png,jpg,pdf,svg}" // Binary files
//#endregion
],
"useGitignore": true,

6
go.mod
View File

@@ -9,7 +9,7 @@ require (
github.com/coreos/go-oidc/v3 v3.17.0
github.com/getsentry/sentry-go v0.43.0
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-ldap/ldap/v3 v3.4.12
github.com/go-ldap/ldap/v3 v3.4.13
github.com/go-openapi/runtime v0.29.3
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/uuid v1.6.0
@@ -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.20260309103029-7c71e7d5673a
goauthentik.io/api/v3 v3.2026020.17-0.20260317190750-6ec0d12b221b
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.36.0
golang.org/x/sync v0.20.0
@@ -41,7 +41,7 @@ require (
)
require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/Azure/go-ntlmssp v0.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect

14
go.sum
View File

@@ -2,8 +2,8 @@ beryju.io/ldap v0.1.0 h1:rPjGE3qR1Klbvn9N+iECWdzt/tK87XHgz8W5wZJg9B8=
beryju.io/ldap v0.1.0/go.mod h1:sOrYV+ZlDTDu/IvIiEiuAaXzjcpMBE+XXr4V+NJ0pWI=
beryju.io/radius-eap v0.1.0 h1:5M3HwkzH3nIEBcKDA2z5+sb4nCY3WdKL/SDDKTBvoqw=
beryju.io/radius-eap v0.1.0/go.mod h1:yYtO59iyoLNEepdyp1gZ0i1tGdjPbrR2M/v5yOz7Fkc=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A=
github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/avast/retry-go/v4 v4.7.0 h1:yjDs35SlGvKwRNSykujfjdMxMhMQQM0TnIjJaHB+Zio=
@@ -34,8 +34,8 @@ github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4=
github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo=
github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ=
github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -213,10 +213,8 @@ go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
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=
goauthentik.io/api/v3 v3.2026020.17-0.20260309103029-7c71e7d5673a h1:CipAaiYqzzyhQDO6xg3YfEC0saoyVCFFbUjRfAsJrxs=
goauthentik.io/api/v3 v3.2026020.17-0.20260309103029-7c71e7d5673a/go.mod h1:uYa+yGMglhJy8ymyUQ8KQiJjOb3UZTuPQ24Ot2s9BCo=
goauthentik.io/api/v3 v3.2026020.17-0.20260317190750-6ec0d12b221b h1:p+iDEXjvC15pC1VscaR59Vud9/c/xeNeTFmlv4arkNI=
goauthentik.io/api/v3 v3.2026020.17-0.20260317190750-6ec0d12b221b/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

@@ -50,12 +50,12 @@ type PostgreSQLConfig struct {
}
type ListenConfig struct {
HTTP string `yaml:"http" env:"HTTP, overwrite"`
HTTPS string `yaml:"https" env:"HTTPS, overwrite"`
LDAP string `yaml:"ldap" env:"LDAP, overwrite"`
LDAPS string `yaml:"ldaps" env:"LDAPS, overwrite"`
Radius string `yaml:"radius" env:"RADIUS, overwrite"`
Metrics string `yaml:"metrics" env:"METRICS, overwrite"`
HTTP []string `yaml:"http" env:"HTTP, overwrite"`
HTTPS []string `yaml:"https" env:"HTTPS, overwrite"`
LDAP []string `yaml:"ldap" env:"LDAP, overwrite"`
LDAPS []string `yaml:"ldaps" env:"LDAPS, overwrite"`
Radius []string `yaml:"radius" env:"RADIUS, overwrite"`
Metrics []string `yaml:"metrics" env:"METRICS, overwrite"`
Debug string `yaml:"debug" env:"DEBUG, overwrite"`
TrustedProxyCIDRs []string `yaml:"trusted_proxy_cidrs" env:"TRUSTED_PROXY_CIDRS, overwrite"`
}

View File

@@ -5,6 +5,7 @@ import (
"crypto/fips140"
"fmt"
"math/rand"
"net"
"net/http"
"net/url"
"os"
@@ -54,19 +55,44 @@ type APIController struct {
// NewAPIController initialise new API Controller instance from URL and API token
func NewAPIController(akURL url.URL, token string) *APIController {
rsp := sentry.StartSpan(context.Background(), "authentik.outposts.init")
log := log.WithField("logger", "authentik.outpost.ak-api-controller")
originalAkURL := akURL
var client http.Client
if akURL.Scheme == "unix" {
log.WithField("host", akURL.Host).WithField("path", akURL.Path).Debug("using unix socket")
socketPath := akURL.Host
client = http.Client{
Transport: web.NewUserAgentTransport(
constants.UserAgentOutpost(),
web.NewTracingTransport(
rsp.Context(),
&http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", socketPath)
},
},
),
),
}
akURL.Scheme = "http"
akURL.Host = "localhost"
} else {
client = http.Client{
Transport: web.NewUserAgentTransport(
constants.UserAgentOutpost(),
web.NewTracingTransport(
rsp.Context(),
GetTLSTransport(),
),
),
}
}
apiConfig := api.NewConfiguration()
apiConfig.Host = akURL.Host
apiConfig.Scheme = akURL.Scheme
apiConfig.HTTPClient = &http.Client{
Transport: web.NewUserAgentTransport(
constants.UserAgentOutpost(),
web.NewTracingTransport(
rsp.Context(),
GetTLSTransport(),
),
),
}
apiConfig.HTTPClient = &client
apiConfig.Servers = api.ServerConfigurations{
{
URL: fmt.Sprintf("%sapi/v3", akURL.Path),
@@ -77,8 +103,6 @@ func NewAPIController(akURL url.URL, token string) *APIController {
// create the API client, with the transport
apiClient := api.NewAPIClient(apiConfig)
log := log.WithField("logger", "authentik.outpost.ak-api-controller")
// Because we don't know the outpost UUID, we simply do a list and pick the first
// The service account this token belongs to should only have access to a single outpost
outposts, _ := retry.DoWithData[*api.PaginatedOutpostList](
@@ -124,7 +148,7 @@ func NewAPIController(akURL url.URL, token string) *APIController {
}
ac.logger.WithField("embedded", ac.IsEmbedded()).Info("Outpost mode")
ac.logger.WithField("offset", ac.reloadOffset.String()).Debug("HA Reload offset")
err = ac.initEvent(akURL, outpost.Pk)
err = ac.initEvent(originalAkURL, outpost.Pk)
if err != nil {
go ac.recentEvents()
}

View File

@@ -5,6 +5,7 @@ import (
"crypto/tls"
"fmt"
"maps"
"net"
"net/http"
"net/url"
"strconv"
@@ -45,9 +46,19 @@ func (ac *APIController) initEvent(akURL url.URL, outpostUUID string) error {
dialer := websocket.Dialer{
Proxy: http.ProxyFromEnvironment,
HandshakeTimeout: 10 * time.Second,
TLSClientConfig: &tls.Config{
}
if akURL.Scheme == "unix" {
ac.logger.WithField("host", akURL.Host).WithField("path", akURL.Path).Debug("websocket is using unix connection")
socketPath := akURL.Host
dialer.NetDialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
return (&net.Dialer{}).DialContext(ctx, "unix", socketPath)
}
akURL.Scheme = "http"
akURL.Host = "localhost"
} else {
dialer.TLSClientConfig = &tls.Config{
InsecureSkipVerify: config.Get().AuthentikInsecure,
},
}
}
wsu := ac.getWebsocketURL(akURL, outpostUUID, query).String()

View File

@@ -1,13 +1,16 @@
package healthcheck
import (
"fmt"
"context"
"net"
"net/http"
"os"
"path"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"goauthentik.io/internal/config"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/utils/web"
)
@@ -21,9 +24,15 @@ var Command = &cobra.Command{
func check() int {
h := &http.Client{
Transport: web.NewUserAgentTransport("goauthentik.io/healthcheck", http.DefaultTransport),
Transport: web.NewUserAgentTransport("goauthentik.io/healthcheck",
&http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", path.Join(os.TempDir(), ak.MetricsSocketName))
},
},
),
}
url := fmt.Sprintf("http://%s/outpost.goauthentik.io/ping", config.Get().Listen.Metrics)
url := "http://localhost/outpost.goauthentik.io/ping"
res, err := h.Head(url)
if err != nil {
log.WithError(err).Warning("failed to send healthcheck request")

View File

@@ -1,12 +1,22 @@
package ak
import (
"net/http"
"os"
"path"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/utils/sentry"
"goauthentik.io/internal/utils/unix"
)
var (
OutpostInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
MetricsSocketName = "authentik-metrics.sock"
OutpostInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "authentik_outpost_info",
Help: "Outpost info",
}, []string{"outpost_name", "outpost_type", "uuid", "version", "build"})
@@ -19,3 +29,43 @@ var (
Help: "Connection status",
}, []string{"outpost_name", "outpost_type", "uuid"})
)
func MetricsRouter() *mux.Router {
m := mux.NewRouter()
m.Use(sentry.SentryNoSampleMiddleware)
m.HandleFunc("/outpost.goauthentik.io/ping", func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(204)
})
m.Path("/metrics").Handler(promhttp.Handler())
return m
}
func RunMetricsServer(listen string, router *mux.Router) {
l := log.WithField("logger", "authentik.outpost.metrics").WithField("listen", listen)
l.Info("Starting Metrics server")
err := http.ListenAndServe(listen, router)
if err != nil {
l.WithError(err).Warning("Failed to start metrics listener")
}
}
func RunMetricsUnix(router *mux.Router) {
socketPath := path.Join(os.TempDir(), MetricsSocketName)
l := log.WithField("logger", "authentik.outpost.metrics").WithField("listen", socketPath)
ln, err := unix.Listen(socketPath)
if err != nil {
l.WithError(err).Warning("failed to listen")
return
}
defer func() {
err := ln.Close()
if err != nil {
l.WithError(err).Warning("failed to close listener")
}
}()
l.WithField("listen", socketPath).Info("Starting Metrics server")
err = http.Serve(ln, router)
if err != nil {
l.WithError(err).Warning("Failed to start metrics listener")
}
}

View File

@@ -11,7 +11,6 @@ import (
"goauthentik.io/internal/config"
"goauthentik.io/internal/crypto"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/ldap/metrics"
"goauthentik.io/internal/utils"
"beryju.io/ldap"
@@ -63,9 +62,7 @@ func (ls *LDAPServer) Type() string {
return "ldap"
}
func (ls *LDAPServer) StartLDAPServer() error {
listen := config.Get().Listen.LDAP
func (ls *LDAPServer) StartLDAPServer(listen string) error {
ln, err := net.Listen("tcp", listen)
if err != nil {
ls.log.WithField("listen", listen).WithError(err).Warning("Failed to listen (SSL)")
@@ -89,26 +86,40 @@ func (ls *LDAPServer) StartLDAPServer() error {
}
func (ls *LDAPServer) Start() error {
listenLdap := config.Get().Listen.LDAP
listenLdaps := config.Get().Listen.LDAPS
listenMetrics := config.Get().Listen.Metrics
metricsRouter := ak.MetricsRouter()
wg := sync.WaitGroup{}
wg.Add(3)
wg.Add(len(listenLdap) + len(listenLdaps) + 1 + len(listenMetrics))
for _, listen := range listenLdap {
go func() {
defer wg.Done()
err := ls.StartLDAPServer(listen)
if err != nil {
panic(err)
}
}()
}
for _, listen := range listenLdaps {
go func() {
defer wg.Done()
err := ls.StartLDAPTLSServer(listen)
if err != nil {
panic(err)
}
}()
}
go func() {
defer wg.Done()
metrics.RunServer()
}()
go func() {
defer wg.Done()
err := ls.StartLDAPServer()
if err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
err := ls.StartLDAPTLSServer()
if err != nil {
panic(err)
}
ak.RunMetricsUnix(metricsRouter)
}()
for _, listen := range listenMetrics {
go func() {
defer wg.Done()
ak.RunMetricsServer(listen, metricsRouter)
}()
}
wg.Wait()
return nil
}

View File

@@ -5,7 +5,6 @@ import (
"net"
"github.com/pires/go-proxyproto"
"goauthentik.io/internal/config"
"goauthentik.io/internal/utils"
)
@@ -37,8 +36,7 @@ func (ls *LDAPServer) getCertificates(info *tls.ClientHelloInfo) (*tls.Certifica
return ls.defaultCert, nil
}
func (ls *LDAPServer) StartLDAPTLSServer() error {
listen := config.Get().Listen.LDAPS
func (ls *LDAPServer) StartLDAPTLSServer(listen string) error {
tlsConfig := utils.GetTLSConfig()
tlsConfig.GetCertificate = ls.getCertificates

View File

@@ -1,16 +1,8 @@
package metrics
import (
"net/http"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/config"
"goauthentik.io/internal/utils/sentry"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
@@ -23,19 +15,3 @@ var (
Help: "Total number of rejected requests",
}, []string{"outpost_name", "type", "reason", "app"})
)
func RunServer() {
m := mux.NewRouter()
l := log.WithField("logger", "authentik.outpost.metrics")
m.Use(sentry.SentryNoSampleMiddleware)
m.HandleFunc("/outpost.goauthentik.io/ping", func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(204)
})
m.Path("/metrics").Handler(promhttp.Handler())
listen := config.Get().Listen.Metrics
l.WithField("listen", listen).Info("Starting Metrics server")
err := http.ListenAndServe(listen, m)
if err != nil {
l.WithError(err).Warning("Failed to start metrics listener")
}
}

View File

@@ -1,16 +1,8 @@
package metrics
import (
"net/http"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/config"
"goauthentik.io/internal/utils/sentry"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
@@ -23,19 +15,3 @@ var (
Help: "Proxy upstream response latencies in seconds",
}, []string{"outpost_name", "method", "scheme", "host", "upstream_host"})
)
func RunServer() {
m := mux.NewRouter()
l := log.WithField("logger", "authentik.outpost.metrics")
m.Use(sentry.SentryNoSampleMiddleware)
m.HandleFunc("/outpost.goauthentik.io/ping", func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(204)
})
m.Path("/metrics").Handler(promhttp.Handler())
listen := config.Get().Listen.Metrics
l.WithField("listen", listen).Info("Starting Metrics server")
err := http.ListenAndServe(listen, m)
if err != nil {
l.WithError(err).Warning("Failed to start metrics listener")
}
}

View File

@@ -18,7 +18,6 @@ import (
"goauthentik.io/internal/crypto"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/proxyv2/application"
"goauthentik.io/internal/outpost/proxyv2/metrics"
"goauthentik.io/internal/utils"
sentryutils "goauthentik.io/internal/utils/sentry"
"goauthentik.io/internal/utils/web"
@@ -127,11 +126,10 @@ func (ps *ProxyServer) getCertificates(info *tls.ClientHelloInfo) (*tls.Certific
}
// ServeHTTP constructs a net.Listener and starts handling HTTP requests
func (ps *ProxyServer) ServeHTTP() {
listenAddress := config.Get().Listen.HTTP
listener, err := net.Listen("tcp", listenAddress)
func (ps *ProxyServer) ServeHTTP(listen string) {
listener, err := net.Listen("tcp", listen)
if err != nil {
ps.log.WithField("listen", listenAddress).WithError(err).Warning("Failed to listen")
ps.log.WithField("listen", listen).WithError(err).Warning("Failed to listen")
return
}
proxyListener := &proxyproto.Listener{Listener: listener, ConnPolicy: utils.GetProxyConnectionPolicy()}
@@ -142,18 +140,17 @@ func (ps *ProxyServer) ServeHTTP() {
}
}()
ps.log.WithField("listen", listenAddress).Info("Starting HTTP server")
ps.log.WithField("listen", listen).Info("Starting HTTP server")
ps.serve(proxyListener)
ps.log.WithField("listen", listenAddress).Info("Stopping HTTP server")
ps.log.WithField("listen", listen).Info("Stopping HTTP server")
}
// ServeHTTPS constructs a net.Listener and starts handling HTTPS requests
func (ps *ProxyServer) ServeHTTPS() {
listenAddress := config.Get().Listen.HTTPS
func (ps *ProxyServer) ServeHTTPS(listen string) {
tlsConfig := utils.GetTLSConfig()
tlsConfig.GetCertificate = ps.getCertificates
ln, err := net.Listen("tcp", listenAddress)
ln, err := net.Listen("tcp", listen)
if err != nil {
ps.log.WithError(err).Warning("Failed to listen (TLS)")
return
@@ -167,26 +164,40 @@ func (ps *ProxyServer) ServeHTTPS() {
}()
tlsListener := tls.NewListener(proxyListener, tlsConfig)
ps.log.WithField("listen", listenAddress).Info("Starting HTTPS server")
ps.log.WithField("listen", listen).Info("Starting HTTPS server")
ps.serve(tlsListener)
ps.log.WithField("listen", listenAddress).Info("Stopping HTTPS server")
ps.log.WithField("listen", listen).Info("Stopping HTTPS server")
}
func (ps *ProxyServer) Start() error {
listenHttp := config.Get().Listen.HTTP
listenHttps := config.Get().Listen.HTTPS
listenMetrics := config.Get().Listen.Metrics
metricsRouter := ak.MetricsRouter()
wg := sync.WaitGroup{}
wg.Add(3)
wg.Add(len(listenHttp) + len(listenHttps) + 1 + len(listenMetrics))
for _, listen := range listenHttp {
go func() {
defer wg.Done()
ps.ServeHTTP(listen)
}()
}
for _, listen := range listenHttps {
go func() {
defer wg.Done()
ps.ServeHTTPS(listen)
}()
}
go func() {
defer wg.Done()
ps.ServeHTTP()
}()
go func() {
defer wg.Done()
ps.ServeHTTPS()
}()
go func() {
defer wg.Done()
metrics.RunServer()
ak.RunMetricsUnix(metricsRouter)
}()
for _, listen := range listenMetrics {
go func() {
defer wg.Done()
ak.RunMetricsServer(listen, metricsRouter)
}()
}
return nil
}

View File

@@ -1,28 +0,0 @@
package metrics
import (
"net/http"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/config"
"goauthentik.io/internal/utils/sentry"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func RunServer() {
m := mux.NewRouter()
l := log.WithField("logger", "authentik.outpost.metrics")
m.Use(sentry.SentryNoSampleMiddleware)
m.HandleFunc("/outpost.goauthentik.io/ping", func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(204)
})
m.Path("/metrics").Handler(promhttp.Handler())
listen := config.Get().Listen.Metrics
l.WithField("listen", listen).Info("Starting Metrics server")
err := http.ListenAndServe(listen, m)
if err != nil {
l.WithError(err).Warning("Failed to start metrics listener")
}
}

View File

@@ -9,9 +9,9 @@ import (
log "github.com/sirupsen/logrus"
"github.com/wwt/guac"
"goauthentik.io/internal/config"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/rac/connection"
"goauthentik.io/internal/outpost/rac/metrics"
)
type RACServer struct {
@@ -92,12 +92,10 @@ func (rs *RACServer) wsHandler(ctx context.Context, msg ak.Event) error {
}
func (rs *RACServer) Start() error {
listenMetrics := config.Get().Listen.Metrics
metricsRouter := ak.MetricsRouter()
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
metrics.RunServer()
}()
wg.Add(1 + 1 + len(listenMetrics))
go func() {
defer wg.Done()
err := rs.startGuac()
@@ -105,6 +103,16 @@ func (rs *RACServer) Start() error {
panic(err)
}
}()
go func() {
defer wg.Done()
ak.RunMetricsUnix(metricsRouter)
}()
for _, listen := range listenMetrics {
go func() {
defer wg.Done()
ak.RunMetricsServer(listen, metricsRouter)
}()
}
wg.Wait()
return nil
}

View File

@@ -1,16 +1,8 @@
package metrics
import (
"net/http"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/config"
"goauthentik.io/internal/utils/sentry"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
@@ -23,19 +15,3 @@ var (
Help: "Total number of rejected requests",
}, []string{"outpost_name", "reason", "app"})
)
func RunServer() {
m := mux.NewRouter()
l := log.WithField("logger", "authentik.outpost.metrics")
m.Use(sentry.SentryNoSampleMiddleware)
m.HandleFunc("/outpost.goauthentik.io/ping", func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(204)
})
m.Path("/metrics").Handler(promhttp.Handler())
listen := config.Get().Listen.Metrics
l.WithField("listen", listen).Info("Starting Metrics server")
err := http.ListenAndServe(listen, m)
if err != nil {
l.WithError(err).Warning("Failed to start metrics listener")
}
}

View File

@@ -10,7 +10,7 @@ import (
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/config"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/radius/metrics"
"golang.org/x/sync/errgroup"
"layeh.com/radius"
)
@@ -30,7 +30,7 @@ type ProviderInstance struct {
}
type RadiusServer struct {
s radius.PacketServer
s []*radius.PacketServer
log *log.Entry
ac *ak.APIController
cryptoStore *ak.CryptoStore
@@ -45,10 +45,13 @@ func NewServer(ac *ak.APIController) ak.Outpost {
providers: map[int32]*ProviderInstance{},
cryptoStore: ak.NewCryptoStore(ac.Client.CryptoAPI),
}
rs.s = radius.PacketServer{
Handler: rs,
SecretSource: rs,
Addr: config.Get().Listen.Radius,
listenRadius := config.Get().Listen.Radius
for _, listen := range listenRadius {
rs.s = append(rs.s, &radius.PacketServer{
Handler: rs,
SecretSource: rs,
Addr: listen,
})
}
return rs
}
@@ -95,29 +98,44 @@ func (rs *RadiusServer) RADIUSSecret(ctx context.Context, remoteAddr net.Addr) (
}
func (rs *RadiusServer) Start() error {
listenMetrics := config.Get().Listen.Metrics
metricsRouter := ak.MetricsRouter()
wg := sync.WaitGroup{}
wg.Add(2)
wg.Add(len(rs.s) + 1 + len(listenMetrics))
for _, s := range rs.s {
go func() {
defer wg.Done()
rs.log.WithField("listen", s.Addr).Info("Starting radius server")
err := s.ListenAndServe()
if err != nil {
panic(err)
}
}()
}
go func() {
defer wg.Done()
metrics.RunServer()
}()
go func() {
defer wg.Done()
rs.log.WithField("listen", rs.s.Addr).Info("Starting radius server")
err := rs.s.ListenAndServe()
if err != nil {
panic(err)
}
ak.RunMetricsUnix(metricsRouter)
}()
for _, listen := range listenMetrics {
go func() {
defer wg.Done()
ak.RunMetricsServer(listen, metricsRouter)
}()
}
wg.Wait()
return nil
}
func (rs *RadiusServer) Stop() error {
ctx, cancel := context.WithCancel(context.Background())
err := rs.s.Shutdown(ctx)
errs := new(errgroup.Group)
for _, s := range rs.s {
errs.Go(func() error {
return s.Shutdown(ctx)
})
}
cancel()
return err
return errs.Wait()
}
func (rs *RadiusServer) TimerFlowCacheExpiry(context.Context) {}

View File

@@ -0,0 +1,43 @@
package unix
import (
"net"
)
type Listener struct {
*net.UnixListener
}
type Conn struct {
net.Conn
}
func Listen(path string) (*Listener, error) {
addr, err := net.ResolveUnixAddr("unix", path)
if err != nil {
return nil, err
}
ln, err := net.ListenUnix("unix", addr)
if err != nil {
return nil, err
}
return &Listener{
ln,
}, nil
}
func (l *Listener) Accept() (net.Conn, error) {
c, err := l.UnixListener.Accept()
if err != nil {
return nil, err
}
return &Conn{c}, nil
}
func (c *Conn) LocalAddr() net.Addr {
return &net.TCPAddr{IP: net.IPv6loopback, Port: 0}
}
func (c *Conn) RemoteAddr() net.Addr {
return &net.TCPAddr{IP: net.IPv6loopback, Port: 0}
}

View File

@@ -19,7 +19,7 @@ var Requests = promauto.NewHistogramVec(prometheus.HistogramOpts{
Help: "API request latencies in seconds",
}, []string{"dest"})
func (ws *WebServer) runMetricsServer() {
func (ws *WebServer) runMetricsServer(listen string) {
l := log.WithField("logger", "authentik.router.metrics")
m := mux.NewRouter()
@@ -49,10 +49,10 @@ func (ws *WebServer) runMetricsServer() {
return
}
})
l.WithField("listen", config.Get().Listen.Metrics).Info("Starting Metrics server")
err := http.ListenAndServe(config.Get().Listen.Metrics, m)
l.WithField("listen", listen).Info("Starting Metrics server")
err := http.ListenAndServe(listen, m)
if err != nil {
l.WithError(err).Warning("Failed to start metrics server")
}
l.WithField("listen", config.Get().Listen.Metrics).Info("Stopping Metrics server")
l.WithField("listen", listen).Info("Stopping Metrics server")
}

View File

@@ -21,17 +21,18 @@ import (
"goauthentik.io/internal/config"
"goauthentik.io/internal/constants"
"goauthentik.io/internal/gounicorn"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/proxyv2"
"goauthentik.io/internal/utils"
"goauthentik.io/internal/utils/unix"
"goauthentik.io/internal/utils/web"
"goauthentik.io/internal/web/brand_tls"
)
const (
SocketName = "authentik.sock"
IPCKeyFile = "authentik-core-ipc.key"
MetricsKeyFile = "authentik-core-metrics.key"
UnixSocketName = "authentik-core.sock"
CoreSocketName = "authentik-core.sock"
)
type WebServer struct {
@@ -64,7 +65,7 @@ func NewWebServer() *WebServer {
loggingHandler.Use(web.NewLoggingHandler(l, nil))
tmp := os.TempDir()
socketPath := path.Join(tmp, UnixSocketName)
socketPath := path.Join(tmp, CoreSocketName)
// create http client to talk to backend, normal client if we're in debug more
// and a client that connects to our socket when in non debug mode
@@ -140,7 +141,8 @@ func (ws *WebServer) prepareKeys() {
func (ws *WebServer) Start() {
ws.prepareKeys()
u, err := url.Parse(fmt.Sprintf("http://%s%s", config.Get().Listen.HTTP, config.Get().Web.Path))
socketPath := path.Join(os.TempDir(), SocketName)
u, err := url.Parse(fmt.Sprintf("http://localhost%s", config.Get().Web.Path))
if err != nil {
panic(err)
}
@@ -150,7 +152,11 @@ func (ws *WebServer) Start() {
apiConfig.HTTPClient = &http.Client{
Transport: web.NewUserAgentTransport(
constants.UserAgentIPC(),
ak.GetTLSTransport(),
&http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", socketPath)
},
},
),
}
apiConfig.Servers = api.ServerConfigurations{
@@ -171,10 +177,18 @@ func (ws *WebServer) Start() {
go tw.Start()
})
go ws.runMetricsServer()
for _, listen := range config.Get().Listen.Metrics {
go ws.runMetricsServer(listen)
}
go ws.attemptStartBackend()
go ws.listenPlain()
go ws.listenTLS()
_ = os.Remove(socketPath)
go ws.listenUnix(socketPath)
for _, listen := range config.Get().Listen.HTTP {
go ws.listenPlain(listen)
}
for _, listen := range config.Get().Listen.HTTPS {
go ws.listenTLS(listen)
}
}
func (ws *WebServer) attemptStartBackend() {
@@ -225,23 +239,41 @@ func (ws *WebServer) Shutdown() {
ws.stop <- struct{}{}
}
func (ws *WebServer) listenPlain() {
ln, err := net.Listen("tcp", config.Get().Listen.HTTP)
func (ws *WebServer) listenUnix(listen string) {
ln, err := unix.Listen(listen)
if err != nil {
ws.log.WithError(err).Warning("failed to listen")
ws.log.WithField("listen", listen).WithError(err).Warning("failed to listen")
return
}
defer func() {
err := ln.Close()
if err != nil {
ws.log.WithField("listen", listen).WithError(err).Warning("failed to close listener")
}
}()
ws.log.WithField("listen", listen).Info("Starting HTTP server")
ws.serve(ln)
ws.log.WithField("listen", listen).Info("Stopping HTTP server")
}
func (ws *WebServer) listenPlain(listen string) {
ln, err := net.Listen("tcp", listen)
if err != nil {
ws.log.WithField("listen", listen).WithError(err).Warning("failed to listen")
return
}
proxyListener := &proxyproto.Listener{Listener: ln, ConnPolicy: utils.GetProxyConnectionPolicy()}
defer func() {
err := proxyListener.Close()
if err != nil {
ws.log.WithError(err).Warning("failed to close proxy listener")
ws.log.WithField("listen", listen).WithError(err).Warning("failed to close proxy listener")
}
}()
ws.log.WithField("listen", config.Get().Listen.HTTP).Info("Starting HTTP server")
ws.log.WithField("listen", listen).Info("Starting HTTP server")
ws.serve(proxyListener)
ws.log.WithField("listen", config.Get().Listen.HTTP).Info("Stopping HTTP server")
ws.log.WithField("listen", listen).Info("Stopping HTTP server")
}
func (ws *WebServer) serve(listener net.Listener) {

View File

@@ -6,7 +6,6 @@ import (
"github.com/pires/go-proxyproto"
"goauthentik.io/internal/config"
"goauthentik.io/internal/crypto"
"goauthentik.io/internal/utils"
"goauthentik.io/internal/utils/web"
@@ -48,13 +47,13 @@ func (ws *WebServer) GetCertificate() func(ch *tls.ClientHelloInfo) (*tls.Config
}
// ServeHTTPS constructs a net.Listener and starts handling HTTPS requests
func (ws *WebServer) listenTLS() {
func (ws *WebServer) listenTLS(listen string) {
tlsConfig := utils.GetTLSConfig()
tlsConfig.GetConfigForClient = ws.GetCertificate()
ln, err := net.Listen("tcp", config.Get().Listen.HTTPS)
ln, err := net.Listen("tcp", listen)
if err != nil {
ws.log.WithError(err).Warning("failed to listen (TLS)")
ws.log.WithField("listen", listen).WithError(err).Warning("failed to listen (TLS)")
return
}
proxyListener := &proxyproto.Listener{
@@ -71,7 +70,7 @@ func (ws *WebServer) listenTLS() {
}()
tlsListener := tls.NewListener(proxyListener, tlsConfig)
ws.log.WithField("listen", config.Get().Listen.HTTPS).Info("Starting HTTPS server")
ws.log.WithField("listen", listen).Info("Starting HTTPS server")
ws.serve(tlsListener)
ws.log.WithField("listen", config.Get().Listen.HTTPS).Info("Stopping HTTPS server")
ws.log.WithField("listen", listen).Info("Stopping HTTPS server")
}

View File

@@ -1,13 +1,9 @@
#!/usr/bin/env -S bash
set -e -o pipefail
MODE_FILE="${TMPDIR}/authentik-mode"
#!/usr/bin/env bash
if [[ -z "${PROMETHEUS_MULTIPROC_DIR}" ]]; then
export PROMETHEUS_MULTIPROC_DIR="${TMPDIR:-/tmp}/authentik_prometheus_tmp"
fi
set -e -o pipefail
function log {
printf '{"event": "%s", "level": "info", "logger": "bootstrap"}\n' "$@" >/dev/stderr
printf '{"event": "%s", "level": "info", "logger": "bootstrap"}\n' "$@" >&2
}
function wait_for_db {
@@ -15,10 +11,18 @@ function wait_for_db {
log "Bootstrap completed"
}
function check_if_root {
function run_authentik {
if [[ -x "$(command -v authentik)" ]]; then
echo authentik "$@"
else
echo cargo run -- "$@"
fi
}
function check_if_root_and_run {
if [[ $EUID -ne 0 ]]; then
log "Not running as root, disabling permission fixes"
exec $1
exec $(run_authentik "$@")
return
fi
SOCKET="/var/run/docker.sock"
@@ -26,36 +30,19 @@ function check_if_root {
if [[ -e "$SOCKET" ]]; then
# Get group ID of the docker socket, so we can create a matching group and
# add ourselves to it
DOCKER_GID=$(stat -c '%g' $SOCKET)
DOCKER_GID="$(stat -c "%g" "${SOCKET}")"
# Ensure group for the id exists
getent group $DOCKER_GID || groupadd -f -g $DOCKER_GID docker
usermod -a -G $DOCKER_GID authentik
getent group "${DOCKER_GID}" || groupadd -f -g "${DOCKER_GID}" docker
usermod -a -G "${DOCKER_GID}" authentik
# since the name of the group might not be docker, we need to lookup the group id
GROUP_NAME=$(getent group $DOCKER_GID | sed 's/:/\n/g' | head -1)
GROUP_NAME=$(getent group "${DOCKER_GID}" | sed 's/:/\n/g' | head -1)
GROUP="authentik:${GROUP_NAME}"
fi
# Fix permissions of certs and media
chown -R authentik:authentik /data /certs "${PROMETHEUS_MULTIPROC_DIR}"
chmod ug+rwx /data
chmod ug+rx /certs
exec chpst -u authentik:$GROUP env HOME=/authentik $1
}
function run_authentik {
if [[ -x "$(command -v authentik)" ]]; then
exec authentik $@
else
exec go run -v ./cmd/server/ $@
fi
}
function set_mode {
echo $1 >$MODE_FILE
trap cleanup EXIT
}
function cleanup {
rm -f ${MODE_FILE}
exec chpst -u authentik:"${GROUP}" env HOME=/authentik $(run_authentik "$@")
}
function prepare_debug {
@@ -72,38 +59,31 @@ function prepare_debug {
chown authentik:authentik /unittest.xml
}
if [[ -z "${PROMETHEUS_MULTIPROC_DIR}" ]]; then
export PROMETHEUS_MULTIPROC_DIR="${TMPDIR:-/tmp}/authentik_prometheus_tmp"
fi
mkdir -p "${PROMETHEUS_MULTIPROC_DIR}"
if [[ "$(python -m authentik.lib.config debugger 2>/dev/null)" == "True" ]]; then
prepare_debug
fi
if [[ "$1" == "server" ]]; then
set_mode "server"
run_authentik
elif [[ "$1" == "worker" ]]; then
set_mode "worker"
shift
# If we have bootstrap credentials set, run bootstrap tasks outside of main server
# sync, so that we can sure the first start actually has working bootstrap
# credentials
if [[ -n "${AUTHENTIK_BOOTSTRAP_PASSWORD}" || -n "${AUTHENTIK_BOOTSTRAP_TOKEN}" ]]; then
python -m manage apply_blueprint system/bootstrap.yaml || true
fi
check_if_root "python -m manage worker --pid-file ${TMPDIR}/authentik-worker.pid $@"
elif [[ "$1" == "bash" ]]; then
/bin/bash
elif [[ "$1" == "test-all" ]]; then
prepare_debug
chmod 777 /root
check_if_root "python -m manage test authentik"
elif [[ "$1" == "healthcheck" ]]; then
run_authentik healthcheck $(cat $MODE_FILE)
if [[ "$1" == "bash" ]]; then
exec /usr/bin/env -S bash "$@"
elif [[ "$1" == "dump_config" ]]; then
shift
exec python -m authentik.lib.config $@
shift 1
exec python -m authentik.lib.config "$@"
elif [[ "$1" == "debug" ]]; then
exec sleep infinity
elif [[ "$1" == "test-all" ]]; then
wait_for_db
prepare_debug
chmod 777 /root
check_if_root_and_run manage test authentik
elif [[ "$1" == "allinone" ]] || [[ "$1" == "server" ]] || [[ "$1" == "worker" ]] || [[ "$1" == "proxy" ]] || [[ "$1" == "manage" ]]; then
wait_for_db
check_if_root_and_run "$@"
else
wait_for_db
exec python -m manage "$@"
fi

View File

@@ -9,7 +9,7 @@
"version": "0.0.0",
"license": "MIT",
"devDependencies": {
"aws-cdk": "^2.1110.0",
"aws-cdk": "^2.1112.0",
"cross-env": "^10.1.0"
},
"engines": {
@@ -25,9 +25,9 @@
"license": "MIT"
},
"node_modules/aws-cdk": {
"version": "2.1110.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1110.0.tgz",
"integrity": "sha512-t881rXhuHWbiCXf8nuzf81jyOzHCgX1DNiCD3COwVGpT6DYna2SjsrDbraenJM722Oc+2OOAAMpKNEtVNj7mEg==",
"version": "2.1112.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1112.0.tgz",
"integrity": "sha512-IYUbsd9tpBQRqEO2evWsG+p2ZNa6wG5/sJvmWaqo45V1ep8BW+mrf+jEpFLD9uDPXqRA57EZGVGils7QLbOhNA==",
"dev": true,
"license": "Apache-2.0",
"bin": {

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