Compare commits

..

163 Commits

Author SHA1 Message Date
Dominic R
0b57dfb261 Reorganize docs sidebar with top-level Learning Center
- Group existing sections under a new `Documentation` category
- Promote `Learning Center` to its own top-level sidebar section
- Move `Enterprise` and `Developer` into top-level navigation
2026-03-06 15:57:51 -05:00
Dominic R
4317f5863d first article done 2026-03-04 16:01:42 -05:00
Dominic R
b1ddb11761 wip 2026-03-04 15:42:11 -05:00
Dominic R
c34b1344e7 dont highlight level or art count 2026-03-03 15:28:56 -05:00
Dominic R
84e031f3cf reorg 2026-03-03 15:28:56 -05:00
Dominic R
66617e5af2 code cleanup 2026-03-03 15:28:56 -05:00
Dominic R
c193b9558d Revert "wip"
This reverts commit b224beeefe0855a9cf8ffa811cb5463f7369c297.
2026-03-03 15:28:55 -05:00
Dominic R
5fef9e74d6 wip 2026-03-03 15:28:55 -05:00
Dominic R
efe1ebfe8f wip 2026-03-03 15:28:55 -05:00
Dominic R
374115b721 website: LearningCenter Meta (#15929)
* website: Tutorial meta

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

* Update index.md

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

---------

Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Tana Berry <tana@goauthentik.io>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2026-03-03 15:28:55 -05:00
Dominic R
eea7d0973c website: LearningCenter (#16006)
* website: LearningCenter

* wip

* website/docs: Learning Center: Work with Tana

* website/docs: Learning Center: Design option A addition + rm tags

* website: Lint

* website/docs: learning center: humanize a bit for demo
2026-03-03 15:28:54 -05:00
Marc 'risson' Schmitt
3927130233 packages/django-channels-postgres: eagerly delete messages (#20687)
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-03 16:20:37 +01:00
dependabot[bot]
01dd629f02 core: bump sentry-sdk from 2.53.0 to 2.54.0 (#20673)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.53.0 to 2.54.0.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.53.0...2.54.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-version: 2.54.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-03 13:18:11 +01:00
dependabot[bot]
559bbd4580 core: bump cachetools from 7.0.1 to 7.0.2 (#20674)
Bumps [cachetools](https://github.com/tkem/cachetools) from 7.0.1 to 7.0.2.
- [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/tkem/cachetools/compare/v7.0.1...v7.0.2)

---
updated-dependencies:
- dependency-name: cachetools
  dependency-version: 7.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-03 13:17:35 +01:00
dependabot[bot]
d2fbf901de core: bump google-api-python-client from 2.190.0 to 2.191.0 (#20671)
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 2.190.0 to 2.191.0.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v2.190.0...v2.191.0)

---
updated-dependencies:
- dependency-name: google-api-python-client
  dependency-version: 2.191.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-03 13:17:11 +01:00
dependabot[bot]
b50ee1deff core: bump pytest-github-actions-annotate-failures from 0.3.0 to 0.4.0 (#20675)
Bumps [pytest-github-actions-annotate-failures](https://github.com/pytest-dev/pytest-github-actions-annotate-failures) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/pytest-dev/pytest-github-actions-annotate-failures/releases)
- [Commits](https://github.com/pytest-dev/pytest-github-actions-annotate-failures/compare/v0.3.0...v0.4.0)

---
updated-dependencies:
- dependency-name: pytest-github-actions-annotate-failures
  dependency-version: 0.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-03 13:16:49 +01:00
dependabot[bot]
dcf1272561 core: bump aws-cdk-lib from 2.240.0 to 2.241.0 (#20676)
Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk) from 2.240.0 to 2.241.0.
- [Release notes](https://github.com/aws/aws-cdk/releases)
- [Changelog](https://github.com/aws/aws-cdk/blob/main/CHANGELOG.v2.alpha.md)
- [Commits](https://github.com/aws/aws-cdk/compare/v2.240.0...v2.241.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-03 13:16:16 +01:00
dependabot[bot]
1a88e3c931 ci: bump tj-actions/changed-files from 47.0.4 to 47.0.5 (#20677)
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 47.0.4 to 47.0.5.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](7dee1b0c15...22103cc46b)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-version: 47.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-03 13:15:47 +01:00
dependabot[bot]
f11bbb72da core: bump goauthentik/fips-debian from 1b2c47d to 44cd26c in /lifecycle/container (#20678)
core: bump goauthentik/fips-debian in /lifecycle/container

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-03 13:15:28 +01:00
dependabot[bot]
402bfe6a80 core: bump goauthentik/fips-python from 98b4f6a to 7f4c8cb in /lifecycle/container (#20679)
core: bump goauthentik/fips-python in /lifecycle/container

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

---
updated-dependencies:
- dependency-name: goauthentik/fips-python
  dependency-version: 3.14.3-slim-trixie-fips
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-03 13:15:07 +01:00
Dominic R
7f1f3de386 core: fix get_provider returning base Provider instead of subclass (#19064)
Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
2026-03-02 21:27:39 -05:00
Dewi Roberts
ef4d04c29c website/docs: kerberos: add note about caching (#20663)
* Add note about caching

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

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

---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
2026-03-02 16:58:05 +00:00
Dominic R
e5a261a0e5 admin/files: allow configuring S3 signature version (#20639) 2026-03-02 15:03:02 +01:00
Dewi Roberts
cd53bc1d1d website/docs: entra id provider: add custom email domain info (#20444)
* WIP

* WIP

* Apply suggestions from code review

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

---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
2026-03-02 13:29:32 +00:00
dependabot[bot]
f6076d1230 web: bump the storybook group across 1 directory with 5 updates (#20618)
Bumps the storybook group with 4 updates in the /web directory: [@storybook/addon-docs](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/docs), [@storybook/addon-links](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/links), [@storybook/web-components](https://github.com/storybookjs/storybook/tree/HEAD/code/renderers/web-components) and [@storybook/web-components-vite](https://github.com/storybookjs/storybook/tree/HEAD/code/frameworks/web-components-vite).


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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 12:51:46 +01:00
dependabot[bot]
6b05b5d79c web: bump country-flag-icons from 1.6.14 to 1.6.15 in /web (#20623)
Bumps [country-flag-icons](https://gitlab.com/catamphetamine/country-flag-icons) from 1.6.14 to 1.6.15.
- [Changelog](https://gitlab.com/catamphetamine/country-flag-icons/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/catamphetamine/country-flag-icons/compare/v1.6.14...v1.6.15)

---
updated-dependencies:
- dependency-name: country-flag-icons
  dependency-version: 1.6.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 12:51:11 +01:00
authentik-automation[bot]
d11f33c564 stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#20642)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-03-02 12:51:00 +01:00
dependabot[bot]
80ca1ab954 core: bump goauthentik/selenium from 145.0-ak-0.40.1 to 145.0-ak-0.40.2 in /tests/e2e (#20650)
core: bump goauthentik/selenium in /tests/e2e

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 12:50:25 +01:00
dependabot[bot]
a748bccec9 core: bump astral-sh/uv from 0.10.6 to 0.10.7 in /lifecycle/container (#20653)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.10.6 to 0.10.7.
- [Release notes](https://github.com/astral-sh/uv/releases)
- [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/uv/compare/0.10.6...0.10.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 12:50:18 +01:00
dependabot[bot]
8319f0c45a web: bump the swc group across 1 directory with 11 updates (#20655)
Bumps the swc group with 1 update in the /web directory: [@swc/core](https://github.com/swc-project/swc/tree/HEAD/packages/core).


Updates `@swc/core` from 1.15.13 to 1.15.18
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/commits/v1.15.18/packages/core)

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 12:50:09 +01:00
dependabot[bot]
91b25a8896 web: bump @types/node from 25.3.1 to 25.3.3 in /web (#20656)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.3.1 to 25.3.3.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 12:49:47 +01:00
dependabot[bot]
46d8c3864e ci: bump astral-sh/setup-uv from 7.3.0 to 7.3.1 in /.github/actions/setup (#20652)
ci: bump astral-sh/setup-uv in /.github/actions/setup

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 12:49:19 +01:00
dependabot[bot]
f9006bbddd core: bump goauthentik/fips-python from b83a0cb to 98b4f6a in /lifecycle/container (#20654)
core: bump goauthentik/fips-python in /lifecycle/container

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

---
updated-dependencies:
- dependency-name: goauthentik/fips-python
  dependency-version: 3.14.3-slim-trixie-fips
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 12:49:09 +01:00
Connor Peshek
7dd36eae9a enterprise/wsfed: Fix metadata export and signing logic (#20643) 2026-03-01 20:15:18 -06:00
Dominic R
3d28439a9e website/docs: fix upgrade link in 2026.5 release notes (#20638) 2026-03-01 02:21:38 +01:00
Simonyi Gergő
9ebf463397 packages/django-dramatiq-postgres: fix worker startup on macos (#20637)
fix worker startup on macos
2026-03-01 02:12:25 +01:00
djagoo
1c05cdaa78 website/integrations: add forgejo (#20635)
* added forgejo to integrations

* Remove screenshot, change some formatting and language

---------

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

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

* bump port

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

* fix healthcheck port

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

* check for 1 worker

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

* disable the flaky test

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

---------

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

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

* fix worker status

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

* only update when needed

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

* dont use status middleware in tests?

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

* types

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

* t

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

* tasks: improved tests

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

# Conflicts:
#	authentik/tasks/test.py

* cleanup

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

* format

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

* replace sleep with threading event

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

* better typing

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

* fix forks override

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

* fix run signature

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

---------

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

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

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

* Apply suggestion from @dewi-tik

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

---------

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

* Fix mounted references.

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

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

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

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

* Fix package resolution.

---------

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

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

* fix actually

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

---------

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

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-26 13:13:21 +01:00
Dominic R
c54011bd8a website/integrations: ak_groups -> groups (#20579) 2026-02-26 08:59:10 +00:00
Dominic R
393a4aa75d providers/scim: ak_groups -> groups in tests (#20580) 2026-02-26 04:20:03 +01:00
dependabot[bot]
e6e3c87dc3 core: bump astral-sh/uv from 0.10.4 to 0.10.6 in /lifecycle/container (#20557)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.10.4 to 0.10.6.
- [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.4...0.10.6)

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

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


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

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

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

---
updated-dependencies:
- dependency-name: "@goauthentik/esbuild-plugin-live-reload"
  dependency-version: 1.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: goauthentik
- dependency-name: "@goauthentik/eslint-config"
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: goauthentik
- dependency-name: "@goauthentik/prettier-config"
  dependency-version: 3.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: goauthentik
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 15:01:44 +01:00
dependabot[bot]
d186f8b38e core: bump github.com/getsentry/sentry-go from 0.42.0 to 0.43.0 (#20552)
Bumps [github.com/getsentry/sentry-go](https://github.com/getsentry/sentry-go) from 0.42.0 to 0.43.0.
- [Release notes](https://github.com/getsentry/sentry-go/releases)
- [Changelog](https://github.com/getsentry/sentry-go/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-go/compare/v0.42.0...v0.43.0)

---
updated-dependencies:
- dependency-name: github.com/getsentry/sentry-go
  dependency-version: 0.43.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 15:00:42 +01:00
authentik-automation[bot]
466f0f4137 core: bump goauthentik.io/api/v3 to 3.2026.5.0-rc1-1771856193 (#20475)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-02-25 13:55:25 +00:00
dependabot[bot]
223a8ffbb6 core: bump axllent/mailpit from v1.29.1 to v1.29.2 in /tests/e2e (#20553)
Bumps axllent/mailpit from v1.29.1 to v1.29.2.

---
updated-dependencies:
- dependency-name: axllent/mailpit
  dependency-version: v1.29.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 14:43:39 +01:00
dependabot[bot]
4831497369 core: bump goauthentik/selenium from 144.0-ak-0.40.0 to 145.0-ak-0.40.0 in /tests/e2e (#20554)
core: bump goauthentik/selenium in /tests/e2e

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 14:43:31 +01:00
dependabot[bot]
675cce81b0 core: bump library/golang from 889885d to d0a3e4b in /lifecycle/container (#20556)
core: bump library/golang in /lifecycle/container

Bumps library/golang from `889885d` to `d0a3e4b`.

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 14:43:15 +01:00
Tom Klingenberg
4692079320 website/integrations: gitea: ak_groups -> groups (#20565)
* Fix condition checks for user groups in Gitea integration

update deprecated filter for gitea groups.

Signed-off-by: Tom Klingenberg <76167763+dronebeelinux@users.noreply.github.com>

* Apply suggestions from code review

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

---------

Signed-off-by: Tom Klingenberg <76167763+dronebeelinux@users.noreply.github.com>
Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Dominic R <dominic@sdko.org>
2026-02-25 13:42:00 +00:00
dependabot[bot]
fd06ad2e5a lifecycle/aws: bump aws-cdk from 2.1106.1 to 2.1107.0 in /lifecycle/aws (#20493)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 13:40:59 +00:00
Simonyi Gergő
9e79ba29cc ci: add reason change to versions repo bump (#20562)
add `reason` change to versions repo bump
2026-02-25 14:32:32 +01:00
Jens L.
849c37806d internal: make http timeouts configurable (#20472)
* internal: make http timeouts configurable

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

* Changed formatting to match the rest of the doc

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-02-25 14:21:13 +01:00
dependabot[bot]
9f3d1c3f90 core: bump aws-cdk-lib from 2.239.0 to 2.240.0 (#20494)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 14:15:58 +01:00
dependabot[bot]
b4f66f43e5 core: bump goauthentik/fips-debian from d6def0a to 7b82e24 in /lifecycle/container (#20497)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 14:15:49 +01:00
dependabot[bot]
d3775be5d2 core: bump goauthentik/fips-python from bccefee to de8ad64 in /lifecycle/container (#20496)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 14:15:37 +01:00
authentik-automation[bot]
74329eb46b core, web: update translations (#20543)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-02-25 14:13:41 +01:00
dependabot[bot]
e0c36da8f9 core: bump library/nginx from 341bf0f to 0d1b1f0 in /website (#20559)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 14:12:04 +01:00
dependabot[bot]
b152cb4323 core: bump library/node from 43d1f7a to c58d9e7 in /website (#20558)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 14:11:57 +01:00
Dominic R
4c8916adde docs: fix typos and wording in docs and integrations (#20550) 2026-02-25 09:23:39 +00:00
dependabot[bot]
9b142e12e0 web: bump the eslint group across 1 directory with 5 updates (#20492)
* web: bump the eslint group across 1 directory with 5 updates

Bumps the eslint group with 5 updates in the /web directory:

| Package | From | To |
| --- | --- | --- |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.39.2` | `10.0.1` |
| [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.56.0` | `8.56.1` |
| [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.56.0` | `8.56.1` |
| [eslint](https://github.com/eslint/eslint) | `9.39.2` | `10.0.2` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.56.0` | `8.56.1` |



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

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

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

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

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

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 10.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: eslint
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.56.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: eslint
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.56.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: eslint
- dependency-name: eslint
  dependency-version: 10.0.2
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: eslint
- dependency-name: typescript-eslint
  dependency-version: 8.56.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: eslint
...

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

* Bump packages. Fix ESLint version ranges.

* Bump version. Update description.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2026-02-25 02:41:08 +00:00
Teffen Ellis
d30a18e0a5 web: Packagify Logger (#20541)
* Prep logger for use outside web workspace.

* Bump. Prep.

* Add to publish list.

* Update deps.

* Add package directory.
2026-02-25 02:03:25 +00:00
Dewi Roberts
61c594301f website/docs: remove bad logs redirect (#20522)
* Remove bad redirect

* Remove space
2026-02-25 01:12:08 +00:00
Dewi Roberts
54373cfea5 website/docs: revamp enterprise section (#20379)
* Begin

* WIP

* WIP

* WIP

* Fix link

* Fix spellig and links

* Enterprise vs enterprise plus

* Changes based on Tana's comment

* Update website/docs/enterprise/enterprise-features.md

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

* Update website/docs/enterprise/enterprise-features.md

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

* Update website/docs/enterprise/enterprise-features.md

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

* Update website/docs/enterprise/enterprise-features.md

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

* Apply suggestions

* Apply suggestion from Eric

* Update doc title after discussion with Tana

* Fix links

* Update website/docs/enterprise/manage-enterprise.mdx

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

* Update website/docs/enterprise/manage-enterprise.mdx

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

* Apply suggestions

* US dollars

* Apply Fletcher's suggestions

---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Dominic R <dominic@sdko.org>
2026-02-25 01:11:52 +00:00
Ken Sternberg
c29427cfbe web/flow: Tidy identification stage (#20261)
* web: Add InvalidationFlow to Radius Provider dialogues

## What

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

## Note

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

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

\# What

\# Why

\# How

\# Designs

\# Test Steps

\# Other Notes

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

This reverts commit dddde09be5.

* website: fix bad escaping of URLs in release notes

## What

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

v2024.6.4 had entries that looked like this:

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

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

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

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

## Notes

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

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

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

* web/flow: refactor FlowExecutor so that client-side stage selection is separate from stage execution

# What

Extracts and normalizes the *massive* switch/case statement into a table, eliminating as much repetition as possible. Where the server-side stage token and the client-side component have the same tag, only one is required. There were three different patterns for prop definitions, and those have been regularized into an expression with a compile-time type check, and the most common one can be omitted from the stage definition table.

# Why

1.  Because it’s hella cleaner. Stages are clear and easy to spot in the table (especially when it’s alphabetically ordered, OMG). Stages that disagree in name with their components, stages that take props different from the “standard” set, and stages that need `import` statements, are all easy to identify.
2.  Because identifying what we *do* with our web components is critical to their success, and to the success of the styling system the authentik web team envisions. FlowExecutor provides selection and execution of stages, but it also provides the inspector, the locale selector, headers, footers, customizations, and branding. Clearing away clutter to make that easier to see makes future refactoring for compatibility mode and dark theme handling much easier.

* web/flow: clean up state representation in FlowExecutor

# What

Cleans up the state and lifecycle of FlowExecutor.

*As state lifecycle*, the two fields `challenge` and `flowInfo` are synonymous: they are modified at the same time, once in the setter, and once in `updated()`; flowInfo is always a derived consequence of that current challenge. Making `challenge` the property that we are monitoring and `flowInfo` a simple accessor on `challenge` eliminates duplication of state management.

Lit automatically schedules a re-render whenever `challenge` is changed; the `requestUpdate()` is therefore not needed.

With that, the only thing left is where or when to change the document title. That too is moved to `updated()` and happens without checking for need; it does no harm to replace a string with its own value, the performance loss is so small as to be non-existent, it will not confuse the browser or the environment. Eliminating an `if` and reducing the code surface to a pattern check is a win.

FlowExecutor now has only three states: Loading, Challenge Available, and… Inspector? Let’s see what we can do about cleaning these up as well. Loading and Challenge do not seem synonymous: the challenge should not be altered until the fetch is complete, to prevent blank displays.

* web/flow: dedupe the set error flow state

# What

Extracts the logic for setting the flow state to FlowError.

# Why

It was just duplication. Trying to clean up state management is easier when special state handling is isolated into a single method.

* web/flow: dedupe the creation of fresh FlowApi instances

# What

Generates a single instance of FlowApi() that the FlowExecutor can use over the course of its lifetime.

# Why

Looking at the code generated by OpenApi, it’s clear that the parameters with which the API commits network transactions are immutable after construction; likewise, our particular invocation of `DEFAULT_GLOBALS` is also immutable with respect to a single instance of the FlowExecutor. With that in mind, there’s no reason to keep rebuilding the same network transaction object over and over; just instantiate it and live with it. In the conflict between rules-of-thumb “Never store what you can express” and “Extract repetitious expressions into instances,” the latter rule wins here.

* Intermediate. Gonna check against results.

* web/flow: extract inspector into standalone lifecycle

# What

Removes all of the code from `FlowExecutor` related to the inspector and isolates it into its own component. The lifecycle of FlowExecutor’s inspector handling has been adjusted to maintain the existing behavior.

# How

FlowExecutor is reduced to merely presenting the button:

- In `FlowExecutor`:
  - Remove all the controls and references to FlowInspector
    - Remove the capabilities check
    - Remove the inspector listener
    - Remove the render guards
    - Remove the “inspect” PropVariant (and remove it from `FlowExecutorSelections`)
    - Remove the inspector toggle
    - Remove the inspector renderer
  - Always dispatch FlowAdvance events (if the inspector is not present they will be ignored)
  - Adjust `ak-stage-redirect` to not take “promptUser” as an attribute
  - Replace the whole render-inspector-button clause with `ak-flow-inspector-button`
  - Adjust CSS to use `ak-flow-inspector-button` instead of `.inspector-button`

RedirectStage now queries the context for inspector availability and state:

- In `stages/RedirectStage`:
  - Change `promptUser` from a property accessor to a simple accessor that queries the parent context for the inspector state
  - Remove the `@property` clause

FlowInspectorButton takes over these responsibilities, isolating this separate concern into a single file:

- Manages loaded, available, and open states
- Does the capabilities check
- Listens for FlowInspectorChangeEvents on the window object
- Renders nothing at all if the inspector is inaccessible or if the inspector is present and covering up the button
- On connectedCallback checks if the URL indicate the inspector should already be open
- Manages loading the FlowInspector on demand and toggles the drawer on state change

To my great surprise, `FlowInspector` itself required no changes.

* Initial experiment to move stages into the light.

* web/flow: clean up state representation in FlowExecutor (#20027)

* web/flow: clean up state representation in FlowExecutor

# What

Cleans up the state and lifecycle of FlowExecutor.

*As state lifecycle*, the two fields `challenge` and `flowInfo` are synonymous: they are modified at the same time, once in the setter, and once in `updated()`; flowInfo is always a derived consequence of that current challenge. Making `challenge` the property that we are monitoring and `flowInfo` a simple accessor on `challenge` eliminates duplication of state management.

Lit automatically schedules a re-render whenever `challenge` is changed; the `requestUpdate()` is therefore not needed.

With that, the only thing left is where or when to change the document title. That too is moved to `updated()` and happens without checking for need; it does no harm to replace a string with its own value, the performance loss is so small as to be non-existent, it will not confuse the browser or the environment. Eliminating an `if` and reducing the code surface to a pattern check is a win.

FlowExecutor now has only three states: Loading, Challenge Available, and… Inspector? Let’s see what we can do about cleaning these up as well. Loading and Challenge do not seem synonymous: the challenge should not be altered until the fetch is complete, to prevent blank displays.

* web/flow: dedupe the set error flow state (#20029)

* web/flow: dedupe the set error flow state

# What

Extracts the logic for setting the flow state to FlowError.

# Why

It was just duplication. Trying to clean up state management is easier when special state handling is isolated into a single method.

* Protected.

---------

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

---------

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

* Fix types.

* web: Flesh out module driven tag names.

* Experiment continues: first-tier into the light.

* web/flow: optimize table for type safety

# What

Separate out the “here’s how a stage is defined” from “Here’s how a stage is represented internally.” This gives us a nice central store of where to define how the server-side componentName relates to a client-side customElementName while also guaranteeing that the componenName or supplied customElementName exists and corresponds. Type safety has been preserved system-wide (thanks, @GirlBossRush!)

* Prettier is still having opinions.

* web/flow: re-arrange IdentificationStage for maintainability

# What

Every conditional section of the IdentificationStage has been separated out into its own individual render function. Where possible, the information passed to the renderer has been reduced to a bare minimum (i.e if the function only needed the `passwordlessUrl`, that’s the only thing that’s passed to it), which helps highlight some inconsistencies in the API.

# No change

This is a purely maintenance-level change to the code, to make it obvious what needs to be plumbed/corrected in order to expose our dialogs to password managers. No functionality has been changed.

# Why

Figuring out how to turn our web components into proper elements, where what they contain is not isolated from the view of password managers, requires pulling out the functionality into small, readable components.

# Future work

Doing this has exposed several fundamental issues:

- auto-redirect is a state change from one LoginChallenge to another under a collection of conditions available on the challenge, triggered when FlowExecutor writes a new challenge. “Which challenge?” in FlowExecutor ought to be handling this, not handing it off to IdentificationStage.

- Everything about Captcha is about Captcha. It ought to be in its own little state managing class, perhaps as a lit controller.

- The same is true about WebAuthn.

- `host` is doing very little work; at best, it’s receiving a “change this” or “submit that” message, which is an Event. Look forward to that.

* Tidy.

* Removed the cache; it's extra code for no benefit whatsoever; the table is constructed ONCE at start-up, there's never going to be a cache hit.  The FlowExecutorStageFactory produces StageMappings (StageMapping[]), which is itself a warehouse of singular server-component -> client-component relationships, fetching the client from the bundle as needed.  The StageMapping only does the fetch once per instance, so (for example) a password failure will reinstantiate a PasswordStage, but it will not fetch it a second time.

* Removed comments about the cache.  Added comments about where to find the FlowExecutor stage table. Moved the import of WebAuthnAuthenticticatorRegisterState from FlowExecutor.ts to FlowExecutorStages.ts; both files are bundled together, so this is a no-op functionally, but it's easier to confirm that StageEntries without import expressions (STageModuleCallbacks) have their stages bundled (pre-imported) if the import statement is in the same file.

* Of COURSE prettier had opinions!

* Since the check for `this.can(CapabilitiesEnum.CanDebug))` has been moved into the FlowInspectorButton, FlowExecutor no longer needs the capabilities check at all.

* Move the inspector into its own folder.

* web: Flesh out stage mapping error handling. (#20292)

Co-authored-by: Ken Sternberg <ken@goauthentik.io>

* Weird merge bug: same function appeared twice.

* Added some visibility keys, as per @GirlBossRush

---------

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2026-02-24 16:35:32 -08:00
Simonyi Gergő
6912bbec77 website/docs: fix upgrade link in release notes (#20540)
fix upgrade link in release notes
2026-02-25 00:08:10 +00:00
Simonyi Gergő
b43276d39a website/docs: fix upgrade link in 2026.2 release notes (#20539)
fix upgrade link in `2026.2` release notes
2026-02-25 00:03:09 +00:00
Simonyi Gergő
80c3d73912 website/docs: update supported versions (#20534)
update supported versions
2026-02-24 22:16:56 +00:00
Simonyi Gergő
36f4d25f95 website/docs: create draft release notes for 2026.5 (#20529)
* create draft release notes for `2026.5`

* fixup! create draft release notes for `2026.5`
2026-02-24 22:40:17 +01:00
Diego Bravo
6269da916b Fix redirect URI in Seafile integration documentation (#20532)
If you just follow orders exactly as said here it'll lead you to an error because of the strict policy having a slash at the end but in the seahub_settings.py in the docs tells you to put it without it. This minor change can help people to not encounter this minimal error sometimes difficult to see.

Signed-off-by: Diego Bravo <10383549+DystopianRescuer@users.noreply.github.com>
2026-02-24 21:12:21 +00:00
Simonyi Gergő
884e662277 website/docs: autogenerate release notes (#20527)
* autogenerate minor changes and API changes

* lint

* spellcheck

This is in a commit message, so technically it's not correct, but
at this point I don't care :))

* finalize release notes for `2026.2`
2026-02-24 19:03:17 +00:00
Jens L.
34c5b7add3 providers/oauth2: add jti claim (#20484)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-02-24 18:38:11 +01:00
Jens L.
37e701e458 providers/oauth2: deactivate locale after testing (#20518)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-02-24 15:47:55 +01:00
Jens L.
edf5ec972f policies: fix PolicyEngineMode ALL with static binding optimization (#20430)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-02-24 15:47:12 +01:00
Jens L.
00722115bf website/docs: fix linux setup docs (#20508)
* docs: add auth config steps

* tweak

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

* Changed wording

* Fix broken link

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Connor Peshek <connor@connorpeshek.me>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-02-24 13:18:55 +01:00
Simonyi Gergő
f5adf8d4c7 web: fix Edit Policy button on Flow view page (#20511)
fix Edit Policy button on Flow view page
2026-02-24 12:02:26 +00:00
Connor Peshek
d2b234cf86 endpoints: fix infinite recursion in stage with unsupported connector (#20485)
* stages: fix infinite recursion

* respect mode

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

* add tests

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-02-24 13:01:31 +01:00
Simonyi Gergő
a5df6820ce enterprise: add ES384 to enterprise license algorithms (#20507)
add `ES384` to enterprise license algorithms
2026-02-24 10:51:14 +00:00
Ken Sternberg
8d2b463a9a web/flow: fix typo in RedirectStage (#20488) 2026-02-24 11:15:32 +01:00
Dominic R
9da1014271 website/docs: fix GitHub social-login wording and capitalization (#20489)
Update the GitHub social-login guide to consistently reference GitHub Developer Settings and correct provider wording.

Standardize GitHub capitalization across the page text and inline policy comments.
2026-02-24 09:17:59 +00:00
dependabot[bot]
21be5fec43 web: bump knip from 5.84.1 to 5.85.0 in /web (#20464)
* web: bump knip from 5.84.1 to 5.85.0 in /web

Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 5.84.1 to 5.85.0.
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@5.85.0/packages/knip)

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

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

* Update prettier packages.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2026-02-24 03:15:58 +00:00
Dewi Roberts
a49764dee0 website/integrations: standardize resource sections and update template (#20423)
Standardize resource sections and update template

Co-authored-by: Dominic R <dominic@sdko.org>
2026-02-24 02:27:37 +00:00
dependabot[bot]
089cc011f1 core, web: bump ajv from 6.12.6 to 6.14.0 in /packages/eslint-config (#20478)
* core, web: bump ajv from 6.12.6 to 6.14.0 in /packages/eslint-config

Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.12.6 to 6.14.0.
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v6.12.6...v6.14.0)

---
updated-dependencies:
- dependency-name: ajv
  dependency-version: 6.14.0
  dependency-type: indirect
...

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

* Bump packages. Fix order.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2026-02-24 03:12:39 +01:00
Connor Peshek
8b6be1d997 sources/saml: improve exception handling for saml response parsing (#20125)
improve exception handling
2026-02-23 15:31:35 -06:00
Ken Sternberg
179a9b76f2 web/flow: separate flow inspector lifecycle from flow executor lifecycle (#20063)
* web: Add InvalidationFlow to Radius Provider dialogues

## What

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

## Note

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

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

\# What

\# Why

\# How

\# Designs

\# Test Steps

\# Other Notes

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

This reverts commit dddde09be5.

* website: fix bad escaping of URLs in release notes

## What

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

v2024.6.4 had entries that looked like this:

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

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

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

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

## Notes

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

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

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

* web/flow: refactor FlowExecutor so that client-side stage selection is separate from stage execution

# What

Extracts and normalizes the *massive* switch/case statement into a table, eliminating as much repetition as possible. Where the server-side stage token and the client-side component have the same tag, only one is required. There were three different patterns for prop definitions, and those have been regularized into an expression with a compile-time type check, and the most common one can be omitted from the stage definition table.

# Why

1.  Because it’s hella cleaner. Stages are clear and easy to spot in the table (especially when it’s alphabetically ordered, OMG). Stages that disagree in name with their components, stages that take props different from the “standard” set, and stages that need `import` statements, are all easy to identify.
2.  Because identifying what we *do* with our web components is critical to their success, and to the success of the styling system the authentik web team envisions. FlowExecutor provides selection and execution of stages, but it also provides the inspector, the locale selector, headers, footers, customizations, and branding. Clearing away clutter to make that easier to see makes future refactoring for compatibility mode and dark theme handling much easier.

* web/flow: clean up state representation in FlowExecutor

# What

Cleans up the state and lifecycle of FlowExecutor.

*As state lifecycle*, the two fields `challenge` and `flowInfo` are synonymous: they are modified at the same time, once in the setter, and once in `updated()`; flowInfo is always a derived consequence of that current challenge. Making `challenge` the property that we are monitoring and `flowInfo` a simple accessor on `challenge` eliminates duplication of state management.

Lit automatically schedules a re-render whenever `challenge` is changed; the `requestUpdate()` is therefore not needed.

With that, the only thing left is where or when to change the document title. That too is moved to `updated()` and happens without checking for need; it does no harm to replace a string with its own value, the performance loss is so small as to be non-existent, it will not confuse the browser or the environment. Eliminating an `if` and reducing the code surface to a pattern check is a win.

FlowExecutor now has only three states: Loading, Challenge Available, and… Inspector? Let’s see what we can do about cleaning these up as well. Loading and Challenge do not seem synonymous: the challenge should not be altered until the fetch is complete, to prevent blank displays.

* web/flow: dedupe the set error flow state

# What

Extracts the logic for setting the flow state to FlowError.

# Why

It was just duplication. Trying to clean up state management is easier when special state handling is isolated into a single method.

* web/flow: dedupe the creation of fresh FlowApi instances

# What

Generates a single instance of FlowApi() that the FlowExecutor can use over the course of its lifetime.

# Why

Looking at the code generated by OpenApi, it’s clear that the parameters with which the API commits network transactions are immutable after construction; likewise, our particular invocation of `DEFAULT_GLOBALS` is also immutable with respect to a single instance of the FlowExecutor. With that in mind, there’s no reason to keep rebuilding the same network transaction object over and over; just instantiate it and live with it. In the conflict between rules-of-thumb “Never store what you can express” and “Extract repetitious expressions into instances,” the latter rule wins here.

* Intermediate. Gonna check against results.

* web/flow: extract inspector into standalone lifecycle

# What

Removes all of the code from `FlowExecutor` related to the inspector and isolates it into its own component. The lifecycle of FlowExecutor’s inspector handling has been adjusted to maintain the existing behavior.

# How

FlowExecutor is reduced to merely presenting the button:

- In `FlowExecutor`:
  - Remove all the controls and references to FlowInspector
    - Remove the capabilities check
    - Remove the inspector listener
    - Remove the render guards
    - Remove the “inspect” PropVariant (and remove it from `FlowExecutorSelections`)
    - Remove the inspector toggle
    - Remove the inspector renderer
  - Always dispatch FlowAdvance events (if the inspector is not present they will be ignored)
  - Adjust `ak-stage-redirect` to not take “promptUser” as an attribute
  - Replace the whole render-inspector-button clause with `ak-flow-inspector-button`
  - Adjust CSS to use `ak-flow-inspector-button` instead of `.inspector-button`

RedirectStage now queries the context for inspector availability and state:

- In `stages/RedirectStage`:
  - Change `promptUser` from a property accessor to a simple accessor that queries the parent context for the inspector state
  - Remove the `@property` clause

FlowInspectorButton takes over these responsibilities, isolating this separate concern into a single file:

- Manages loaded, available, and open states
- Does the capabilities check
- Listens for FlowInspectorChangeEvents on the window object
- Renders nothing at all if the inspector is inaccessible or if the inspector is present and covering up the button
- On connectedCallback checks if the URL indicate the inspector should already be open
- Manages loading the FlowInspector on demand and toggles the drawer on state change

To my great surprise, `FlowInspector` itself required no changes.

* web/flow: clean up state representation in FlowExecutor (#20027)

* web/flow: clean up state representation in FlowExecutor

# What

Cleans up the state and lifecycle of FlowExecutor.

*As state lifecycle*, the two fields `challenge` and `flowInfo` are synonymous: they are modified at the same time, once in the setter, and once in `updated()`; flowInfo is always a derived consequence of that current challenge. Making `challenge` the property that we are monitoring and `flowInfo` a simple accessor on `challenge` eliminates duplication of state management.

Lit automatically schedules a re-render whenever `challenge` is changed; the `requestUpdate()` is therefore not needed.

With that, the only thing left is where or when to change the document title. That too is moved to `updated()` and happens without checking for need; it does no harm to replace a string with its own value, the performance loss is so small as to be non-existent, it will not confuse the browser or the environment. Eliminating an `if` and reducing the code surface to a pattern check is a win.

FlowExecutor now has only three states: Loading, Challenge Available, and… Inspector? Let’s see what we can do about cleaning these up as well. Loading and Challenge do not seem synonymous: the challenge should not be altered until the fetch is complete, to prevent blank displays.

* web/flow: dedupe the set error flow state (#20029)

* web/flow: dedupe the set error flow state

# What

Extracts the logic for setting the flow state to FlowError.

# Why

It was just duplication. Trying to clean up state management is easier when special state handling is isolated into a single method.

* Protected.

---------

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

---------

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

* Fix types.

* web: Flesh out module driven tag names.

* web/flow: optimize table for type safety

# What

Separate out the “here’s how a stage is defined” from “Here’s how a stage is represented internally.” This gives us a nice central store of where to define how the server-side componentName relates to a client-side customElementName while also guaranteeing that the componenName or supplied customElementName exists and corresponds. Type safety has been preserved system-wide (thanks, @GirlBossRush!)

* Prettier is still having opinions.

* Tidy.

* Removed the cache; it's extra code for no benefit whatsoever; the table is constructed ONCE at start-up, there's never going to be a cache hit.  The FlowExecutorStageFactory produces StageMappings (StageMapping[]), which is itself a warehouse of singular server-component -> client-component relationships, fetching the client from the bundle as needed.  The StageMapping only does the fetch once per instance, so (for example) a password failure will reinstantiate a PasswordStage, but it will not fetch it a second time.

* Removed comments about the cache.  Added comments about where to find the FlowExecutor stage table. Moved the import of WebAuthnAuthenticticatorRegisterState from FlowExecutor.ts to FlowExecutorStages.ts; both files are bundled together, so this is a no-op functionally, but it's easier to confirm that StageEntries without import expressions (STageModuleCallbacks) have their stages bundled (pre-imported) if the import statement is in the same file.

* Of COURSE prettier had opinions!

* Since the check for `this.can(CapabilitiesEnum.CanDebug))` has been moved into the FlowInspectorButton, FlowExecutor no longer needs the capabilities check at all.

* Move the inspector into its own folder.

* web: Flesh out stage mapping error handling. (#20292)

Co-authored-by: Ken Sternberg <ken@goauthentik.io>

* Weird merge bug: same function appeared twice.

---------

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2026-02-23 12:59:40 -08:00
Ken Sternberg
254bfd2a60 web/maintenance: no unknown attributes part 2 (#19014)
* web: Add InvalidationFlow to Radius Provider dialogues

## What

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

## Note

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

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

\# What

\# Why

\# How

\# Designs

\# Test Steps

\# Other Notes

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

This reverts commit dddde09be5.

* website: fix bad escaping of URLs in release notes

## What

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

v2024.6.4 had entries that looked like this:

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

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

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

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

## Notes

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

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

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

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

# What

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

The following issues were uncovered.

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

These guideposts will be placed on the PR.

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

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

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

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

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

# What

This commit is a collection of fixes and adaptations discovered while running lit-analyzer in a stricter role than usual.

- `src/admin/endpoints/connectors/agent/AgentConnectorSetup.ts`

After talking to @beryju, we determined that these labels aren’t shown and aren’t used.

- `src/admin/admin-overview/AdminOverviewPage.ts`
- `src/admin/admin-overview/DashboardUserPage.ts`
- `src/elements/cards/AggregatePromiseCard.ts`
- `src/elements/cards/stories/AggregatePromiseCard.stories.ts`

The `Promise` version of our card is not used by any client code. The Dashboard pages that were importing it want the vanilla `AggregateCard` instead.

- `./src/flow/stages/identification/IdentificationStage.ts`

Anchors do not have a `name` attribute, I cannot find any code using the name attributes as lookups, nor any CSS that might use the name attributes as guides. `ak-flow-password-input` is always required; the flag is unsupported and unnecessary.

- `./src/flow/stages/password/PasswordStage.ts`

Anchors do not have a `name` attribute, I cannot find any code using the name attributes as lookups, nor any CSS that might use the name attributes as guides. `ak-flow-password-input` is always required; the flag is unsupported and unnecessary.

- `src/user/user-settings/UserSettingsPage.ts`

This change to the `UserSettingsPage`:

``` diff
-                                userId=${ifPresent(currentUser?.pk)}
+                                user-id=${ifPresent(currentUser?.pk)}
```

… corresponds correctly with:

``` typescript
    @property({ type: Number, attribute: "user-id" })
    public userId?: number;
```

I find it odd (and remarkable) that nobody has complained about this yet. I even went so far as to [confirm my understanding](https://codepen.io/kensternberg-authentik/pen/raLNBwO) and, yes:

- when an attribute is truthy, property syntax does not set the field
- when an attribute is deliberately given a kebab-case name, using the camelCase variant does not set the field

However, when the attribute is truthy, attribute names are case-insensitive: ‘user-id’ and ‘User-Id’ in client code would work just fine.

## Note

A large enough number of warnings remain. Some of those are due to `lit-analyzer` not being updated to recognize newly Baseline global DOM properties like `inert` or `popover`. The rest are from RapiDoc and QrCode, which do not supply sufficient documentation or metadata for Lit-anaylzer to read correctly.

* web/bug/hidden-secrets-not-propagating

# What

This commit updates ak-secret-text-input, adding the `name` attribute to all valid input fields and updating the value writer to match those of known-working components, to ensure that either variety of the display is fully and correctly updated with the content of the hidden secret.

# Why

The hidden input field is the one that HorizontalFormElement was expecting to read its value from, but that field never received a `name` because it wasn’t present when the field was first updated.

HorizontalFormElement writes the `name` field to the first `<input>` it finds. That was the “dummy” input field, which has no working value.

Form ignored the input element because the value it read came with an undefined name.

Object-oriented state management sometimes bites.

* Turns out, I was wrong. Someone *does* use the `name` attribute: the tests.  MDN says that `name`
is incorrect, and we should use `id` instead.  I have compromised; I have switched to using the
Open UI Automation ID instead, since that's what we're doing: automated tests.

\# What

\# Why

\# How

\# Designs

\# Test Steps

\# Other Notes

---------

Signed-off-by: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com>
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Co-authored-by: Jens L. <jens@goauthentik.io>
2026-02-23 12:55:26 -08:00
Tana M Berry
991f5fc536 website/docs: add info about make install and recovery key (#20447)
* add info about make install and recovery key

* fix formatting on troubleshooting tip

* Apply suggestion from @dominic-r

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

* tweak to bump

* tweak

* tweaked words abouot make install per jens

* build

---------

Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Marcelo Elizeche Landó <marcelo@goauthentik.io>
2026-02-23 13:18:59 -06:00
dependabot[bot]
72a9a1ab76 web: bump ajv from 6.12.6 to 6.14.0 in /web (#20479)
Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.12.6 to 6.14.0.
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v6.12.6...v6.14.0)

---
updated-dependencies:
- dependency-name: ajv
  dependency-version: 6.14.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-02-23 17:31:00 +01:00
Brolywood
17ab3a4b73 providers/proxy: preserve URL-encoded path characters in redirect (#20476)
Use r.URL.EscapedPath() instead of r.URL.Path when building the
redirect URL in redirectToStart(). The decoded Path field converts
%2F to /, which url.JoinPath then collapses via path.Clean, stripping
encoded slashes from the URL. EscapedPath() preserves the original
encoding, fixing 301 redirects that break apps like RabbitMQ which
use %2F in their API paths.
2026-02-23 17:30:47 +01:00
Jens L.
93e916c8ad policies: measure policy process from manager (#20477)
* policies: measure policy process from manager

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

* fix constructor

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-02-23 17:30:36 +01:00
Simonyi Gergő
52e2460927 enterprise: monkey patch pyjwt to accept mismatching key (#20402)
* monkey patch pyjwt to accept mismatching key

* restore `_validate_curve` after monkeypatch

* add explanatory comment

* next year is 2027, dummy
2026-02-23 14:55:57 +01:00
Alexander Tereshkin
9ba7b373b1 enterprise/lifecycle: use datetime instead of date to track review cycles (#20283)
* enterprise/lifecycle: use datetime instead of date to track review cycles (fix for #20265)

* Update authentik/enterprise/lifecycle/api/iterations.py

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

* enterprise/lifecycle: replace extend_schema_field with type annotations

---------

Signed-off-by: Alexander Tereshkin <96586+atereshkin@users.noreply.github.com>
Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
Co-authored-by: Jens L. <jens@beryju.org>
2026-02-23 14:55:44 +01:00
Simonyi Gergő
6e04a4264e root: run npm i with npm@11.10.1 in all subdirectories (#20471)
run `npm i` with `npm@11.10.1` in all subdirectories
2026-02-23 13:34:19 +01:00
Michael Beigelmacher
d9df013a48 providers/oauth2: device code flow client id via auth header (#20457)
* Use `extract_client_auth` which can get client id from either HTTP
Authorization header or POST body

* Update documentation to reflect allow sending client id via header

* Add tests for using HTTP Basic Auth to pass in client id
2026-02-23 13:18:07 +01:00
dependabot[bot]
278c8e7098 core: bump goauthentik/fips-debian from 4419749 to d6def0a in /lifecycle/container (#20467)
core: bump goauthentik/fips-debian in /lifecycle/container

Bumps goauthentik/fips-debian from `4419749` to `d6def0a`.

---
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-02-23 12:21:48 +01:00
dependabot[bot]
0055518f99 core: bump goauthentik/fips-python from d973c46 to bccefee in /lifecycle/container (#20466)
core: bump goauthentik/fips-python in /lifecycle/container

Bumps goauthentik/fips-python from `d973c46` to `bccefee`.

---
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-02-23 12:19:00 +01:00
dependabot[bot]
1be23e2ae9 core, web: bump ajv from 6.12.6 to 6.14.0 in /packages/prettier-config (#20462)
Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.12.6 to 6.14.0.
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v6.12.6...v6.14.0)

---
updated-dependencies:
- dependency-name: ajv
  dependency-version: 6.14.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-02-22 17:44:20 +01:00
Jens L.
f62479c2c7 ci: bump and fix daily (#20461)
* ci: bump and fix daily

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

* run daily ci when changed

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

* fix missing dir

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

* download to correct path

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-02-22 15:29:35 +01:00
Daniil Kamakin
7a28837622 website/integrations: fix Vaultwarden SSO_SCOPES syntax (#20459)
fix Vaultwarden SSO_SCOPES syntax

Remove literal quotes to prevent injection errors and remove redundant openid scope

Signed-off-by: Daniil Kamakin <54929583+dkamakin@users.noreply.github.com>
2026-02-22 12:44:42 +00:00
Jens L.
1031b050c2 stages/user_login: log correct user when session binding is broken (#20094)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-02-21 18:37:59 +01:00
Ken Sternberg
395e6829d0 web/flow: generate a single API object for network transactions and use it for the lifetime of the FlowExecutor (#20030)
* web: Add InvalidationFlow to Radius Provider dialogues

## What

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

## Note

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

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

\# What

\# Why

\# How

\# Designs

\# Test Steps

\# Other Notes

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

This reverts commit dddde09be5.

* website: fix bad escaping of URLs in release notes

## What

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

v2024.6.4 had entries that looked like this:

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

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

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

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

## Notes

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

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

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

* web/flow: refactor FlowExecutor so that client-side stage selection is separate from stage execution

# What

Extracts and normalizes the *massive* switch/case statement into a table, eliminating as much repetition as possible. Where the server-side stage token and the client-side component have the same tag, only one is required. There were three different patterns for prop definitions, and those have been regularized into an expression with a compile-time type check, and the most common one can be omitted from the stage definition table.

# Why

1.  Because it’s hella cleaner. Stages are clear and easy to spot in the table (especially when it’s alphabetically ordered, OMG). Stages that disagree in name with their components, stages that take props different from the “standard” set, and stages that need `import` statements, are all easy to identify.
2.  Because identifying what we *do* with our web components is critical to their success, and to the success of the styling system the authentik web team envisions. FlowExecutor provides selection and execution of stages, but it also provides the inspector, the locale selector, headers, footers, customizations, and branding. Clearing away clutter to make that easier to see makes future refactoring for compatibility mode and dark theme handling much easier.

* web/flow: clean up state representation in FlowExecutor

# What

Cleans up the state and lifecycle of FlowExecutor.

*As state lifecycle*, the two fields `challenge` and `flowInfo` are synonymous: they are modified at the same time, once in the setter, and once in `updated()`; flowInfo is always a derived consequence of that current challenge. Making `challenge` the property that we are monitoring and `flowInfo` a simple accessor on `challenge` eliminates duplication of state management.

Lit automatically schedules a re-render whenever `challenge` is changed; the `requestUpdate()` is therefore not needed.

With that, the only thing left is where or when to change the document title. That too is moved to `updated()` and happens without checking for need; it does no harm to replace a string with its own value, the performance loss is so small as to be non-existent, it will not confuse the browser or the environment. Eliminating an `if` and reducing the code surface to a pattern check is a win.

FlowExecutor now has only three states: Loading, Challenge Available, and… Inspector? Let’s see what we can do about cleaning these up as well. Loading and Challenge do not seem synonymous: the challenge should not be altered until the fetch is complete, to prevent blank displays.

* web/flow: dedupe the set error flow state

# What

Extracts the logic for setting the flow state to FlowError.

# Why

It was just duplication. Trying to clean up state management is easier when special state handling is isolated into a single method.

* web/flow: dedupe the creation of fresh FlowApi instances

# What

Generates a single instance of FlowApi() that the FlowExecutor can use over the course of its lifetime.

# Why

Looking at the code generated by OpenApi, it’s clear that the parameters with which the API commits network transactions are immutable after construction; likewise, our particular invocation of `DEFAULT_GLOBALS` is also immutable with respect to a single instance of the FlowExecutor. With that in mind, there’s no reason to keep rebuilding the same network transaction object over and over; just instantiate it and live with it. In the conflict between rules-of-thumb “Never store what you can express” and “Extract repetitious expressions into instances,” the latter rule wins here.

* web/flow: clean up state representation in FlowExecutor (#20027)

* web/flow: clean up state representation in FlowExecutor

# What

Cleans up the state and lifecycle of FlowExecutor.

*As state lifecycle*, the two fields `challenge` and `flowInfo` are synonymous: they are modified at the same time, once in the setter, and once in `updated()`; flowInfo is always a derived consequence of that current challenge. Making `challenge` the property that we are monitoring and `flowInfo` a simple accessor on `challenge` eliminates duplication of state management.

Lit automatically schedules a re-render whenever `challenge` is changed; the `requestUpdate()` is therefore not needed.

With that, the only thing left is where or when to change the document title. That too is moved to `updated()` and happens without checking for need; it does no harm to replace a string with its own value, the performance loss is so small as to be non-existent, it will not confuse the browser or the environment. Eliminating an `if` and reducing the code surface to a pattern check is a win.

FlowExecutor now has only three states: Loading, Challenge Available, and… Inspector? Let’s see what we can do about cleaning these up as well. Loading and Challenge do not seem synonymous: the challenge should not be altered until the fetch is complete, to prevent blank displays.

* web/flow: dedupe the set error flow state (#20029)

* web/flow: dedupe the set error flow state

# What

Extracts the logic for setting the flow state to FlowError.

# Why

It was just duplication. Trying to clean up state management is easier when special state handling is isolated into a single method.

* Protected.

---------

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

---------

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

* Fix types.

* web: Flesh out module driven tag names.

* web/flow: optimize table for type safety

# What

Separate out the “here’s how a stage is defined” from “Here’s how a stage is represented internally.” This gives us a nice central store of where to define how the server-side componentName relates to a client-side customElementName while also guaranteeing that the componenName or supplied customElementName exists and corresponds. Type safety has been preserved system-wide (thanks, @GirlBossRush!)

* Tidy.

* Removed the cache; it's extra code for no benefit whatsoever; the table is constructed ONCE at start-up, there's never going to be a cache hit.  The FlowExecutorStageFactory produces StageMappings (StageMapping[]), which is itself a warehouse of singular server-component -> client-component relationships, fetching the client from the bundle as needed.  The StageMapping only does the fetch once per instance, so (for example) a password failure will reinstantiate a PasswordStage, but it will not fetch it a second time.

* Removed comments about the cache.  Added comments about where to find the FlowExecutor stage table. Moved the import of WebAuthnAuthenticticatorRegisterState from FlowExecutor.ts to FlowExecutorStages.ts; both files are bundled together, so this is a no-op functionally, but it's easier to confirm that StageEntries without import expressions (STageModuleCallbacks) have their stages bundled (pre-imported) if the import statement is in the same file.

* web: Flesh out stage mapping error handling. (#20292)

Co-authored-by: Ken Sternberg <ken@goauthentik.io>

---------

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2026-02-20 18:11:58 -08:00
Ken Sternberg
6e91af6dfc web/flow: refactor flow executor so component selection is in an easy-to-maintain table (#19999)
* web: Add InvalidationFlow to Radius Provider dialogues

## What

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

## Note

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

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

\# What

\# Why

\# How

\# Designs

\# Test Steps

\# Other Notes

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

This reverts commit dddde09be5.

* website: fix bad escaping of URLs in release notes

## What

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

v2024.6.4 had entries that looked like this:

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

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

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

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

## Notes

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

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

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

* web/flow: refactor FlowExecutor so that client-side stage selection is separate from stage execution

# What

Extracts and normalizes the *massive* switch/case statement into a table, eliminating as much repetition as possible. Where the server-side stage token and the client-side component have the same tag, only one is required. There were three different patterns for prop definitions, and those have been regularized into an expression with a compile-time type check, and the most common one can be omitted from the stage definition table.

# Why

1.  Because it’s hella cleaner. Stages are clear and easy to spot in the table (especially when it’s alphabetically ordered, OMG). Stages that disagree in name with their components, stages that take props different from the “standard” set, and stages that need `import` statements, are all easy to identify.
2.  Because identifying what we *do* with our web components is critical to their success, and to the success of the styling system the authentik web team envisions. FlowExecutor provides selection and execution of stages, but it also provides the inspector, the locale selector, headers, footers, customizations, and branding. Clearing away clutter to make that easier to see makes future refactoring for compatibility mode and dark theme handling much easier.

* web/flow: clean up state representation in FlowExecutor (#20027)

* web/flow: clean up state representation in FlowExecutor

# What

Cleans up the state and lifecycle of FlowExecutor.

*As state lifecycle*, the two fields `challenge` and `flowInfo` are synonymous: they are modified at the same time, once in the setter, and once in `updated()`; flowInfo is always a derived consequence of that current challenge. Making `challenge` the property that we are monitoring and `flowInfo` a simple accessor on `challenge` eliminates duplication of state management.

Lit automatically schedules a re-render whenever `challenge` is changed; the `requestUpdate()` is therefore not needed.

With that, the only thing left is where or when to change the document title. That too is moved to `updated()` and happens without checking for need; it does no harm to replace a string with its own value, the performance loss is so small as to be non-existent, it will not confuse the browser or the environment. Eliminating an `if` and reducing the code surface to a pattern check is a win.

FlowExecutor now has only three states: Loading, Challenge Available, and… Inspector? Let’s see what we can do about cleaning these up as well. Loading and Challenge do not seem synonymous: the challenge should not be altered until the fetch is complete, to prevent blank displays.

* web/flow: dedupe the set error flow state (#20029)

* web/flow: dedupe the set error flow state

# What

Extracts the logic for setting the flow state to FlowError.

# Why

It was just duplication. Trying to clean up state management is easier when special state handling is isolated into a single method.

* Protected.

---------

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

---------

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

* Fix types.

* web: Flesh out module driven tag names.

* web/flow: optimize table for type safety

# What

Separate out the “here’s how a stage is defined” from “Here’s how a stage is represented internally.” This gives us a nice central store of where to define how the server-side componentName relates to a client-side customElementName while also guaranteeing that the componenName or supplied customElementName exists and corresponds. Type safety has been preserved system-wide (thanks, @GirlBossRush!)

* Tidy.

* Removed the cache; it's extra code for no benefit whatsoever; the table is constructed ONCE at start-up, there's never going to be a cache hit.  The FlowExecutorStageFactory produces StageMappings (StageMapping[]), which is itself a warehouse of singular server-component -> client-component relationships, fetching the client from the bundle as needed.  The StageMapping only does the fetch once per instance, so (for example) a password failure will reinstantiate a PasswordStage, but it will not fetch it a second time.

* Removed comments about the cache.  Added comments about where to find the FlowExecutor stage table. Moved the import of WebAuthnAuthenticticatorRegisterState from FlowExecutor.ts to FlowExecutorStages.ts; both files are bundled together, so this is a no-op functionally, but it's easier to confirm that StageEntries without import expressions (STageModuleCallbacks) have their stages bundled (pre-imported) if the import statement is in the same file.

* web: Flesh out stage mapping error handling. (#20292)

Co-authored-by: Ken Sternberg <ken@goauthentik.io>

* Restore fallback to use token if neither tag nor import are present.

* Bad check.

---------

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2026-02-20 17:35:33 -08:00
Kofl
d2c06e6f0d website/integrations: gatus: fix config block (#20446)
Update OIDC configuration formatting

The original documentation caused an error with the current gatus version. It has been fixed with the correct formatting.

Signed-off-by: Kofl <thomas@kofler.tk>
2026-02-20 15:12:17 -05:00
dependabot[bot]
934f783bc7 core: bump msgraph-sdk from 1.54.0 to 1.55.0 (#20432)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-20 17:46:18 +01:00
dependabot[bot]
a74ea64431 core: bump aws-cdk-lib from 2.238.0 to 2.239.0 (#20434)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-20 17:46:17 +01:00
dependabot[bot]
462864d57e core: bump constructs from 10.5.0 to 10.5.1 (#20433)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-20 17:45:59 +01:00
dependabot[bot]
3838078761 core: bump goauthentik/fips-python from c272691 to d973c46 in /lifecycle/container (#20437)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-20 17:45:44 +01:00
dependabot[bot]
e0a7d0fe1c core: bump goauthentik/fips-debian from b0917af to 4419749 in /lifecycle/container (#20438)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-20 17:45:35 +01:00
Ken Sternberg
ab981dec86 web/admin/bugfix: Edit Stage not working. Invoking IdentificationStageForm not working (#20429)
* web/admin/bugfix: Edit Stage not working. Invoking IdentificationStageForm not working.

## What

1.  Fix the field being referenced by Flows -\> \[One Flow\] -\> StageBindings -\> \[Edit Stage\] to use the PK for the *stage*, rather than the *binding*.
2.  Added a check in `StrictUnsafe`: if the property is “wrapped” and untyped, treat it as an attribute, not a property.
3.  Edit the `ak-bound-stages-list` target attribute to be an attribute, not a property.

## Why

1.  This looks like a simple typo. To avoid this in the future, *we need tests*.
2.  `ModelForm` uses both a converter and get/set accessors to manage the pk (primary key) of the object it is being invoked to edit: the first because Django primary keys can be either strings or numbers, and the latter because we have special transactional requirements when a primary key changes. Lit’s magic for handling this creates some weirdness around JavaScript prototyping (`wrapped` becomes the only key on the object; all the other keys become delegated to a prototype object), so `hasOwn()` can’t be used; we just have to check for `wrapped` and `!type`.
3.  PKs are either strings or numbers, and ModelForm has a smart converter. There’s no need to shove the values around as properties and, in fact, that’ll break some things because there’s a working `attribute` field on ModelForm! Removing the `.` property marker both avoids this issue and makes visible exactly what item-id is being referenced.

* Forced update of package lock.  AGAIN.

* Sigh

* Sigh. Again.

* Sigh. But this time, with an empty cache.

* Prettier and its opinions.

* Clearing the cache broke relationships inside SFE. That has been updated.

* WTF, over?
2026-02-20 08:44:51 -08:00
dependabot[bot]
d8f78ff653 core: bump ruff from 0.15.1 to 0.15.2 (#20435)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-20 16:43:42 +00:00
Jens L.
055d1302e1 enterprise/providers/microsoft_entra: only check upn when set (#20441)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-02-20 17:13:36 +01:00
dependabot[bot]
3bdf3d6745 core: bump selenium from 4.40.0 to 4.41.0 (#20436)
Bumps [selenium](https://github.com/SeleniumHQ/Selenium) from 4.40.0 to 4.41.0.
- [Release notes](https://github.com/SeleniumHQ/Selenium/releases)
- [Commits](https://github.com/SeleniumHQ/Selenium/commits)

---
updated-dependencies:
- dependency-name: selenium
  dependency-version: 4.41.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-02-20 17:09:40 +01:00
Kofl
f56d3aba31 website/docs: change permission name from 'Can view Admin interface' to 'Can access…' (#20412)
* Update permission name from 'Can view Admin interface' to 'Can access admin interface'

based on the current 2025.12 release

Signed-off-by: Kofl <thomas@kofler.tk>

* Fix other references to old permission name

---------

Signed-off-by: Kofl <thomas@kofler.tk>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-02-20 09:28:22 +00:00
Andreas Brain
a52ecd30b7 website/integrations: add OIDC and update SAML instructions for Zammad (#20421)
* website/integrations: add OIDC method for Zammad

* Minor changes

* Add configuration verification

* Update index.md

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

* Update SAML instructions for Zammad

* wip

---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
2026-02-20 09:27:29 +00:00
kgoode517
05ad1f0363 website/integrations: update wazuh acs url (#20401)
* Update index.mdx

Minor changes for readiblity and updated ui in authentik

Signed-off-by: kgoode517 <Kgoode517@yahoo.com>

* Apply suggestion from @dewi-tik

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

---------

Signed-off-by: kgoode517 <Kgoode517@yahoo.com>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-02-19 22:15:24 +00:00
Teffen Ellis
c35bfe9f04 web: Center footer links. (#20345)
* web: Center footer links.

* Refine track resizing behavior.

* Fix odd scenario.

* Tidy padding.
2026-02-19 18:55:55 +01:00
authentik-automation[bot]
2804fd2d57 core: bump goauthentik.io/api/v3 to 3.2026.5.0-rc1-1771349690 (#20367)
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>
Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
2026-02-19 12:35:14 +00:00
dependabot[bot]
ae803d47d6 ci: bump tj-actions/changed-files from 47.0.3 to 47.0.4 (#20374)
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 47.0.3 to 47.0.4.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](28b28f6e4e...7dee1b0c15)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-version: 47.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>
Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
2026-02-19 12:35:09 +00:00
dependabot[bot]
bc0f4984b5 ci: bump helm/kind-action from 1.13.0 to 1.14.0 (#20375)
Bumps [helm/kind-action](https://github.com/helm/kind-action) from 1.13.0 to 1.14.0.
- [Release notes](https://github.com/helm/kind-action/releases)
- [Commits](92086f6be0...ef37e7f390)

---
updated-dependencies:
- dependency-name: helm/kind-action
  dependency-version: 1.14.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>
Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
2026-02-19 12:35:04 +00:00
dependabot[bot]
122cee049a core: bump library/golang from 1.25.5-trixie to 1.26.0-trixie in /lifecycle/container (#20381)
* core: bump library/golang in /lifecycle/container

Bumps library/golang from 1.25.5-trixie to 1.26.0-trixie.

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

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

* bump & fix

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

* bump docs too

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-02-19 12:35:00 +00:00
dependabot[bot]
ebc06a8453 core: bump goauthentik/fips-debian from 10dadf1 to b0917af in /lifecycle/container (#20382)
core: bump goauthentik/fips-debian in /lifecycle/container

Bumps goauthentik/fips-debian from `10dadf1` to `b0917af`.

---
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-02-19 12:34:56 +00:00
dependabot[bot]
3f6afde8b1 core: bump goauthentik/fips-python from 3.14.2-slim-trixie-fips to 3.14.3-slim-trixie-fips in /lifecycle/container (#20383)
core: bump goauthentik/fips-python in /lifecycle/container

Bumps goauthentik/fips-python from 3.14.2-slim-trixie-fips to 3.14.3-slim-trixie-fips.

---
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-02-19 12:34:52 +00:00
dependabot[bot]
58b8b8b708 core: bump twilio from 9.10.1 to 9.10.2 (#20384)
Bumps [twilio](https://github.com/twilio/twilio-python) from 9.10.1 to 9.10.2.
- [Release notes](https://github.com/twilio/twilio-python/releases)
- [Changelog](https://github.com/twilio/twilio-python/blob/main/CHANGES.md)
- [Commits](https://github.com/twilio/twilio-python/compare/9.10.1...9.10.2)

---
updated-dependencies:
- dependency-name: twilio
  dependency-version: 9.10.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-02-19 12:34:48 +00:00
dependabot[bot]
1132dbef02 core: bump astral-sh/uv from 0.9.18 to 0.10.4 in /lifecycle/container (#20385)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.9.18 to 0.10.4.
- [Release notes](https://github.com/astral-sh/uv/releases)
- [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/uv/compare/0.9.18...0.10.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-19 12:34:44 +00:00
dependabot[bot]
2083177e0c web: bump the storybook group across 1 directory with 5 updates (#20386)
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.9 to 10.2.10
- [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.10/code/addons/docs)

Updates `@storybook/addon-links` from 10.2.9 to 10.2.10
- [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.10/code/addons/links)

Updates `@storybook/web-components` from 10.2.9 to 10.2.10
- [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.10/code/renderers/web-components)

Updates `@storybook/web-components-vite` from 10.2.9 to 10.2.10
- [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.10/code/frameworks/web-components-vite)

Updates `storybook` from 10.2.9 to 10.2.10
- [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.10/code/core)

---
updated-dependencies:
- dependency-name: "@storybook/addon-docs"
  dependency-version: 10.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/addon-links"
  dependency-version: 10.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components"
  dependency-version: 10.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components-vite"
  dependency-version: 10.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: storybook
  dependency-version: 10.2.10
  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-02-19 12:34:40 +00:00
dependabot[bot]
0b164c7f56 web: bump globby from 16.1.0 to 16.1.1 in /web (#20387)
Bumps [globby](https://github.com/sindresorhus/globby) from 16.1.0 to 16.1.1.
- [Release notes](https://github.com/sindresorhus/globby/releases)
- [Commits](https://github.com/sindresorhus/globby/compare/v16.1.0...v16.1.1)

---
updated-dependencies:
- dependency-name: globby
  dependency-version: 16.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-19 12:34:36 +00:00
dependabot[bot]
e87318715a lifecycle/aws: bump aws-cdk from 2.1106.0 to 2.1106.1 in /lifecycle/aws (#20403)
Bumps [aws-cdk](https://github.com/aws/aws-cdk-cli/tree/HEAD/packages/aws-cdk) from 2.1106.0 to 2.1106.1.
- [Release notes](https://github.com/aws/aws-cdk-cli/releases)
- [Commits](https://github.com/aws/aws-cdk-cli/commits/aws-cdk@v2.1106.1/packages/aws-cdk)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-19 12:34:32 +00:00
dependabot[bot]
81887a0b37 core: bump psycopg[c,pool] from 3.3.2 to 3.3.3 (#20404)
Bumps [psycopg[c,pool]](https://github.com/psycopg/psycopg) from 3.3.2 to 3.3.3.
- [Changelog](https://github.com/psycopg/psycopg/blob/master/docs/news.rst)
- [Commits](https://github.com/psycopg/psycopg/compare/3.3.2...3.3.3)

---
updated-dependencies:
- dependency-name: psycopg[c,pool]
  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-02-19 12:34:28 +00:00
dependabot[bot]
924a5af625 web: bump knip from 5.83.1 to 5.84.1 in /web (#20406)
Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 5.83.1 to 5.84.1.
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@5.84.1/packages/knip)

---
updated-dependencies:
- dependency-name: knip
  dependency-version: 5.84.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-02-19 12:34:24 +00:00
dependabot[bot]
c2043f9823 web: bump @types/node from 25.2.3 to 25.3.0 in /web (#20407)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.2.3 to 25.3.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.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-19 12:34:20 +00:00
Ken Sternberg
2eedae7011 web/admin: maintenance: centralize types that are used across stages (#20398)
## What

Re-arranges where types are declared and how they’re accessed. In some cases, refine the types to provide stronger build-time guarantees.

## Why

A lot of our stages use bits and pieces of types from a base state, from the executor, and in some cases from redundant sources.

Union types are great for some things, but Typescript can get really hung up on passing a union type as a function parameter when there’s some low-level non-conformance across all the types in the union. Because OpenAPI doesn’t do abstractions, this commit introduces a `StageChallengeLike`, which abstracts all of the Challenge types, asserting that there is a unifying parent class that contains the minimum collection fields found across all challenges, and this lets us address the different challenge types and their corresponding components before worrying about refining the type for construction and deployment.

On the other hand, sometimes you want to be able to assert that *all* of the member of a union correspond to some shape of data, and you can always use `Pick<>` on the FormChallenge type to assert that at build time.

Other than that, this centralizes the types into locations in the codebase with well-known names.
2026-02-18 13:40:40 -08:00
Kofl
7f92b4249d website/integrations: beszel: remove slug reference (#20393)
Update authentik configuration steps

Removed the noting of 'slug' from the authentik configuration steps, as its not required on the Beszel site

Signed-off-by: Kofl <thomas@kofler.tk>
2026-02-18 20:02:36 +00:00
Ken Sternberg
1df540105a web/admin: maintenance: give dialogs default exports (#20397)
## What

Provide dynamically imported dialogs and forms a default export.

Some minor cleanup of types: `PropertyValues` -\> `PropertyValues<this>` provides stronger type guarantees.

## Why

We define stages and other dynamic processes on the server side using a token, the server-side component name. Client-side elements are instantiated with a constructor and identified with an element tag name.

Dynamically importing components automatically registered the element tag name with the constructor, enabling custom elements to work. Without the default export, the registration goes to the browser but the identity of the component’s underlying constructor is lost. Browsers provide a reverse lookup: given a component’s constructor it can provide the registration tag. By having default exports, we allow dynamic imports to record the constructor, retrieve the tag, and dynamically construct the templates without having to manually maintain the tag/constructor relationship (which is already complicated enough by that server-side component/client-side element relationship).

## Testing

This is purely internal maintenance; it’s about hardening the build, not changing behavior. If it lints and builds cleanly, the only real test is that nothing is borken afterwards.
2026-02-18 18:32:32 +00:00
Teffen Ellis
6a7162fd3b web: Fix element property names with custom attributes. (#20396) 2026-02-18 18:27:56 +00:00
Jens L.
e7ea15c791 enterprise/providers/microsoft_entra: fix dangling comma (#20391)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-02-18 18:37:33 +01:00
Ken Sternberg
d3b69b25f1 web/admin: bug: stage update forms not rendering, several modal form buttons missing (#20373)
## What

Names being passed to the browser were being incorrectly rendered. This commit updates the code in `StrictUnsafe` so that after the correct-use assertion is passed, the elementProperties are checked to see if the attribute has been named differently from the typed attribute field, and if so, retrieves the attribute name and passes it, rather than the field name, to the browser.

## Why

Since we have a lot of components with similar interfaces, it makes sense to try and check that they’re being used correctly and that the types associated with them are correct. Plus Lit, unlike React, doesn’t have a self-erasing syntax: every Lit element *is* an element, whereas JSX is an esoteric function call syntax that happens to look like XML. JavaScript templates aren’t as pretty as JSX, but they get the job done just as readily.

But in this case, cleverness bit us: we want to use the component’s JavaScript field names and types to validate that we’re using it correctly and passing the right types, but in the end we’re constructing a tag that will trigger the browser to construct the component and use it– and the field names don’t always correspond to the attribute name. Lit has a syntax for mapping the one to the other and stores it in the `elementProperties` field.

This code checks that, after we’ve determined the correct prefix for an property field that has been passed into the component, that we’ve also checked and extracted the correct *attribute name* for that property field. Most of the time it will be the same as the property field, but it muts always be checked.
2026-02-18 08:15:58 -08:00
Jens L.
3ca055f3f4 lifecycle: bump rac guacd base image (#20390)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-02-18 16:28:57 +01:00
354 changed files with 22079 additions and 11256 deletions

View File

@@ -22,7 +22,7 @@ runs:
sudo rm -rf /usr/local/lib/android
- name: Install uv
if: ${{ contains(inputs.dependencies, 'python') }}
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v5
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v5
with:
enable-cache: true
- name: Setup python
@@ -44,7 +44,7 @@ runs:
registry-url: 'https://registry.npmjs.org'
- name: Setup go
if: ${{ contains(inputs.dependencies, 'go') }}
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v5
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v5
with:
go-version-file: "go.mod"
- name: Setup docker cache

View File

@@ -72,7 +72,7 @@ jobs:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: "go.mod"
- name: Generate API Clients
@@ -95,7 +95,7 @@ jobs:
platforms: linux/${{ inputs.image_arch }}
cache-from: type=registry,ref=${{ steps.ev.outputs.attestImageNames }}:buildcache-${{ inputs.image_arch }}
cache-to: ${{ steps.ev.outputs.cacheTo }}
- uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v3
id: attest
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
with:

View File

@@ -97,7 +97,7 @@ jobs:
sources: |
${{ steps.ev.outputs.attestImageNames }}@${{ needs.build-server-amd64.outputs.image-digest }}
${{ steps.ev.outputs.attestImageNames }}@${{ needs.build-server-arm64.outputs.image-digest }}
- uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v3
id: attest
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}

View File

@@ -55,7 +55,7 @@ jobs:
env:
NODE_ENV: production
run: npm run build -w api
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v4
with:
name: api-docs
path: website/api/build
@@ -67,7 +67,7 @@ jobs:
- build
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v5
- uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v5
with:
name: api-docs
path: website/api/build

View File

@@ -105,7 +105,7 @@ jobs:
context: .
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-docs:buildcache
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' && 'type=registry,ref=ghcr.io/goauthentik/dev-docs:buildcache,mode=max' || '' }}
- uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v3
id: attest
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
with:

View File

@@ -6,6 +6,10 @@ on:
schedule:
# Every night at 3am
- cron: "0 3 * * *"
pull_request:
paths:
# Needs to refer to itself
- .github/workflows/ci-main-daily.yml
jobs:
test-container:
@@ -15,14 +19,14 @@ jobs:
matrix:
version:
- docs
- version-2025-4
- version-2025-2
- version-2025-12
- version-2025-10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- run: |
current="$(pwd)"
dir="/tmp/authentik/${{ matrix.version }}"
mkdir -p $dir
cd $dir
wget https://${{ matrix.version }}.goauthentik.io/compose.yml
${current}/scripts/test_docker.sh
mkdir -p "${dir}/lifecycle/container"
cd "${dir}"
wget "https://${{ matrix.version }}.goauthentik.io/docker-compose.yml" -O "${dir}/lifecycle/container/compose.yml"
"${current}/scripts/test_docker.sh"

View File

@@ -170,7 +170,7 @@ jobs:
- name: Setup authentik env
uses: ./.github/actions/setup
- name: Create k8s Kind Cluster
uses: helm/kind-action@92086f6be054225fa813e0a4b13787fc9088faab # v1.13.0
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
- name: run integration
run: |
uv run coverage run manage.py test tests/integration
@@ -279,7 +279,7 @@ jobs:
with:
flags: conformance
- if: ${{ !cancelled() }}
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: conformance-certification-${{ matrix.job.name }}
path: tests/openid_conformance/exports/

View File

@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: "go.mod"
- name: Prepare and generate API
@@ -43,7 +43,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: "go.mod"
- name: Setup authentik env
@@ -122,7 +122,7 @@ jobs:
context: .
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-${{ matrix.type }}:buildcache
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' && format('type=registry,ref=ghcr.io/goauthentik/dev-{0}:buildcache,mode=max', matrix.type) || '' }}
- uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v3
id: attest
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
with:
@@ -148,7 +148,7 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: "go.mod"
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v5

View File

@@ -29,6 +29,7 @@ jobs:
- packages/eslint-config
- packages/prettier-config
- packages/docusaurus-config
- packages/logger-js
- packages/esbuild-plugin-live-reload
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
@@ -40,7 +41,7 @@ jobs:
registry-url: "https://registry.npmjs.org"
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@28b28f6e4e9e3d997beb9dce86cfd8cf0ce7c7f6 # 24d32ffd492484c1d75e0c0b894501ddb9d30d62
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # 24d32ffd492484c1d75e0c0b894501ddb9d30d62
with:
files: |
${{ matrix.package }}/package.json

View File

@@ -58,7 +58,7 @@ jobs:
push: true
platforms: linux/amd64,linux/arm64
context: .
- uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v3
id: attest
if: true
with:
@@ -84,7 +84,7 @@ jobs:
- rac
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: "go.mod"
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v5
@@ -129,7 +129,7 @@ jobs:
file: lifecycle/container/${{ matrix.type }}.Dockerfile
platforms: linux/amd64,linux/arm64
context: .
- uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3
- uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v3
id: attest
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
@@ -152,7 +152,7 @@ jobs:
goarch: [amd64, arm64]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: "go.mod"
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v5
@@ -180,7 +180,7 @@ jobs:
export CGO_ENABLED=0
go build -tags=outpost_static_embed -v -o ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }} ./cmd/${{ matrix.type }}
- name: Upload binaries to release
uses: svenstaro/upload-release-action@6b7fa9f267e90b50a19fef07b3596790bb941741 # v2
uses: svenstaro/upload-release-action@b98a3b12e86552593f3e4e577ca8a62aa2f3f22b # v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}

View File

@@ -174,21 +174,25 @@ jobs:
if: "${{ inputs.release_reason == 'feature' }}"
run: |
changelog_url="https://docs.goauthentik.io/docs/releases/${{ needs.check-inputs.outputs.major_version }}"
reason="{{ inputs.release_reason }}"
jq \
--arg version "${{ inputs.version }}" \
--arg changelog "See ${changelog_url}" \
--arg changelog_url "${changelog_url}" \
'.stable.version = $version | .stable.changelog = $changelog | .stable.changelog_url = $changelog_url' version.json > version.new.json
--arg reason "${reason}" \
'.stable.version = $version | .stable.changelog = $changelog | .stable.changelog_url = $changelog_url | .stable.reason = $reason' version.json > version.new.json
mv version.new.json version.json
- name: Bump version
if: "${{ inputs.release_reason != 'feature' }}"
run: |
changelog_url="https://docs.goauthentik.io/docs/releases/${{ needs.check-inputs.outputs.major_version }}#fixed-in-$(echo -n ${{ inputs.version}} | sed 's/\.//g')"
reason="{{ inputs.release_reason }}"
jq \
--arg version "${{ inputs.version }}" \
--arg changelog "See ${changelog_url}" \
--arg changelog_url "${changelog_url}" \
'.stable.version = $version | .stable.changelog = $changelog | .stable.changelog_url = $changelog_url' version.json > version.new.json
--arg reason "${reason}" \
'.stable.version = $version | .stable.changelog = $changelog | .stable.changelog_url = $changelog_url | .stable.reason = $reason' version.json > version.new.json
mv version.new.json version.json
- name: Create pull request
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7

View File

@@ -34,6 +34,7 @@ packages/docusaurus-config @goauthentik/frontend
packages/esbuild-plugin-live-reload @goauthentik/frontend
packages/eslint-config @goauthentik/frontend
packages/prettier-config @goauthentik/frontend
packages/logger-js @goauthentik/frontend
packages/tsconfig @goauthentik/frontend
# Web
web/ @goauthentik/frontend

View File

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

View File

@@ -20,8 +20,8 @@ Even if the issue is not a CVE, we still greatly appreciate your help in hardeni
| Version | Supported |
| ---------- | ---------- |
| 2025.10.x | ✅ |
| 2025.12.x | ✅ |
| 2026.2.x | ✅ |
## Reporting a Vulnerability

View File

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

View File

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

View File

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

View File

@@ -78,7 +78,7 @@ def generate_key_id_legacy(key_data: str) -> str:
"""Generate Key ID using MD5 (legacy format for backwards compatibility)."""
if not key_data:
return ""
return md5(key_data.encode("utf-8")).hexdigest() # nosec
return md5(key_data.encode("utf-8"), usedforsecurity=False).hexdigest() # nosec
class CertificateKeyPair(SerializerModel, ManagedModel, CreatedUpdatedModel):

View File

@@ -1,5 +1,6 @@
from hashlib import sha256
from json import loads
from unittest.mock import PropertyMock, patch
from django.urls import reverse
from jwt import encode
@@ -232,3 +233,43 @@ class TestEndpointStage(FlowTestCase):
plan = plan()
self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context)
self.assertEqual(plan.context[PLAN_CONTEXT_DEVICE], self.device)
def test_endpoint_stage_connector_no_stage_optional(self):
flow = create_test_flow()
stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.OPTIONAL)
FlowStageBinding.objects.create(stage=stage, target=flow, order=0)
with patch(
"authentik.endpoints.connectors.agent.models.AgentConnector.stage",
PropertyMock(return_value=None),
):
with self.assertFlowFinishes() as plan:
res = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
self.assertStageRedirects(res, reverse("authentik_core:root-redirect"))
plan = plan()
self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context)
self.assertNotIn(PLAN_CONTEXT_DEVICE, plan.context)
def test_endpoint_stage_connector_no_stage_required(self):
flow = create_test_flow()
stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.REQUIRED)
FlowStageBinding.objects.create(stage=stage, target=flow, order=0)
with patch(
"authentik.endpoints.connectors.agent.models.AgentConnector.stage",
PropertyMock(return_value=None),
):
with self.assertFlowFinishes() as plan:
res = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
self.assertStageResponse(
res,
component="ak-stage-access-denied",
error_message="Invalid stage configuration",
)
plan = plan()
self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context)
self.assertNotIn(PLAN_CONTEXT_DEVICE, plan.context)

View File

@@ -1,4 +1,4 @@
from authentik.endpoints.models import EndpointStage
from authentik.endpoints.models import EndpointStage, StageMode
from authentik.flows.stage import StageView
PLAN_CONTEXT_ENDPOINT_CONNECTOR = "endpoint_connector"
@@ -6,15 +6,24 @@ PLAN_CONTEXT_ENDPOINT_CONNECTOR = "endpoint_connector"
class EndpointStageView(StageView):
def _get_inner(self):
def _get_inner(self) -> StageView | None:
stage: EndpointStage = self.executor.current_stage
inner_stage: type[StageView] | None = stage.connector.stage
if not inner_stage:
return self.executor.stage_ok()
return None
return inner_stage(self.executor, request=self.request)
def dispatch(self, request, *args, **kwargs):
return self._get_inner().dispatch(request, *args, **kwargs)
inner = self._get_inner()
if inner is None:
stage: EndpointStage = self.executor.current_stage
if stage.mode == StageMode.OPTIONAL:
return self.executor.stage_ok()
else:
return self.executor.stage_invalid("Invalid stage configuration")
return inner.dispatch(request, *args, **kwargs)
def cleanup(self):
return self._get_inner().cleanup()
inner = self._get_inner()
if inner is not None:
return inner.cleanup()

View File

@@ -15,6 +15,7 @@ from django.core.cache import cache
from django.db.models.query import QuerySet
from django.utils.timezone import now
from jwt import PyJWTError, decode, get_unverified_header
from jwt.algorithms import ECAlgorithm
from rest_framework.exceptions import ValidationError
from rest_framework.fields import (
ChoiceField,
@@ -109,13 +110,20 @@ class LicenseKey:
intermediate.verify_directly_issued_by(get_licensing_key())
except InvalidSignature, TypeError, ValueError, Error:
raise ValidationError("Unable to verify license") from None
_validate_curve_original = ECAlgorithm._validate_curve
try:
# authentik's license are generated with `algorithm="ES512"` and signed with
# a key of curve `secp384r1`. Starting with version 2.11.0, pyjwt enforces the spec, see
# https://github.com/jpadilla/pyjwt/commit/5b8622773358e56d3d3c0a9acf404809ff34433a
# authentik will change its license generation to `algorithm="ES384"` in 2026.
# TODO: remove this when the last incompatible license runs out.
ECAlgorithm._validate_curve = lambda *_: True
body = from_dict(
LicenseKey,
decode(
jwt,
our_cert.public_key(),
algorithms=["ES512"],
algorithms=["ES384", "ES512"],
audience=get_license_aud(),
options={"verify_exp": check_expiry, "verify_signature": check_expiry},
),
@@ -125,6 +133,8 @@ class LicenseKey:
if unverified["aud"] != get_license_aud():
raise ValidationError("Invalid Install ID in license") from None
raise ValidationError("Unable to verify license") from None
finally:
ECAlgorithm._validate_curve = _validate_curve_original
return body
@staticmethod

View File

@@ -1,11 +1,11 @@
from datetime import date
from datetime import datetime
from django.db.models import BooleanField as ModelBooleanField
from django.db.models import Case, Q, Value, When
from django_filters.rest_framework import BooleanFilter, FilterSet
from drf_spectacular.utils import extend_schema, extend_schema_field
from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action
from rest_framework.fields import DateField, IntegerField, SerializerMethodField
from rest_framework.fields import IntegerField, SerializerMethodField
from rest_framework.mixins import CreateModelMixin
from rest_framework.request import Request
from rest_framework.response import Response
@@ -21,6 +21,7 @@ from authentik.enterprise.lifecycle.utils import (
ReviewerUserSerializer,
admin_link_for_model,
parse_content_type,
start_of_day,
)
from authentik.lib.utils.time import timedelta_from_string
@@ -67,13 +68,13 @@ class LifecycleIterationSerializer(EnterpriseRequiredMixin, ModelSerializer):
def get_object_admin_url(self, iteration: LifecycleIteration) -> str:
return admin_link_for_model(iteration.object)
@extend_schema_field(DateField())
def get_grace_period_end(self, iteration: LifecycleIteration) -> date:
return iteration.opened_on + timedelta_from_string(iteration.rule.grace_period)
def get_grace_period_end(self, iteration: LifecycleIteration) -> datetime:
return start_of_day(
iteration.opened_on + timedelta_from_string(iteration.rule.grace_period)
)
@extend_schema_field(DateField())
def get_next_review_date(self, iteration: LifecycleIteration):
return iteration.opened_on + timedelta_from_string(iteration.rule.interval)
def get_next_review_date(self, iteration: LifecycleIteration) -> datetime:
return start_of_day(iteration.opened_on + timedelta_from_string(iteration.rule.interval))
def get_user_can_review(self, iteration: LifecycleIteration) -> bool:
return iteration.user_can_review(self.context["request"].user)

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.11 on 2026-02-13 09:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_lifecycle", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="lifecycleiteration",
name="opened_on",
field=models.DateTimeField(auto_now_add=True),
),
]

View File

@@ -1,3 +1,4 @@
from datetime import timedelta
from uuid import uuid4
from django.contrib.contenttypes.fields import GenericForeignKey
@@ -13,7 +14,7 @@ from rest_framework.serializers import BaseSerializer
from authentik.blueprints.models import ManagedModel
from authentik.core.models import Group, User
from authentik.enterprise.lifecycle.utils import link_for_model
from authentik.enterprise.lifecycle.utils import link_for_model, start_of_day
from authentik.events.models import Event, EventAction, NotificationSeverity, NotificationTransport
from authentik.lib.models import SerializerModel
from authentik.lib.utils.time import timedelta_from_string, timedelta_string_validator
@@ -98,7 +99,9 @@ class LifecycleRule(SerializerModel):
def _get_newly_overdue_iterations(self) -> QuerySet[LifecycleIteration]:
return self.lifecycleiteration_set.filter(
opened_on__lte=timezone.now() - timedelta_from_string(self.grace_period),
opened_on__lt=start_of_day(
timezone.now() + timedelta(days=1) - timedelta_from_string(self.grace_period)
),
state=ReviewState.PENDING,
)
@@ -106,7 +109,9 @@ class LifecycleRule(SerializerModel):
recent_iteration_ids = LifecycleIteration.objects.filter(
content_type=self.content_type,
object_id__isnull=False,
opened_on__gte=timezone.now() - timedelta_from_string(self.interval),
opened_on__gte=start_of_day(
timezone.now() + timedelta(days=1) - timedelta_from_string(self.interval)
),
).values_list(Cast("object_id", output_field=self._get_pk_field()), flat=True)
return self.get_objects().exclude(pk__in=recent_iteration_ids)
@@ -186,7 +191,7 @@ class LifecycleIteration(SerializerModel, ManagedModel):
rule = models.ForeignKey(LifecycleRule, null=True, on_delete=models.SET_NULL)
state = models.CharField(max_length=10, choices=ReviewState, default=ReviewState.PENDING)
opened_on = models.DateField(auto_now_add=True)
opened_on = models.DateTimeField(auto_now_add=True)
class Meta:
indexes = [models.Index(fields=["content_type", "opened_on"])]

View File

@@ -1,3 +1,4 @@
import datetime as dt
from datetime import timedelta
from unittest.mock import patch
@@ -319,7 +320,7 @@ class TestLifecycleModels(TestCase):
content_type=content_type, object_id=str(app_one.pk), rule=rule_overdue
)
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=(timezone.now().date() - timedelta(days=20))
opened_on=(timezone.now() - timedelta(days=20))
)
# Apply again to trigger overdue logic
@@ -383,7 +384,7 @@ class TestLifecycleModels(TestCase):
content_type=content_type, object_id=str(app_overdue.pk), rule=rule_overdue
)
LifecycleIteration.objects.filter(pk=overdue_iteration.pk).update(
opened_on=(timezone.now().date() - timedelta(days=20))
opened_on=(timezone.now() - timedelta(days=20))
)
# Apply overdue rule to mark iteration as overdue
@@ -667,3 +668,178 @@ class TestLifecycleModels(TestCase):
reviewers = list(rule.get_reviewers())
self.assertIn(explicit_reviewer, reviewers)
self.assertIn(group_member, reviewers)
class TestLifecycleDateBoundaries(TestCase):
"""Verify that start_of_day normalization ensures correct overdue/due
detection regardless of exact task execution time within a day.
The daily task may run at any point during the day. The start_of_day
normalization in _get_newly_overdue_iterations and _get_newly_due_objects
ensures that the boundary is always at midnight, so millisecond variations
in task execution time do not affect results."""
def _create_rule_and_iteration(self, grace_period="days=1", interval="days=365"):
app = Application.objects.create(name=generate_id(), slug=generate_id())
content_type = ContentType.objects.get_for_model(Application)
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=content_type,
object_id=str(app.pk),
interval=interval,
grace_period=grace_period,
)
iteration = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(app.pk), rule=rule
)
return app, rule, iteration
def test_overdue_iteration_opened_yesterday(self):
"""grace_period=1 day: iteration opened yesterday at any time is overdue today."""
_, rule, iteration = self._create_rule_and_iteration(grace_period="days=1")
fixed_now = dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC)
for opened_on in [
dt.datetime(2025, 6, 14, 0, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 14, 12, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 14, 23, 59, 59, 999999, tzinfo=dt.UTC),
]:
with self.subTest(opened_on=opened_on):
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=opened_on, state=ReviewState.PENDING
)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertIn(iteration, list(rule._get_newly_overdue_iterations()))
def test_not_overdue_iteration_opened_today(self):
"""grace_period=1 day: iteration opened today at any time is NOT overdue."""
_, rule, iteration = self._create_rule_and_iteration(grace_period="days=1")
fixed_now = dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC)
for opened_on in [
dt.datetime(2025, 6, 15, 0, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 23, 59, 59, 999999, tzinfo=dt.UTC),
]:
with self.subTest(opened_on=opened_on):
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=opened_on, state=ReviewState.PENDING
)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertNotIn(iteration, list(rule._get_newly_overdue_iterations()))
def test_overdue_independent_of_task_execution_time(self):
"""Overdue detection gives the same result whether the task runs at 00:00:01 or 23:59:59."""
_, rule, iteration = self._create_rule_and_iteration(grace_period="days=1")
opened_on = dt.datetime(2025, 6, 14, 18, 0, 0, tzinfo=dt.UTC)
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=opened_on, state=ReviewState.PENDING
)
for task_time in [
dt.datetime(2025, 6, 15, 0, 0, 1, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 12, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 23, 59, 59, tzinfo=dt.UTC),
]:
with self.subTest(task_time=task_time):
with patch("django.utils.timezone.now", return_value=task_time):
self.assertIn(iteration, list(rule._get_newly_overdue_iterations()))
def test_overdue_boundary_multi_day_grace_period(self):
"""grace_period=30 days: overdue after 30 full days, not after 29."""
_, rule, iteration = self._create_rule_and_iteration(grace_period="days=30")
fixed_now = dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC)
# Opened 30 days ago (May 16), should go overdue
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=dt.datetime(2025, 5, 16, 12, 0, 0, tzinfo=dt.UTC),
state=ReviewState.PENDING,
)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertIn(iteration, list(rule._get_newly_overdue_iterations()))
# Opened 29 days ago (May 17), should NOT go overdue
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=dt.datetime(2025, 5, 17, 12, 0, 0, tzinfo=dt.UTC),
state=ReviewState.PENDING,
)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertNotIn(iteration, list(rule._get_newly_overdue_iterations()))
def test_due_object_iteration_opened_yesterday(self):
"""interval=1 day: object with iteration opened yesterday is due for a new review."""
app, rule, iteration = self._create_rule_and_iteration(interval="days=1")
fixed_now = dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC)
for opened_on in [
dt.datetime(2025, 6, 14, 0, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 14, 12, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 14, 23, 59, 59, 999999, tzinfo=dt.UTC),
]:
with self.subTest(opened_on=opened_on):
LifecycleIteration.objects.filter(pk=iteration.pk).update(opened_on=opened_on)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertIn(app, list(rule._get_newly_due_objects()))
def test_not_due_object_iteration_opened_today(self):
"""interval=1 day: object with iteration opened today is NOT due."""
app, rule, iteration = self._create_rule_and_iteration(interval="days=1")
fixed_now = dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC)
for opened_on in [
dt.datetime(2025, 6, 15, 0, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 23, 59, 59, 999999, tzinfo=dt.UTC),
]:
with self.subTest(opened_on=opened_on):
LifecycleIteration.objects.filter(pk=iteration.pk).update(opened_on=opened_on)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertNotIn(app, list(rule._get_newly_due_objects()))
def test_due_independent_of_task_execution_time(self):
"""Due detection gives the same result whether the task runs at 00:00:01 or 23:59:59."""
app, rule, iteration = self._create_rule_and_iteration(interval="days=1")
opened_on = dt.datetime(2025, 6, 14, 18, 0, 0, tzinfo=dt.UTC)
LifecycleIteration.objects.filter(pk=iteration.pk).update(opened_on=opened_on)
for task_time in [
dt.datetime(2025, 6, 15, 0, 0, 1, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 12, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 23, 59, 59, tzinfo=dt.UTC),
]:
with self.subTest(task_time=task_time):
with patch("django.utils.timezone.now", return_value=task_time):
self.assertIn(app, list(rule._get_newly_due_objects()))
def test_due_boundary_multi_day_interval(self):
"""interval=30 days: due after 30 full days, not after 29."""
app, rule, iteration = self._create_rule_and_iteration(interval="days=30")
fixed_now = dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC)
# Previous review opened 30 days ago (May 16), review is due for the object
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=dt.datetime(2025, 5, 16, 12, 0, 0, tzinfo=dt.UTC)
)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertIn(app, list(rule._get_newly_due_objects()))
# Previous review opened 29 days ago (May 17), new review is NOT due
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=dt.datetime(2025, 5, 17, 12, 0, 0, tzinfo=dt.UTC)
)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertNotIn(app, list(rule._get_newly_due_objects()))
def test_apply_overdue_at_boundary(self):
"""apply() marks iteration overdue when grace period just expired,
regardless of what time the daily task runs."""
_, rule, iteration = self._create_rule_and_iteration(
grace_period="days=1", interval="days=365"
)
opened_on = dt.datetime(2025, 6, 14, 20, 0, 0, tzinfo=dt.UTC)
for task_time in [
dt.datetime(2025, 6, 15, 0, 0, 1, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 23, 59, 59, tzinfo=dt.UTC),
]:
with self.subTest(task_time=task_time):
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=opened_on, state=ReviewState.PENDING
)
with patch("django.utils.timezone.now", return_value=task_time):
rule.apply()
iteration.refresh_from_db()
self.assertEqual(iteration.state, ReviewState.OVERDUE)

View File

@@ -1,3 +1,4 @@
from datetime import datetime
from urllib import parse
from django.contrib.contenttypes.models import ContentType
@@ -39,6 +40,10 @@ def link_for_model(model: Model) -> str:
return f"{reverse("authentik_core:if-admin")}#{admin_link_for_model(model)}"
def start_of_day(dt: datetime) -> datetime:
return dt.replace(hour=0, minute=0, second=0, microsecond=0)
class ContentTypeField(ChoiceField):
def __init__(self, **kwargs):
super().__init__(choices=model_choices(), **kwargs)

View File

@@ -78,7 +78,8 @@ class MicrosoftEntraUserClient(MicrosoftEntraSyncClient[User, MicrosoftEntraProv
def create(self, user: User):
"""Create user from scratch and create a connection object"""
microsoft_user = self.to_schema(user, None)
self.check_email_valid(microsoft_user.user_principal_name)
if microsoft_user.user_principal_name:
self.check_email_valid(microsoft_user.user_principal_name)
with transaction.atomic():
try:
response = self._request(self.client.users.post(microsoft_user))
@@ -118,7 +119,8 @@ class MicrosoftEntraUserClient(MicrosoftEntraSyncClient[User, MicrosoftEntraProv
def update(self, user: User, connection: MicrosoftEntraProviderUser):
"""Update existing user"""
microsoft_user = self.to_schema(user, connection)
self.check_email_valid(microsoft_user.user_principal_name)
if microsoft_user.user_principal_name:
self.check_email_valid(microsoft_user.user_principal_name)
response = self._request(
self.client.users.by_user_id(connection.microsoft_id).patch(microsoft_user)
)

View File

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

View File

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

View File

@@ -141,6 +141,10 @@ web:
# workers: 2
threads: 4
path: /
timeout_http_read_header: 5s
timeout_http_read: 30s
timeout_http_write: 60s
timeout_http_idle: 120s
worker:
processes: 1
@@ -162,6 +166,7 @@ storage:
# region: "us-east-1"
# use_ssl: True
# endpoint: "https://s3.us-east-1.amazonaws.com"
# signature_version: "s3v4"
# access_key: ""
# secret_key: ""
# bucket_name: "authentik-data"

View File

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

View File

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

View File

@@ -132,9 +132,14 @@ class PolicyEngine:
# If we didn't find any static bindings, do nothing
return
self.logger.debug("P_ENG: Found static bindings", **matched_bindings)
if matched_bindings.get("passing", 0) > 0:
# Any passing static binding -> passing
passing = True
if self.mode == PolicyEngineMode.MODE_ANY:
if matched_bindings.get("passing", 0) > 0:
# Any passing static binding -> passing
passing = True
elif self.mode == PolicyEngineMode.MODE_ALL:
if matched_bindings.get("passing", 0) == matched_bindings["total"]:
# All static bindings are passing -> passing
passing = True
elif matched_bindings["total"] > 0 and matched_bindings.get("passing", 0) < 1:
# No matching static bindings but at least one is configured -> not passing
passing = False
@@ -185,6 +190,16 @@ class PolicyEngine:
# Only call .recv() if no result is saved, otherwise we just deadlock here
if not proc_info.result:
proc_info.result = proc_info.connection.recv()
if proc_info.result and proc_info.result._exec_time:
HIST_POLICIES_EXECUTION_TIME.labels(
binding_order=proc_info.binding.order,
binding_target_type=proc_info.binding.target_type,
binding_target_name=proc_info.binding.target_name,
object_type=(
class_to_path(self.request.obj.__class__) if self.request.obj else ""
),
mode="execute_process",
).observe(proc_info.result._exec_time)
return self
@property

View File

@@ -2,6 +2,7 @@
from multiprocessing import get_context
from multiprocessing.connection import Connection
from time import perf_counter
from django.core.cache import cache
from sentry_sdk import start_span
@@ -11,8 +12,6 @@ from structlog.stdlib import get_logger
from authentik.events.models import Event, EventAction
from authentik.lib.config import CONFIG
from authentik.lib.utils.errors import exception_to_dict
from authentik.lib.utils.reflection import class_to_path
from authentik.policies.apps import HIST_POLICIES_EXECUTION_TIME
from authentik.policies.exceptions import PolicyException
from authentik.policies.models import PolicyBinding
from authentik.policies.types import CACHE_PREFIX, PolicyRequest, PolicyResult
@@ -123,18 +122,9 @@ class PolicyProcess(PROCESS_CLASS):
def profiling_wrapper(self):
"""Run with profiling enabled"""
with (
start_span(
op="authentik.policy.process.execute",
) as span,
HIST_POLICIES_EXECUTION_TIME.labels(
binding_order=self.binding.order,
binding_target_type=self.binding.target_type,
binding_target_name=self.binding.target_name,
object_type=class_to_path(self.request.obj.__class__) if self.request.obj else "",
mode="execute_process",
).time(),
):
with start_span(
op="authentik.policy.process.execute",
) as span:
span: Span
span.set_data("policy", self.binding.policy)
span.set_data("request", self.request)
@@ -142,8 +132,14 @@ class PolicyProcess(PROCESS_CLASS):
def run(self): # pragma: no cover
"""Task wrapper to run policy checking"""
result = None
try:
self.connection.send(self.profiling_wrapper())
start = perf_counter()
result = self.profiling_wrapper()
end = perf_counter()
result._exec_time = max((end - start), 0)
except Exception as exc: # noqa
LOGGER.warning("Policy failed to run", exc=exc)
self.connection.send(PolicyResult(False, str(exc)))
result = PolicyResult(False, str(exc))
finally:
self.connection.send(result)

View File

@@ -33,6 +33,9 @@ class TestPolicyEngine(TestCase):
self.policy_raises = ExpressionPolicy.objects.create(
name=generate_id(), expression="{{ 0/0 }}"
)
self.group_member = Group.objects.create(name=generate_id())
self.user.groups.add(self.group_member)
self.group_non_member = Group.objects.create(name=generate_id())
def test_engine_empty(self):
"""Ensure empty policy list passes"""
@@ -51,7 +54,7 @@ class TestPolicyEngine(TestCase):
self.assertEqual(result.passing, True)
self.assertEqual(result.messages, ("dummy",))
def test_engine_mode_all(self):
def test_engine_mode_all_dyn(self):
"""Ensure all policies passes with AND mode (false and true -> false)"""
pbm = PolicyBindingModel.objects.create(policy_engine_mode=PolicyEngineMode.MODE_ALL)
PolicyBinding.objects.create(target=pbm, policy=self.policy_false, order=0)
@@ -67,7 +70,7 @@ class TestPolicyEngine(TestCase):
),
)
def test_engine_mode_any(self):
def test_engine_mode_any_dyn(self):
"""Ensure all policies passes with OR mode (false and true -> true)"""
pbm = PolicyBindingModel.objects.create(policy_engine_mode=PolicyEngineMode.MODE_ANY)
PolicyBinding.objects.create(target=pbm, policy=self.policy_false, order=0)
@@ -83,6 +86,26 @@ class TestPolicyEngine(TestCase):
),
)
def test_engine_mode_all_static(self):
"""Ensure all policies passes with OR mode (false and true -> true)"""
pbm = PolicyBindingModel.objects.create(policy_engine_mode=PolicyEngineMode.MODE_ALL)
PolicyBinding.objects.create(target=pbm, group=self.group_member, order=0)
PolicyBinding.objects.create(target=pbm, group=self.group_non_member, order=1)
engine = PolicyEngine(pbm, self.user)
result = engine.build().result
self.assertEqual(result.passing, False)
self.assertEqual(result.messages, ())
def test_engine_mode_any_static(self):
"""Ensure all policies passes with OR mode (false and true -> true)"""
pbm = PolicyBindingModel.objects.create(policy_engine_mode=PolicyEngineMode.MODE_ANY)
PolicyBinding.objects.create(target=pbm, group=self.group_member, order=0)
PolicyBinding.objects.create(target=pbm, group=self.group_non_member, order=1)
engine = PolicyEngine(pbm, self.user)
result = engine.build().result
self.assertEqual(result.passing, True)
self.assertEqual(result.messages, ())
def test_engine_negate(self):
"""Test negate flag"""
pbm = PolicyBindingModel.objects.create()

View File

@@ -77,6 +77,8 @@ class PolicyResult:
log_messages: list[LogEvent] | None
_exec_time: int | None
def __init__(self, passing: bool, *messages: str):
self.passing = passing
self.messages = messages
@@ -84,6 +86,7 @@ class PolicyResult:
self.source_binding = None
self.source_results = []
self.log_messages = []
self._exec_time = None
def __repr__(self):
return self.__str__()

View File

@@ -68,6 +68,8 @@ class IDToken:
at_hash: str | None = None
# Session ID, https://openid.net/specs/openid-connect-frontchannel-1_0.html#ClaimsContents
sid: str | None = None
# JWT ID, https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.7
jti: str | None = None
claims: dict[str, Any] = field(default_factory=dict)
@@ -81,6 +83,7 @@ class IDToken:
(token.expires if token.expires is not None else default_token_duration()).timestamp()
)
id_token.iss = provider.get_issuer(request)
id_token.jti = generate_id()
id_token.aud = provider.client_id
id_token.claims = {}

View File

@@ -5,6 +5,7 @@ from urllib.parse import parse_qs, urlparse
from django.test import RequestFactory
from django.urls import reverse
from django.utils import translation
from django.utils.timezone import now
from authentik.blueprints.tests import apply_blueprint
@@ -690,18 +691,21 @@ class TestAuthorize(OAuthTestCase):
Application.objects.create(name="app", slug="app", provider=provider)
state = generate_id()
self.client.logout()
response = self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "code",
"client_id": "test",
"state": state,
"redirect_uri": "foo://localhost",
"ui_locales": "invalid fr",
},
)
parsed = parse_qs(urlparse(response.url).query)
self.assertEqual(parsed["locale"], ["fr"])
try:
response = self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "code",
"client_id": "test",
"state": state,
"redirect_uri": "foo://localhost",
"ui_locales": "invalid fr",
},
)
parsed = parse_qs(urlparse(response.url).query)
self.assertEqual(parsed["locale"], ["fr"])
finally:
translation.deactivate()
@apply_blueprint("default/flow-default-authentication-flow.yaml")
def test_ui_locales_invalid(self):

View File

@@ -1,5 +1,6 @@
"""Device backchannel tests"""
from base64 import b64encode
from json import loads
from django.urls import reverse
@@ -26,7 +27,7 @@ class TesOAuth2DeviceBackchannel(OAuthTestCase):
provider=self.provider,
)
def test_backchannel_invalid(self):
def test_backchannel_invalid_client_id_via_post_body(self):
"""Test backchannel"""
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
@@ -50,7 +51,7 @@ class TesOAuth2DeviceBackchannel(OAuthTestCase):
)
self.assertEqual(res.status_code, 400)
def test_backchannel(self):
def test_backchannel_client_id_via_post_body(self):
"""Test backchannel"""
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
@@ -61,3 +62,37 @@ class TesOAuth2DeviceBackchannel(OAuthTestCase):
self.assertEqual(res.status_code, 200)
body = loads(res.content.decode())
self.assertEqual(body["expires_in"], 60)
def test_backchannel_invalid_client_id_via_auth_header(self):
"""Test backchannel"""
creds = b64encode(b"foo:").decode()
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
HTTP_AUTHORIZATION=f"Basic {creds}",
)
self.assertEqual(res.status_code, 400)
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
)
self.assertEqual(res.status_code, 400)
# test without application
self.application.provider = None
self.application.save()
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
data={
"client_id": "test",
},
)
self.assertEqual(res.status_code, 400)
def test_backchannel_client_id_via_auth_header(self):
"""Test backchannel"""
creds = b64encode(f"{self.provider.client_id}:".encode()).decode()
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
HTTP_AUTHORIZATION=f"Basic {creds}",
)
self.assertEqual(res.status_code, 200)
body = loads(res.content.decode())
self.assertEqual(body["expires_in"], 60)

View File

@@ -16,7 +16,7 @@ from authentik.lib.config import CONFIG
from authentik.lib.utils.time import timedelta_from_string
from authentik.providers.oauth2.errors import DeviceCodeError
from authentik.providers.oauth2.models import DeviceToken, OAuth2Provider
from authentik.providers.oauth2.utils import TokenResponse
from authentik.providers.oauth2.utils import TokenResponse, extract_client_auth
from authentik.providers.oauth2.views.device_init import QS_KEY_CODE
LOGGER = get_logger()
@@ -32,7 +32,7 @@ class DeviceView(View):
def parse_request(self):
"""Parse incoming request"""
client_id = self.request.POST.get("client_id", None)
client_id, _ = extract_client_auth(self.request)
if not client_id:
raise DeviceCodeError("invalid_client")
provider = OAuth2Provider.objects.filter(client_id=client_id).first()

View File

@@ -52,10 +52,10 @@ class SCIMApplicationPoliciesTests(TestCase):
email=f"{uid}@goauthentik.io",
)
self.users[1].ak_groups.add(self.group1)
self.users[2].ak_groups.add(self.group2)
self.users[4].ak_groups.add(self.group1)
self.users[4].ak_groups.add(self.group2)
self.users[1].groups.add(self.group1)
self.users[2].groups.add(self.group2)
self.users[4].groups.add(self.group1)
self.users[4].groups.add(self.group2)
def test_no_group_policy(self):
"""Test with no group policy set"""

View File

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

View File

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

View File

@@ -14,24 +14,6 @@ class SAMLException(SentryIgnoredException):
return self.default_message
class MissingSAMLResponse(SAMLException):
"""Exception raised when request does not contain SAML Response."""
default_message = "Request does not contain a SAML response."
class UnsupportedNameIDFormat(SAMLException):
"""Exception raised when SAML Response contains NameID Format not supported."""
default_message = "The NameID Format in the SAML Response is not supported."
class MismatchedRequestID(SAMLException):
"""Exception raised when the returned request ID doesn't match the saved ID."""
default_message = "The SAML Response ID does not match the original request ID."
class InvalidEncryption(SAMLException):
"""Encryption of XML Object is either missing or invalid."""
@@ -42,3 +24,21 @@ class InvalidSignature(SAMLException):
"""Signature of XML Object is either missing or invalid."""
default_message = "The signature of the SAML object is either missing or invalid."
class MismatchedRequestID(SAMLException):
"""Exception raised when the returned request ID doesn't match the saved ID."""
default_message = "The SAML Response ID does not match the original request ID."
class MissingSAMLResponse(SAMLException):
"""Exception raised when request does not contain SAML Response."""
default_message = "Request does not contain a SAML response."
class UnsupportedNameIDFormat(SAMLException):
"""Exception raised when SAML Response contains NameID Format not supported."""
default_message = "The NameID Format in the SAML Response is not supported."

View File

@@ -4,6 +4,7 @@ from urllib.parse import parse_qsl, urlparse, urlunparse
from django.contrib.auth import logout
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import SuspiciousOperation
from django.http import Http404, HttpRequest, HttpResponse
from django.http.response import HttpResponseBadRequest
from django.shortcuts import get_object_or_404, redirect
@@ -37,7 +38,9 @@ from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSI
from authentik.lib.views import bad_request_message
from authentik.providers.saml.utils.encoding import nice64
from authentik.sources.saml.exceptions import (
InvalidEncryption,
InvalidSignature,
MismatchedRequestID,
MissingSAMLResponse,
UnsupportedNameIDFormat,
)
@@ -156,7 +159,15 @@ class ACSView(View):
processor = ResponseProcessor(source, request)
try:
processor.parse()
except (InvalidSignature, MissingSAMLResponse, VerificationError, ValueError) as exc:
except (
InvalidEncryption,
InvalidSignature,
MismatchedRequestID,
MissingSAMLResponse,
SuspiciousOperation,
VerificationError,
ValueError,
) as exc:
return bad_request_message(request, str(exc))
try:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,6 +6,7 @@ from django.contrib.auth.views import redirect_to_login
from django.http.request import HttpRequest
from structlog.stdlib import get_logger
from authentik.core.middleware import get_user
from authentik.core.models import Session
from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR
from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR
@@ -54,11 +55,13 @@ class SessionBindingBroken(SentryIgnoredException):
def logout_extra(request: HttpRequest, exc: SessionBindingBroken):
"""Similar to django's logout method, but able to carry more info to the signal"""
# Dispatch the signal before the user is logged out so the receivers have a
# chance to find out *who* logged out.
user = getattr(request, "user", None)
# Since this middleware runs before the AuthenticationMiddleware, we can't use `request.user`
# as it hasn't been populated yet.
user = get_user(request)
if not getattr(user, "is_authenticated", True):
user = None
# Dispatch the signal before the user is logged out so the receivers have a
# chance to find out *who* logged out.
user_logged_out.send(
sender=user.__class__, request=request, user=user, event_extra=exc.to_event()
)

View File

@@ -10,6 +10,8 @@ from django.utils.timezone import now
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import AuthenticatedSession, Session
from authentik.core.tests.utils import create_test_flow, create_test_user
from authentik.events.models import Event, EventAction
from authentik.events.utils import get_user
from authentik.flows.markers import StageMarker
from authentik.flows.models import FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
@@ -270,6 +272,7 @@ class TestUserLoginStage(FlowTestCase):
def test_session_binding_broken(self):
"""Test session binding"""
Event.objects.all().delete()
self.client.force_login(self.user)
session = self.client.session
session[Session.Keys.LAST_IP] = "192.0.2.1"
@@ -285,3 +288,5 @@ class TestUserLoginStage(FlowTestCase):
)
+ f"?{NEXT_ARG_NAME}={reverse("authentik_api:user-me")}",
)
event = Event.objects.filter(action=EventAction.LOGOUT).first()
self.assertEqual(event.user, get_user(self.user))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -29,7 +29,7 @@ entries:
password=request.user.password
)
# ...otherwise we set an immutable ID based on the user's UID
user["on_premises_immutable_id"] = request.user.uid,
user["on_premises_immutable_id"] = request.user.uid
return user
- identifiers:
managed: goauthentik.io/providers/microsoft_entra/group

6
go.mod
View File

@@ -1,13 +1,13 @@
module goauthentik.io
go 1.25.5
go 1.26.0
require (
beryju.io/ldap v0.1.0
beryju.io/radius-eap v0.1.0
github.com/avast/retry-go/v4 v4.7.0
github.com/coreos/go-oidc/v3 v3.17.0
github.com/getsentry/sentry-go v0.42.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-openapi/runtime v0.29.2
@@ -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.20260213141435-0db2228fbd47
goauthentik.io/api/v3 v3.2026020.17-0.20260223141659-4c1444ee54d9
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.35.0
golang.org/x/sync v0.19.0

16
go.sum
View File

@@ -20,8 +20,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/getsentry/sentry-go v0.42.0 h1:eeFMACuZTbUQf90RE8dE4tXeSe4CZyfvR1MBL7RLEt8=
github.com/getsentry/sentry-go v0.42.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s=
github.com/getsentry/sentry-go v0.43.0 h1:XbXLpFicpo8HmBDaInk7dum18G9KSLcjZiyUKS+hLW4=
github.com/getsentry/sentry-go v0.43.0/go.mod h1:XDotiNZbgf5U8bPDUAfvcFmOnMQQceESxyKaObSssW0=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
@@ -214,14 +214,10 @@ 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.20260210174940-ae049de99535 h1:DPk8z6SGesp0gbmaD2zTAKVSd/NQ++Nu+lu3UrCkNvE=
goauthentik.io/api/v3 v3.2026020.17-0.20260210174940-ae049de99535/go.mod h1:uYa+yGMglhJy8ymyUQ8KQiJjOb3UZTuPQ24Ot2s9BCo=
goauthentik.io/api/v3 v3.2026020.17-0.20260211005401-cdd71ec2f62f h1:KK5lBHSvZSlMbUViB7KStlkP9kC1t9JeiMawa7wyI6Q=
goauthentik.io/api/v3 v3.2026020.17-0.20260211005401-cdd71ec2f62f/go.mod h1:uYa+yGMglhJy8ymyUQ8KQiJjOb3UZTuPQ24Ot2s9BCo=
goauthentik.io/api/v3 v3.2026020.17-0.20260211204352-035cbbe57393 h1:eLRd2GC+pxvwd3m2msJRNB9upH7pcIZH5V4L9/WhRcw=
goauthentik.io/api/v3 v3.2026020.17-0.20260211204352-035cbbe57393/go.mod h1:uYa+yGMglhJy8ymyUQ8KQiJjOb3UZTuPQ24Ot2s9BCo=
goauthentik.io/api/v3 v3.2026020.17-0.20260213141435-0db2228fbd47 h1:quNPFsxsMNKICxrJP3dFehxgCvt3Qi9UeV8HzcEk17c=
goauthentik.io/api/v3 v3.2026020.17-0.20260213141435-0db2228fbd47/go.mod h1:uYa+yGMglhJy8ymyUQ8KQiJjOb3UZTuPQ24Ot2s9BCo=
goauthentik.io/api/v3 v3.2026020.17-0.20260217173516-3a500f6eed7d h1:Gb26L41O+Q7l+57wkXI1BaG+lCWRteZ9tlaabjMkb3U=
goauthentik.io/api/v3 v3.2026020.17-0.20260217173516-3a500f6eed7d/go.mod h1:uYa+yGMglhJy8ymyUQ8KQiJjOb3UZTuPQ24Ot2s9BCo=
goauthentik.io/api/v3 v3.2026020.17-0.20260223141659-4c1444ee54d9 h1:tuvgm4e1nV0ZPZy24wOeJcuAbMnhbJA09BuI2fzBHRk=
goauthentik.io/api/v3 v3.2026020.17-0.20260223141659-4c1444ee54d9/go.mod h1:uYa+yGMglhJy8ymyUQ8KQiJjOb3UZTuPQ24Ot2s9BCo=
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

@@ -128,9 +128,9 @@ func (c *Config) fromEnv() error {
return nil
}
func (c *Config) walkScheme(v interface{}) {
func (c *Config) walkScheme(v any) {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
if rv.Kind() != reflect.Pointer || rv.IsNil() {
return
}

View File

@@ -104,7 +104,11 @@ type OutpostConfig struct {
}
type WebConfig struct {
Path string `yaml:"path" env:"PATH, overwrite"`
Path string `yaml:"path" env:"PATH, overwrite"`
TimeoutHttpReadHeader string `yaml:"timeout_http_read_header" env:"TIMEOUT_HTTP_READ_HEADER, overwrite"`
TimeoutHttpRead string `yaml:"timeout_http_read" env:"TIMEOUT_HTTP_READ, overwrite"`
TimeoutHttpWrite string `yaml:"timeout_http_write" env:"TIMEOUT_HTTP_WRITE, overwrite"`
TimeoutHttpIdle string `yaml:"timeout_http_idle" env:"TIMEOUT_HTTP_IDLE, overwrite"`
}
type LogConfig struct {

View File

@@ -204,8 +204,8 @@ func (a *APIController) OnRefresh() error {
return err
}
func (a *APIController) getEventPingArgs() map[string]interface{} {
args := map[string]interface{}{
func (a *APIController) getEventPingArgs() map[string]any {
args := map[string]any{
"version": constants.VERSION(),
"buildHash": constants.BUILD(""),
"uuid": a.instanceUUID.String(),

View File

@@ -186,7 +186,7 @@ func (ac *APIController) startEventHealth() {
time.Sleep(time.Second * 5)
continue
}
err := ac.SendEventHello(map[string]interface{}{})
err := ac.SendEventHello(map[string]any{})
if err != nil {
ac.logger.WithField("loop", "event-health").WithError(err).Warning("event write error")
go ac.recentEvents()
@@ -240,11 +240,9 @@ func (a *APIController) AddEventHandler(handler EventHandler) {
a.eventHandlers = append(a.eventHandlers, handler)
}
func (a *APIController) SendEventHello(args map[string]interface{}) error {
func (a *APIController) SendEventHello(args map[string]any) error {
allArgs := a.getEventPingArgs()
for key, value := range args {
allArgs[key] = value
}
maps.Copy(allArgs, args)
aliveMsg := Event{
Instruction: EventKindHello,
Args: allArgs,

View File

@@ -24,11 +24,11 @@ const (
type EventHandler func(ctx context.Context, msg Event) error
type Event struct {
Instruction EventKind `json:"instruction"`
Args interface{} `json:"args"`
Instruction EventKind `json:"instruction"`
Args any `json:"args"`
}
func (wm Event) ArgsAs(out interface{}) error {
func (wm Event) ArgsAs(out any) error {
return mapstructure.Decode(wm.Args, out)
}

View File

@@ -38,7 +38,7 @@ func Paginator[Tobj any, Treq any, Tres PaginatorResponse[Tobj]](
if opts.Logger == nil {
opts.Logger = log.NewEntry(log.StandardLogger())
}
var bfreq, cfreq interface{}
var bfreq, cfreq any
fetchOffset := func(page int32) (Tres, error) {
bfreq = req.Page(page)
cfreq = bfreq.(PaginatorRequest[Treq, Tres]).PageSize(int32(opts.PageSize))

View File

@@ -27,10 +27,10 @@ func (pi *ProviderInstance) UserEntry(u api.User) *ldap.Entry {
})
if u.IsActive == nil {
u.IsActive = api.PtrBool(false)
u.IsActive = new(false)
}
if u.Email == nil {
u.Email = api.PtrString("")
u.Email = new("")
}
attrs = utils.EnsureAttributes(attrs, map[string][]string{
"ak-active": {strings.ToUpper(strconv.FormatBool(*u.IsActive))},

View File

@@ -20,7 +20,7 @@ type LDAPGroup struct {
MemberOf []string
IsSuperuser bool
IsVirtualGroup bool
Attributes map[string]interface{}
Attributes map[string]any
}
func (lg *LDAPGroup) Entry() *ldap.Entry {

View File

@@ -1,3 +1,3 @@
package handler
type Handler interface{}
type Handler any

View File

@@ -83,8 +83,8 @@ func normalizeAttributes(attributes []string) []string {
for _, attr := range attributes {
if strings.Contains(attr, ",") {
// Split comma-separated attributes and add them individually
parts := strings.Split(attr, ",")
for _, part := range parts {
parts := strings.SplitSeq(attr, ",")
for part := range parts {
part = strings.TrimSpace(part)
if part != "" {
result = append(result, part)

View File

@@ -17,7 +17,7 @@ func AttributeKeySanitize(key string) string {
)
}
func stringify(in interface{}) *string {
func stringify(in any) *string {
switch t := in.(type) {
case string:
return &t
@@ -45,7 +45,7 @@ func stringify(in interface{}) *string {
}
func AttributesToLDAP(
attrs map[string]interface{},
attrs map[string]any,
keyFormatter func(key string) string,
valueFormatter func(value []string) []string,
) []*ldap.EntryAttribute {
@@ -60,7 +60,7 @@ func AttributesToLDAP(
entry.Values = valueFormatter(t)
case *[]string:
entry.Values = valueFormatter(*t)
case []interface{}:
case []any:
vv := make([]string, 0)
for _, v := range t {
v := stringify(v)

View File

@@ -16,7 +16,7 @@ func TestAKAttrsToLDAP_String(t *testing.T) {
u := api.User{}
// normal string
u.Attributes = map[string]interface{}{
u.Attributes = map[string]any{
"foo": "bar",
}
mapped := AttributesToLDAP(u.Attributes, func(key string) string {
@@ -28,8 +28,8 @@ func TestAKAttrsToLDAP_String(t *testing.T) {
assert.Equal(t, "foo", mapped[0].Name)
assert.Equal(t, []string{"bar"}, mapped[0].Values)
// pointer string
u.Attributes = map[string]interface{}{
"foo": api.PtrString("bar"),
u.Attributes = map[string]any{
"foo": new("bar"),
}
mapped = AttributesToLDAP(u.Attributes, func(key string) string {
return AttributeKeySanitize(key)
@@ -44,7 +44,7 @@ func TestAKAttrsToLDAP_String(t *testing.T) {
func TestAKAttrsToLDAP_String_List(t *testing.T) {
u := api.User{}
// string list
u.Attributes = map[string]interface{}{
u.Attributes = map[string]any{
"foo": []string{"bar"},
}
mapped := AttributesToLDAP(u.Attributes, func(key string) string {
@@ -56,7 +56,7 @@ func TestAKAttrsToLDAP_String_List(t *testing.T) {
assert.Equal(t, "foo", mapped[0].Name)
assert.Equal(t, []string{"bar"}, mapped[0].Values)
// pointer string list
u.Attributes = map[string]interface{}{
u.Attributes = map[string]any{
"foo": &[]string{"bar"},
}
mapped = AttributesToLDAP(u.Attributes, func(key string) string {
@@ -71,7 +71,7 @@ func TestAKAttrsToLDAP_String_List(t *testing.T) {
func TestAKAttrsToLDAP_Dict(t *testing.T) {
// dict
d := map[string]interface{}{
d := map[string]any{
"foo": map[string]string{
"foo": "bar",
},
@@ -88,8 +88,8 @@ func TestAKAttrsToLDAP_Dict(t *testing.T) {
func TestAKAttrsToLDAP_Mixed(t *testing.T) {
// dict
d := map[string]interface{}{
"foo": []interface{}{
d := map[string]any{
"foo": []any{
"foo",
6,
},

View File

@@ -250,7 +250,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server, old
if *p.SkipPathRegex != "" {
a.UnauthenticatedRegex = make([]*regexp.Regexp, 0)
for _, regex := range strings.Split(*p.SkipPathRegex, "\n") {
for regex := range strings.SplitSeq(*p.SkipPathRegex, "\n") {
re, err := regexp.Compile(regex)
if err != nil {
// TODO: maybe create event for this?

View File

@@ -62,7 +62,7 @@ func (a *Application) getHeaders(c *types.Claims) map[string]string {
if additionalHeaders == nil {
return headers
}
for key, value := range additionalHeaders.(map[string]interface{}) {
for key, value := range additionalHeaders.(map[string]any) {
headers[key] = toString(value)
}
}
@@ -134,13 +134,13 @@ func (a *Application) getNginxForwardUrl(r *http.Request) (*url.URL, error) {
return u, nil
}
func (a *Application) ReportMisconfiguration(r *http.Request, msg string, fields map[string]interface{}) {
func (a *Application) ReportMisconfiguration(r *http.Request, msg string, fields map[string]any) {
fields["message"] = msg
a.log.WithFields(fields).Error("Reporting configuration error")
req := api.EventRequest{
Action: api.EVENTACTIONS_CONFIGURATION_ERROR,
App: "authentik.providers.proxy", // must match python apps.py name
ClientIp: *api.NewNullableString(api.PtrString(r.RemoteAddr)),
ClientIp: *api.NewNullableString(new(r.RemoteAddr)),
Context: fields,
}
_, _, err := a.ak.Client.EventsAPI.EventsEventsCreate(context.Background()).EventRequest(req).Execute()

View File

@@ -82,9 +82,9 @@ func TestAdHeaders_Standard(t *testing.T) {
func TestAdHeaders_BasicAuth(t *testing.T) {
a := newTestApplication()
a.proxyConfig.BasicAuthEnabled = api.PtrBool(true)
a.proxyConfig.BasicAuthUserAttribute = api.PtrString("user")
a.proxyConfig.BasicAuthPasswordAttribute = api.PtrString("pass")
a.proxyConfig.BasicAuthEnabled = new(true)
a.proxyConfig.BasicAuthUserAttribute = new("user")
a.proxyConfig.BasicAuthPasswordAttribute = new("pass")
h := http.Header{}
a.addHeaders(h, &types.Claims{
PreferredUsername: "foo",

View File

@@ -28,7 +28,7 @@ func (a *Application) forwardHandleTraefik(rw http.ResponseWriter, r *http.Reque
// First check if we've got everything we need
fwd, err := a.getTraefikForwardUrl(r)
if err != nil {
a.ReportMisconfiguration(r, fmt.Sprintf("Outpost %s (Provider %s) failed to detect a forward URL from Traefik", a.outpostName, a.proxyConfig.Name), map[string]interface{}{
a.ReportMisconfiguration(r, fmt.Sprintf("Outpost %s (Provider %s) failed to detect a forward URL from Traefik", a.outpostName, a.proxyConfig.Name), map[string]any{
"provider": a.proxyConfig.Name,
"outpost": a.outpostName,
"url": r.URL.String(),
@@ -71,7 +71,7 @@ func (a *Application) forwardHandleCaddy(rw http.ResponseWriter, r *http.Request
// First check if we've got everything we need
fwd, err := a.getTraefikForwardUrl(r)
if err != nil {
a.ReportMisconfiguration(r, fmt.Sprintf("Outpost %s (Provider %s) failed to detect a forward URL from Caddy", a.outpostName, a.proxyConfig.Name), map[string]interface{}{
a.ReportMisconfiguration(r, fmt.Sprintf("Outpost %s (Provider %s) failed to detect a forward URL from Caddy", a.outpostName, a.proxyConfig.Name), map[string]any{
"provider": a.proxyConfig.Name,
"outpost": a.outpostName,
"url": r.URL.String(),
@@ -113,7 +113,7 @@ func (a *Application) forwardHandleNginx(rw http.ResponseWriter, r *http.Request
a.log.WithField("header", r.Header).Trace("tracing headers for debug")
fwd, err := a.getNginxForwardUrl(r)
if err != nil {
a.ReportMisconfiguration(r, fmt.Sprintf("Outpost %s (Provider %s) failed to detect a forward URL from nginx", a.outpostName, a.proxyConfig.Name), map[string]interface{}{
a.ReportMisconfiguration(r, fmt.Sprintf("Outpost %s (Provider %s) failed to detect a forward URL from nginx", a.outpostName, a.proxyConfig.Name), map[string]any{
"provider": a.proxyConfig.Name,
"outpost": a.outpostName,
"url": r.URL.String(),

View File

@@ -74,10 +74,10 @@ func TestForwardHandleCaddy_Single_Claims(t *testing.T) {
s.Values[constants.SessionClaims] = types.Claims{
Sub: "foo",
Proxy: &types.ProxyClaims{
UserAttributes: map[string]interface{}{
UserAttributes: map[string]any{
"username": "foo",
"password": "bar",
"additionalHeaders": map[string]interface{}{
"additionalHeaders": map[string]any{
"foo": "bar",
},
},
@@ -110,7 +110,7 @@ func TestForwardHandleCaddy_Single_Claims(t *testing.T) {
func TestForwardHandleCaddy_Domain_Blank(t *testing.T) {
a := newTestApplication()
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
a.proxyConfig.CookieDomain = api.PtrString("foo")
a.proxyConfig.CookieDomain = new("foo")
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/caddy", nil)
rr := httptest.NewRecorder()
@@ -122,7 +122,7 @@ func TestForwardHandleCaddy_Domain_Blank(t *testing.T) {
func TestForwardHandleCaddy_Domain_Header(t *testing.T) {
a := newTestApplication()
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
a.proxyConfig.CookieDomain = api.PtrString("foo")
a.proxyConfig.CookieDomain = new("foo")
a.proxyConfig.ExternalHost = "http://auth.test.goauthentik.io"
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/caddy", nil)
req.Header.Set("X-Forwarded-Proto", "http")

View File

@@ -56,10 +56,10 @@ func TestForwardHandleEnvoy_Single_Claims(t *testing.T) {
s.Values[constants.SessionClaims] = types.Claims{
Sub: "foo",
Proxy: &types.ProxyClaims{
UserAttributes: map[string]interface{}{
UserAttributes: map[string]any{
"username": "foo",
"password": "bar",
"additionalHeaders": map[string]interface{}{
"additionalHeaders": map[string]any{
"foo": "bar",
},
},
@@ -92,7 +92,7 @@ func TestForwardHandleEnvoy_Single_Claims(t *testing.T) {
func TestForwardHandleEnvoy_Domain_Header(t *testing.T) {
a := newTestApplication()
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
a.proxyConfig.CookieDomain = api.PtrString("foo")
a.proxyConfig.CookieDomain = new("foo")
a.proxyConfig.ExternalHost = "http://auth.test.goauthentik.io"
req, _ := http.NewRequest("GET", "http:///app", nil)
req.Host = "test.goauthentik.io"

View File

@@ -75,10 +75,10 @@ func TestForwardHandleNginx_Single_Claims(t *testing.T) {
s.Values[constants.SessionClaims] = types.Claims{
Sub: "foo",
Proxy: &types.ProxyClaims{
UserAttributes: map[string]interface{}{
UserAttributes: map[string]any{
"username": "foo",
"password": "bar",
"additionalHeaders": map[string]interface{}{
"additionalHeaders": map[string]any{
"foo": "bar",
},
},
@@ -111,7 +111,7 @@ func TestForwardHandleNginx_Single_Claims(t *testing.T) {
func TestForwardHandleNginx_Domain_Blank(t *testing.T) {
a := newTestApplication()
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
a.proxyConfig.CookieDomain = api.PtrString("foo")
a.proxyConfig.CookieDomain = new("foo")
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/nginx", nil)
rr := httptest.NewRecorder()
@@ -123,7 +123,7 @@ func TestForwardHandleNginx_Domain_Blank(t *testing.T) {
func TestForwardHandleNginx_Domain_Header(t *testing.T) {
a := newTestApplication()
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
a.proxyConfig.CookieDomain = api.PtrString("foo")
a.proxyConfig.CookieDomain = new("foo")
a.proxyConfig.ExternalHost = "http://auth.test.goauthentik.io"
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/nginx", nil)
req.Header.Set("X-Original-URL", "http://test.goauthentik.io/app")

View File

@@ -74,10 +74,10 @@ func TestForwardHandleTraefik_Single_Claims(t *testing.T) {
s.Values[constants.SessionClaims] = types.Claims{
Sub: "foo",
Proxy: &types.ProxyClaims{
UserAttributes: map[string]interface{}{
UserAttributes: map[string]any{
"username": "foo",
"password": "bar",
"additionalHeaders": map[string]interface{}{
"additionalHeaders": map[string]any{
"foo": "bar",
},
},
@@ -110,7 +110,7 @@ func TestForwardHandleTraefik_Single_Claims(t *testing.T) {
func TestForwardHandleTraefik_Domain_Blank(t *testing.T) {
a := newTestApplication()
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
a.proxyConfig.CookieDomain = api.PtrString("foo")
a.proxyConfig.CookieDomain = new("foo")
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/traefik", nil)
rr := httptest.NewRecorder()
@@ -122,7 +122,7 @@ func TestForwardHandleTraefik_Domain_Blank(t *testing.T) {
func TestForwardHandleTraefik_Domain_Header(t *testing.T) {
a := newTestApplication()
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
a.proxyConfig.CookieDomain = api.PtrString("foo")
a.proxyConfig.CookieDomain = new("foo")
a.proxyConfig.ExternalHost = "http://auth.test.goauthentik.io"
req, _ := http.NewRequest("GET", "/outpost.goauthentik.io/auth/traefik", nil)
req.Header.Set("X-Forwarded-Proto", "http")

View File

@@ -76,7 +76,7 @@ func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) {
}
}
redirectUrl := urlJoin(a.proxyConfig.ExternalHost, r.URL.Path)
redirectUrl := urlJoin(a.proxyConfig.ExternalHost, r.URL.EscapedPath())
if a.Mode() == api.PROXYMODE_FORWARD_DOMAIN {
dom := strings.TrimPrefix(*a.proxyConfig.CookieDomain, ".")

View File

@@ -106,7 +106,7 @@ func (a *Application) createState(r *http.Request, w http.ResponseWriter, fwd st
func (a *Application) stateFromRequest(rw http.ResponseWriter, r *http.Request) *OAuthState {
stateJwt := r.URL.Query().Get("state")
token, err := jwt.Parse(stateJwt, func(token *jwt.Token) (interface{}, error) {
token, err := jwt.Parse(stateJwt, func(token *jwt.Token) (any, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])

View File

@@ -55,7 +55,7 @@ func TestCheckRedirectParam_ValidPartial(t *testing.T) {
func TestCheckRedirectParam_Domain(t *testing.T) {
a := newTestApplication()
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
a.proxyConfig.CookieDomain = api.PtrString("t.goauthentik.io")
a.proxyConfig.CookieDomain = new("t.goauthentik.io")
req, _ := http.NewRequest("GET", "https://a.t.goauthentik.io/outpost.goauthentik.io/auth/start", nil)
rd, ok := a.checkRedirectParam(req)

View File

@@ -53,13 +53,13 @@ func TestPostgresStore_SessionLifecycle(t *testing.T) {
userID := uuid.New()
sessionKey := "test_session_" + uuid.New().String()
sessionData := map[string]interface{}{
constants.SessionClaims: map[string]interface{}{
sessionData := map[string]any{
constants.SessionClaims: map[string]any{
"sub": userID.String(),
"email": "test@example.com",
"preferred_username": "testuser",
"custom_claim": "custom_value",
"groups": []interface{}{"admin", "user"},
"groups": []any{"admin", "user"},
},
}
sessionDataJSON, err := json.Marshal(sessionData)
@@ -89,11 +89,11 @@ func TestPostgresStore_SessionLifecycle(t *testing.T) {
assert.Equal(t, userID, *retrievedSession.UserID)
// Parse session data
var parsedData map[string]interface{}
var parsedData map[string]any
err = json.Unmarshal([]byte(retrievedSession.SessionData), &parsedData)
require.NoError(t, err)
claims, ok := parsedData[constants.SessionClaims].(map[string]interface{})
claims, ok := parsedData[constants.SessionClaims].(map[string]any)
assert.True(t, ok)
assert.Equal(t, "test@example.com", claims["email"])
assert.Equal(t, "testuser", claims["preferred_username"])
@@ -109,8 +109,8 @@ func TestPostgresStore_LogoutSessions(t *testing.T) {
user2 := uuid.New()
createSessionData := func(userID uuid.UUID, email string) string {
sessionData := map[string]interface{}{
constants.SessionClaims: map[string]interface{}{
sessionData := map[string]any{
constants.SessionClaims: map[string]any{
"sub": userID.String(),
"email": email,
},
@@ -229,13 +229,13 @@ func TestPostgresStore_SessionClaims(t *testing.T) {
// Create session with complex claims
userID := uuid.New()
sessionData := map[string]interface{}{
constants.SessionClaims: map[string]interface{}{
sessionData := map[string]any{
constants.SessionClaims: map[string]any{
"sub": userID.String(),
"email": "test@example.com",
"preferred_username": "testuser",
"groups": []interface{}{"admin", "user"},
"entitlements": []interface{}{"read", "write"},
"groups": []any{"admin", "user"},
"entitlements": []any{"read", "write"},
"custom_field": "custom_value",
},
}
@@ -261,24 +261,24 @@ func TestPostgresStore_SessionClaims(t *testing.T) {
assert.Equal(t, userID, *retrieved.UserID)
// Parse and verify session data
var parsedData map[string]interface{}
var parsedData map[string]any
err = json.Unmarshal([]byte(retrieved.SessionData), &parsedData)
require.NoError(t, err)
claims, ok := parsedData[constants.SessionClaims].(map[string]interface{})
claims, ok := parsedData[constants.SessionClaims].(map[string]any)
assert.True(t, ok)
assert.Equal(t, "test@example.com", claims["email"])
assert.Equal(t, "testuser", claims["preferred_username"])
assert.Equal(t, "custom_value", claims["custom_field"])
// Verify groups array
groups, ok := claims["groups"].([]interface{})
groups, ok := claims["groups"].([]any)
assert.True(t, ok)
assert.Contains(t, groups, "admin")
assert.Contains(t, groups, "user")
// Verify entitlements array
entitlements, ok := claims["entitlements"].([]interface{})
entitlements, ok := claims["entitlements"].([]any)
assert.True(t, ok)
assert.Contains(t, entitlements, "read")
assert.Contains(t, entitlements, "write")

View File

@@ -19,7 +19,7 @@ func newTestServer() *testServer {
return &testServer{
api: ak.MockAK(
api.Outpost{
Config: map[string]interface{}{
Config: map[string]any{
"authentik_host": ak.TestSecret(),
},
},
@@ -50,18 +50,18 @@ func newTestApplication() *Application {
a, _ := NewApplication(
api.ProxyOutpostConfig{
Name: ak.TestSecret(),
ClientId: api.PtrString(ak.TestSecret()),
ClientSecret: api.PtrString(ak.TestSecret()),
CookieDomain: api.PtrString(""),
CookieSecret: api.PtrString(ak.TestSecret()),
ClientId: new(ak.TestSecret()),
ClientSecret: new(ak.TestSecret()),
CookieDomain: new(""),
CookieSecret: new(ak.TestSecret()),
ExternalHost: "https://ext.t.goauthentik.io",
InternalHost: api.PtrString("http://backend"),
InternalHostSslValidation: api.PtrBool(true),
InternalHost: new("http://backend"),
InternalHostSslValidation: new(true),
Mode: api.PROXYMODE_FORWARD_SINGLE.Ptr(),
SkipPathRegex: api.PtrString("/skip.*"),
BasicAuthEnabled: api.PtrBool(true),
BasicAuthUserAttribute: api.PtrString("username"),
BasicAuthPasswordAttribute: api.PtrString("password"),
SkipPathRegex: new("/skip.*"),
BasicAuthEnabled: new(true),
BasicAuthUserAttribute: new("username"),
BasicAuthPasswordAttribute: new("password"),
OidcConfiguration: api.OpenIDConnectConfiguration{
AuthorizationEndpoint: "http://fake-auth.t.goauthentik.io/auth",
TokenEndpoint: "http://fake-auth.t.goauthentik.io/token",

View File

@@ -3,6 +3,7 @@ package application
import (
"net/http"
"net/url"
"slices"
"strconv"
)
@@ -29,7 +30,7 @@ func (a *Application) redirect(rw http.ResponseWriter, r *http.Request) {
}
// toString Generic to string function, currently supports actual strings and integers
func toString(in interface{}) string {
func toString(in any) string {
switch v := in.(type) {
case string:
return v
@@ -42,12 +43,7 @@ func toString(in interface{}) string {
}
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
return slices.Contains(s, e)
}
func cleanseHeaders(headers http.Header) map[string]string {

View File

@@ -27,6 +27,24 @@ func TestRedirectToStart_Proxy(t *testing.T) {
assert.Equal(t, "https://test.goauthentik.io/foo/bar/baz", s.Values[constants.SessionRedirect])
}
func TestRedirectToStart_Proxy_EncodedSlash(t *testing.T) {
a := newTestApplication()
a.proxyConfig.Mode = api.PROXYMODE_PROXY.Ptr()
a.proxyConfig.ExternalHost = "https://test.goauthentik.io"
// %2F is a URL-encoded forward slash, used by apps like RabbitMQ in queue paths
req, _ := http.NewRequest("GET", "/api/queues/%2F/MYChannelCreated", nil)
rr := httptest.NewRecorder()
a.redirectToStart(rr, req)
assert.Equal(t, http.StatusFound, rr.Code)
loc, _ := rr.Result().Location()
assert.Contains(t, loc.String(), "%252F", "encoded slash %2F must be preserved in redirect URL")
s, _ := a.sessions.Get(req, a.SessionName())
assert.Contains(t, s.Values[constants.SessionRedirect].(string), "%2F", "encoded slash %2F must be preserved in session redirect")
}
func TestRedirectToStart_Forward(t *testing.T) {
a := newTestApplication()
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_SINGLE.Ptr()
@@ -46,7 +64,7 @@ func TestRedirectToStart_Forward(t *testing.T) {
func TestRedirectToStart_Forward_Domain_Invalid(t *testing.T) {
a := newTestApplication()
a.proxyConfig.CookieDomain = api.PtrString("foo")
a.proxyConfig.CookieDomain = new("foo")
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
a.proxyConfig.ExternalHost = "https://test.goauthentik.io"
req, _ := http.NewRequest("GET", "/foo/bar/baz", nil)
@@ -64,7 +82,7 @@ func TestRedirectToStart_Forward_Domain_Invalid(t *testing.T) {
func TestRedirectToStart_Forward_Domain(t *testing.T) {
a := newTestApplication()
a.proxyConfig.CookieDomain = api.PtrString("goauthentik.io")
a.proxyConfig.CookieDomain = new("goauthentik.io")
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_DOMAIN.Ptr()
a.proxyConfig.ExternalHost = "https://test.goauthentik.io"
req, _ := http.NewRequest("GET", "/foo/bar/baz", nil)

View File

@@ -32,12 +32,12 @@ func CodecsFromPairs(maxAge int, keyPairs ...[]byte) []securecookie.Codec {
return codecs
}
func (s *Codec) Encode(name string, value interface{}) (string, error) {
func (s *Codec) Encode(name string, value any) (string, error) {
log.Trace("cookie encode")
return s.SecureCookie.Encode("authentik_proxy", value)
}
func (s *Codec) Decode(name string, value string, dst interface{}) error {
func (s *Codec) Decode(name string, value string, dst any) error {
log.Trace("cookie decode")
return s.SecureCookie.Decode("authentik_proxy", value, dst)
}

View File

@@ -22,7 +22,7 @@ func NewKeySet(secret string) *KeySet {
}
func (ks *KeySet) VerifySignature(ctx context.Context, rawJWT string) ([]byte, error) {
_, err := jwt.Parse(rawJWT, func(token *jwt.Token) (interface{}, error) {
_, err := jwt.Parse(rawJWT, func(token *jwt.Token) (any, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])

View File

@@ -177,7 +177,7 @@ func (p *RefreshableConnPool) PrepareContext(ctx context.Context, query string)
}
// ExecContext implements gorm.ConnPool interface
func (p *RefreshableConnPool) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
func (p *RefreshableConnPool) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) {
var result sql.Result
err := p.tryWithRefresh(ctx, func() error {
p.mu.RLock()
@@ -190,7 +190,7 @@ func (p *RefreshableConnPool) ExecContext(ctx context.Context, query string, arg
}
// QueryContext implements gorm.ConnPool interface
func (p *RefreshableConnPool) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
func (p *RefreshableConnPool) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) {
var rows *sql.Rows
err := p.tryWithRefresh(ctx, func() error {
p.mu.RLock()
@@ -203,7 +203,7 @@ func (p *RefreshableConnPool) QueryContext(ctx context.Context, query string, ar
}
// QueryRowContext implements gorm.ConnPool interface
func (p *RefreshableConnPool) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
func (p *RefreshableConnPool) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row {
// Note: sql.Row doesn't return errors until Scan() is called, so we can't detect auth errors here
// The error will be caught in higher-level GORM operations
p.mu.RLock()
@@ -237,15 +237,15 @@ func (tx *refreshableTx) PrepareContext(ctx context.Context, query string) (*sql
return tx.Tx.PrepareContext(ctx, query)
}
func (tx *refreshableTx) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
func (tx *refreshableTx) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) {
return tx.Tx.ExecContext(ctx, query, args...)
}
func (tx *refreshableTx) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
func (tx *refreshableTx) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) {
return tx.Tx.QueryContext(ctx, query, args...)
}
func (tx *refreshableTx) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
func (tx *refreshableTx) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row {
return tx.Tx.QueryRowContext(ctx, query, args...)
}

View File

@@ -132,11 +132,11 @@ func TestRefreshableConnPool_ConcurrentAccess(t *testing.T) {
var wg sync.WaitGroup
errChan := make(chan error, numGoroutines*numQueries)
for i := 0; i < numGoroutines; i++ {
for i := range numGoroutines {
wg.Add(1)
go func(goroutineID int) {
defer wg.Done()
for j := 0; j < numQueries; j++ {
for range numQueries {
var result int
err := db.WithContext(ctx).Raw("SELECT 1").Scan(&result).Error
if err != nil {

View File

@@ -22,15 +22,15 @@ func (l *logrusLogger) LogMode(gormlogger.LogLevel) gormlogger.Interface {
return l
}
func (l *logrusLogger) Info(ctx context.Context, s string, args ...interface{}) {
func (l *logrusLogger) Info(ctx context.Context, s string, args ...any) {
l.logger.WithContext(ctx).Infof(s, args...)
}
func (l *logrusLogger) Warn(ctx context.Context, s string, args ...interface{}) {
func (l *logrusLogger) Warn(ctx context.Context, s string, args ...any) {
l.logger.WithContext(ctx).Warnf(s, args...)
}
func (l *logrusLogger) Error(ctx context.Context, s string, args ...interface{}) {
func (l *logrusLogger) Error(ctx context.Context, s string, args ...any) {
l.logger.WithContext(ctx).Errorf(s, args...)
}

View File

@@ -226,7 +226,7 @@ func parseConnOptions(encoded string) (map[string]string, error) {
}
// Parse JSON
var opts map[string]interface{}
var opts map[string]any
if err := json.Unmarshal(decoded, &opts); err != nil {
return nil, fmt.Errorf("invalid JSON: %w", err)
}
@@ -473,7 +473,7 @@ func (s *PostgresStore) Close() error {
// save writes session to PostgreSQL
func (s *PostgresStore) save(ctx context.Context, session *sessions.Session) error {
// Convert session.Values (map[interface{}]interface{}) to map[string]interface{} for JSON marshaling
stringKeyedValues := make(map[string]interface{})
stringKeyedValues := make(map[string]any)
for k, v := range session.Values {
if key, ok := k.(string); ok {
stringKeyedValues[key] = v
@@ -489,7 +489,7 @@ func (s *PostgresStore) save(ctx context.Context, session *sessions.Session) err
// Extract user ID from claims if it exists
var userID *uuid.UUID
if claims, hasClaims := session.Values[constants.SessionClaims]; hasClaims {
if claimsMap, ok := claims.(map[string]interface{}); ok {
if claimsMap, ok := claims.(map[string]any); ok {
if sub, exists := claimsMap["sub"]; exists {
if subStr, ok := sub.(string); ok {
if parsedUUID, err := uuid.Parse(subStr); err == nil {
@@ -539,14 +539,14 @@ func (s *PostgresStore) load(ctx context.Context, session *sessions.Session) err
// Deserialize session data from JSON
if proxySession.SessionData != "" {
// First unmarshal to map[string]interface{}
var stringKeyedValues map[string]interface{}
var stringKeyedValues map[string]any
err = json.Unmarshal([]byte(proxySession.SessionData), &stringKeyedValues)
if err != nil {
return fmt.Errorf("failed to unmarshal session data: %w", err)
}
// Convert back to map[interface{}]interface{} for gorilla/sessions compatibility
session.Values = make(map[interface{}]interface{})
session.Values = make(map[any]any)
for k, v := range stringKeyedValues {
session.Values[k] = v
}
@@ -595,7 +595,7 @@ func (s *PostgresStore) LogoutSessions(ctx context.Context, filter func(c types.
continue
}
var sessionData map[string]interface{}
var sessionData map[string]any
if err := json.Unmarshal([]byte(session.SessionData), &sessionData); err != nil {
continue
}
@@ -605,7 +605,7 @@ func (s *PostgresStore) LogoutSessions(ctx context.Context, filter func(c types.
continue
}
claimsMap, ok := claimsData.(map[string]interface{})
claimsMap, ok := claimsData.(map[string]any)
if !ok {
continue
}

View File

@@ -16,6 +16,7 @@ import (
"path/filepath"
"reflect"
"runtime"
"slices"
"testing"
"time"
@@ -89,7 +90,7 @@ func TestPostgresStore_Save(t *testing.T) {
// Set up session claims
userID := uuid.New()
claims := map[string]interface{}{
claims := map[string]any{
"sub": userID.String(),
"email": "test@example.com",
"preferred_username": "testuser",
@@ -108,11 +109,11 @@ func TestPostgresStore_Save(t *testing.T) {
assert.Equal(t, userID, *savedSession.UserID)
// Verify session data contains claims
var sessionData map[string]interface{}
var sessionData map[string]any
err = json.Unmarshal([]byte(savedSession.SessionData), &sessionData)
assert.NoError(t, err)
claimsData, ok := sessionData[constants.SessionClaims].(map[string]interface{})
claimsData, ok := sessionData[constants.SessionClaims].(map[string]any)
assert.True(t, ok)
assert.Equal(t, "test@example.com", claimsData["email"])
assert.Equal(t, "testuser", claimsData["preferred_username"])
@@ -128,8 +129,8 @@ func TestPostgresStore_Load(t *testing.T) {
userID := uuid.New()
sessionKey := "test_session_123"
sessionData := map[string]interface{}{
constants.SessionClaims: map[string]interface{}{
sessionData := map[string]any{
constants.SessionClaims: map[string]any{
"sub": userID.String(),
"email": "test@example.com",
"preferred_username": "testuser",
@@ -158,7 +159,7 @@ func TestPostgresStore_Load(t *testing.T) {
assert.NoError(t, err)
// Verify claims were loaded correctly
claims, ok := session.Values[constants.SessionClaims].(map[string]interface{})
claims, ok := session.Values[constants.SessionClaims].(map[string]any)
assert.True(t, ok)
assert.Equal(t, userID.String(), claims["sub"])
assert.Equal(t, "test@example.com", claims["email"])
@@ -209,7 +210,7 @@ func TestPostgresStore_LogoutSessions_ByUserID(t *testing.T) {
UUID: uuid.New(),
SessionKey: "test_session_user1_1",
UserID: &user1,
SessionData: createSessionData(t, map[string]interface{}{
SessionData: createSessionData(t, map[string]any{
"sub": user1.String(),
"email": "user1@example.com",
}),
@@ -218,7 +219,7 @@ func TestPostgresStore_LogoutSessions_ByUserID(t *testing.T) {
UUID: uuid.New(),
SessionKey: "test_session_user1_2",
UserID: &user1,
SessionData: createSessionData(t, map[string]interface{}{
SessionData: createSessionData(t, map[string]any{
"sub": user1.String(),
"email": "user1@example.com",
}),
@@ -227,7 +228,7 @@ func TestPostgresStore_LogoutSessions_ByUserID(t *testing.T) {
UUID: uuid.New(),
SessionKey: "test_session_user2_1",
UserID: &user2,
SessionData: createSessionData(t, map[string]interface{}{
SessionData: createSessionData(t, map[string]any{
"sub": user2.String(),
"email": "user2@example.com",
}),
@@ -267,21 +268,21 @@ func TestPostgresStore_LogoutSessions_ByEmail(t *testing.T) {
{
UUID: uuid.New(),
SessionKey: "test_session_admin_1",
SessionData: createSessionData(t, map[string]interface{}{
SessionData: createSessionData(t, map[string]any{
"email": "admin@example.com",
}),
},
{
UUID: uuid.New(),
SessionKey: "test_session_admin_2",
SessionData: createSessionData(t, map[string]interface{}{
SessionData: createSessionData(t, map[string]any{
"email": "admin@example.com",
}),
},
{
UUID: uuid.New(),
SessionKey: "test_session_user_1",
SessionData: createSessionData(t, map[string]interface{}{
SessionData: createSessionData(t, map[string]any{
"email": "user@example.com",
}),
},
@@ -308,10 +309,10 @@ func TestPostgresStore_LogoutSessions_ByEmail(t *testing.T) {
err = db.Where("session_key LIKE 'test_%'").First(&remaining).Error
assert.NoError(t, err)
var sessionData map[string]interface{}
var sessionData map[string]any
err = json.Unmarshal([]byte(remaining.SessionData), &sessionData)
require.NoError(t, err)
claims := sessionData[constants.SessionClaims].(map[string]interface{})
claims := sessionData[constants.SessionClaims].(map[string]any)
assert.Equal(t, "user@example.com", claims["email"])
}
@@ -325,25 +326,25 @@ func TestPostgresStore_LogoutSessions_WithGroups(t *testing.T) {
{
UUID: uuid.New(),
SessionKey: "test_session_admin_user",
SessionData: createSessionData(t, map[string]interface{}{
SessionData: createSessionData(t, map[string]any{
"email": "admin@example.com",
"groups": []interface{}{"admin", "user"},
"groups": []any{"admin", "user"},
}),
},
{
UUID: uuid.New(),
SessionKey: "test_session_regular_user",
SessionData: createSessionData(t, map[string]interface{}{
SessionData: createSessionData(t, map[string]any{
"email": "user@example.com",
"groups": []interface{}{"user"},
"groups": []any{"user"},
}),
},
{
UUID: uuid.New(),
SessionKey: "test_session_guest",
SessionData: createSessionData(t, map[string]interface{}{
SessionData: createSessionData(t, map[string]any{
"email": "guest@example.com",
"groups": []interface{}{"guest"},
"groups": []any{"guest"},
}),
},
}
@@ -356,12 +357,7 @@ func TestPostgresStore_LogoutSessions_WithGroups(t *testing.T) {
// Logout all sessions that have "admin" group
ctx := context.Background()
err := store.LogoutSessions(ctx, func(c types.Claims) bool {
for _, group := range c.Groups {
if group == "admin" {
return true
}
}
return false
return slices.Contains(c.Groups, "admin")
})
assert.NoError(t, err)
@@ -376,10 +372,10 @@ func TestPostgresStore_LogoutSessions_WithGroups(t *testing.T) {
assert.NoError(t, err)
for _, session := range remainingSessions {
var sessionData map[string]interface{}
var sessionData map[string]any
err := json.Unmarshal([]byte(session.SessionData), &sessionData)
require.NoError(t, err)
claims := sessionData[constants.SessionClaims].(map[string]interface{})
claims := sessionData[constants.SessionClaims].(map[string]any)
assert.NotEqual(t, "admin@example.com", claims["email"])
}
}
@@ -391,8 +387,8 @@ func TestPostgresStore_LoadExpiredSession(t *testing.T) {
store := NewTestStore(db, pool)
// Create an expired session
sessionKey := "test_expired_load"
expiredData := map[string]interface{}{
constants.SessionClaims: map[string]interface{}{
expiredData := map[string]any{
constants.SessionClaims: map[string]any{
"sub": "test-user",
},
}
@@ -432,7 +428,7 @@ func TestPostgresStore_ConcurrentSessionAccess(t *testing.T) {
const numGoroutines = 10
done := make(chan error, numGoroutines)
for i := 0; i < numGoroutines; i++ {
for i := range numGoroutines {
go func(id int) {
// Each goroutine creates its own unique session
req := httptest.NewRequest("GET", "/", nil)
@@ -473,7 +469,7 @@ func TestPostgresStore_ConcurrentSessionAccess(t *testing.T) {
}
// Wait for all goroutines to complete
for i := 0; i < numGoroutines; i++ {
for range numGoroutines {
err := <-done
assert.NoError(t, err)
}
@@ -942,7 +938,7 @@ func TestPostgresStore_ConnectionPoolSettings(t *testing.T) {
const numConcurrentOps = 20
done := make(chan error, numConcurrentOps)
for i := 0; i < numConcurrentOps; i++ {
for i := range numConcurrentOps {
go func(id int) {
req := httptest.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
@@ -960,7 +956,7 @@ func TestPostgresStore_ConnectionPoolSettings(t *testing.T) {
}
// Collect results
for i := 0; i < numConcurrentOps; i++ {
for i := range numConcurrentOps {
err := <-done
assert.NoError(t, err, "Concurrent operation %d should succeed", i)
}
@@ -1182,8 +1178,8 @@ func TestBuildConnConfig_Base64JSONConnOptions(t *testing.T) {
}
// Helper function to create session data JSON
func createSessionData(t *testing.T, claims map[string]interface{}) string {
sessionData := map[string]interface{}{
func createSessionData(t *testing.T, claims map[string]any) string {
sessionData := map[string]any{
constants.SessionClaims: claims,
}
sessionDataJSON, err := json.Marshal(sessionData)

View File

@@ -9,7 +9,7 @@ import (
)
var (
internalOpcodeIns = []byte(fmt.Sprint(len(guac.InternalDataOpcode), ".", guac.InternalDataOpcode))
internalOpcodeIns = fmt.Append(nil, len(guac.InternalDataOpcode), ".", guac.InternalDataOpcode)
authentikOpcode = []byte("0.authentik.")
)

View File

@@ -77,14 +77,14 @@ func (rs *RACServer) wsHandler(ctx context.Context, msg ak.Event) error {
cc.OnError = func(err error) {
rs.connm.Lock()
delete(rs.conns, wsm.ConnID)
_ = rs.ac.SendEventHello(map[string]interface{}{
_ = rs.ac.SendEventHello(map[string]any{
"active_connections": len(rs.conns),
})
rs.connm.Unlock()
}
rs.connm.Lock()
rs.conns[wsm.ConnID] = *cc
_ = rs.ac.SendEventHello(map[string]interface{}{
_ = rs.ac.SendEventHello(map[string]any{
"active_connections": len(rs.conns),
})
rs.connm.Unlock()

View File

@@ -47,19 +47,19 @@ type logrusAdapter struct {
entry *logrus.Entry
}
func (l *logrusAdapter) Debug(format string, args ...interface{}) {
func (l *logrusAdapter) Debug(format string, args ...any) {
l.entry.Debugf(format, args...)
}
func (l *logrusAdapter) Info(format string, args ...interface{}) {
func (l *logrusAdapter) Info(format string, args ...any) {
l.entry.Infof(format, args...)
}
func (l *logrusAdapter) Warn(format string, args ...interface{}) {
func (l *logrusAdapter) Warn(format string, args ...any) {
l.entry.Warnf(format, args...)
}
func (l *logrusAdapter) Error(format string, args ...interface{}) {
func (l *logrusAdapter) Error(format string, args ...any) {
l.entry.Errorf(format, args...)
}
func (l *logrusAdapter) With(args ...interface{}) protocol.Logger {
func (l *logrusAdapter) With(args ...any) protocol.Logger {
f := make(map[string]any, len(args)/2)
i := Fields(args).Iterator()
for i.Next() {

View File

@@ -3,15 +3,26 @@ package web
import (
"net/http"
"time"
"goauthentik.io/internal/config"
)
func durationOrFallback(raw string, fallback time.Duration) time.Duration {
p, err := time.ParseDuration(raw)
if err != nil {
return fallback
}
return p
}
func Server(h http.Handler) *http.Server {
c := config.Get()
return &http.Server{
Handler: h,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 30 * time.Second,
WriteTimeout: 60 * time.Second,
IdleTimeout: 120 * time.Second,
ReadHeaderTimeout: durationOrFallback(c.Web.TimeoutHttpReadHeader, 5*time.Second),
ReadTimeout: durationOrFallback(c.Web.TimeoutHttpRead, 30*time.Second),
WriteTimeout: durationOrFallback(c.Web.TimeoutHttpWrite, 60*time.Second),
IdleTimeout: durationOrFallback(c.Web.TimeoutHttpIdle, 120*time.Second),
MaxHeaderBytes: http.DefaultMaxHeaderBytes,
}
}

View File

@@ -29,11 +29,11 @@ func storageTokenIsValid(usage string, r *http.Request) bool {
}
claims := &StorageClaims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (any, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method")
}
key := []byte(fmt.Sprintf("%s:%s", config.Get().SecretKey, usage))
key := fmt.Appendf(nil, "%s:%s", config.Get().SecretKey, usage)
hash := sha256.Sum256(key)
hexDigest := hex.EncodeToString(hash[:])
return []byte(hexDigest), nil

View File

@@ -9,7 +9,7 @@
"version": "0.0.0",
"license": "MIT",
"devDependencies": {
"aws-cdk": "^2.1106.0",
"aws-cdk": "^2.1108.0",
"cross-env": "^10.1.0"
},
"engines": {
@@ -25,9 +25,9 @@
"license": "MIT"
},
"node_modules/aws-cdk": {
"version": "2.1106.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1106.0.tgz",
"integrity": "sha512-1tyQNnuCnH3nc0QpOL84UNhr+y73fyS75nwSnuy5z7XtRwdsOuqyqcDxd6tvCXkUBA7fdgu8p1FR3hkqrW0GWA==",
"version": "2.1108.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1108.0.tgz",
"integrity": "sha512-FHnyhnYZoRc2W0C9mNzhNn6fO2vH4xNINsKfJaA7AFDuymgQ39JhEnrM4AHaoikIBqXYeNLWElvvkusY9l3ulw==",
"dev": true,
"license": "Apache-2.0",
"bin": {

View File

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

View File

@@ -26,7 +26,7 @@ RUN npm run build && \
npm run build:sfe
# Stage 2: Build go proxy
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.25.5-trixie@sha256:8e8f9c84609b6005af0a4a8227cee53d6226aab1c6dcb22daf5aeeb8b05480e1 AS go-builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.0-trixie@sha256:100774df3fd947fbd1f2b674e71215af6bf087b3676d9fcfdf1a9d0ec9e5cb9c AS go-builder
ARG TARGETOS
ARG TARGETARCH
@@ -78,9 +78,9 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
# Stage 4: Download uv
FROM ghcr.io/astral-sh/uv:0.9.18@sha256:5713fa8217f92b80223bc83aac7db36ec80a84437dbc0d04bbc659cae030d8c9 AS uv
FROM ghcr.io/astral-sh/uv:0.10.7@sha256:edd1fd89f3e5b005814cc8f777610445d7b7e3ed05361f9ddfae67bebfe8456a AS uv
# Stage 5: Base python image
FROM ghcr.io/goauthentik/fips-python:3.14.2-slim-trixie-fips@sha256:46c0658052e43ad303da39e461ad106c499a03fabd3512d05ff586e506507242 AS python-base
FROM ghcr.io/goauthentik/fips-python:3.14.3-slim-trixie-fips@sha256:7f4c8cb05d92c2033a41bc2f9323ba77e631f67127c94bce20d59eb4024b84ba AS python-base
ENV VENV_PATH="/ak-root/.venv" \
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \

View File

@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
# Stage 1: Build
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.25.5-trixie@sha256:8e8f9c84609b6005af0a4a8227cee53d6226aab1c6dcb22daf5aeeb8b05480e1 AS builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.0-trixie@sha256:100774df3fd947fbd1f2b674e71215af6bf087b3676d9fcfdf1a9d0ec9e5cb9c AS builder
ARG TARGETOS
ARG TARGETARCH
@@ -31,7 +31,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
go build -o /go/ldap ./cmd/ldap
# Stage 2: Run
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:10dadf1df1337e8eb4218acef6a3027abebf0a155a95d792af736f85ab0e9588
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:44cd26c754bfa8f7dd646b94e0e6a4f438a4ec9f7325d7f850941633cd09fcad
ARG VERSION
ARG GIT_BUILD_HASH

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