Compare commits

...

220 Commits

Author SHA1 Message Date
Marc 'risson' Schmitt
1e42c27ed5 packages/django-dramatiq-postgres: remove tenacity and enqueue retries
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-04-13 17:46:03 +02:00
Ken Sternberg
562368683a web: build system had some legacy stuff that I found confusing while working on the CSS ordering (#20698)
* .

* Did I miss something?

* That was a stupid spelling error.

* This was an unpopular move.
2026-04-13 15:37:21 +00:00
dependabot[bot]
8cf21da502 core: bump ruff from 0.15.9 to 0.15.10 (#21559)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 13:57:09 +00:00
dependabot[bot]
75a7a7f5b8 core: bump types-jwcrypto from 1.5.0.20260408 to 1.5.7.20260409 (#21561)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 13:34:20 +00:00
dependabot[bot]
6ea5b2a7fc core: bump lxml from 6.0.3 to 6.0.4 (#21560)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 13:34:11 +00:00
dependabot[bot]
dee79baed7 ci: bump peter-evans/create-pull-request from 8.1.0 to 8.1.1 (#21566)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 13:34:09 +00:00
dependabot[bot]
b76e536d25 ci: bump docker/build-push-action from 7.0.0 to 7.1.0 (#21563)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 13:34:03 +00:00
dependabot[bot]
c6389c82fd ci: bump actions/upload-artifact from 7.0.0 to 7.0.1 (#21565)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 13:34:00 +00:00
dependabot[bot]
53a0370eee core: bump library/golang from da39430 to c0074c7 in /lifecycle/container (#21567)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 13:33:52 +00:00
dependabot[bot]
cae4ffd25b ci: bump taiki-e/install-action from 2.75.1 to 2.75.5 in /.github/actions/setup (#21569)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 13:33:50 +00:00
dependabot[bot]
f60527ce94 web: bump mermaid from 11.13.0 to 11.14.0 in /web (#21527)
Bumps [mermaid](https://github.com/mermaid-js/mermaid) from 11.13.0 to 11.14.0.
- [Release notes](https://github.com/mermaid-js/mermaid/releases)
- [Commits](https://github.com/mermaid-js/mermaid/compare/mermaid@11.13.0...mermaid@11.14.0)

---
updated-dependencies:
- dependency-name: mermaid
  dependency-version: 11.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>
2026-04-12 13:31:33 +02:00
authentik-automation[bot]
4b0831d840 core, web: update translations (#21552)
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-04-12 13:31:17 +02:00
dependabot[bot]
0856bb1ad5 web: bump basic-ftp from 5.2.1 to 5.2.2 in /web (#21543)
Bumps [basic-ftp](https://github.com/patrickjuchli/basic-ftp) from 5.2.1 to 5.2.2.
- [Release notes](https://github.com/patrickjuchli/basic-ftp/releases)
- [Changelog](https://github.com/patrickjuchli/basic-ftp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/patrickjuchli/basic-ftp/compare/v5.2.1...v5.2.2)

---
updated-dependencies:
- dependency-name: basic-ftp
  dependency-version: 5.2.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-11 15:55:41 +02:00
Fletcher Heisler
03e67aea34 web: User Wizard, Modal Revisions Merge Branch (#21336)
* web/elements: rename hasSlotted to findSlotted and refactor host styles

Rename the slot-inspection helper on `AKElement` from `hasSlotted` to
`findSlotted` and return the first matching element rather than a
boolean, so callers can both check for presence and reach the node.
Update every call site in the tree (default callers pass no argument
instead of `null`).

Along the way, tidy `AKElement`'s host-style plumbing: expose
`hostStyles` as a getter/setter backed by a `CSSStyleSheet` cache and
move the adoption logic into `attachHostStyles` / `detachHostStyles`
class methods, so subclasses can share the lifecycle. Drop the now
unused `@localized` decorator import.

Also add a `findAssignedSlot` helper in `elements/utils/slots.ts` for
light-DOM → slot lookups, and give `EmptyState` an explicit
`display: block` so empty-state placement doesn't collapse when
wrapped.

* web/chips: tighten chip group rendering and add placeholder class

Make `ChipGroup` generic over its chip value type, expose a
`placeholder` property that renders an inline placeholder when the
default slot is empty, and intercept clicks that land on child chips
so outer handlers can tell "clicked the group" apart from "clicked a
chip". Give the host an explicit `display: block` so the group
participates in layout correctly.

Move the removal tooltip on `Chip` to the right so it doesn't clip at
the top of the row.

In `base/common.css`, add the `ak-m-placeholder` class used by the
new chip-group placeholder and extend `.ak-fade-in` with an opt-in
`ak-m-delayed` modifier that animates height alongside the fade via
`interpolate-size`, so loading cards can slide in without jank.

* web/elements: add scrollbar helpers and polish table styles

Introduce `elements/utils/scrollbars.ts` with `measureScrollbarWidth`
and `applyScrollbarClass`, and call it from `Interface` so the root
document picks up `ak-m-visible-scrollbars` / `ak-m-overlay-scrollbars`
depending on the platform. Add an `ak-m-thin-scrollbar` selector to
the thin-scrollbar rule in `base/scrollbars.css` so ad-hoc containers
can opt in.

Refresh `Table.css`: expose `search-form`, `search-input`,
`pagination-bottom`, and `table` parts; introduce
`--ak-c-table--expandable-overlay--Color` theming for expandable rows
(including a nested-table background pass); add an
`ak-c-table__actions` helper so per-row action buttons wrap
consistently; and teach the host to honor `display-box="contents"` so
tables embedded in `display: contents` parents still participate in
layout checks.

Drop the unused `elements/utils/isVisible.ts`; the only live
`isVisible` helpers live beside their callers under SearchSelect.

* web/buttons: support split-button Dropdown layout

Teach `ak-dropdown` to recognize a PatternFly split-button toggle —
look for `.pf-c-dropdown__toggle.pf-m-split-button .pf-c-dropdown__toggle-button:last-child`
first and fall back to the single-button selector — so a primary
action and a menu trigger can coexist in one dropdown. Drop the
workaround that skipped wiring menu-item click handlers: now that
dropdowns live inside native dialogs, letting a menu-item click
bubble no longer closes the parent modal. Switch the private fields
to `protected` so subclasses can reach them, and anchor the
AKRefreshEvent and outside-click listeners at `window` explicitly
(matching the new `@listen` default).

In `@listen`, flip the default target from `window` to `this`. A
component's own element is the more intuitive default for a decorator
attached to an instance method, and call sites that want the window
now opt in explicitly.

Extend `Dropdown/dropdown.css` with `--pf-c-dropdown__toggle--*`
padding variables so split-button variants get consistent spacing.

* web/forms: improve form ARIA scaffolding and tighten group styles

Add a sticky `ak-c-form__header` row to `Form.css` with a
`form-actions` part so form headers can host an inline title and
action cluster without each form reinventing the layout.

In `Form/form.css`, add a `.ak-m-content-center` variant for forms
that center their body inside a fixed-size container, and introduce a
PatternFly-compatible grid-based Radio label so the input and its
description align cleanly and the whole row is clickable.

Tighten the `FormGroup` summary spacing (use `spacer--sm` inline and
`spacer-xs` block) and hoist the high-contrast overrides onto the
open group so the details marker stays aligned.

Make `AKControlElement` abstract (requiring a `name`), rename
`isValid` → `valid`, declare it as implementing the new
`FormField<T>` interface, and mark it deprecated in favor of
`FormAssociatedElement`. Make `FormField` generic over the JSON
value type, extend `HTMLElement`, and drop the `Jsonifiable` runtime
import in favor of a type-only import. `HorizontalFormElement` now
searches for either legacy control elements or the new `FormField`
shape when picking its focus target.

* web/elements: migrate modal plumbing to the native <dialog> element

Replace the bespoke modal stack with an `<ak-modal>` built on the
browser's native `<dialog>`, and collect every piece of the new
infrastructure under `#elements/dialogs`:

 * `ak-modal.ts` / `ak-modal.css` — the element + its PatternFly
   compatible styles.
 * `dialog.css` — the global `ak-c-dialog` token and backdrop rules,
   imported via the new `components/Modal/modal.css` entry point
   (replacing the old `base/modal.css` import in `base.css` and
   `interface.global.css`).
 * `shared.ts` — the `TransclusionChildElement` /
   `TransclusionChildSymbol` contract plus the parent-side helpers
   (`isTransclusionParentElement`, `slottedElementUpdatedAt`), so
   forms and tables hosted inside a modal can signal re-render hints
   to the dialog wrapper.
 * `directives.ts` / `invokers.ts` / `utils.ts` — the
   `modalInvoker`, `renderModal`, and `DialogInit` helpers that
   declarative call sites use to open a modal from a button without
   imperatively mounting the element.
 * `components/` — the ready-made invoker buttons
   (`ModalInvokerButton`, `IconEditButton`, `IconEditButtonByTagName`,
   `IconPermissionButton`) and the `components.ts` barrel.
 * `components/Modal/modal.css` — the short host wrapper that pulls
   `dialog.css` into the bundled base stylesheet chain.

Rewire the existing modal consumers to use the new contract:

 * `Form` now implements `TransclusionChildElement`, exposes
   `verboseName`/`verboseNamePlural`/`createLabel`/`submitVerb`
   statics, tracks visibility via `intersectionObserver`, and
   forwards `asModalInvoker` / `showModal` through the new
   `modalInvoker` / `renderModal` helpers. `ModalForm` and
   `ModelForm` follow the same shape. `ModalButton` drops its own
   `pf-c-modal-box` padding fix (the dialog handles it).
 * `Table` implements `TransclusionChildElement`, dispatches refresh
   via `AKRefreshEvent`, and exposes `display-box="contents"` so
   tables embedded in dialogs participate in layout checks.
   `TablePage` / `TableSearch` widen types and surface `search-form`
   / `search-input` parts for dialog-scoped styling.
 * `ak-about-modal`, `ObjectPermissionModal`,
   `RACLaunchEndpointModal`, the command palette, and the admin/user
   interface roots all move off `#elements/modals` and onto
   `#elements/dialogs`.
 * `AdminSettingsForm` / `AdminSettingsPage` render their header /
   actions through the new `ak-c-form__header` + `form-actions`
   slots introduced in the prior Form CSS commit, and swap the
   outermost `<section>` for `<main>` for better landmark semantics.
 * `elements/utils/render-roots.ts` and
   `elements/utils/unsafe.ts` gain dialog-aware helpers (notably a
   directive-based replacement for the old `unsafe` builder).
 * `base/globals.css` disables overscroll while any dialog is open
   via `html[data-dialog-count]`; `package.json` adds the
   `#elements/dialogs` barrel alias.

Delete the old `elements/modals/` directory (`ak-modal.ts`,
`shared.ts`, `styles.css`, `utils.ts`) and `styles/authentik/base/modal.css`
now that nothing imports them.

* web/wizards: refactor wizards to dialog-based flow

Rebuild the shared Wizard primitives on top of the new <dialog> contract:
split CreateWizard/utils out of Wizard, rename admin *Wizard.ts entry
points to ak-*-wizard.ts (Policy, Provider, Source, Stage,
PropertyMapping, ServiceConnection), and port the Application wizard
steps to the new WizardStep base. Adds the user wizard and recovery
invoker plus the refreshed Wizard component styles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* web/admin: migrate forms and list pages to dialog-based modals

Port every admin form, list page, and RBAC surface to the new
TransclusionChildElement / asModalInvoker contract introduced with the
native <dialog> migration. Replace the old ModalButton-driven helpers
with the new modalInvoker/renderModal flow, add the shared
IconCopyButton/IconTokenCopyButton/IconEnrollmentTokenCopyButton
components (with .ak-c-button--icon__progress styling), and refresh
messages, notifications, flow inspector, and user portal consumers to
match. Includes small common/element utility updates picked up along
the way.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* web/test: update browser e2e tests for dialog-based flow

Adjust application, group, session, and user browser tests to the new
wizard and modal selectors introduced by the <dialog> migration and
relax a handful of timeouts that were tight against the old
ModalButton animation sequence.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix visibility detection.

* Fix layout, behavior.

* Fix type.

* Flesh out test revisions.

* Fix type.

* Format.

* Use plural path.

* Fix strict selector in Safari.

* Remove unused.

* Spellcheck.

* Partial type fix.

* Fix translation.

---------

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 07:00:49 +00:00
Teffen Ellis
1858125d3d web/elements: default @listen target to host element and add split-button Dropdown (#21512)
web/buttons: support split-button Dropdown and default @listen to element

Change the `@listen` decorator default target from `window` to `this`
so listeners bind to the host element by default — the more common
case for Lit components. Add explicit `target: window` to the five
existing call sites that dispatch on the window (ak-interface-admin,
APIDrawer, SidebarItem, FlowExecutor, and Dropdown's own refresh
listener).

Also add split-button support to the Dropdown component with
`SplitButtonSelector` / `ToggleButtonSelector` statics and
corresponding CSS padding variables.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 05:47:43 +00:00
Marc 'risson' Schmitt
2aa9906583 ci: parallel tests (#21515) 2026-04-10 16:36:56 +00:00
Tana M Berry
32b9ae6ee8 website/docs: add another sentence to First Steps about restricting access to apps (#21517)
* add another sentence about restricting access to apps

* tweaks

* Update website/docs/install-config/first-steps/index.mdx

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

* Lint fix

---------

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-04-10 11:17:15 -05:00
Teffen Ellis
2f3b38623a web/elements: add scrollbar helpers and apply to Interface (#21511)
Introduce `elements/utils/scrollbars.ts` with `measureScrollbarWidth`
and `applyScrollbarClass`, and call it from `Interface` so the root
document picks up `ak-m-visible-scrollbars` / `ak-m-overlay-scrollbars`
depending on the platform. Add an `ak-m-thin-scrollbar` selector to
`base/scrollbars.css` so ad-hoc containers can opt in.

Drop the unused `elements/utils/isVisible.ts`.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 15:52:20 +00:00
Teffen Ellis
b590bffa57 web/elements: add viewport helpers and extend intersection observer (#21508)
web/elements: add viewport helpers and opt-in ancestor-box to intersection observer

Some lazy-loaded elements render with `display: contents` so they
don't produce a layout box of their own, which makes
`IntersectionObserver` report them as never visible. Add
`useAncestorBox` to the `intersectionObserver` decorator: when set
(or when the element sets `displayBox="contents"`), fall back to the
nearest ancestor that actually has a layout box and test that against
the viewport.

Extract the lookups into a new `elements/utils/viewport.ts` with
`findNearestBoxTarget` and `isInViewport` helpers that can be reused
outside the decorator.
2026-04-10 15:51:56 +00:00
Teffen Ellis
bb20350a2a web/e2e: accept options in NavigatorFixture.waitForPathname (#21507)
Forward an optional second argument through to Playwright's
`waitForURL`, so tests can set per-call timeouts and other options
without abandoning the fixture helper.
2026-04-10 17:15:03 +02:00
Teffen Ellis
4a417ba904 web/styles: switch to upstream RedHat variable fonts and brighten orange palette (#21509)
web/styles: drop modified RedHat fonts and brighten the orange palette

Swap the custom "Modified" RedHat variable fonts for the upstream
variable files (`RedHatDisplayVF.woff2`, `RedHatTextVF.woff2`, plus
italics). The Safari font stack was rendering artifacts on the
modified faces, and the upstream files render identically on the
engines where the modified copies used to be needed.

Also refresh the `--pf-global--palette--orange-*` ramp in
`base/colors.css` with a more saturated oklab curve. The old values
leaned muted and washed out against the new dialog backdrops; the
new values match the branding guide and have enough chroma to be
distinguishable from the tan/gold palette.
2026-04-10 17:13:23 +02:00
Teffen Ellis
e4934681e9 web/styles: add ak-c-loading-skeleton CSS component (#21510)
web/styles: add ak-c-loading-skeleton component

Introduce `components/Skeleton/skeleton.css`, a small utility class
system for loading placeholders. `.ak-c-loading-skeleton` draws a
configurable grid of "bones" with a shimmer animation and an opt-in
fade-in that respects `prefers-reduced-motion`. The component is
configured with `--ak-c-skeleton--*` custom properties so individual
wizards / forms can size and tint skeletons without bespoke CSS.

No consumers yet; the follow-up wizard refactor uses it in place of
the current bullseye spinner during async step loading.
2026-04-10 17:13:09 +02:00
authentik-automation[bot]
64f677b66d core, web: update translations (#21532)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-04-10 12:53:47 +02:00
dependabot[bot]
51b06ad097 core: bump lxml from 6.0.2 to 6.0.3 (#21523)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-10 10:53:31 +00:00
dependabot[bot]
97f1f24520 core: bump library/node from 45babd1 to 9707cd4 in /lifecycle/container (#21522)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-10 10:53:02 +00:00
Marc 'risson' Schmitt
4e8baeb8b5 tasks: better error message for Retry exceptions (#18235)
* tasks: better error message for Retry exceptions

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

* fixup

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

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-04-10 12:50:13 +02:00
Jens L.
00291a82bd web/admin: fix user list avatar (#21531)
* fix alingment

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

* also this that I meant to change

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-04-10 12:45:05 +02:00
Marcelo Elizeche Landó
76a5e62405 core: bump django from v5.2.12 to 5.2.13 (#21520) 2026-04-09 18:08:00 +00:00
Marcelo Elizeche Landó
b09b6e0cb2 core: add cooldown to dependabot (#21286)
* add exclude_newer to pyproject.toml

* Add .npmrc with min-release-age setting

* Revert "Add .npmrc with min-release-age setting"

This reverts commit 5a1b5c13f5.

* Revert "add exclude_newer to pyproject.toml"

This reverts commit 5a148bbff2.

* Use dependabot cooldown instead of pyproject.toml and .npmrc

* Add psycopg and pyopenssl to the cooldown exclude list
2026-04-09 18:02:40 +00:00
Jens L.
2334bdc01a web/admin: include avatar in user list page (#21518)
* include user avatar in user list

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

* cleanup

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

* fix navbar image squashed

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

* include avatar on user page

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-04-09 19:30:03 +02:00
Marc 'risson' Schmitt
a761cc0738 events: add index on Event.user.pk (#19576) 2026-04-09 16:50:11 +00:00
Marc 'risson' Schmitt
bc2dbe93f6 ci: always run apt update (#21516) 2026-04-09 15:51:44 +00:00
Fletcher Heisler
c32f21046d enterprise/search: move QL to open source] (#21484)
* enterprise/search move to /search

* use make gen for schema updates

* update docs

* re-org

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

* cleanup more

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

* fix web

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

* oops

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

* huh

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

* typing

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

* gen

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

* fix tests

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2026-04-09 16:37:11 +02:00
Nuno Alves
92fceb1524 core: add logging when session decode fails (#21514)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-04-09 16:34:10 +02:00
Dominic R
f819775475 website/docs: Refactor email configuration (#21130)
* Refactor email configuration docs

* SMTP intro

* FROM wording

* Hostname hint

* Docker intro

* TLS inline

* Quote tip

* FROM sample

* K8s intro

* Helm auth

* Implicit TLS

* From formats

* Stage SMTP

* Compose phrasing

* GWS heading

* GWS relay IP

* GWS deploy

* TLS heading

* CA verify

* Overview

* TLS modes

* Test note

* Stage link

* SMTP creds

* Trim repetition

* Container names

* Email intro

* Config note

* Global settings

* Stage SMTP

* Docker services

* Kubernetes services

---------

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2026-04-09 09:14:38 -05:00
dependabot[bot]
2cd1620267 core: bump types-ldap3 from 2.9.13.20260402 to 2.9.13.20260408 (#21493)
Bumps [types-ldap3](https://github.com/python/typeshed) from 2.9.13.20260402 to 2.9.13.20260408.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-ldap3
  dependency-version: 2.9.13.20260408
  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-04-09 12:13:24 +00:00
Marc 'risson' Schmitt
0dbd6a68b6 packages/ak-common/db: init (#21357) 2026-04-09 13:57:44 +02:00
Marc 'risson' Schmitt
dedbbee55c packages/ak-axum/extract/host: init (#21323) 2026-04-09 13:57:15 +02:00
dependabot[bot]
165297dcd4 web: bump knip from 6.3.0 to 6.3.1 in /web (#21505)
Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 6.3.0 to 6.3.1.
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@6.3.1/packages/knip)

---
updated-dependencies:
- dependency-name: knip
  dependency-version: 6.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-04-09 13:48:29 +02:00
dependabot[bot]
e767558a55 core: bump types-docker from 7.1.0.20260403 to 7.1.0.20260408 (#21494)
Bumps [types-docker](https://github.com/python/typeshed) from 7.1.0.20260403 to 7.1.0.20260408.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-docker
  dependency-version: 7.1.0.20260408
  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-04-09 12:47:46 +01:00
dependabot[bot]
faddc6f681 core: bump types-requests from 2.33.0.20260402 to 2.33.0.20260408 (#21496)
Bumps [types-requests](https://github.com/python/typeshed) from 2.33.0.20260402 to 2.33.0.20260408.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-version: 2.33.0.20260408
  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-04-09 12:47:42 +01:00
dependabot[bot]
653181c386 web: bump basic-ftp from 5.2.0 to 5.2.1 in /web (#21486)
Bumps [basic-ftp](https://github.com/patrickjuchli/basic-ftp) from 5.2.0 to 5.2.1.
- [Release notes](https://github.com/patrickjuchli/basic-ftp/releases)
- [Changelog](https://github.com/patrickjuchli/basic-ftp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/patrickjuchli/basic-ftp/compare/v5.2.0...v5.2.1)

---
updated-dependencies:
- dependency-name: basic-ftp
  dependency-version: 5.2.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 12:35:33 +02:00
dependabot[bot]
2a1dde2d30 website: bump react-dom from 19.2.4 to 19.2.5 in /website (#21491)
Bumps [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) from 19.2.4 to 19.2.5.
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.5/packages/react-dom)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 12:35:19 +02:00
dependabot[bot]
85adad16ce web: bump the react group across 1 directory with 2 updates (#21503)
Bumps the react group with 2 updates in the /web directory: [react](https://github.com/facebook/react/tree/HEAD/packages/react) and [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom).


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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 12:35:01 +02:00
authentik-automation[bot]
e66cde6fd2 core, web: update translations (#21488)
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-04-09 11:33:41 +01:00
dependabot[bot]
445b2a2334 lifecycle/aws: bump aws-cdk from 2.1117.0 to 2.1118.0 in /lifecycle/aws (#21492)
Bumps [aws-cdk](https://github.com/aws/aws-cdk-cli/tree/HEAD/packages/aws-cdk) from 2.1117.0 to 2.1118.0.
- [Release notes](https://github.com/aws/aws-cdk-cli/releases)
- [Commits](https://github.com/aws/aws-cdk-cli/commits/aws-cdk@v2.1118.0/packages/aws-cdk)

---
updated-dependencies:
- dependency-name: aws-cdk
  dependency-version: 2.1118.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-04-09 11:33:37 +01:00
dependabot[bot]
880c514da6 core: bump types-channels from 4.3.0.20260402 to 4.3.0.20260408 (#21495)
Bumps [types-channels](https://github.com/python/typeshed) from 4.3.0.20260402 to 4.3.0.20260408.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-channels
  dependency-version: 4.3.0.20260408
  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-04-09 11:33:30 +01:00
dependabot[bot]
1c112cdce8 core: bump types-jwcrypto from 1.5.0.20260402 to 1.5.0.20260408 (#21497)
Bumps [types-jwcrypto](https://github.com/python/typeshed) from 1.5.0.20260402 to 1.5.0.20260408.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-jwcrypto
  dependency-version: 1.5.0.20260408
  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-04-09 11:33:26 +01:00
dependabot[bot]
1dcfa43cd0 core: bump google-api-python-client from 2.193.0 to 2.194.0 (#21498)
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 2.193.0 to 2.194.0.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v2.193.0...v2.194.0)

---
updated-dependencies:
- dependency-name: google-api-python-client
  dependency-version: 2.194.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-04-09 11:33:21 +01:00
dependabot[bot]
37d1b29a0c core: bump types-zxcvbn from 4.5.0.20250809 to 4.5.0.20260408 (#21499)
Bumps [types-zxcvbn](https://github.com/python/typeshed) from 4.5.0.20250809 to 4.5.0.20260408.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-zxcvbn
  dependency-version: 4.5.0.20260408
  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-04-09 11:33:17 +01:00
dependabot[bot]
2373fdcfaf ci: bump taiki-e/install-action from 2.75.0 to 2.75.1 in /.github/actions/setup (#21500)
ci: bump taiki-e/install-action in /.github/actions/setup

Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.75.0 to 2.75.1.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](cf39a74df4...80e6af7a2e)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.75.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-04-09 11:33:12 +01:00
dependabot[bot]
551f158731 core: bump astral-sh/uv from 0.11.4 to 0.11.5 in /lifecycle/container (#21501)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.11.4 to 0.11.5.
- [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.11.4...0.11.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 11:33:08 +01:00
dependabot[bot]
1ba4746274 core: bump library/nginx from e2e661b to 7f0adca in /website (#21502)
Bumps library/nginx from `e2e661b` to `7f0adca`.

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 11:33:03 +01:00
dependabot[bot]
bfc9be0463 web: bump @types/node from 25.5.0 to 25.5.2 in /web (#21504)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.5.0 to 25.5.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.5.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-04-09 11:32:59 +01:00
dependabot[bot]
bcb4050c77 core: bump tokio from 1.51.0 to 1.51.1 (#21506)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.51.0 to 1.51.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.51.0...tokio-1.51.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.51.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-04-09 11:32:55 +01:00
leodlsrt
f3ac7db3fd website/integrations: update FortiGate SSLVPN doc (#21475)
Update FortiGate SSLVPN Documentation

Signed-off-by: leodlsrt <50668162+leodlsrt@users.noreply.github.com>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-04-08 16:30:31 +00:00
Marc 'risson' Schmitt
e6519abc0c ci: cache apt install (#21480)
Co-authored-by: Jens L. <jens@goauthentik.io>
2026-04-08 15:50:10 +00:00
Marc 'risson' Schmitt
ad9f0feb68 packages/ak-common: use imports where possible (#21478) 2026-04-08 14:58:55 +00:00
Marc 'risson' Schmitt
300e77b30c packages/ak-axum/server: cleanup unix socket (#21477) 2026-04-08 14:52:12 +00:00
Marc 'risson' Schmitt
318ed2eca0 packages/ak-common, ak-axum: improve logging (#21476) 2026-04-08 14:48:48 +00:00
Marc 'risson' Schmitt
d4e651d893 packages/ak-axum/extract/scheme: init (#21322) 2026-04-08 14:39:58 +00:00
Simonyi Gergő
2b8313ee91 core: fix policy binding objects not being nullable (#21421)
* fix policy binding objects not being nullable

* `make gen-clients`

* fix schema

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

* tidy

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

* fix test

* `make gen`

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2026-04-08 16:39:00 +02:00
Marc 'risson' Schmitt
c4627de55e packages/ak-axum/extract/client_ip: init (#21321) 2026-04-08 14:03:30 +00:00
transifex-integration[bot]
94254c18a8 translate: Updates for project authentik and language fr_FR (#21474)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-04-08 14:02:26 +00:00
dependabot[bot]
7d4f98c73c website: bump the docusaurus group in /website with 10 updates (#21452)
* website: bump the docusaurus group in /website with 10 updates

Bumps the docusaurus group in /website with 10 updates:

| Package | From | To |
| --- | --- | --- |
| [@docusaurus/preset-classic](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-preset-classic) | `3.9.2` | `3.10.0` |
| [@docusaurus/core](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus) | `3.9.2` | `3.10.0` |
| [@docusaurus/faster](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-faster) | `3.9.2` | `3.10.0` |
| [@docusaurus/module-type-aliases](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases) | `3.9.2` | `3.10.0` |
| [@docusaurus/plugin-client-redirects](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-client-redirects) | `3.9.2` | `3.10.0` |
| [@docusaurus/plugin-content-docs](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-content-docs) | `3.9.2` | `3.10.0` |
| [@docusaurus/theme-common](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-theme-common) | `3.9.2` | `3.10.0` |
| [@docusaurus/tsconfig](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-tsconfig) | `3.9.2` | `3.10.0` |
| [@docusaurus/types](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-types) | `3.9.2` | `3.10.0` |
| [@docusaurus/theme-mermaid](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-theme-mermaid) | `3.9.2` | `3.10.0` |


Updates `@docusaurus/preset-classic` from 3.9.2 to 3.10.0
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.10.0/packages/docusaurus-preset-classic)

Updates `@docusaurus/core` from 3.9.2 to 3.10.0
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.10.0/packages/docusaurus)

Updates `@docusaurus/faster` from 3.9.2 to 3.10.0
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.10.0/packages/docusaurus-faster)

Updates `@docusaurus/module-type-aliases` from 3.9.2 to 3.10.0
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.10.0/packages/docusaurus-module-type-aliases)

Updates `@docusaurus/plugin-client-redirects` from 3.9.2 to 3.10.0
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.10.0/packages/docusaurus-plugin-client-redirects)

Updates `@docusaurus/plugin-content-docs` from 3.9.2 to 3.10.0
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.10.0/packages/docusaurus-plugin-content-docs)

Updates `@docusaurus/theme-common` from 3.9.2 to 3.10.0
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.10.0/packages/docusaurus-theme-common)

Updates `@docusaurus/tsconfig` from 3.9.2 to 3.10.0
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.10.0/packages/docusaurus-tsconfig)

Updates `@docusaurus/types` from 3.9.2 to 3.10.0
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.10.0/packages/docusaurus-types)

Updates `@docusaurus/theme-mermaid` from 3.9.2 to 3.10.0
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.10.0/packages/docusaurus-theme-mermaid)

---
updated-dependencies:
- dependency-name: "@docusaurus/preset-classic"
  dependency-version: 3.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docusaurus
- dependency-name: "@docusaurus/core"
  dependency-version: 3.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docusaurus
- dependency-name: "@docusaurus/faster"
  dependency-version: 3.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docusaurus
- dependency-name: "@docusaurus/module-type-aliases"
  dependency-version: 3.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docusaurus
- dependency-name: "@docusaurus/plugin-client-redirects"
  dependency-version: 3.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docusaurus
- dependency-name: "@docusaurus/plugin-content-docs"
  dependency-version: 3.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docusaurus
- dependency-name: "@docusaurus/theme-common"
  dependency-version: 3.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docusaurus
- dependency-name: "@docusaurus/tsconfig"
  dependency-version: 3.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docusaurus
- dependency-name: "@docusaurus/types"
  dependency-version: 3.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docusaurus
- dependency-name: "@docusaurus/theme-mermaid"
  dependency-version: 3.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: docusaurus
...

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

* fix config

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

* update config

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

* bump docusaurus-plugin-openapi-docs

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-04-08 15:30:01 +02:00
Jens L.
5dc2f2e2b4 packages/docusaurus-config: update config for docusaurus 3.10 (#21471)
* packages/docusaurus-config: update config for docusaurus 3.10

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

* bump deps

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-04-08 15:08:31 +02:00
Marc 'risson' Schmitt
5b3caa598f packages/ak-axum/extract/trusted_proxy: init (#21320) 2026-04-08 13:03:14 +00:00
Teffen Ellis
59ac8ba597 web: Fix duplicate Turnstile widgets after extended idle (#21380)
* Flesh out turnstile fixes.

* format

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-04-08 14:41:08 +02:00
Marc 'risson' Schmitt
e2a578fc66 packages/ak-axum/accept/proxy_protocol: init (#21319) 2026-04-08 14:33:32 +02:00
dependabot[bot]
85f4c6d414 web: bump chromedriver from 147.0.0 to 147.0.1 in /web (#21467)
* web: bump chromedriver from 147.0.0 to 147.0.1 in /web

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

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

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

* sigh

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2026-04-08 13:59:37 +02:00
Simonyi Gergő
eaa7a2dbff ci: fix docker-push-variables (#21470)
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2026-04-08 13:39:19 +02:00
authentik-automation[bot]
4499711260 core, web: update translations (#21450)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-04-08 13:14:45 +02:00
Dominic R
e4308317da docs,ci: fix main daily compose downloads + release template (#21448)
ci: fix main daily compose downloads
2026-04-08 11:52:20 +02:00
dependabot[bot]
f0db1364b9 web: bump the storybook group across 1 directory with 5 updates (#21460)
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.3.4 to 10.3.5
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.3.5/code/addons/docs)

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-08 11:49:32 +02:00
dependabot[bot]
3549432873 core: bump cryptography from 46.0.6 to 46.0.7 (#21456)
Bumps [cryptography](https://github.com/pyca/cryptography) from 46.0.6 to 46.0.7.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/46.0.6...46.0.7)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 46.0.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-04-08 11:47:31 +02:00
dependabot[bot]
35487063f4 core: bump pytest from 9.0.2 to 9.0.3 (#21455)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 9.0.2 to 9.0.3.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/9.0.2...9.0.3)

---
updated-dependencies:
- dependency-name: pytest
  dependency-version: 9.0.3
  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-04-08 11:47:23 +02:00
dependabot[bot]
0d0bfc6e1c core: bump github.com/coreos/go-oidc/v3 from 3.17.0 to 3.18.0 (#21451)
Bumps [github.com/coreos/go-oidc/v3](https://github.com/coreos/go-oidc) from 3.17.0 to 3.18.0.
- [Release notes](https://github.com/coreos/go-oidc/releases)
- [Commits](https://github.com/coreos/go-oidc/compare/v3.17.0...v3.18.0)

---
updated-dependencies:
- dependency-name: github.com/coreos/go-oidc/v3
  dependency-version: 3.18.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-04-08 11:46:38 +02:00
dependabot[bot]
f9afb21bf3 core: bump astral-sh/uv from 0.11.3 to 0.11.4 in /lifecycle/container (#21458)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.11.3 to 0.11.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.11.3...0.11.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-08 11:46:09 +02:00
dependabot[bot]
b810b45a4d core: bump library/golang from 1.26.1-trixie to 1.26.2-trixie in /lifecycle/container (#21459)
core: bump library/golang in /lifecycle/container

Bumps library/golang from 1.26.1-trixie to 1.26.2-trixie.

---
updated-dependencies:
- dependency-name: library/golang
  dependency-version: 1.26.2-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-04-08 11:45:58 +02:00
dependabot[bot]
fe8fb4ed87 ci: bump taiki-e/install-action from 2.74.0 to 2.75.0 in /.github/actions/setup (#21461)
ci: bump taiki-e/install-action in /.github/actions/setup

Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.74.0 to 2.75.0.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](94cb46f8d6...cf39a74df4)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.75.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-04-08 11:45:49 +02:00
dependabot[bot]
6415c54882 core: bump library/nginx from 7150b3a to e2e661b in /website (#21462)
Bumps library/nginx from `7150b3a` to `e2e661b`.

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-08 11:45:38 +02:00
dependabot[bot]
392d5099d3 core: bump library/node from 6caf08a to f57f0c7 in /website (#21463)
Bumps library/node from `6caf08a` to `f57f0c7`.

---
updated-dependencies:
- dependency-name: library/node
  dependency-version: 25.9.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-04-08 11:45:27 +02:00
dependabot[bot]
e223e752c8 web: bump the bundler group across 1 directory with 3 updates (#21464)
Bumps the bundler group with 1 update in the /web directory: [@vitest/browser](https://github.com/vitest-dev/vitest/tree/HEAD/packages/browser).


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

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

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

---
updated-dependencies:
- dependency-name: "@vitest/browser"
  dependency-version: 4.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: bundler
- dependency-name: "@vitest/browser-playwright"
  dependency-version: 4.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: bundler
- dependency-name: vitest
  dependency-version: 4.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-08 11:45:04 +02:00
dependabot[bot]
bedf443fd9 web: bump @playwright/test from 1.58.2 to 1.59.1 in /web (#21465)
Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.58.2 to 1.59.1.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.58.2...v1.59.1)

---
updated-dependencies:
- dependency-name: "@playwright/test"
  dependency-version: 1.59.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-04-08 11:44:42 +02:00
dependabot[bot]
f49962ab2c web: bump vite from 8.0.5 to 8.0.7 in /web (#21466)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.5 to 8.0.7.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.7/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.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-04-08 11:44:30 +02:00
Simonyi Gergő
698df5a8c9 website/docs: fix typo (#21446) 2026-04-07 14:45:36 -04:00
Marc 'risson' Schmitt
b7b91c7132 ci: avoid running setup before docker build (#21443) 2026-04-07 18:31:56 +00:00
Jens L.
4b28480e81 root: include relative time for each test case in logs (#21445)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-04-07 20:13:13 +02:00
Marc 'risson' Schmitt
ab911c364e packages/ak-axum/accept/tls: init (#21318) 2026-04-07 17:56:17 +00:00
Marc 'risson' Schmitt
db9de1ba3c packages/ak-axum/server: init (#21317) 2026-04-07 17:11:53 +00:00
Jens L.
314101e71e enterprise/stages/mtls: fix traefik cert encoding (#20483)
* enterprise/stages/mtls: fix traefik cert encoding

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

* fix tests

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-04-07 18:39:50 +02:00
Marc 'risson' Schmitt
f76736be2f packages/ak-axum/tracing: init (#21316) 2026-04-07 16:18:08 +00:00
Simonyi Gergő
46210d2e3f website/docs: add release notes for 2026.2.2 (#21442)
* add release notes for `2026.2.2`

* remove further items

thank you @rissson

Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>

---------

Signed-off-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-04-07 18:15:03 +02:00
Marc 'risson' Schmitt
34da1bbd6f packages/ak-axum/error: init (#21315) 2026-04-07 15:26:01 +00:00
Marc 'risson' Schmitt
a5aac6e0d2 packages/ak-axum: init (#21313) 2026-04-07 14:22:22 +00:00
dependabot[bot]
2e3b0ea47e website: bump the build group across 1 directory with 9 updates (#21396)
Bumps the build group with 9 updates in the /website directory:

| Package | From | To |
| --- | --- | --- |
| [@rspack/binding-darwin-arm64](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack) | `1.7.10` | `1.7.11` |
| [@rspack/binding-linux-arm64-gnu](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack) | `1.7.10` | `1.7.11` |
| [@rspack/binding-linux-x64-gnu](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack) | `1.7.10` | `1.7.11` |
| [@swc/core-darwin-arm64](https://github.com/swc-project/swc) | `1.15.21` | `1.15.24` |
| [@swc/core-linux-arm64-gnu](https://github.com/swc-project/swc) | `1.15.21` | `1.15.24` |
| [@swc/core-linux-x64-gnu](https://github.com/swc-project/swc) | `1.15.21` | `1.15.24` |
| [@swc/html-darwin-arm64](https://github.com/swc-project/swc) | `1.15.21` | `1.15.24` |
| [@swc/html-linux-arm64-gnu](https://github.com/swc-project/swc) | `1.15.21` | `1.15.24` |
| [@swc/html-linux-x64-gnu](https://github.com/swc-project/swc) | `1.15.21` | `1.15.24` |



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

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

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

Updates `@swc/core-darwin-arm64` from 1.15.21 to 1.15.24
- [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.21...v1.15.24)

Updates `@swc/core-linux-arm64-gnu` from 1.15.21 to 1.15.24
- [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.21...v1.15.24)

Updates `@swc/core-linux-x64-gnu` from 1.15.21 to 1.15.24
- [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.21...v1.15.24)

Updates `@swc/html-darwin-arm64` from 1.15.21 to 1.15.24
- [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.21...v1.15.24)

Updates `@swc/html-linux-arm64-gnu` from 1.15.21 to 1.15.24
- [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.21...v1.15.24)

Updates `@swc/html-linux-x64-gnu` from 1.15.21 to 1.15.24
- [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.21...v1.15.24)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-07 15:16:14 +01:00
dependabot[bot]
2455a3685a core: bump jwcrypto from 1.5.6 to 1.5.7 (#21423)
Bumps [jwcrypto](https://github.com/latchset/jwcrypto) from 1.5.6 to 1.5.7.
- [Release notes](https://github.com/latchset/jwcrypto/releases)
- [Commits](https://github.com/latchset/jwcrypto/compare/v1.5.6...v1.5.7)

---
updated-dependencies:
- dependency-name: jwcrypto
  dependency-version: 1.5.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-04-07 15:16:09 +01:00
dependabot[bot]
232933f46c web: bump fuse.js from 7.1.0 to 7.3.0 in /web (#21429)
Bumps [fuse.js](https://github.com/krisk/Fuse) from 7.1.0 to 7.3.0.
- [Release notes](https://github.com/krisk/Fuse/releases)
- [Changelog](https://github.com/krisk/Fuse/blob/main/CHANGELOG.md)
- [Commits](https://github.com/krisk/Fuse/compare/v7.1.0...v7.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-07 16:16:06 +02:00
dependabot[bot]
a64e747b55 web: bump the bundler group across 1 directory with 3 updates (#21425)
* web: bump the bundler group across 1 directory with 3 updates

Bumps the bundler group with 3 updates in the /web directory: [@esbuild/darwin-arm64](https://github.com/evanw/esbuild), [@esbuild/linux-arm64](https://github.com/evanw/esbuild) and [@esbuild/linux-x64](https://github.com/evanw/esbuild).


Updates `@esbuild/darwin-arm64` from 0.27.7 to 0.28.0
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.7...v0.28.0)

Updates `@esbuild/linux-arm64` from 0.27.7 to 0.28.0
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.7...v0.28.0)

Updates `@esbuild/linux-x64` from 0.27.7 to 0.28.0
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.7...v0.28.0)

---
updated-dependencies:
- dependency-name: "@esbuild/darwin-arm64"
  dependency-version: 0.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: bundler
- dependency-name: "@esbuild/linux-arm64"
  dependency-version: 0.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: bundler
- dependency-name: "@esbuild/linux-x64"
  dependency-version: 0.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: bundler
...

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

* qwer

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-04-07 15:15:58 +01:00
dependabot[bot]
ac59d446a6 web: bump cspell from 9.7.0 to 10.0.0 (#21427)
Bumps [cspell](https://github.com/streetsidesoftware/cspell/tree/HEAD/packages/cspell) from 9.7.0 to 10.0.0.
- [Release notes](https://github.com/streetsidesoftware/cspell/releases)
- [Changelog](https://github.com/streetsidesoftware/cspell/blob/main/packages/cspell/CHANGELOG.md)
- [Commits](https://github.com/streetsidesoftware/cspell/commits/v10.0.0/packages/cspell)

---
updated-dependencies:
- dependency-name: cspell
  dependency-version: 10.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-07 15:15:53 +01:00
dependabot[bot]
17322ea038 web: bump knip from 6.1.0 to 6.3.0 in /web (#21428)
Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 6.1.0 to 6.3.0.
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@6.3.0/packages/knip)

---
updated-dependencies:
- dependency-name: knip
  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-04-07 15:15:48 +01:00
Jens L.
57d2135c8a sources/ldap: Switch to new connection tracking, deprecated attribute-based connection (#21392)
* init user

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

* fix and update groups

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

* split api

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

* include user and group in ldap conn

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

* unrelated cleanup

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

* add ldap users/groups page

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

* ui cleanup

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

* fixup

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

* update error message

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

* fix import

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

* add forms for user/group connections

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

* fix py sync

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

* fixup web

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

* fix connection not always saved

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

* more tests

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

* fix help text

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-04-07 16:13:05 +02:00
Marc 'risson' Schmitt
5c33cedc20 packages/ak-common/mode: init (#21259)
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-04-07 13:55:41 +00:00
Marc 'risson' Schmitt
6792c2afeb packages/ak-common/tracing: init (#21263) 2026-04-07 13:42:45 +00:00
Connor Peshek
db5a154230 web/admin: Improve WS-Fed algo selection logic (#20881)
* web/wsfed: Improve algo selection logic
2026-04-07 08:28:02 -05:00
Marc 'risson' Schmitt
53c99429c9 packages/ak-common/tls: init (#21262) 2026-04-07 15:06:06 +02:00
Marc 'risson' Schmitt
a36a6faf65 packages/ak-common/config: add set helper for tests (#21356) 2026-04-07 13:02:53 +00:00
Marc 'risson' Schmitt
1349662d5f tasks: allow retry for rejected tasks only (#21433) 2026-04-07 12:15:35 +00:00
authentik-automation[bot]
cc196dd9db core, web: update translations (#21394)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-04-07 13:50:03 +02:00
Dominic R
77a1c181fc website/docs: clarify file upload troubleshooting (#21361)
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-04-07 13:39:48 +02:00
dependabot[bot]
163f56a337 ci: bump aws-actions/configure-aws-credentials from 6.0.0 to 6.1.0 (#21424)
Bumps [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) from 6.0.0 to 6.1.0.
- [Release notes](https://github.com/aws-actions/configure-aws-credentials/releases)
- [Changelog](https://github.com/aws-actions/configure-aws-credentials/blob/main/CHANGELOG.md)
- [Commits](8df5847569...ec61189d14)

---
updated-dependencies:
- dependency-name: aws-actions/configure-aws-credentials
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-07 11:06:32 +00:00
dependabot[bot]
9c31e15bc7 core: bump uvicorn[standard] from 0.43.0 to 0.44.0 (#21422)
Bumps [uvicorn[standard]](https://github.com/Kludex/uvicorn) from 0.43.0 to 0.44.0.
- [Release notes](https://github.com/Kludex/uvicorn/releases)
- [Changelog](https://github.com/Kludex/uvicorn/blob/main/docs/release-notes.md)
- [Commits](https://github.com/Kludex/uvicorn/compare/0.43.0...0.44.0)

---
updated-dependencies:
- dependency-name: uvicorn[standard]
  dependency-version: 0.44.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-04-07 11:06:13 +00:00
dependabot[bot]
196f3c3c14 ci: bump taiki-e/install-action from 2.73.0 to 2.74.0 in /.github/actions/setup (#21426)
ci: bump taiki-e/install-action in /.github/actions/setup

Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.73.0 to 2.74.0.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](7a562dfa95...94cb46f8d6)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.74.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-07 11:02:25 +00:00
Dewi Roberts
d34a58eb5f security: add item to intended behavior section of security policy (#21430)
Add section
2026-04-07 13:00:26 +02:00
Connor Peshek
8c3d5f1269 providers/oauth: post_logout_redirect_uri support (#20011)
* oauth2/providers: add post logout redirect uri to providers

* properly handle post_logout_redirect_uri and frontchannel message to rp

* add backchannel support

* move logout url logic

* hanlde forbidden_uri_schemes on post_logout_redirect_uri

* merge post_logout with redirect_uri

---------

Signed-off-by: Connor Peshek <connor@connorpeshek.me>
Co-authored-by: Jens L. <jens@goauthentik.io>
2026-04-07 03:46:11 -05:00
dependabot[bot]
507fe39112 web: bump the bundler group across 1 directory with 4 updates (#21373)
Bumps the bundler group with 1 update in the /web directory: [esbuild](https://github.com/evanw/esbuild).


Updates `esbuild` from 0.27.5 to 0.28.0
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.5...v0.28.0)

Updates `@esbuild/darwin-arm64` from 0.27.5 to 0.27.7
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.5...v0.27.7)

Updates `@esbuild/linux-arm64` from 0.27.5 to 0.27.7
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.5...v0.27.7)

Updates `@esbuild/linux-x64` from 0.27.5 to 0.27.7
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.5...v0.27.7)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: bundler
- dependency-name: "@esbuild/darwin-arm64"
  dependency-version: 0.27.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: bundler
- dependency-name: "@esbuild/linux-arm64"
  dependency-version: 0.27.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: bundler
- dependency-name: "@esbuild/linux-x64"
  dependency-version: 0.27.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-07 01:31:30 +02:00
Bapuji Koraganti
ae27fe4ce8 internal: fix certificate fallback without SNI (#21417)
21412: fix falls back to RSA instead of configured other TLS Certificates for a brand/domain

Honor the other certificates other than RSA
2026-04-07 01:28:44 +02:00
dependabot[bot]
0f401a262c web: bump vite from 8.0.3 to 8.0.5 in /web (#21414)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.3 to 8.0.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.5/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.5
  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-04-07 01:23:57 +02:00
Dominic R
5e33093072 website/docs: document Grafana OAuth admin sync workaround (#21360)
* website/docs: document Grafana OAuth admin sync workaround

Clarify a Grafana Generic OAuth failure mode when an existing local admin account overlaps with the first OAuth login, and document where the Grafana admin assignment toggle lives.

Also fix the broken Grafana integration link in the first-steps guide.

Closes #21249

* Apply suggestion from @dewi-tik

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

* Apply suggestion from @dewi-tik

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

* Apply suggestion from @dewi-tik

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

* Lint fix

---------

Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-04-06 15:03:55 +00:00
Jens L.
1e0f18f63e lifecycle: disable gunicorn control socket (#21408)
* lifecycle: disable gunicorn control socket

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

* format

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-04-06 13:34:37 +02:00
Jens L.
ff50357afc sources/oauth: correctly check requests' exception response (#21386)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-04-06 11:55:04 +02:00
dependabot[bot]
53ed4307f5 web: bump the swc group across 1 directory with 11 updates (#21404)
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.21 to 1.15.24
- [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.24/packages/core)

Updates `@swc/core-darwin-arm64` from 1.15.21 to 1.15.24
- [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.21...v1.15.24)

Updates `@swc/core-darwin-x64` from 1.15.21 to 1.15.24
- [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.21...v1.15.24)

Updates `@swc/core-linux-arm-gnueabihf` from 1.15.21 to 1.15.24
- [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.21...v1.15.24)

Updates `@swc/core-linux-arm64-gnu` from 1.15.21 to 1.15.24
- [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.21...v1.15.24)

Updates `@swc/core-linux-arm64-musl` from 1.15.21 to 1.15.24
- [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.21...v1.15.24)

Updates `@swc/core-linux-x64-gnu` from 1.15.21 to 1.15.24
- [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.21...v1.15.24)

Updates `@swc/core-linux-x64-musl` from 1.15.21 to 1.15.24
- [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.21...v1.15.24)

Updates `@swc/core-win32-arm64-msvc` from 1.15.21 to 1.15.24
- [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.21...v1.15.24)

Updates `@swc/core-win32-ia32-msvc` from 1.15.21 to 1.15.24
- [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.21...v1.15.24)

Updates `@swc/core-win32-x64-msvc` from 1.15.21 to 1.15.24
- [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.21...v1.15.24)

---
updated-dependencies:
- dependency-name: "@swc/core"
  dependency-version: 1.15.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-arm64"
  dependency-version: 1.15.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-x64"
  dependency-version: 1.15.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm-gnueabihf"
  dependency-version: 1.15.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-gnu"
  dependency-version: 1.15.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-musl"
  dependency-version: 1.15.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-gnu"
  dependency-version: 1.15.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-musl"
  dependency-version: 1.15.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-arm64-msvc"
  dependency-version: 1.15.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-ia32-msvc"
  dependency-version: 1.15.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-x64-msvc"
  dependency-version: 1.15.24
  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-04-06 11:29:20 +02:00
dependabot[bot]
3ea11f6164 lifecycle/aws: bump aws-cdk from 2.1116.0 to 2.1117.0 in /lifecycle/aws (#21397)
Bumps [aws-cdk](https://github.com/aws/aws-cdk-cli/tree/HEAD/packages/aws-cdk) from 2.1116.0 to 2.1117.0.
- [Release notes](https://github.com/aws/aws-cdk-cli/releases)
- [Commits](https://github.com/aws/aws-cdk-cli/commits/aws-cdk@v2.1117.0/packages/aws-cdk)

---
updated-dependencies:
- dependency-name: aws-cdk
  dependency-version: 2.1117.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-04-06 10:07:59 +01:00
dependabot[bot]
37c6359fdf core: bump axllent/mailpit from v1.29.5 to v1.29.6 in /tests/e2e (#21398)
Bumps axllent/mailpit from v1.29.5 to v1.29.6.

---
updated-dependencies:
- dependency-name: axllent/mailpit
  dependency-version: v1.29.6
  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-04-06 10:07:55 +01:00
dependabot[bot]
8ff3b40355 core: bump uvicorn[standard] from 0.42.0 to 0.43.0 (#21399)
Bumps [uvicorn[standard]](https://github.com/Kludex/uvicorn) from 0.42.0 to 0.43.0.
- [Release notes](https://github.com/Kludex/uvicorn/releases)
- [Changelog](https://github.com/Kludex/uvicorn/blob/main/docs/release-notes.md)
- [Commits](https://github.com/Kludex/uvicorn/compare/0.42.0...0.43.0)

---
updated-dependencies:
- dependency-name: uvicorn[standard]
  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-04-06 10:07:52 +01:00
dependabot[bot]
65219c0823 core: bump aws-cdk-lib from 2.247.0 to 2.248.0 (#21400)
Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk) from 2.247.0 to 2.248.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.247.0...v2.248.0)

---
updated-dependencies:
- dependency-name: aws-cdk-lib
  dependency-version: 2.248.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-04-06 10:07:48 +01:00
dependabot[bot]
a03db6e8a6 core: bump ua-parser from 1.0.1 to 1.0.2 (#21401)
Bumps [ua-parser](https://github.com/ua-parser/uap-python) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/ua-parser/uap-python/releases)
- [Commits](https://github.com/ua-parser/uap-python/compare/1.0.1...1.0.2)

---
updated-dependencies:
- dependency-name: ua-parser
  dependency-version: 1.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-04-06 10:07:44 +01:00
dependabot[bot]
3f44af35b6 ci: bump taiki-e/install-action from 2.71.2 to 2.73.0 in /.github/actions/setup (#21403)
ci: bump taiki-e/install-action in /.github/actions/setup

Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.71.2 to 2.73.0.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](d858f81139...7a562dfa95)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.73.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-04-06 10:07:41 +01:00
dependabot[bot]
481b94abfc core: bump tokio from 1.50.0 to 1.51.0 (#21405)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.50.0 to 1.51.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.50.0...tokio-1.51.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.51.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-04-06 10:07:38 +01:00
dependabot[bot]
56bec78d51 core: bump arc-swap from 1.9.0 to 1.9.1 (#21406)
Bumps [arc-swap](https://github.com/vorner/arc-swap) from 1.9.0 to 1.9.1.
- [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/arc-swap/compare/v1.9.0...v1.9.1)

---
updated-dependencies:
- dependency-name: arc-swap
  dependency-version: 1.9.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-04-06 10:07:34 +01:00
Dewi Roberts
3f617c2c30 website/integrations: add property mappings to GLPI (#21374)
Add property mappings
2026-04-06 08:04:07 +01:00
transifex-integration[bot]
0c9c1ec251 translate: Updates for project authentik and language fr_FR (#21378)
* translate: Translate web/xliff/en.xlf in fr_FR

100% translated source file: 'web/xliff/en.xlf'
on 'fr_FR'.

* translate: Translate django.po in fr_FR

100% translated source file: 'django.po'
on 'fr_FR'.

* translate: Removing web/xliff/en.xlf in fr_FR

99% of minimum 100% translated source file: 'web/xliff/en.xlf'
on 'fr_FR'.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-04-05 22:13:26 +02:00
Jens L.
a6775bc61e tests: refactor test harness to split apart a single file (#21391)
* re-instate previously flaky test

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

* break up big file

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

* move geoip data to subdir

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

* i am but a weak man

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

* fix ldap disconnect in testing

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

* account for mismatched uid due to test server process

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-04-05 22:12:52 +02:00
Jens L.
debd09135a sources/ldap: Better Active Directory tests (#21281)
* sources/ldap: Better Active Directory tests

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

* sigh pytest

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-04-05 21:41:17 +02:00
Jens L.
dc320df3a3 providers/rac: add e2e tests (#21390)
* add test_runner option to not capture stdout

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

* fix exception for container failing to start not being raised

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

* maybe use channels server for testing?

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

* simplify and patch enterprise

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

* simplify waiting for outpost

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

* add rac SSH tests

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

* fix rac missing in CI

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

* retry on container failure

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

* bump healthcheck tries

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

* patch email port always

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

* fixup?

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

* fix guardian cache

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

* only build webui when using selenium

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

* only use channels when needed

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

* fix coverage and combine

based on https://github.com/django/channels/issues/2063#issuecomment-2067722400

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

* dont even cache

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

* test with delete_token_on_disconnect

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-04-05 19:07:31 +02:00
authentik-automation[bot]
c93e0115d0 core, web: update translations (#21387)
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-04-05 16:58:33 +02:00
Jens L.
adbc8ca335 root: fix scripts compose & gen-diff (#21389)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-04-05 16:58:19 +02:00
Jens L.
ea2bdde5a3 enterprise/providers/ssf: test conformance (#21383)
* bump conformance server

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

* add support for rfc push

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

* make format and aud optional

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

* fix some endpoints

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

* force 401

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

* implement get and patch for streams

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

* cleanup

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

* enable async stream deletion

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

* allow configuring remote certificate validation

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

* add verification endpoint

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

* add support for authorization_header

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

* set default aud cause spec cant agree with itself

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

* bump timeout

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

* fix header `typ`

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

* enabled -> status

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

* re-migrate

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

* gen

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

* more tests

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

* more tests and a fix

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

* make streams deletable

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

* and more logs and fix a silly bug

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

* add stream status endpoint

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

* move ssf out of preview

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

* unrelated typing fix

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

* format

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

* sigh

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

* more tests

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-04-05 16:35:39 +02:00
Jens L.
f38584b343 root: misc API client and web typing fixes (#21388)
* fix relObjId type

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

* fix slot comments

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

* sigh

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

* use prettier on generated ts code

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-04-05 13:46:08 +02:00
Jens L.
d5ee53feb2 providers/ldap: inherit adjustable page size for LDAP searchers (#21377)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-04-04 22:42:35 +02:00
Jens L.
827a77dd52 web/admin: more and more polish (#21303)
* fix user edit button

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

* fix impersonate button not aligned

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

* cleanup oauth2 provider page

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

* better desc for outpost health

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

* fix static table not updating when items change

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

* fix lint

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

* include oidc providers in ssf provider retrieve

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

* consistent oauth provider label

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

* rework ssf view page

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

* make client-rust makefile on macos

specifically when gnu sed is installed in the path

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-04-04 22:35:11 +02:00
Lars
418fa620fe website/integrations: immich: set correct issuer url (#21379) 2026-04-03 21:51:11 +00:00
dependabot[bot]
1c8a082760 core: bump library/node from 25.8.2-trixie to 25.9.0-trixie in /website (#21372)
Bumps library/node from 25.8.2-trixie to 25.9.0-trixie.

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-03 15:42:03 +02:00
dependabot[bot]
7ebaf1d2c3 ci: bump taiki-e/install-action from 2.71.1 to 2.71.2 in /.github/actions/setup (#21370)
ci: bump taiki-e/install-action in /.github/actions/setup

Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.71.1 to 2.71.2.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](0cccd59f03...d858f81139)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.71.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-04-03 15:41:32 +02:00
dependabot[bot]
460abb2ab5 core: bump github.com/go-jose/go-jose/v4 from 4.1.3 to 4.1.4 (#21364)
Bumps [github.com/go-jose/go-jose/v4](https://github.com/go-jose/go-jose) from 4.1.3 to 4.1.4.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Commits](https://github.com/go-jose/go-jose/compare/v4.1.3...v4.1.4)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v4
  dependency-version: 4.1.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-03 14:35:17 +01:00
dependabot[bot]
d40c1513ed core: bump mypy from 1.19.1 to 1.20.0 (#21365)
Bumps [mypy](https://github.com/python/mypy) from 1.19.1 to 1.20.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.19.1...v1.20.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-version: 1.20.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-04-03 14:35:14 +01:00
dependabot[bot]
e1948de78e core: bump ruff from 0.15.8 to 0.15.9 (#21366)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.8 to 0.15.9.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.15.8...0.15.9)

---
updated-dependencies:
- dependency-name: ruff
  dependency-version: 0.15.9
  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-04-03 14:35:11 +01:00
dependabot[bot]
2433e92fb9 core: bump types-docker from 7.1.0.20260402 to 7.1.0.20260403 (#21367)
Bumps [types-docker](https://github.com/python/typeshed) from 7.1.0.20260402 to 7.1.0.20260403.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-docker
  dependency-version: 7.1.0.20260403
  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-04-03 14:35:07 +01:00
dependabot[bot]
3a7842b4eb core: bump aws-cdk-lib from 2.246.0 to 2.247.0 (#21368)
Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk) from 2.246.0 to 2.247.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.246.0...v2.247.0)

---
updated-dependencies:
- dependency-name: aws-cdk-lib
  dependency-version: 2.247.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-04-03 14:35:03 +01:00
dependabot[bot]
ea4f9b7832 ci: bump docker/login-action from 4.0.0 to 4.1.0 (#21369)
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](b45d80f862...4907a6ddec)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-03 14:35:00 +01:00
dependabot[bot]
31d6e08c0f web: bump the storybook group across 1 directory with 5 updates (#21371)
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.3.3 to 10.3.4
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v10.3.4/code/addons/docs)

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

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

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

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

---
updated-dependencies:
- dependency-name: "@storybook/addon-docs"
  dependency-version: 10.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/addon-links"
  dependency-version: 10.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components"
  dependency-version: 10.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components-vite"
  dependency-version: 10.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: storybook
  dependency-version: 10.3.4
  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-04-03 14:34:55 +01:00
Dominic R
b96c477b6a website/docs: Clean up PostgreSQL documentation (#21131)
* Clean up PostgreSQL documentation

* Overview

* SSL wording

* Conn age

* Schema text

* Replica line

* Direct tip

* Backup text

* Restore text

* Access text

* Copy text

* Issue text

* Sentence case

* Section intro

* Primary reads

* Username text

* Password text

* TLS modes

* Health checks

* Replica case

* Replica intro

* Backup guides

* Docker intro

* Stop stack

* Stop wording

* Backup alt

* Dump wording

* Remove alt

* Network note

* Verify login

* Dump safety

* Log names
2026-04-02 13:37:38 -04:00
Marc 'risson' Schmitt
111f0c072f root: fix compose generation for patch releases release candidates (#21353)
* root: fix compose generation for patch releases release candidates

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

* add comment

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

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-04-02 18:51:47 +02:00
dependabot[bot]
84581a0dbd web: bump @swc/cli from 0.8.0 to 0.8.1 in /web in the swc group across 1 directory (#21300)
web: bump @swc/cli in /web in the swc group across 1 directory

Bumps the swc group with 1 update in the /web directory: [@swc/cli](https://github.com/swc-project/pkgs).


Updates `@swc/cli` from 0.8.0 to 0.8.1
- [Commits](https://github.com/swc-project/pkgs/commits)

---
updated-dependencies:
- dependency-name: "@swc/cli"
  dependency-version: 0.8.1
  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-04-02 17:01:33 +02:00
Marc 'risson' Schmitt
1ceb46ca15 providers/proxy: fix oidc client not using socket in embedded outpost (#21280)
* providers/proxy: fix oidc client not using socket in embedded outpost

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

* cleanup and switch

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

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2026-04-02 16:50:12 +02:00
Dominic R
78f98641be packages/client-rust: fix portable sed usage (#21337)
* packages/client-rust: fix portable sed usage

* cleanup

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-04-02 14:30:51 +00:00
Marc 'risson' Schmitt
62ccf88512 packages/ak-common/tokio/proxy_procotol: init (#21311) 2026-04-02 13:40:38 +00:00
Marc 'risson' Schmitt
3355669274 packages/ak-common/config: init (#21256) 2026-04-02 15:05:35 +02:00
dependabot[bot]
ba82c97409 core: bump beryju.io/ldap from 0.1.0 to 0.2.1 (#21235)
* core: bump beryju.io/ldap from 0.1.0 to 0.2.1

Bumps [beryju.io/ldap](https://github.com/beryju/ldap) from 0.1.0 to 0.2.1.
- [Commits](https://github.com/beryju/ldap/compare/v0.1.0...v0.2.1)

---
updated-dependencies:
- dependency-name: beryju.io/ldap
  dependency-version: 0.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

* update code

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-04-02 14:37:00 +02:00
dependabot[bot]
478d76206f web: bump @sentry/browser from 10.46.0 to 10.47.0 in /web in the sentry group across 1 directory (#21297)
web: bump @sentry/browser in /web in the sentry group across 1 directory

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


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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-02 14:28:10 +02:00
Marc 'risson' Schmitt
d3fca338b3 packages/ak-common/arbiter: init (#21253)
* packages/ak-arbiter: init

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

* fixup

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

* add tests

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

* lint

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

* sort out package versions

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

* rename to ak-lib

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

* fixup

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

* packages/ak-lib: init

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

* fixup

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

* root: fix rustfmt config

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

* packages/ak-common: rename from ak-lib

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

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-04-02 14:06:28 +02:00
Dominic R
b3036776ed website/docs: fix full dev setup ordering (#21332) 2026-04-02 07:11:47 -04:00
dependabot[bot]
fbd507e5fc core: bump types-docker from 7.1.0.20260328 to 7.1.0.20260402 (#21342)
Bumps [types-docker](https://github.com/python/typeshed) from 7.1.0.20260328 to 7.1.0.20260402.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-docker
  dependency-version: 7.1.0.20260402
  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-04-02 11:05:49 +00:00
Marc 'risson' Schmitt
df6d580150 packages/ak-common: rename from ak-lib (#21314)
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-04-02 11:00:01 +00:00
Marc 'risson' Schmitt
a8db5f1bfa root: fix rustfmt config (#21312) 2026-04-02 12:37:08 +02:00
dependabot[bot]
5a5ca9aa02 core: bump types-ldap3 from 2.9.13.20260319 to 2.9.13.20260402 (#21343)
Bumps [types-ldap3](https://github.com/python/typeshed) from 2.9.13.20260319 to 2.9.13.20260402.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-ldap3
  dependency-version: 2.9.13.20260402
  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-04-02 10:34:06 +00:00
dependabot[bot]
79654d9864 web: bump the bundler group across 1 directory with 4 updates (#21345)
Bumps the bundler group with 1 update in the /web directory: [esbuild](https://github.com/evanw/esbuild).


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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-02 12:00:42 +02:00
dependabot[bot]
e7bc1a88ef core: bump aiohttp from 3.13.3 to 3.13.4 (#21333)
---
updated-dependencies:
- dependency-name: aiohttp
  dependency-version: 3.13.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-02 10:57:34 +01:00
authentik-automation[bot]
2f65ff003e core, web: update translations (#21335)
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-04-02 10:57:30 +01:00
dependabot[bot]
c06083ab87 lifecycle/aws: bump aws-cdk from 2.1115.1 to 2.1116.0 in /lifecycle/aws (#21338)
Bumps [aws-cdk](https://github.com/aws/aws-cdk-cli/tree/HEAD/packages/aws-cdk) from 2.1115.1 to 2.1116.0.
- [Release notes](https://github.com/aws/aws-cdk-cli/releases)
- [Commits](https://github.com/aws/aws-cdk-cli/commits/aws-cdk@v2.1116.0/packages/aws-cdk)

---
updated-dependencies:
- dependency-name: aws-cdk
  dependency-version: 2.1116.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-04-02 10:57:27 +01:00
dependabot[bot]
07753ce8bb core: bump types-requests from 2.33.0.20260327 to 2.33.0.20260402 (#21339)
Bumps [types-requests](https://github.com/python/typeshed) from 2.33.0.20260327 to 2.33.0.20260402.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-version: 2.33.0.20260402
  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-04-02 10:57:23 +01:00
dependabot[bot]
aefd583b0a core: bump django-stubs[compatible-mypy] from 6.0.1 to 6.0.2 (#21340)
Bumps [django-stubs[compatible-mypy]](https://github.com/typeddjango/django-stubs) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/typeddjango/django-stubs/releases)
- [Commits](https://github.com/typeddjango/django-stubs/compare/6.0.1...6.0.2)

---
updated-dependencies:
- dependency-name: django-stubs[compatible-mypy]
  dependency-version: 6.0.2
  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-04-02 10:57:20 +01:00
dependabot[bot]
b6df1a8058 core: bump types-channels from 4.3.0.20260321 to 4.3.0.20260402 (#21341)
Bumps [types-channels](https://github.com/python/typeshed) from 4.3.0.20260321 to 4.3.0.20260402.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-channels
  dependency-version: 4.3.0.20260402
  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-04-02 10:57:16 +01:00
dependabot[bot]
25a44ca35f core: bump types-jwcrypto from 1.5.0.20251102 to 1.5.0.20260402 (#21344)
Bumps [types-jwcrypto](https://github.com/python/typeshed) from 1.5.0.20251102 to 1.5.0.20260402.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-jwcrypto
  dependency-version: 1.5.0.20260402
  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-04-02 10:57:09 +01:00
dependabot[bot]
fe870ea0f0 core: bump astral-sh/uv from 0.11.2 to 0.11.3 in /lifecycle/container (#21346)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.11.2 to 0.11.3.
- [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.11.2...0.11.3)

---
updated-dependencies:
- dependency-name: astral-sh/uv
  dependency-version: 0.11.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-04-02 10:57:06 +01:00
dependabot[bot]
c085be8d1b ci: bump taiki-e/install-action from 2.70.4 to 2.71.1 in /.github/actions/setup (#21347)
ci: bump taiki-e/install-action in /.github/actions/setup

Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.70.4 to 2.71.1.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](bfadeaba21...0cccd59f03)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.71.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-04-02 10:57:02 +01:00
Jens L.
1964394399 ci: allow setting working directory for setup action (#21329)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-04-02 00:33:24 +02:00
Jens L.
5bf11f71f1 security: update policy to include explicit intended functionality (#21308)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-04-01 23:39:00 +02:00
Marc 'risson' Schmitt
7a8a25a6ff packages/django-postgres-cache: fix expiry and delete (#21307) 2026-04-01 14:28:40 +00:00
Dewi Roberts
dea66394c7 website/docs: entra scim: add note about validator (#21273)
Add note
2026-04-01 14:13:45 +00:00
dependabot[bot]
4dd1f0c346 core: bump djangorestframework-stubs[compatible-mypy] from 3.16.8 to 3.16.9 (#21294)
core: bump djangorestframework-stubs[compatible-mypy]

Bumps [djangorestframework-stubs[compatible-mypy]](https://github.com/typeddjango/djangorestframework-stubs) from 3.16.8 to 3.16.9.
- [Release notes](https://github.com/typeddjango/djangorestframework-stubs/releases)
- [Commits](https://github.com/typeddjango/djangorestframework-stubs/compare/3.16.8...3.16.9)

---
updated-dependencies:
- dependency-name: djangorestframework-stubs[compatible-mypy]
  dependency-version: 3.16.9
  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-04-01 13:20:46 +00:00
dependabot[bot]
b58e673f96 web: bump @xmldom/xmldom from 0.8.11 to 0.8.12 in /web (#21301)
Bumps [@xmldom/xmldom](https://github.com/xmldom/xmldom) from 0.8.11 to 0.8.12.
- [Release notes](https://github.com/xmldom/xmldom/releases)
- [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/xmldom/xmldom/compare/0.8.11...0.8.12)

---
updated-dependencies:
- dependency-name: "@xmldom/xmldom"
  dependency-version: 0.8.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-01 15:03:52 +02:00
Jens L.
8610c25bd3 blueprints: rework one-time import (#18074)
* initial move

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

* rework permissions

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

* initial UI rework

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

* add option to one-time import from file

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

* adjust ui

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

* update api

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

* fix import form logs

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

* reset correctly

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

* improve error handling

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-04-01 15:03:16 +02:00
dependabot[bot]
82c8b3ff75 lifecycle/aws: bump aws-cdk from 2.1115.0 to 2.1115.1 in /lifecycle/aws (#21293)
Bumps [aws-cdk](https://github.com/aws/aws-cdk-cli/tree/HEAD/packages/aws-cdk) from 2.1115.0 to 2.1115.1.
- [Release notes](https://github.com/aws/aws-cdk-cli/releases)
- [Commits](https://github.com/aws/aws-cdk-cli/commits/aws-cdk@v2.1115.1/packages/aws-cdk)

---
updated-dependencies:
- dependency-name: aws-cdk
  dependency-version: 2.1115.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-04-01 12:59:26 +00:00
authentik-automation[bot]
e2379f9c3b core, web: update translations (#21288)
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-04-01 12:59:12 +00:00
dependabot[bot]
2e9f40b4ce core: bump sentry-sdk from 2.56.0 to 2.57.0 (#21295)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.56.0 to 2.57.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.56.0...2.57.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-version: 2.57.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-04-01 12:52:20 +00:00
dependabot[bot]
f0270e1151 core: bump aws-cdk-lib from 2.245.0 to 2.246.0 (#21296)
Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk) from 2.245.0 to 2.246.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.245.0...v2.246.0)

---
updated-dependencies:
- dependency-name: aws-cdk-lib
  dependency-version: 2.246.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-04-01 12:52:08 +00:00
authentik-automation[bot]
1faa2cdbb7 stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#21290)
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-04-01 13:52:02 +01:00
dependabot[bot]
197934837d ci: bump getsentry/action-release from 3.5.0 to 3.6.0 (#21298)
Bumps [getsentry/action-release](https://github.com/getsentry/action-release) from 3.5.0 to 3.6.0.
- [Release notes](https://github.com/getsentry/action-release/releases)
- [Changelog](https://github.com/getsentry/action-release/blob/master/CHANGELOG.md)
- [Commits](dab6548b3c...5657c9e888)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-01 12:51:46 +00:00
dependabot[bot]
5ffa209515 ci: bump taiki-e/install-action from 2.70.3 to 2.70.4 in /.github/actions/setup (#21299)
ci: bump taiki-e/install-action in /.github/actions/setup

Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.70.3 to 2.70.4.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](6ef672efc2...bfadeaba21)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.70.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-01 12:51:25 +00:00
Jens L.
dc96bda2d3 website/docs: add example recovery flow with MFA (#19497)
* website/docs: add example recovery flow with MFA

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

* Apply suggestion from @tanberry

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Jens L. <jens@beryju.org>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens L. <jens@beryju.org>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-04-01 12:24:33 +00:00
Dominic R
fabe43127a website/docs: format cache settings (#21289) 2026-04-01 07:08:41 -04:00
Connor Peshek
8dddc05bc0 source/saml: Add forceauthn to saml authnrequest (#20883)
* source/saml: Add ForceAuthn support to SAML AuthnRequest
2026-03-31 22:54:01 -05:00
transifex-integration[bot]
1f872d1721 translate: Updates for project authentik and language fr_FR (#21285)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-03-31 17:43:07 +00:00
Marc 'risson' Schmitt
fd3196744e packages/django-postgres-cache: rework to use ORM (#17771) 2026-03-31 17:05:14 +00:00
Connor Peshek
a6064ec334 providers/saml: Fix redirect for saml slo (#21258)
* providers/saml: fix redirect for logouts

* lint

* update logic

* fix tests

* update build

* fix makefile

* remove sed backup artifacts (.rs-e files)
2026-03-31 18:27:36 +02:00
Jens L.
06408cba59 core: fix provider not nullable (#21275)
* core: fix provider not nullable

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

* fix more inconsistencies

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

* idk man

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-03-31 18:27:22 +02:00
Dewi Roberts
f4ba5ee885 website/docs: ad source: add note about ldap signing (#21274)
Add note
2026-03-31 11:24:20 -04:00
Marc 'risson' Schmitt
be77dc910e website/api: update API clients doc (#21202) 2026-03-31 07:52:28 -05:00
dependabot[bot]
b9b34102ac ci: bump taiki-e/install-action from 2.70.2 to 2.70.3 in /.github/actions/setup (#21267)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-31 14:07:30 +02:00
dependabot[bot]
9d9be53d6f lifecycle/aws: bump aws-cdk from 2.1114.1 to 2.1115.0 in /lifecycle/aws (#21265)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-31 14:01:13 +02:00
authentik-automation[bot]
2d73ea6cb4 core, web: update translations (#21264)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-03-31 14:00:42 +02:00
Marc 'risson' Schmitt
55e555c047 packages/ak-lib: init (#21257)
* packages/ak-lib: init

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

* fixup

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

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-03-31 11:33:46 +02:00
Shiv Tyagi
b9cc9e9cc3 website/docs: document group_uuid as a property for group object (#20865)
The application might need a unique id for a group to uniquely identify it. It can help in various cases like detecting group renames and more.
We should document `group_uuid` field of the group object to make users aware that it can be used in custom property mappings.

Signed-off-by: Shiv Tyagi <67995771+shiv-tyagi@users.noreply.github.com>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-03-31 08:41:32 +01:00
Ken Sternberg
86f16921a3 web/flow: extract lifecycle events peripheral to stage management into their own controllers (#20898)
* web/flow: extract lifecycle events peripheral to stage management into their own controllers

## What

Three features embedded in FlowExecutor, Iframe message handling (from captchas), Multitab message handling, and Websocket message handling, have been extracted from the FlowExecutor and placed into their own controllers.

The `renderFrameBackground()` method has been removed.

# Why

The three features mentioned are all *peripheral* to the task of coordinating challenges. The Iframe message handling may result in a challenge being returned, but there’s a bit of set-up and tear down that doesn’t really correspond well to the central concern of the FlowExecutor; it’s more like a sub-stage of IdentificationStage. By being attached to the executor as Controllers they participate in the executor’s lifecycle and have access to it, but their own internal logic is separated out, making them easier to understand and maintain. As a result, all of the associated machinery– attaching to `window`, disconnecting the websocket client, and so on– can be removed from the FlowExecutor.

The `renderFrameBackground()` method is not used.

* Darn spelling errors.

* Removed debugging line; added some comments.

* Restore frame-based backgrounds to executor; fix comments in FlowIframeMessageController

* Fix comment.

* Prettier and its opinions.

* Web/elements/drawer (#21149)

* .

* .

* .

* .

* .

* .

* Prettier had opinions.

* ## What

Componentize the drawer.

Remove unused CSS.

Provide a better mechanism for manipulating classes than “classMap”;

## Why

### The drawer

The drawer was the last thing that we loaded “native” into the UI. This is “the stupidest thing that could work,” just pasting @beryju’s drawer pattern into a component and giving it some functionality. It’s an excellent start to P5 the thing, however.

The two portions of the drawer, the “content” and the “panel”, are slots; the content is from the anonymous slot. This mirrors my philosophy that components are for layout and control, but the look and feel of their content should be driven by the content, not the component.

### Remove unused CSS

I literally could not find a reason any of these were in the top-level CSS; they don’t set CSS Custom Properties not accessible within the components that use them, they don’t affect the visuals of the components that are present within the top-level DOM, and they were just filling up space.

### class-list

ClassMap always bothered me as an especially clunky solution to what is essentially a problem in set theory: the `element.classlist` needs to be adjusted to match “the set of all classes currently active on this component.” ClassList is my solution: a directive that takes a *list* of classes and does the same set-theoretic comparisons as ClassMap, but with a cleaner API. Anything in the list that is a non-empty string is valid: like ClassMap, it will be left or added to ClassList; everything else (`false`, `""`, `null`, `undefined`) will be removed. (Symbols, numbers, and objects are technically possible and will be reject as “not part of the classList set”, but Typescript won’t allow you to pass those in.)

This allows us to say things like:

    const open = (this.open && "pf-m-expanded") || "pf-m-collapsed"
    ...
    class="pf-c-drawer ${classList(open)}"

… which I think is cleaner than:

    const open = {
       "pf-m-expanded": this.open,
       "pf-m-collapsed": !this.open
    };
    ...
    class="pf-c-drawer ${classMap(open)}"

- \[🦤\] The code has been formatted (`make web`)

* Revised comments; changed a variable name.

* Update after merge.

---------

Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2026-03-30 15:46:02 -07:00
dependabot[bot]
18ee19e49c core: bump pygments from 2.19.2 to 2.20.0 (#21260)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 18:49:49 +00:00
Jens L.
20e2d3fac7 website/docs: add grafana dashboard (#21254)
* website/docs: add grafana dashboard

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

* Optimised images with calibre/image-actions

* Optimised images with calibre/image-actions

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-03-30 19:32:49 +02:00
Jens L.
0b1ba60354 stages/authenticator_webauthn: save attestation certificate when creating credential (#20095)
* stages/authenticator_webauthn: save attestation certificate when creating credential

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

* add toggle

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

* fix migration

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

* gen

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

* squash

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

* better test

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

* ui

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

* docs

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

* gen

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-03-30 13:55:39 +02:00
Jens L.
0748a3800f web/admin: fix missing icon on app view page (#21251)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-03-30 12:30:09 +02:00
Jens L.
453c0c04a2 web/elements: allow table per-column options (#21250)
* web/elements: allow table per-column options

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

* style param instead

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-03-30 12:02:55 +02:00
dependabot[bot]
7ff87bb401 ci: bump actions/setup-go from 6.3.0 to 6.4.0 (#21245)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6.3.0 to 6.4.0.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](4b73464bb3...4a3601121d)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 11:59:31 +02:00
dependabot[bot]
8045b141c1 web: bump knip from 6.0.6 to 6.1.0 in /web (#21241)
Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 6.0.6 to 6.1.0.
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@6.1.0/packages/knip)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 11:59:01 +02:00
dependabot[bot]
1538f74acc web: bump globby from 16.1.1 to 16.2.0 in /web (#21242)
Bumps [globby](https://github.com/sindresorhus/globby) from 16.1.1 to 16.2.0.
- [Release notes](https://github.com/sindresorhus/globby/releases)
- [Commits](https://github.com/sindresorhus/globby/compare/v16.1.1...v16.2.0)

---
updated-dependencies:
- dependency-name: globby
  dependency-version: 16.2.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-30 11:58:46 +02:00
dependabot[bot]
b1c2535c85 core: bump types-requests from 2.32.4.20260324 to 2.33.0.20260327 (#21236)
Bumps [types-requests](https://github.com/python/typeshed) from 2.32.4.20260324 to 2.33.0.20260327.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-version: 2.33.0.20260327
  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-30 10:58:12 +01:00
dependabot[bot]
c78514ed01 core: bump types-docker from 7.1.0.20260322 to 7.1.0.20260328 (#21237)
Bumps [types-docker](https://github.com/python/typeshed) from 7.1.0.20260322 to 7.1.0.20260328.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-docker
  dependency-version: 7.1.0.20260328
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 10:58:08 +01:00
dependabot[bot]
44db237ce9 core: bump aws-cdk-lib from 2.244.0 to 2.245.0 (#21238)
Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk) from 2.244.0 to 2.245.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.244.0...v2.245.0)

---
updated-dependencies:
- dependency-name: aws-cdk-lib
  dependency-version: 2.245.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-30 10:58:03 +01:00
dependabot[bot]
e45a76e26d ci: bump int128/docker-manifest-create-action from 2.16.0 to 2.17.0 (#21244)
Bumps [int128/docker-manifest-create-action](https://github.com/int128/docker-manifest-create-action) from 2.16.0 to 2.17.0.
- [Release notes](https://github.com/int128/docker-manifest-create-action/releases)
- [Commits](8aac06098a...44422a4b04)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 10:57:59 +01:00
dependabot[bot]
d5055eba1a ci: bump astral-sh/setup-uv from 7.6.0 to 8.0.0 in /.github/actions/setup (#21246)
ci: bump astral-sh/setup-uv in /.github/actions/setup

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 10:57:54 +01:00
dependabot[bot]
e00cf88867 ci: bump taiki-e/install-action from 2.69.12 to 2.70.2 in /.github/actions/setup (#21247)
ci: bump taiki-e/install-action in /.github/actions/setup

Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.69.12 to 2.70.2.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](80a23c5ba9...e9e8e031bc)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.70.2
  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-30 10:57:50 +01:00
dependabot[bot]
d2eba75203 ci: bump actions/setup-go from 6.3.0 to 6.4.0 in /.github/actions/setup (#21248)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6.3.0 to 6.4.0.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](4b73464bb3...4a3601121d)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 10:57:47 +01:00
1621 changed files with 122206 additions and 62365 deletions

5
.cargo/config.toml Normal file
View File

@@ -0,0 +1,5 @@
[alias]
t = ["nextest", "run"]
[build]
rustflags = ["--cfg", "tokio_unstable"]

View File

@@ -2,12 +2,14 @@
allow = [
"Apache-2.0",
"BSD-3-Clause",
"CC0-1.0",
"CDLA-Permissive-2.0",
"ISC",
"MIT",
"MPL-2.0",
"OpenSSL",
"Unicode-3.0",
"Zlib",
]
[licenses.private]

View File

@@ -12,5 +12,4 @@ reorder_impl_items = true
style_edition = "2024"
use_field_init_shorthand = true
use_try_shorthand = true
where_single_line = true
wrap_comments = true

View File

@@ -10,4 +10,4 @@ build_docs/**
blueprints/local
.git
.venv
target/
target

View File

@@ -54,10 +54,6 @@ outputs:
runs:
using: "composite"
steps:
- name: Setup authentik env
uses: ./.github/actions/setup
with:
dependencies: "python"
- name: Generate config
id: ev
shell: bash
@@ -68,4 +64,4 @@ runs:
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
REF: ${{ github.ref }}
run: |
uv run python3 ${{ github.action_path }}/push_vars.py
python3 ${{ github.action_path }}/push_vars.py

View File

@@ -2,10 +2,19 @@
import os
from json import dumps
from pathlib import Path
from sys import exit as sysexit
from time import time
from typing import Any
from authentik import authentik_version
def authentik_version() -> str:
init = Path(__file__).parent.parent.parent.parent / "authentik" / "__init__.py"
with open(init) as f:
content = f.read()
locals: dict[str, Any] = {}
exec(content, None, locals) # nosec
return str(locals["VERSION"])
def must_or_fail(input: str | None, error: str) -> str:
@@ -97,6 +106,7 @@ if os.getenv("RELEASE", "false").lower() == "true":
image_build_args = [f"VERSION={os.getenv('REF')}"]
else:
image_build_args = [f"GIT_BUILD_HASH={sha}"]
image_build_args_str = "\n".join(image_build_args)
with open(os.environ["GITHUB_OUTPUT"], "a+", encoding="utf-8") as _output:
print(f"shouldPush={str(should_push).lower()}", file=_output)
@@ -109,4 +119,4 @@ with open(os.environ["GITHUB_OUTPUT"], "a+", encoding="utf-8") as _output:
print(f"imageMainTag={image_main_tag}", file=_output)
print(f"imageMainName={image_tags[0]}", file=_output)
print(f"cacheTo={cache_to}", file=_output)
print(f"imageBuildArgs={"\n".join(image_build_args)}", file=_output)
print(f"imageBuildArgs={image_build_args_str}", file=_output)

View File

@@ -8,31 +8,47 @@ inputs:
postgresql_version:
description: "Optional postgresql image tag"
default: "16"
working-directory:
description: |
Optional working directory if this repo isn't in the root of the actions workspace.
When set, needs to contain a trailing slash
default: ""
runs:
using: "composite"
steps:
- name: Install apt deps & cleanup
- name: Cleanup apt
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies, 'python') }}
shell: bash
run: sudo apt-get remove --purge man-db
- name: Install apt deps
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies, 'python') }}
uses: gerlero/apt-install@f4fa5265092af9e750549565d28c99aec7189639
with:
packages: libpq-dev openssl libxmlsec1-dev pkg-config gettext krb5-multidev libkrb5-dev heimdal-multidev libclang-dev krb5-kdc krb5-user krb5-admin-server
update: true
upgrade: false
install-recommends: false
- name: Make space on disk
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies, 'python') }}
shell: bash
run: |
sudo apt-get remove --purge man-db
sudo apt-get update
sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext krb5-multidev libkrb5-dev heimdal-multidev libclang-dev krb5-kdc krb5-user krb5-admin-server
sudo rm -rf /usr/local/lib/android
sudo mkdir -p /tmp/empty/
sudo rsync -a --delete /tmp/empty/ /usr/local/lib/android/
- name: Install uv
if: ${{ contains(inputs.dependencies, 'python') }}
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v5
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v5
with:
enable-cache: true
- name: Setup python
if: ${{ contains(inputs.dependencies, 'python') }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v5
with:
python-version-file: "pyproject.toml"
python-version-file: "${{ inputs.working-directory }}pyproject.toml"
- name: Install Python deps
if: ${{ contains(inputs.dependencies, 'python') }}
shell: bash
working-directory: ${{ inputs.working-directory }}
run: uv sync --all-extras --dev --frozen
- name: Setup rust (stable)
if: ${{ contains(inputs.dependencies, 'rust') && !contains(inputs.dependencies, 'rust-nightly') }}
@@ -48,34 +64,35 @@ runs:
rustflags: ""
- name: Setup rust dependencies
if: ${{ contains(inputs.dependencies, 'rust') }}
uses: taiki-e/install-action@80a23c5ba9e1100fd8b777106e810018ed662a7b # v2
uses: taiki-e/install-action@0abfcd587b70a713fdaa7fb502c885e2112acb15 # v2
with:
tool: cargo-deny cargo-machete cargo-llvm-cov nextest
- name: Setup node (web)
if: ${{ contains(inputs.dependencies, 'node') }}
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4
with:
node-version-file: web/package.json
node-version-file: "${{ inputs.working-directory }}web/package.json"
cache: "npm"
cache-dependency-path: web/package-lock.json
cache-dependency-path: "${{ inputs.working-directory }}web/package-lock.json"
registry-url: "https://registry.npmjs.org"
- name: Setup node (root)
if: ${{ contains(inputs.dependencies, 'node') }}
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4
with:
node-version-file: package.json
node-version-file: "${{ inputs.working-directory }}package.json"
cache: "npm"
cache-dependency-path: package-lock.json
cache-dependency-path: "${{ inputs.working-directory }}package-lock.json"
registry-url: "https://registry.npmjs.org"
- name: Install Node deps
if: ${{ contains(inputs.dependencies, 'node') }}
shell: bash
working-directory: ${{ inputs.working-directory }}
run: npm ci
- name: Setup go
if: ${{ contains(inputs.dependencies, 'go') }}
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v5
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v5
with:
go-version-file: "go.mod"
go-version-file: "${{ inputs.working-directory }}go.mod"
- name: Setup docker cache
if: ${{ contains(inputs.dependencies, 'runtime') }}
uses: AndreKurait/docker-cache@0fe76702a40db986d9663c24954fc14c6a6031b7
@@ -84,6 +101,7 @@ runs:
- name: Setup dependencies
if: ${{ contains(inputs.dependencies, 'runtime') }}
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
export PSQL_TAG=${{ inputs.postgresql_version }}
docker compose -f .github/actions/setup/compose.yml up -d
@@ -91,6 +109,7 @@ runs:
- name: Generate config
if: ${{ contains(inputs.dependencies, 'python') }}
shell: uv run python {0}
working-directory: ${{ inputs.working-directory }}
run: |
from authentik.lib.generators import generate_id
from yaml import safe_dump

View File

@@ -20,6 +20,8 @@ updates:
prefix: "ci:"
labels:
- dependencies
cooldown:
default-days: 3
#endregion
@@ -35,6 +37,16 @@ updates:
prefix: "core:"
labels:
- dependencies
cooldown:
default-days: 7
semver-major-days: 14
semver-patch-days: 3
exclude:
- "golang.org/x/crypto"
- "golang.org/x/net"
- "github.com/golang-jwt/jwt/*"
- "github.com/coreos/go-oidc/*"
- "github.com/go-ldap/ldap/*"
#endregion
@@ -50,6 +62,10 @@ updates:
prefix: "core:"
labels:
- dependencies
cooldown:
default-days: 7
semver-major-days: 14
semver-patch-days: 3
- package-ecosystem: rust-toolchain
directory: "/"
@@ -61,6 +77,8 @@ updates:
prefix: "core:"
labels:
- dependencies
cooldown:
default-days: 3
#endregion
@@ -79,6 +97,10 @@ updates:
open-pull-requests-limit: 10
commit-message:
prefix: "web:"
cooldown:
default-days: 7
semver-major-days: 14
semver-patch-days: 3
groups:
sentry:
patterns:
@@ -142,6 +164,10 @@ updates:
open-pull-requests-limit: 10
commit-message:
prefix: "core, web:"
cooldown:
default-days: 7
semver-major-days: 14
semver-patch-days: 3
groups:
sentry:
patterns:
@@ -200,6 +226,10 @@ updates:
prefix: "website:"
labels:
- dependencies
cooldown:
default-days: 7
semver-major-days: 14
semver-patch-days: 3
groups:
docusaurus:
patterns:
@@ -238,6 +268,10 @@ updates:
prefix: "lifecycle/aws:"
labels:
- dependencies
cooldown:
default-days: 7
semver-major-days: 14
semver-patch-days: 3
#endregion
@@ -253,6 +287,18 @@ updates:
prefix: "core:"
labels:
- dependencies
cooldown:
default-days: 7
semver-major-days: 14
semver-patch-days: 3
exclude:
- "django"
- "cryptography"
- "pyjwt"
- "xmlsec"
- "lxml"
- "psycopg"
- "pyopenssl"
#endregion
@@ -270,6 +316,8 @@ updates:
prefix: "core:"
labels:
- dependencies
cooldown:
default-days: 3
- package-ecosystem: docker-compose
directories:
- /packages/client-go
@@ -285,5 +333,7 @@ updates:
prefix: "core:"
labels:
- dependencies
cooldown:
default-days: 3
#endregion

View File

@@ -56,27 +56,19 @@ jobs:
release: ${{ inputs.release }}
- name: Login to Docker Hub
if: ${{ inputs.registry_dockerhub }}
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKER_CORP_USERNAME }}
password: ${{ secrets.DOCKER_CORP_PASSWORD }}
- name: Login to GitHub Container Registry
if: ${{ inputs.registry_ghcr }}
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
with:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: "go.mod"
- name: Build Docker Image
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
id: push
with:
context: .

View File

@@ -79,18 +79,18 @@ jobs:
image-name: ${{ inputs.image_name }}
- name: Login to Docker Hub
if: ${{ inputs.registry_dockerhub }}
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKER_CORP_USERNAME }}
password: ${{ secrets.DOCKER_CORP_PASSWORD }}
- name: Login to GitHub Container Registry
if: ${{ inputs.registry_ghcr }}
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: int128/docker-manifest-create-action@8aac06098a12365ccdf99372dcfb453ccce8a0b0 # v2
- uses: int128/docker-manifest-create-action@44422a4b046d55dc036df622039ed3aec43c613c # v2
id: build
with:
tags: ${{ matrix.tag }}

View File

@@ -55,7 +55,7 @@ jobs:
env:
NODE_ENV: production
run: npm run build -w api
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v4
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v4
with:
name: api-docs
path: website/api/build

View File

@@ -89,14 +89,14 @@ jobs:
image-name: ghcr.io/goauthentik/dev-docs
- name: Login to Container Registry
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker Image
id: push
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
tags: ${{ steps.ev.outputs.imageTags }}
file: website/Dockerfile

View File

@@ -20,13 +20,19 @@ jobs:
version:
- docs
- version-2025-12
- version-2025-10
- version-2026-2
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- run: |
set -euo pipefail
current="$(pwd)"
dir="/tmp/authentik/${{ matrix.version }}"
# 2025.12 still serves the legacy docker-compose filename; newer sites use compose.yml.
compose_path="compose.yml"
if [ "${{ matrix.version }}" = "version-2025-12" ]; then
compose_path="docker-compose.yml"
fi
mkdir -p "${dir}/lifecycle/container"
cd "${dir}"
wget "https://${{ matrix.version }}.goauthentik.io/docker-compose.yml" -O "${dir}/lifecycle/container/compose.yml"
wget "https://${{ matrix.version }}.goauthentik.io/${compose_path}" -O "${dir}/lifecycle/container/compose.yml"
"${current}/scripts/test_docker.sh"

View File

@@ -196,6 +196,7 @@ jobs:
- name: run integration
run: |
uv run coverage run manage.py test tests/integration
uv run coverage combine
uv run coverage xml
- uses: ./.github/actions/test-results
if: ${{ always() }}
@@ -223,6 +224,9 @@ jobs:
profiles: selenium
- name: ldap
glob: tests/e2e/test_provider_ldap* tests/e2e/test_source_ldap*
- name: rac
glob: tests/e2e/test_provider_rac*
profiles: selenium
- name: ws-fed
glob: tests/e2e/test_provider_ws_fed*
profiles: selenium
@@ -247,11 +251,12 @@ jobs:
docker compose -f tests/e2e/compose.yml up -d --quiet-pull
- id: cache-web
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v4
if: contains(matrix.job.profiles, 'selenium')
with:
path: web/dist
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
- name: prepare web ui
if: steps.cache-web.outputs.cache-hit != 'true'
if: steps.cache-web.outputs.cache-hit != 'true' && contains(matrix.job.profiles, 'selenium')
working-directory: web
run: |
npm ci
@@ -260,6 +265,7 @@ jobs:
- name: run e2e
run: |
uv run coverage run manage.py test ${{ matrix.job.glob }}
uv run coverage combine
uv run coverage xml
- uses: ./.github/actions/test-results
if: ${{ always() }}
@@ -304,13 +310,14 @@ jobs:
- name: run conformance
run: |
uv run coverage run manage.py test ${{ matrix.job.glob }}
uv run coverage combine
uv run coverage xml
- uses: ./.github/actions/test-results
if: ${{ always() }}
with:
flags: conformance
- if: ${{ !cancelled() }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: conformance-certification-${{ matrix.job.name }}
path: tests/openid_conformance/exports/
@@ -322,7 +329,7 @@ jobs:
- name: Setup authentik env
uses: ./.github/actions/setup
with:
dependencies: rust
dependencies: rust,runtime
- name: run tests
run: |
cargo llvm-cov --no-report nextest --workspace
@@ -333,7 +340,7 @@ jobs:
files: target/llvm-cov-target/rust.json
flags: rust
- if: ${{ !cancelled() }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test-rust
path: target/llvm-cov-target/rust.json

View File

@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: "go.mod"
- name: Prepare and generate API
@@ -41,7 +41,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: "go.mod"
- name: Setup authentik env
@@ -98,14 +98,14 @@ jobs:
image-name: ghcr.io/goauthentik/dev-${{ matrix.type }}
- name: Login to Container Registry
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker Image
id: push
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
tags: ${{ steps.ev.outputs.imageTags }}
file: lifecycle/container/${{ matrix.type }}.Dockerfile
@@ -142,7 +142,7 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: "go.mod"
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5

View File

@@ -42,7 +42,7 @@ jobs:
with:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
compressOnly: ${{ github.event_name != 'pull_request' }}
- uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7
- uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v7
if: "${{ github.event_name != 'pull_request' && steps.compress.outputs.markdown != '' }}"
id: cpr
with:

View File

@@ -26,7 +26,7 @@ jobs:
- name: Setup authentik env
uses: ./.github/actions/setup
- run: uv run ak update_webauthn_mds
- uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7
- uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v7
id: cpr
with:
token: ${{ steps.generate_token.outputs.token }}

View File

@@ -73,7 +73,7 @@ jobs:
- name: Bump version
run: "make bump version=${{ inputs.next_version }}.0-rc1"
- name: Create pull request
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v7
with:
token: ${{ steps.generate_token.outputs.token }}
branch: release-bump-${{ inputs.next_version }}

View File

@@ -44,14 +44,14 @@ jobs:
with:
image-name: ghcr.io/goauthentik/docs
- name: Login to GitHub Container Registry
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker Image
id: push
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
tags: ${{ steps.ev.outputs.imageTags }}
file: website/Dockerfile
@@ -84,7 +84,7 @@ jobs:
- rac
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: "go.mod"
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
@@ -104,18 +104,18 @@ jobs:
with:
image-name: ghcr.io/goauthentik/${{ matrix.type }},authentik/${{ matrix.type }}
- name: Docker Login Registry
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKER_CORP_USERNAME }}
password: ${{ secrets.DOCKER_CORP_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker Image
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
id: push
with:
push: true
@@ -148,7 +148,7 @@ jobs:
goarch: [amd64, arm64]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: "go.mod"
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
@@ -191,7 +191,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0
- uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
with:
role-to-assume: "arn:aws:iam::016170277896:role/github_goauthentik_authentik"
aws-region: ${{ env.AWS_REGION }}
@@ -236,7 +236,7 @@ jobs:
container=$(docker container create ${{ steps.ev.outputs.imageMainName }})
docker cp ${container}:web/ .
- name: Create a Sentry.io release
uses: getsentry/action-release@dab6548b3c03c4717878099e43782cf5be654289 # v3
uses: getsentry/action-release@5657c9e888b4e2cc85f4d29143ea4131fde4a73a # v3
continue-on-error: true
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}

View File

@@ -137,7 +137,7 @@ jobs:
sed -E -i 's/[0-9]{4}\.[0-9]{1,2}\.[0-9]+$/${{ inputs.version }}/' charts/authentik/Chart.yaml
./scripts/helm-docs.sh
- name: Create pull request
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v7
with:
token: "${{ steps.app-token.outputs.token }}"
branch: bump-${{ inputs.version }}
@@ -196,7 +196,7 @@ jobs:
'.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
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v7
with:
token: "${{ steps.app-token.outputs.token }}"
branch: bump-${{ inputs.version }}

View File

@@ -42,7 +42,7 @@ jobs:
make web-check-compile
- name: Create Pull Request
if: ${{ github.event_name != 'pull_request' }}
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v7
with:
token: ${{ steps.generate_token.outputs.token }}
branch: extract-compile-backend-translation

View File

@@ -27,6 +27,7 @@ Makefile @goauthentik/infrastructure
.editorconfig @goauthentik/infrastructure
CODEOWNERS @goauthentik/infrastructure
# Backend packages
packages/ak-* @goauthentik/backend
packages/client-rust @goauthentik/backend
packages/django-channels-postgres @goauthentik/backend
packages/django-postgres-cache @goauthentik/backend

2263
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,10 @@
[workspace]
members = ["packages/client-rust", "website/scripts/docsmg"]
members = [
"packages/ak-axum",
"packages/ak-common",
"packages/client-rust",
"website/scripts/docsmg",
]
resolver = "3"
[workspace.package]
@@ -14,11 +19,29 @@ license-file = "LICENSE"
publish = false
[workspace.dependencies]
arc-swap = "= 1.9.1"
axum-server = { version = "= 0.8.0", features = ["tls-rustls-no-provider"] }
aws-lc-rs = { version = "= 1.16.2", features = ["fips"] }
axum = { version = "= 0.8.8", features = ["http2", "macros", "ws"] }
clap = { version = "= 4.6.0", features = ["derive", "env"] }
client-ip = { version = "0.2.1", features = ["forwarded-header"] }
colored = "= 3.1.1"
config-rs = { package = "config", version = "= 0.15.22", default-features = false, features = [
"json",
"yaml",
] }
console-subscriber = "= 0.5.0"
dotenvy = "= 0.15.7"
durstr = "= 0.5.1"
eyre = "= 0.6.12"
forwarded-header-value = "= 0.1.1"
futures = "= 0.3.32"
glob = "= 0.3.3"
ipnet = { version = "= 2.12.0", features = ["serde"] }
json-subscriber = "= 0.2.8"
nix = { version = "= 0.31.2", features = ["signal"] }
notify = "= 8.2.0"
pin-project-lite = "= 0.2.17"
regex = "= 1.12.3"
reqwest = { version = "= 0.13.2", features = [
"form",
@@ -36,17 +59,54 @@ reqwest-middleware = { version = "= 0.5.1", features = [
"rustls",
] }
rustls = { version = "= 0.23.37", features = ["fips"] }
sentry = { version = "= 0.47.0", default-features = false, features = [
"backtrace",
"contexts",
"debug-images",
"panic",
"rustls",
"reqwest",
"tower",
"tracing",
] }
serde = { version = "= 1.0.228", features = ["derive"] }
serde_json = "= 1.0.149"
serde_repr = "= 0.1.20"
serde_with = { version = "= 3.18.0", default-features = false, features = [
"base64",
] }
tokio = { version = "= 1.50.0", features = ["full"] }
sqlx = { version = "= 0.8.6", default-features = false, features = [
"runtime-tokio",
"tls-rustls-aws-lc-rs",
"postgres",
"derive",
"macros",
"uuid",
"chrono",
"ipnet",
"json",
] }
tempfile = "= 3.27.0"
thiserror = "= 2.0.18"
time = { version = "= 0.3.47", features = ["macros"] }
tokio = { version = "= 1.51.1", features = ["full", "tracing"] }
tokio-rustls = "= 0.26.4"
tokio-util = { version = "= 0.7.18", features = ["full"] }
tower = "= 0.5.3"
tower-http = { version = "= 0.6.8", features = ["timeout"] }
tracing = "= 0.1.44"
tracing-error = "= 0.2.1"
tracing-subscriber = { version = "= 0.3.23", features = [
"env-filter",
"json",
"local-time",
"tracing-log",
] }
url = "= 2.5.8"
uuid = { version = "= 1.23.0", features = ["serde", "v4"] }
ak-common = { package = "authentik-common", version = "2026.5.0-rc1", path = "./packages/ak-common", default-features = false }
[profile.dev.package.backtrace]
opt-level = 3
@@ -89,12 +149,20 @@ perf = { priority = -1, level = "warn" }
style = { priority = -1, level = "warn" }
suspicious = { priority = -1, level = "warn" }
### and disable the ones we don't want
### cargo group
multiple_crate_versions = "allow"
### pedantic group
missing_errors_doc = "allow"
missing_panics_doc = "allow"
must_use_candidate = "allow"
redundant_closure_for_method_calls = "allow"
struct_field_names = "allow"
too_many_lines = "allow"
### nursery
redundant_pub_crate = "allow"
missing_const_for_fn = "allow"
option_if_let_else = "allow"
redundant_pub_crate = "allow"
significant_drop_tightening = "allow"
### restriction group
allow_attributes = "warn"
allow_attributes_without_reason = "warn"
@@ -107,7 +175,6 @@ create_dir = "warn"
dbg_macro = "warn"
default_numeric_fallback = "warn"
disallowed_script_idents = "warn"
doc_paragraphs_missing_punctuation = "warn"
empty_drop = "warn"
empty_enum_variants_with_brackets = "warn"
empty_structs_with_brackets = "warn"

View File

@@ -74,6 +74,7 @@ rust-test: ## Run the Rust tests
test: ## Run the server tests and produce a coverage report (locally)
$(UV) run coverage run manage.py test --keepdb $(or $(filter-out $@,$(MAKECMDGOALS)),authentik)
$(UV) run coverage combine
$(UV) run coverage html
$(UV) run coverage report
@@ -143,8 +144,14 @@ dev-create-db:
dev-reset: dev-drop-db dev-create-db migrate ## Drop and restore the Authentik PostgreSQL instance to a "fresh install" state.
update-test-mmdb: ## Update test GeoIP and ASN Databases
curl -L https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-ASN-Test.mmdb -o ${PWD}/tests/GeoLite2-ASN-Test.mmdb
curl -L https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-City-Test.mmdb -o ${PWD}/tests/GeoLite2-City-Test.mmdb
curl \
-L \
-o ${PWD}/tests/geoip/GeoLite2-ASN-Test.mmdb \
https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-ASN-Test.mmdb
curl \
-L \
-o ${PWD}/tests/geoip/GeoLite2-City-Test.mmdb \
https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-City-Test.mmdb
bump: ## Bump authentik version. Usage: make bump version=20xx.xx.xx
ifndef version
@@ -153,6 +160,7 @@ endif
$(eval current_version := $(shell cat ${PWD}/internal/constants/VERSION))
$(SED_INPLACE) 's/^version = ".*"/version = "$(version)"/' ${PWD}/pyproject.toml
$(SED_INPLACE) 's/^VERSION = ".*"/VERSION = "$(version)"/' ${PWD}/authentik/__init__.py
$(SED_INPLACE) "s/version = \"${current_version}\"/version = \"$(version)\"" ${PWD}/Cargo.toml ${PWD}/Cargo.lock
$(MAKE) gen-build gen-compose aws-cfn
$(SED_INPLACE) "s/\"${current_version}\"/\"$(version)\"/" ${PWD}/package.json ${PWD}/package-lock.json ${PWD}/web/package.json ${PWD}/web/package-lock.json
echo -n $(version) > ${PWD}/internal/constants/VERSION
@@ -284,7 +292,7 @@ docs-api-build:
npm run --prefix website -w api build
docs-api-watch: ## Build and watch the API documentation
npm run --prefix website -w api build:api
npm run --prefix website -w api generate
npm run --prefix website -w api start
docs-api-clean: ## Clean generated API documentation
@@ -342,6 +350,7 @@ ci-lint-clippy: ci--meta-debug
$(CARGO) clippy --workspace -- -D warnings
ci-test: ci--meta-debug
$(UV) run coverage run manage.py test --keepdb authentik
$(UV) run coverage run manage.py test --keepdb --parallel auto authentik
$(UV) run coverage combine
$(UV) run coverage report
$(UV) run coverage xml

View File

@@ -18,10 +18,10 @@ Even if the issue is not a CVE, we still greatly appreciate your help in hardeni
(.x being the latest patch release for each version)
| Version | Supported |
| ---------- | ---------- |
| 2025.12.x | ✅ |
| 2026.2.x | ✅ |
| Version | Supported |
| --------- | --------- |
| 2025.12.x | ✅ |
| 2026.2.x | ✅ |
## Reporting a Vulnerability
@@ -60,6 +60,40 @@ authentik reserves the right to reclassify CVSS as necessary. To determine sever
| 7.0 8.9 | High |
| 9.0 10.0 | Critical |
## Intended functionality
The following capabilities are part of intentional system design and should not be reported as security vulnerabilities:
- Expressions (property mappings/policies/prompts) can execute arbitrary Python code without safeguards.
This is expected behavior. Any user with permission to create or modify objects containing expression fields can write code that is executed within authentik. If a vulnerability allows a user without the required permissions to write or modify code and have it executed, that would be a valid security report.
However, the fact that expressions are executed as part of normal operations is not considered a privilege escalation or security vulnerability.
- Blueprints can access all files on the filesystem.
This access is intentional to allow legitimate configuration and deployment tasks. It does not represent a security problem by itself.
- Importing blueprints allows arbitrary modification of application objects.
This is intended functionality. This behavior reflects the privileged design of blueprint imports. It is "exploitable" when importing blueprints from untrusted sources without reviewing the blueprint beforehand. However, any method to create, modify or execute blueprints without the required permissions would be a valid security report.
- Flow imports may contain objects other than flows (such as policies, users, groups, etc.)
This is expected behavior as flow imports are blueprint files.
- Prompt HTML is not escaped.
Prompts intentionally allow raw HTML, including script tags, so they can be used to create interactive or customized user interface elements. Because of this, scripts within prompts may affect or interact with the surrounding page as designed.
- Open redirects that do not include tokens or other sensitive information are not considered a security vulnerability.
Redirects that only change navigation flow and do not expose session tokens, API keys, or other confidential data are considered acceptable and do not require reporting.
- Outgoing network requests are not filtered.
The destinations of outgoing network requests (HTTP, TCP, etc.) made by authentik to configurable endpoints through objects such as OAuth Sources, SSO Providers, and others are not validated. Depending on your threat model, these requests should be restricted at the network level using appropriate firewall or network policies.
## Disclosure process
1. Report from Github or Issue is reported via Email as listed above.

View File

@@ -1,10 +1,18 @@
"""Pagination which includes total pages and current page"""
from typing import TYPE_CHECKING
from drf_spectacular.plumbing import build_object_type
from rest_framework import pagination
from rest_framework.response import Response
from authentik.api.search.ql import QLSearch
from authentik.api.v3.schema.pagination import PAGINATION
from authentik.api.v3.schema.search import AUTOCOMPLETE_SCHEMA
if TYPE_CHECKING:
from django.db.models import QuerySet
from rest_framework.request import Request
class Pagination(pagination.PageNumberPagination):
@@ -13,14 +21,14 @@ class Pagination(pagination.PageNumberPagination):
page_query_param = "page"
page_size_query_param = "page_size"
def get_page_size(self, request):
def get_page_size(self, request: Request) -> int:
if self.page_size_query_param in request.query_params:
page_size = super().get_page_size(request)
if page_size is not None:
return min(super().get_page_size(request), request.tenant.pagination_max_page_size)
return request.tenant.pagination_default_page_size
def get_paginated_response(self, data):
def get_paginated_response(self, data) -> Response:
previous_page_number = 0
if self.page.has_previous():
previous_page_number = self.page.previous_page_number()
@@ -39,16 +47,33 @@ class Pagination(pagination.PageNumberPagination):
"end_index": self.page.end_index(),
},
"results": data,
"autocomplete": self.get_autocomplete(),
}
)
def paginate_queryset(self, queryset: QuerySet, request: Request, view=None):
self.view = view
return super().paginate_queryset(queryset, request, view)
def get_autocomplete(self):
schema = QLSearch().get_schema(self.request, self.view)
introspections = {}
if hasattr(self.view, "get_ql_fields"):
from authentik.api.search.schema import AKQLSchemaSerializer
introspections = AKQLSchemaSerializer().serialize(
schema(self.page.paginator.object_list.model)
)
return introspections
def get_paginated_response_schema(self, schema):
return build_object_type(
properties={
"pagination": PAGINATION.ref,
"results": schema,
"autocomplete": AUTOCOMPLETE_SCHEMA.ref,
},
required=["pagination", "results"],
required=["pagination", "results", "autocomplete"],
)

View File

@@ -1,25 +1,17 @@
"""DjangoQL search"""
from django.apps import apps
from django.db.models import QuerySet
from djangoql.ast import Name
from djangoql.exceptions import DjangoQLError
from djangoql.queryset import apply_search
from djangoql.schema import DjangoQLSchema
from drf_spectacular.plumbing import ResolvedComponent, build_object_type
from rest_framework.filters import SearchFilter
from rest_framework.request import Request
from structlog.stdlib import get_logger
from authentik.enterprise.search.fields import JSONSearchField
from authentik.api.search.fields import JSONSearchField
LOGGER = get_logger()
AUTOCOMPLETE_SCHEMA = ResolvedComponent(
name="Autocomplete",
object="Autocomplete",
type=ResolvedComponent.SCHEMA,
schema=build_object_type(additionalProperties={}),
)
class BaseSchema(DjangoQLSchema):
@@ -48,10 +40,6 @@ class QLSearch(SearchFilter):
super().__init__()
self._fallback = SearchFilter()
@property
def enabled(self):
return apps.get_app_config("authentik_enterprise").enabled()
def get_search_terms(self, request: Request) -> str:
"""Search terms are set by a ?search=... query parameter,
and may be comma and/or whitespace delimited."""
@@ -73,7 +61,7 @@ class QLSearch(SearchFilter):
def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet:
search_query = self.get_search_terms(request)
schema = self.get_schema(request, view)
if len(search_query) == 0 or not self.enabled:
if len(search_query) == 0:
return self._fallback.filter_queryset(request, queryset, view)
try:
return apply_search(queryset, search_query, schema=schema)

View File

@@ -1,8 +1,6 @@
from djangoql.serializers import DjangoQLSchemaSerializer
from drf_spectacular.generators import SchemaGenerator
from authentik.enterprise.search.fields import JSONSearchField
from authentik.enterprise.search.ql import AUTOCOMPLETE_SCHEMA
from authentik.api.search.fields import JSONSearchField
class AKQLSchemaSerializer(DjangoQLSchemaSerializer):
@@ -20,9 +18,3 @@ class AKQLSchemaSerializer(DjangoQLSchemaSerializer):
if isinstance(field, JSONSearchField):
result["relation"] = field.relation()
return result
def postprocess_schema_search_autocomplete(result, generator: SchemaGenerator, **kwargs):
generator.registry.register_on_missing(AUTOCOMPLETE_SCHEMA)
return result

View File

@@ -1,5 +1,4 @@
from json import loads
from unittest.mock import PropertyMock, patch
from urllib.parse import urlencode
from django.urls import reverse
@@ -8,10 +7,6 @@ from rest_framework.test import APITestCase
from authentik.core.tests.utils import create_test_admin_user
@patch(
"authentik.enterprise.audit.middleware.EnterpriseAuditMiddleware.enabled",
PropertyMock(return_value=True),
)
class QLTest(APITestCase):
def setUp(self):

View File

@@ -0,0 +1,20 @@
from typing import TYPE_CHECKING
from drf_spectacular.plumbing import ResolvedComponent, build_object_type
if TYPE_CHECKING:
from drf_spectacular.generators import SchemaGenerator
AUTOCOMPLETE_SCHEMA = ResolvedComponent(
name="Autocomplete",
object="Autocomplete",
type=ResolvedComponent.SCHEMA,
schema=build_object_type(additionalProperties={}),
)
def postprocess_schema_search_autocomplete(result, generator: SchemaGenerator, **kwargs):
generator.registry.register_on_missing(AUTOCOMPLETE_SCHEMA)
return result

View File

@@ -1,24 +1,60 @@
"""Serializer mixin for managed models"""
from typing import cast
from django.conf import settings
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.utils.translation import gettext_lazy as _
from drf_spectacular.utils import extend_schema, inline_serializer
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, DateTimeField
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.fields import (
BooleanField,
CharField,
DateTimeField,
FileField,
)
from rest_framework.parsers import MultiPartParser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ListSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.api.validation import validate
from authentik.blueprints.models import BlueprintInstance
from authentik.blueprints.v1.common import Blueprint
from authentik.blueprints.v1.importer import Importer
from authentik.blueprints.v1.oci import OCI_PREFIX
from authentik.blueprints.v1.tasks import apply_blueprint, blueprints_find_dict
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import JSONDictField, ModelSerializer, PassiveSerializer
from authentik.core.models import User
from authentik.events.logs import LogEventSerializer
from authentik.rbac.decorators import permission_required
def get_blueprints():
if settings.DEBUG:
return blueprints_find_dict()
return blueprints_find_dict.send().get_result(block=True)
class BlueprintUploadSerializer(PassiveSerializer):
"""Serializer to upload file"""
file = FileField(required=False)
path = CharField(required=False)
def validate_path(self, path: str) -> str:
"""Ensure the path (if set) specified is retrievable"""
if path == "":
return path
files: list[dict] = get_blueprints()
if path not in [file["path"] for file in files]:
raise ValidationError(_("Blueprint file does not exist"))
return path
class ManagedSerializer:
"""Managed Serializer"""
@@ -39,7 +75,7 @@ class BlueprintInstanceSerializer(ModelSerializer):
"""Ensure the path (if set) specified is retrievable"""
if path == "" or path.startswith(OCI_PREFIX):
return path
files: list[dict] = blueprints_find_dict.send().get_result(block=True)
files: list[dict] = get_blueprints()
if path not in [file["path"] for file in files]:
raise ValidationError(_("Blueprint file does not exist"))
return path
@@ -88,6 +124,33 @@ class BlueprintInstanceSerializer(ModelSerializer):
}
def check_blueprint_perms(blueprint: Blueprint, user: User, explicit_action: str | None = None):
"""Check for individual permissions for each model in a blueprint"""
for entry in blueprint.entries:
full_model = entry.get_model(blueprint)
app, __, model = full_model.partition(".")
perms = [
f"{app}.add_{model}",
f"{app}.change_{model}",
f"{app}.delete_{model}",
]
if explicit_action:
perms = [f"{app}.{explicit_action}_{model}"]
for perm in perms:
if not user.has_perm(perm):
raise PermissionDenied(
{
entry.id: _(
"User lacks permission to create {model}".format_map(
{
"model": full_model,
}
)
)
}
)
class BlueprintInstanceViewSet(UsedByMixin, ModelViewSet):
"""Blueprint instances"""
@@ -97,6 +160,12 @@ class BlueprintInstanceViewSet(UsedByMixin, ModelViewSet):
filterset_fields = ["name", "path"]
ordering = ["name"]
class BlueprintImportResultSerializer(PassiveSerializer):
"""Logs of an attempted blueprint import"""
logs = LogEventSerializer(many=True, read_only=True)
success = BooleanField(read_only=True)
@extend_schema(
responses={
200: ListSerializer(
@@ -115,7 +184,7 @@ class BlueprintInstanceViewSet(UsedByMixin, ModelViewSet):
@action(detail=False, pagination_class=None, filter_backends=[])
def available(self, request: Request) -> Response:
"""Get blueprints"""
files: list[dict] = blueprints_find_dict.send().get_result(block=True)
files: list[dict] = get_blueprints()
return Response(files)
@permission_required("authentik_blueprints.view_blueprintinstance")
@@ -131,3 +200,53 @@ class BlueprintInstanceViewSet(UsedByMixin, ModelViewSet):
blueprint = self.get_object()
apply_blueprint.send_with_options(args=(blueprint.pk,), rel_obj=blueprint)
return self.retrieve(request, *args, **kwargs)
@extend_schema(
request={"multipart/form-data": BlueprintUploadSerializer},
responses={
204: BlueprintImportResultSerializer,
400: BlueprintImportResultSerializer,
},
)
@action(url_path="import", detail=False, methods=["POST"], parser_classes=(MultiPartParser,))
@validate(
BlueprintUploadSerializer,
)
def import_(self, request: Request, body: BlueprintUploadSerializer) -> Response:
"""Import blueprint from .yaml file and apply it once, without creating an instance"""
string_contents = ""
if body.validated_data.get("file"):
file = cast(InMemoryUploadedFile, body.validated_data["file"])
string_contents = file.read().decode()
elif body.validated_data.get("path"):
string_contents = BlueprintInstance(
path=body.validated_data.get("path")
).retrieve_file()
else:
raise ValidationError("Either path or file must be set")
importer = Importer.from_string(string_contents)
check_blueprint_perms(importer.blueprint, request.user)
valid, logs = importer.validate()
import_response = self.BlueprintImportResultSerializer(
data={
"logs": [],
"success": False,
}
)
import_response.is_valid(raise_exception=True)
import_response.initial_data["logs"] = [LogEventSerializer(log).data for log in logs]
import_response.initial_data["success"] = valid
import_response.is_valid()
if not valid:
return Response(data=import_response.initial_data, status=200)
successful = importer.apply()
import_response.initial_data["success"] = successful
import_response.is_valid()
if not successful:
return Response(data=import_response.initial_data, status=200)
return Response(data=import_response.initial_data, status=200)

View File

@@ -21,6 +21,9 @@ PROMPT_CONSENT = "consent"
PROMPT_LOGIN = "login"
PLAN_CONTEXT_OIDC_LOGOUT_IFRAME_SESSIONS = "goauthentik.io/providers/oauth2/iframe_sessions"
PLAN_CONTEXT_POST_LOGOUT_REDIRECT_URI = "goauthentik.io/providers/oauth2/post_logout_redirect_uri"
OAUTH2_BINDING = "redirect"
SCOPE_OPENID = "openid"
SCOPE_OPENID_PROFILE = "profile"
@@ -37,6 +40,9 @@ TOKEN_TYPE = "Bearer" # nosec
SCOPE_AUTHENTIK_API = "goauthentik.io/api"
# URI schemes that are forbidden for redirect URIs
FORBIDDEN_URI_SCHEMES = {"javascript", "data", "vbscript"}
# Read/write full user (including email)
SCOPE_GITHUB_USER = "user"
# Read user (without email)

View File

@@ -48,7 +48,12 @@ class ApplicationSerializer(ModelSerializer):
"""Application Serializer"""
launch_url = SerializerMethodField()
provider_obj = ProviderSerializer(source="get_provider", required=False, read_only=True)
provider_obj = ProviderSerializer(
source="get_provider",
required=False,
read_only=True,
allow_null=True,
)
backchannel_providers_obj = ProviderSerializer(
source="backchannel_providers", required=False, read_only=True, many=True
)

View File

@@ -7,6 +7,7 @@ from django.http import Http404
from django.utils.translation import gettext as _
from django_filters.filters import CharFilter, ModelMultipleChoiceFilter
from django_filters.filterset import FilterSet
from djangoql.schema import BoolField, StrField
from drf_spectacular.utils import (
OpenApiParameter,
OpenApiResponse,
@@ -25,6 +26,9 @@ from rest_framework.serializers import ListSerializer, ValidationError
from rest_framework.viewsets import ModelViewSet
from authentik.api.authentication import TokenAuthentication
from authentik.api.search.fields import (
JSONSearchField,
)
from authentik.api.validation import validate
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import JSONDictField, ModelSerializer, PassiveSerializer
@@ -265,12 +269,6 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
]
def get_ql_fields(self):
from djangoql.schema import BoolField, StrField
from authentik.enterprise.search.fields import (
JSONSearchField,
)
return [
StrField(Group, "name"),
BoolField(Group, "is_superuser", nullable=True),

View File

@@ -2,9 +2,8 @@
from django.apps import apps
from django.db.models import Model
from django.utils.translation import gettext as _
from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema, extend_schema_field
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.exceptions import ValidationError
from rest_framework.fields import BooleanField, CharField, ChoiceField, DictField, ListField
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
@@ -13,6 +12,7 @@ from rest_framework.views import APIView
from yaml import ScalarNode
from authentik.api.validation import validate
from authentik.blueprints.api import check_blueprint_perms
from authentik.blueprints.v1.common import (
Blueprint,
BlueprintEntry,
@@ -165,21 +165,7 @@ class TransactionalApplicationView(APIView):
def put(self, request: Request, body: TransactionApplicationSerializer) -> Response:
"""Convert data into a blueprint, validate it and apply it"""
blueprint: Blueprint = body.validated_data
for entry in blueprint.entries:
full_model = entry.get_model(blueprint)
app, __, model = full_model.partition(".")
if not request.user.has_perm(f"{app}.add_{model}"):
raise PermissionDenied(
{
entry.id: _(
"User lacks permission to create {model}".format_map(
{
"model": full_model,
}
)
)
}
)
check_blueprint_perms(blueprint, request.user, explicit_action="add")
importer = Importer(blueprint, {})
applied = importer.apply()
response = {"applied": False, "logs": []}

View File

@@ -22,6 +22,7 @@ from django_filters.filters import (
UUIDFilter,
)
from django_filters.filterset import FilterSet
from djangoql.schema import BoolField, StrField
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import (
OpenApiParameter,
@@ -55,6 +56,10 @@ from rest_framework.viewsets import ModelViewSet
from structlog.stdlib import get_logger
from authentik.api.authentication import TokenAuthentication
from authentik.api.search.fields import (
ChoiceSearchField,
JSONSearchField,
)
from authentik.api.validation import validate
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
from authentik.brands.models import Brand
@@ -524,13 +529,6 @@ class UserViewSet(
]
def get_ql_fields(self):
from djangoql.schema import BoolField, StrField
from authentik.enterprise.search.fields import (
ChoiceSearchField,
JSONSearchField,
)
return [
StrField(User, "username"),
StrField(User, "name"),

View File

@@ -796,11 +796,11 @@ class Application(SerializerModel, PolicyBindingModel):
def backchannel_provider_for[T: Provider](self, provider_type: type[T], **kwargs) -> T | None:
"""Get Backchannel provider for a specific type"""
providers = self.backchannel_providers.filter(
provider: BackchannelProvider | None = self.backchannel_providers.filter(
**{f"{provider_type._meta.model_name}__isnull": False},
**kwargs,
)
return getattr(providers.first(), provider_type._meta.model_name)
).first()
return getattr(provider, provider_type._meta.model_name) if provider else None
def __str__(self):
return str(self.name)

View File

@@ -72,6 +72,7 @@ class SessionStore(SessionBase):
# and their descriptors fail to initialize (e.g., missing storage)
# TypeError - can happen with incompatible pickled objects
# If any of these happen, just return an empty dictionary (an empty session)
LOGGER.warning("Failed to decode session data", exc_info=True)
pass
return {}

View File

@@ -18,7 +18,10 @@ from authentik.rbac.decorators import permission_required
class EnrollmentTokenSerializer(ModelSerializer):
device_group_obj = DeviceAccessGroupSerializer(
source="device_group", read_only=True, required=False
source="device_group",
read_only=True,
required=False,
allow_null=True,
)
def __init__(self, *args, **kwargs) -> None:

View File

@@ -18,6 +18,10 @@ class SSFProviderSerializer(EnterpriseRequiredMixin, ProviderSerializer):
ssf_url = SerializerMethodField()
token_obj = TokenSerializer(source="token", required=False, read_only=True)
oidc_auth_providers_obj = ProviderSerializer(
read_only=True, source="oidc_auth_providers", many=True
)
def get_ssf_url(self, instance: SSFProvider) -> str | None:
request: Request = self._context.get("request")
if not request:
@@ -45,8 +49,10 @@ class SSFProviderSerializer(EnterpriseRequiredMixin, ProviderSerializer):
"signing_key",
"token_obj",
"oidc_auth_providers",
"oidc_auth_providers_obj",
"ssf_url",
"event_retention",
"push_verify_certificates",
]
extra_kwargs = {}
@@ -54,7 +60,7 @@ class SSFProviderSerializer(EnterpriseRequiredMixin, ProviderSerializer):
class SSFProviderViewSet(UsedByMixin, ModelViewSet):
"""SSFProvider Viewset"""
queryset = SSFProvider.objects.all()
queryset = SSFProvider.objects.all().prefetch_related("oidc_auth_providers")
serializer_class = SSFProviderSerializer
filterset_fields = {
"application": ["isnull"],

View File

@@ -1,6 +1,7 @@
"""SSF Stream API Views"""
from rest_framework.viewsets import ReadOnlyModelViewSet
from rest_framework import mixins
from rest_framework.viewsets import GenericViewSet
from authentik.core.api.utils import ModelSerializer
from authentik.enterprise.providers.ssf.api.providers import SSFProviderSerializer
@@ -16,6 +17,7 @@ class SSFStreamSerializer(ModelSerializer):
model = Stream
fields = [
"pk",
"status",
"provider",
"provider_obj",
"delivery_method",
@@ -27,7 +29,12 @@ class SSFStreamSerializer(ModelSerializer):
]
class SSFStreamViewSet(ReadOnlyModelViewSet):
class SSFStreamViewSet(
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet,
):
"""SSFStream Viewset"""
queryset = Stream.objects.all()

View File

@@ -0,0 +1,43 @@
# Generated by Django 5.2.12 on 2026-04-04 16:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_ssf", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="ssfprovider",
name="push_verify_certificates",
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name="stream",
name="authorization_header",
field=models.TextField(default=None, null=True),
),
migrations.AddField(
model_name="stream",
name="status",
field=models.TextField(
choices=[("enabled", "Enabled"), ("paused", "Paused"), ("disabled", "Disabled")],
default="enabled",
),
),
migrations.AlterField(
model_name="stream",
name="delivery_method",
field=models.TextField(
choices=[
("https://schemas.openid.net/secevent/risc/delivery-method/push", "Risc Push"),
("https://schemas.openid.net/secevent/risc/delivery-method/poll", "Risc Poll"),
("urn:ietf:rfc:8935", "SSF RFC Push"),
("urn:ietf:rfc:8936", "SSF RFC Pull"),
]
),
),
]

View File

@@ -33,6 +33,8 @@ class DeliveryMethods(models.TextChoices):
RISC_PUSH = "https://schemas.openid.net/secevent/risc/delivery-method/push"
RISC_POLL = "https://schemas.openid.net/secevent/risc/delivery-method/poll"
RFC_PUSH = "urn:ietf:rfc:8935", _("SSF RFC Push")
RFC_PULL = "urn:ietf:rfc:8936", _("SSF RFC Pull")
class SSFEventStatus(models.TextChoices):
@@ -43,6 +45,13 @@ class SSFEventStatus(models.TextChoices):
SENT = "sent"
class StreamStatus(models.TextChoices):
ENABLED = "enabled"
PAUSED = "paused"
DISABLED = "disabled"
class SSFProvider(TasksModel, BackchannelProvider):
"""Shared Signals Framework provider to allow applications to
receive user events from authentik."""
@@ -54,6 +63,8 @@ class SSFProvider(TasksModel, BackchannelProvider):
help_text=_("Key used to sign the SSF Events."),
)
push_verify_certificates = models.BooleanField(default=True)
oidc_auth_providers = models.ManyToManyField(OAuth2Provider, blank=True, default=None)
token = models.ForeignKey(Token, on_delete=models.CASCADE, null=True, default=None)
@@ -106,10 +117,14 @@ class Stream(models.Model):
"""SSF Stream"""
uuid = models.UUIDField(default=uuid4, primary_key=True, editable=False)
status = models.TextField(choices=StreamStatus.choices, default=StreamStatus.ENABLED)
provider = models.ForeignKey(SSFProvider, on_delete=models.CASCADE)
delivery_method = models.TextField(choices=DeliveryMethods.choices)
endpoint_url = models.TextField(null=True)
authorization_header = models.TextField(null=True, default=None)
events_requested = ArrayField(models.TextField(choices=EventTypes.choices), default=list)
format = models.TextField()
@@ -146,7 +161,7 @@ class Stream(models.Model):
}
def encode(self, data: dict) -> str:
headers = {}
headers = {"typ": "secevent+jwt"}
if self.provider.signing_key:
headers["kid"] = self.provider.signing_key.kid
key, alg = self.provider.jwt_key

View File

@@ -16,6 +16,7 @@ from authentik.enterprise.providers.ssf.models import (
SSFEventStatus,
Stream,
StreamEvent,
StreamStatus,
)
from authentik.lib.utils.http import get_http_session
from authentik.lib.utils.time import timedelta_from_string
@@ -88,23 +89,42 @@ def send_ssf_event(stream_uuid: UUID, event_data: dict[str, Any]):
self.set_uid(event.pk)
if event.status == SSFEventStatus.SENT:
return
if stream.delivery_method != DeliveryMethods.RISC_PUSH:
if stream.delivery_method not in [DeliveryMethods.RISC_PUSH, DeliveryMethods.RFC_PUSH]:
return
headers = {"Content-Type": "application/secevent+jwt", "Accept": "application/json"}
if stream.authorization_header:
headers["Authorization"] = stream.authorization_header
try:
response = session.post(
event.stream.endpoint_url,
data=event.stream.encode(event.payload),
headers={"Content-Type": "application/secevent+jwt", "Accept": "application/json"},
headers=headers,
verify=stream.provider.push_verify_certificates,
timeout=180,
)
response.raise_for_status()
event.status = SSFEventStatus.SENT
event.save()
return
self.info("Event successfully sent", status=response.status_code)
# Cleanup, if we were the last pending message for this stream and it has been deleted
# (status=StreamStatus.DISABLED), then we can delete the stream
if (
not StreamEvent.objects.filter(
stream=stream,
status__in=[SSFEventStatus.PENDING_FAILED, SSFEventStatus.PENDING_NEW],
).exists()
and stream.status == StreamStatus.DISABLED
):
LOGGER.info(
"Deleting inactive stream as all pending messages were sent.", stream=stream
)
self.info("Deleting inactive stream as all pending messages were sent.")
stream.delete()
except RequestException as exc:
LOGGER.warning("Failed to send SSF event", exc=exc)
LOGGER.warning("Failed to send SSF event", exc=exc, stream=stream)
attrs = {}
if exc.response:
if exc.response is not None:
attrs["response"] = {
"content": exc.response.text,
"status": exc.response.status_code,
@@ -113,5 +133,6 @@ def send_ssf_event(stream_uuid: UUID, event_data: dict[str, Any]):
self.warning("Failed to send request", **attrs)
# Re-up the expiry of the stream event
event.expires = now() + timedelta_from_string(event.stream.provider.event_retention)
self.info(f"Event will be re-sent at {event.expires}")
event.status = SSFEventStatus.PENDING_FAILED
event.save()

View File

@@ -0,0 +1,170 @@
import json
from dataclasses import asdict
from django.urls import reverse
from django.utils import timezone
from rest_framework.test import APITestCase
from authentik.core.models import Application, Token, TokenIntents
from authentik.core.tests.utils import (
create_test_admin_user,
create_test_cert,
create_test_flow,
create_test_user,
)
from authentik.enterprise.providers.ssf.models import (
SSFEventStatus,
SSFProvider,
Stream,
StreamEvent,
)
from authentik.lib.generators import generate_id
from authentik.providers.oauth2.id_token import IDToken
from authentik.providers.oauth2.models import AccessToken, OAuth2Provider
class TestSSFAuth(APITestCase):
def setUp(self):
self.application = Application.objects.create(name=generate_id(), slug=generate_id())
self.provider = SSFProvider.objects.create(
name=generate_id(),
signing_key=create_test_cert(),
backchannel_application=self.application,
)
def test_stream_add_token(self):
"""test stream add (token auth)"""
res = self.client.post(
reverse(
"authentik_providers_ssf:stream",
kwargs={"application_slug": self.application.slug},
),
data={
"iss": "https://authentik.company/.well-known/ssf-configuration/foo/5",
"aud": ["https://app.authentik.company"],
"delivery": {
"method": "https://schemas.openid.net/secevent/risc/delivery-method/push",
"endpoint_url": "https://app.authentik.company",
},
"events_requested": [
"https://schemas.openid.net/secevent/caep/event-type/credential-change",
"https://schemas.openid.net/secevent/caep/event-type/session-revoked",
],
"format": "iss_sub",
},
HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}",
)
self.assertEqual(res.status_code, 201)
stream = Stream.objects.filter(provider=self.provider).first()
self.assertIsNotNone(stream)
event = StreamEvent.objects.filter(stream=stream).first()
self.assertIsNotNone(event)
self.assertEqual(event.status, SSFEventStatus.PENDING_FAILED)
self.assertEqual(
event.payload["events"],
{"https://schemas.openid.net/secevent/ssf/event-type/verification": {"state": None}},
)
def test_stream_add_oidc(self):
"""test stream add (oidc auth)"""
provider = OAuth2Provider.objects.create(
name=generate_id(),
authorization_flow=create_test_flow(),
)
self.application.provider = provider
self.application.save()
user = create_test_admin_user()
token = AccessToken.objects.create(
provider=provider,
user=user,
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
),
)
res = self.client.post(
reverse(
"authentik_providers_ssf:stream",
kwargs={"application_slug": self.application.slug},
),
data={
"iss": "https://authentik.company/.well-known/ssf-configuration/foo/5",
"aud": ["https://app.authentik.company"],
"delivery": {
"method": "https://schemas.openid.net/secevent/risc/delivery-method/push",
"endpoint_url": "https://app.authentik.company",
},
"events_requested": [
"https://schemas.openid.net/secevent/caep/event-type/credential-change",
"https://schemas.openid.net/secevent/caep/event-type/session-revoked",
],
"format": "iss_sub",
},
HTTP_AUTHORIZATION=f"Bearer {token.token}",
)
self.assertEqual(res.status_code, 201)
stream = Stream.objects.filter(provider=self.provider).first()
self.assertIsNotNone(stream)
event = StreamEvent.objects.filter(stream=stream).first()
self.assertIsNotNone(event)
self.assertEqual(event.status, SSFEventStatus.PENDING_FAILED)
self.assertEqual(
event.payload["events"],
{"https://schemas.openid.net/secevent/ssf/event-type/verification": {"state": None}},
)
def test_token_invalid(self):
res = self.client.post(
reverse(
"authentik_providers_ssf:stream",
kwargs={"application_slug": self.application.slug},
),
data={
"iss": "https://authentik.company/.well-known/ssf-configuration/foo/5",
"aud": ["https://app.authentik.company"],
"delivery": {
"method": "https://schemas.openid.net/secevent/risc/delivery-method/push",
"endpoint_url": "https://app.authentik.company",
},
"events_requested": [
"https://schemas.openid.net/secevent/caep/event-type/credential-change",
"https://schemas.openid.net/secevent/caep/event-type/session-revoked",
],
"format": "iss_sub",
},
HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}a",
)
# Response code needs to be 401 according to spec
self.assertEqual(res.status_code, 401)
def test_token_unrelated(self):
token = Token.objects.create(
identifier=generate_id(), user=create_test_user(), intent=TokenIntents.INTENT_API
)
res = self.client.post(
reverse(
"authentik_providers_ssf:stream",
kwargs={"application_slug": self.application.slug},
),
data={
"iss": "https://authentik.company/.well-known/ssf-configuration/foo/5",
"aud": ["https://app.authentik.company"],
"delivery": {
"method": "https://schemas.openid.net/secevent/risc/delivery-method/push",
"endpoint_url": "https://app.authentik.company",
},
"events_requested": [
"https://schemas.openid.net/secevent/caep/event-type/credential-change",
"https://schemas.openid.net/secevent/caep/event-type/session-revoked",
],
"format": "iss_sub",
},
HTTP_AUTHORIZATION=f"Bearer {token.key}",
)
# Response code needs to be 401 according to spec
self.assertEqual(res.status_code, 401)

View File

@@ -44,3 +44,15 @@ class TestConfiguration(APITestCase):
self.assertEqual(res.status_code, 200)
content = json.loads(res.content)
self.assertEqual(content["spec_version"], "1_0-ID2")
def test_config_not_found(self):
"""test SSF configuration (authenticated)"""
self.provider.delete()
res = self.client.get(
reverse(
"authentik_providers_ssf:configuration",
kwargs={"application_slug": self.application.slug},
),
HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}",
)
self.assertEqual(res.status_code, 404)

View File

@@ -1,21 +1,18 @@
import json
from dataclasses import asdict
from uuid import uuid4
from django.urls import reverse
from django.utils import timezone
from rest_framework.test import APITestCase
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
from authentik.core.tests.utils import create_test_cert
from authentik.enterprise.providers.ssf.models import (
SSFEventStatus,
SSFProvider,
Stream,
StreamEvent,
StreamStatus,
)
from authentik.lib.generators import generate_id
from authentik.providers.oauth2.id_token import IDToken
from authentik.providers.oauth2.models import AccessToken, OAuth2Provider
class TestStream(APITestCase):
@@ -87,29 +84,71 @@ class TestStream(APITestCase):
{"delivery": {"method": ["Polling for SSF events is not currently supported."]}},
)
def test_stream_add_oidc(self):
"""test stream add (oidc auth)"""
provider = OAuth2Provider.objects.create(
name=generate_id(),
authorization_flow=create_test_flow(),
)
self.application.provider = provider
self.application.save()
user = create_test_admin_user()
token = AccessToken.objects.create(
provider=provider,
user=user,
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
def test_stream_delete(self):
"""delete stream"""
stream = Stream.objects.create(provider=self.provider)
res = self.client.delete(
reverse(
"authentik_providers_ssf:stream",
kwargs={"application_slug": self.application.slug},
),
HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}",
)
self.assertEqual(res.status_code, 204)
stream.refresh_from_db()
self.assertEqual(stream.status, StreamStatus.DISABLED)
res = self.client.post(
def test_stream_get(self):
"""get stream"""
Stream.objects.create(provider=self.provider)
res = self.client.get(
reverse(
"authentik_providers_ssf:stream",
kwargs={"application_slug": self.application.slug},
),
HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}",
)
self.assertEqual(res.status_code, 200)
def test_stream_get_filter_query(self):
"""get stream"""
other_stream = Stream.objects.create(provider=self.provider)
stream = Stream.objects.create(provider=self.provider)
res = self.client.get(
reverse(
"authentik_providers_ssf:stream",
kwargs={"application_slug": self.application.slug},
)
+ f"?stream_id={stream.pk}",
HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}",
)
self.assertEqual(res.status_code, 200)
self.assertIn(str(stream.pk), res.content.decode())
self.assertNotIn(str(other_stream.pk), res.content.decode())
def test_stream_patch(self):
"""patch stream"""
other_stream = Stream.objects.create(provider=self.provider)
stream = Stream.objects.create(provider=self.provider)
res = self.client.patch(
reverse(
"authentik_providers_ssf:stream",
kwargs={"application_slug": self.application.slug},
),
data={
"delivery": {"endpoint_url": "https://localhost"},
"stream_id": str(stream.pk),
},
HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}",
)
self.assertEqual(res.status_code, 200)
self.assertIn(str(stream.pk), res.content.decode())
self.assertNotIn(str(other_stream.pk), res.content.decode())
def test_stream_put(self):
"""put stream"""
stream = Stream.objects.create(provider=self.provider)
res = self.client.put(
reverse(
"authentik_providers_ssf:stream",
kwargs={"application_slug": self.application.slug},
@@ -126,29 +165,63 @@ class TestStream(APITestCase):
"https://schemas.openid.net/secevent/caep/event-type/session-revoked",
],
"format": "iss_sub",
"stream_id": str(stream.pk),
},
HTTP_AUTHORIZATION=f"Bearer {token.token}",
)
self.assertEqual(res.status_code, 201)
stream = Stream.objects.filter(provider=self.provider).first()
self.assertIsNotNone(stream)
event = StreamEvent.objects.filter(stream=stream).first()
self.assertIsNotNone(event)
self.assertEqual(event.status, SSFEventStatus.PENDING_FAILED)
self.assertEqual(
event.payload["events"],
{"https://schemas.openid.net/secevent/ssf/event-type/verification": {"state": None}},
HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}",
)
self.assertEqual(res.status_code, 200)
self.assertIn(str(stream.pk), res.content.decode())
stream.refresh_from_db()
self.assertEqual(stream.aud, ["https://app.authentik.company"])
def test_stream_delete(self):
"""delete stream"""
def test_stream_verify(self):
"""Test stream verify"""
stream = Stream.objects.create(provider=self.provider)
res = self.client.delete(
res = self.client.post(
reverse(
"authentik_providers_ssf:stream",
"authentik_providers_ssf:stream-verify",
kwargs={"application_slug": self.application.slug},
),
data={
"stream_id": str(stream.pk),
},
HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}",
)
self.assertEqual(res.status_code, 204)
self.assertFalse(Stream.objects.filter(pk=stream.pk).exists())
def test_stream_status(self):
"""Test stream status"""
stream = Stream.objects.create(provider=self.provider)
res = self.client.get(
reverse(
"authentik_providers_ssf:stream-status",
kwargs={"application_slug": self.application.slug},
),
data={
"stream_id": str(stream.pk),
},
HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}",
)
self.assertEqual(res.status_code, 200)
self.assertJSONEqual(
res.content,
{
"stream_id": str(stream.pk),
"status": str(stream.status),
},
)
def test_stream_status_not_found(self):
"""Test stream status"""
Stream.objects.create(provider=self.provider)
res = self.client.get(
reverse(
"authentik_providers_ssf:stream-status",
kwargs={"application_slug": self.application.slug},
),
data={
"stream_id": str(uuid4()),
},
HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}",
)
self.assertEqual(res.status_code, 404)

View File

@@ -0,0 +1,123 @@
from jwt import decode_complete
from requests_mock import Mocker
from rest_framework.test import APITestCase
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_cert
from authentik.enterprise.providers.ssf.models import (
DeliveryMethods,
EventTypes,
SSFProvider,
Stream,
StreamStatus,
)
from authentik.enterprise.providers.ssf.tasks import send_ssf_event
from authentik.lib.generators import generate_id
from authentik.tasks.models import TaskLog
class TestTasks(APITestCase):
def setUp(self):
self.application = Application.objects.create(name=generate_id(), slug=generate_id())
self.provider = SSFProvider.objects.create(
name=generate_id(),
signing_key=create_test_cert(),
backchannel_application=self.application,
)
def test_push_simple(self):
stream = Stream.objects.create(
provider=self.provider,
delivery_method=DeliveryMethods.RFC_PUSH,
endpoint_url="http://localhost/ssf-push",
)
event_data = stream.prepare_event_payload(
EventTypes.SET_VERIFICATION,
{"state": None},
sub_id={"format": "opaque", "id": str(stream.uuid)},
)
with Mocker() as mocker:
mocker.post("http://localhost/ssf-push", status_code=202)
send_ssf_event.send_with_options(
args=(stream.pk, event_data), rel_obj=stream.provider
).get_result(block=True, timeout=1)
self.assertEqual(
mocker.request_history[0].headers["Content-Type"], "application/secevent+jwt"
)
jwt = decode_complete(mocker.request_history[0].body, options={"verify_signature": False})
self.assertEqual(jwt["header"]["typ"], "secevent+jwt")
self.assertIsNone(jwt["payload"]["events"][EventTypes.SET_VERIFICATION]["state"])
def test_push_auth(self):
auth = generate_id()
stream = Stream.objects.create(
provider=self.provider,
delivery_method=DeliveryMethods.RFC_PUSH,
endpoint_url="http://localhost/ssf-push",
authorization_header=auth,
)
event_data = stream.prepare_event_payload(
EventTypes.SET_VERIFICATION,
{"state": None},
sub_id={"format": "opaque", "id": str(stream.uuid)},
)
with Mocker() as mocker:
mocker.post("http://localhost/ssf-push", status_code=202)
send_ssf_event.send_with_options(
args=(stream.pk, event_data), rel_obj=stream.provider
).get_result(block=True, timeout=1)
self.assertEqual(mocker.request_history[0].headers["Authorization"], auth)
self.assertEqual(
mocker.request_history[0].headers["Content-Type"], "application/secevent+jwt"
)
jwt = decode_complete(mocker.request_history[0].body, options={"verify_signature": False})
self.assertEqual(jwt["header"]["typ"], "secevent+jwt")
self.assertIsNone(jwt["payload"]["events"][EventTypes.SET_VERIFICATION]["state"])
def test_push_stream_disable(self):
auth = generate_id()
stream = Stream.objects.create(
provider=self.provider,
delivery_method=DeliveryMethods.RFC_PUSH,
endpoint_url="http://localhost/ssf-push",
authorization_header=auth,
status=StreamStatus.DISABLED,
)
event_data = stream.prepare_event_payload(
EventTypes.SET_VERIFICATION,
{"state": None},
sub_id={"format": "opaque", "id": str(stream.uuid)},
)
with Mocker() as mocker:
mocker.post("http://localhost/ssf-push", status_code=202)
send_ssf_event.send_with_options(
args=(stream.pk, event_data), rel_obj=stream.provider
).get_result(block=True, timeout=1)
jwt = decode_complete(mocker.request_history[0].body, options={"verify_signature": False})
self.assertEqual(jwt["header"]["typ"], "secevent+jwt")
self.assertIsNone(jwt["payload"]["events"][EventTypes.SET_VERIFICATION]["state"])
self.assertFalse(Stream.objects.filter(pk=stream.pk).exists())
def test_push_error(self):
stream = Stream.objects.create(
provider=self.provider,
delivery_method=DeliveryMethods.RFC_PUSH,
endpoint_url="http://localhost/ssf-push",
)
event_data = stream.prepare_event_payload(
EventTypes.SET_VERIFICATION,
{"state": None},
sub_id={"format": "opaque", "id": str(stream.uuid)},
)
with Mocker() as mocker:
mocker.post("http://localhost/ssf-push", text="error", status_code=400)
send_ssf_event.send_with_options(
args=(stream.pk, event_data), rel_obj=stream.provider
).get_result(block=True, timeout=1)
logs = (
TaskLog.objects.filter(task__actor_name=send_ssf_event.actor_name)
.order_by("timestamp")
.filter(event="Failed to send request")
.first()
)
self.assertEqual(logs.attributes, {"response": {"status": 400, "content": "error"}})

View File

@@ -6,7 +6,11 @@ from authentik.enterprise.providers.ssf.api.providers import SSFProviderViewSet
from authentik.enterprise.providers.ssf.api.streams import SSFStreamViewSet
from authentik.enterprise.providers.ssf.views.configuration import ConfigurationView
from authentik.enterprise.providers.ssf.views.jwks import JWKSview
from authentik.enterprise.providers.ssf.views.stream import StreamView
from authentik.enterprise.providers.ssf.views.stream import (
StreamStatusView,
StreamVerifyView,
StreamView,
)
urlpatterns = [
path(
@@ -24,6 +28,16 @@ urlpatterns = [
StreamView.as_view(),
name="stream",
),
path(
"application/ssf/<slug:application_slug>/stream/verify/",
StreamVerifyView.as_view(),
name="stream-verify",
),
path(
"application/ssf/<slug:application_slug>/stream/status/",
StreamStatusView.as_view(),
name="stream-status",
),
]
api_urlpatterns = [

View File

@@ -64,3 +64,7 @@ class SSFTokenAuth(BaseAuthentication):
if jwt_token:
return (jwt_token.user, token)
return None
# Required to correctly propagate a 401 header which the SSF spec requires
def authenticate_header(self, request):
return "SSF"

View File

@@ -1,10 +1,10 @@
from django.http import HttpRequest
from django.http import Http404, HttpRequest
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from structlog.stdlib import BoundLogger, get_logger
from authentik.core.models import Application
from authentik.enterprise.providers.ssf.models import SSFProvider
from authentik.enterprise.providers.ssf.models import SSFProvider, Stream, StreamStatus
from authentik.enterprise.providers.ssf.views.auth import SSFTokenAuth
@@ -21,3 +21,18 @@ class SSFView(APIView):
def get_authenticators(self):
return [SSFTokenAuth(self)]
class SSFStreamView(SSFView):
def get_object(self, any_status=False) -> Stream:
streams = Stream.objects.filter(provider=self.provider)
if not any_status:
streams = streams.filter(status__in=[StreamStatus.ENABLED, StreamStatus.PAUSED])
if "stream_id" in self.request.query_params:
streams = streams.filter(pk=self.request.query_params["stream_id"])
if "stream_id" in self.request.data:
streams = streams.filter(pk=self.request.data["stream_id"])
stream = streams.first()
if not stream:
raise Http404()
return stream

View File

@@ -47,9 +47,23 @@ class ConfigurationView(SSFView):
},
)
),
"delivery_methods_supported": [
DeliveryMethods.RISC_PUSH,
],
"verification_endpoint": self.request.build_absolute_uri(
reverse(
"authentik_providers_ssf:stream-verify",
kwargs={
"application_slug": application.slug,
},
)
),
"status_endpoint": self.request.build_absolute_uri(
reverse(
"authentik_providers_ssf:stream-status",
kwargs={
"application_slug": application.slug,
},
)
),
"delivery_methods_supported": [DeliveryMethods.RISC_PUSH, DeliveryMethods.RFC_PUSH],
"authorization_schemes": [{"spec_urn": "urn:ietf:rfc:6749"}],
}
return JsonResponse(data)

View File

@@ -1,3 +1,5 @@
from uuid import uuid4
from django.http import HttpRequest
from django.urls import reverse
from rest_framework.exceptions import PermissionDenied, ValidationError
@@ -13,9 +15,10 @@ from authentik.enterprise.providers.ssf.models import (
EventTypes,
SSFProvider,
Stream,
StreamStatus,
)
from authentik.enterprise.providers.ssf.tasks import send_ssf_events
from authentik.enterprise.providers.ssf.views.base import SSFView
from authentik.enterprise.providers.ssf.views.base import SSFStreamView
LOGGER = get_logger()
@@ -23,6 +26,7 @@ LOGGER = get_logger()
class StreamDeliverySerializer(PassiveSerializer):
method = ChoiceField(choices=[(x.value, x.value) for x in DeliveryMethods])
endpoint_url = CharField(required=False)
authorization_header = CharField(required=False)
def validate_method(self, method: DeliveryMethods):
"""Currently only push is supported"""
@@ -31,7 +35,7 @@ class StreamDeliverySerializer(PassiveSerializer):
return method
def validate(self, attrs: dict) -> dict:
if attrs["method"] == DeliveryMethods.RISC_PUSH:
if attrs.get("method") in [DeliveryMethods.RISC_PUSH, DeliveryMethods.RFC_PUSH]:
if not attrs.get("endpoint_url"):
raise ValidationError("Endpoint URL is required when using push.")
return attrs
@@ -42,8 +46,8 @@ class StreamSerializer(ModelSerializer):
events_requested = ListField(
child=ChoiceField(choices=[(x.value, x.value) for x in EventTypes])
)
format = CharField()
aud = ListField(child=CharField())
format = CharField(default="iss_sub")
aud = ListField(child=CharField(), allow_empty=True, default=list)
def create(self, validated_data):
provider: SSFProvider = validated_data["provider"]
@@ -58,15 +62,19 @@ class StreamSerializer(ModelSerializer):
)
# Ensure that streams always get SET verification events sent to them
validated_data["events_requested"].append(EventTypes.SET_VERIFICATION)
stream_id = uuid4()
default_aud = f"goauthentik.io/providers/ssf/{str(stream_id)}"
return super().create(
{
"delivery_method": validated_data["delivery"]["method"],
"endpoint_url": validated_data["delivery"].get("endpoint_url"),
"authorization_header": validated_data["delivery"].get("authorization_header"),
"format": validated_data["format"],
"provider": validated_data["provider"],
"events_requested": validated_data["events_requested"],
"aud": validated_data["aud"],
"aud": validated_data["aud"] or [default_aud],
"iss": iss,
"pk": stream_id,
}
)
@@ -101,7 +109,14 @@ class StreamResponseSerializer(PassiveSerializer):
return [x.value for x in EventTypes]
class StreamView(SSFView):
class StreamView(SSFStreamView):
def get(self, request: Request, *args, **kwargs):
stream = self.get_object()
return Response(
StreamResponseSerializer(instance=stream, context={"request": request}).data
)
@validate(StreamSerializer)
def post(self, request: Request, *args, body: StreamSerializer, **kwargs) -> Response:
if not request.user.has_perm("authentik_providers_ssf.add_stream", self.provider):
@@ -109,6 +124,8 @@ class StreamView(SSFView):
"User does not have permission to create stream for this provider."
)
instance: Stream = body.save(provider=self.provider)
LOGGER.info("Sending verification event", stream=instance)
send_ssf_events(
EventTypes.SET_VERIFICATION,
{
@@ -120,10 +137,56 @@ class StreamView(SSFView):
response = StreamResponseSerializer(instance=instance, context={"request": request}).data
return Response(response, status=201)
def patch(self, request: Request, *args, **kwargs) -> Response:
stream = self.get_object()
serializer = StreamSerializer(stream, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
response = StreamResponseSerializer(
instance=serializer.instance, context={"request": request}
).data
return Response(response, status=200)
def put(self, request: Request, *args, **kwargs) -> Response:
stream = self.get_object()
serializer = StreamSerializer(stream, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
response = StreamResponseSerializer(
instance=serializer.instance, context={"request": request}
).data
return Response(response, status=200)
def delete(self, request: Request, *args, **kwargs) -> Response:
streams = Stream.objects.filter(provider=self.provider)
# Technically this parameter is required by the spec...
if "stream_id" in request.query_params:
streams = streams.filter(stream_id=request.query_params["stream_id"])
streams.delete()
stream = self.get_object()
stream.status = StreamStatus.DISABLED
stream.save()
return Response(status=204)
class StreamVerifyView(SSFStreamView):
def post(self, request: Request, *args, **kwargs):
stream = self.get_object()
state = request.data.get("state", None)
send_ssf_events(
EventTypes.SET_VERIFICATION,
{
"state": state,
},
stream_filter={"pk": stream.uuid},
sub_id={"format": "opaque", "id": str(stream.uuid)},
)
return Response(status=204)
class StreamStatusView(SSFStreamView):
def get(self, request: Request, *args, **kwargs):
stream = self.get_object(any_status=True)
return Response(
{
"stream_id": str(stream.pk),
"status": str(stream.status),
}
)

View File

@@ -1,12 +0,0 @@
"""Enterprise app config"""
from authentik.enterprise.apps import EnterpriseConfig
class AuthentikEnterpriseSearchConfig(EnterpriseConfig):
"""Enterprise app config"""
name = "authentik.enterprise.search"
label = "authentik_search"
verbose_name = "authentik Enterprise.Search"
default = True

View File

@@ -1,51 +0,0 @@
from rest_framework.response import Response
from authentik.api.pagination import Pagination
from authentik.enterprise.search.ql import AUTOCOMPLETE_SCHEMA, QLSearch
class AutocompletePagination(Pagination):
def paginate_queryset(self, queryset, request, view=None):
self.view = view
return super().paginate_queryset(queryset, request, view)
def get_autocomplete(self):
schema = QLSearch().get_schema(self.request, self.view)
introspections = {}
if hasattr(self.view, "get_ql_fields"):
from authentik.enterprise.search.schema import AKQLSchemaSerializer
introspections = AKQLSchemaSerializer().serialize(
schema(self.page.paginator.object_list.model)
)
return introspections
def get_paginated_response(self, data):
previous_page_number = 0
if self.page.has_previous():
previous_page_number = self.page.previous_page_number()
next_page_number = 0
if self.page.has_next():
next_page_number = self.page.next_page_number()
return Response(
{
"pagination": {
"next": next_page_number,
"previous": previous_page_number,
"count": self.page.paginator.count,
"current": self.page.number,
"total_pages": self.page.paginator.num_pages,
"start_index": self.page.start_index(),
"end_index": self.page.end_index(),
},
"results": data,
"autocomplete": self.get_autocomplete(),
}
)
def get_paginated_response_schema(self, schema):
final_schema = super().get_paginated_response_schema(schema)
final_schema["properties"]["autocomplete"] = AUTOCOMPLETE_SCHEMA.ref
final_schema["required"].append("autocomplete")
return final_schema

View File

@@ -1,20 +0,0 @@
SPECTACULAR_SETTINGS = {
"POSTPROCESSING_HOOKS": [
"authentik.api.v3.schema.response.postprocess_schema_register",
"authentik.api.v3.schema.response.postprocess_schema_responses",
"authentik.api.v3.schema.query.postprocess_schema_query_params",
"authentik.api.v3.schema.cleanup.postprocess_schema_remove_unused",
"authentik.enterprise.search.schema.postprocess_schema_search_autocomplete",
"authentik.api.v3.schema.enum.postprocess_schema_enums",
],
}
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS": "authentik.enterprise.search.pagination.AutocompletePagination",
"DEFAULT_FILTER_BACKENDS": [
"authentik.enterprise.search.ql.QLSearch",
"authentik.rbac.filters.ObjectFilter",
"django_filters.rest_framework.DjangoFilterBackend",
"rest_framework.filters.OrderingFilter",
],
}

View File

@@ -14,7 +14,6 @@ TENANT_APPS = [
"authentik.enterprise.providers.ssf",
"authentik.enterprise.providers.ws_federation",
"authentik.enterprise.reports",
"authentik.enterprise.search",
"authentik.enterprise.stages.authenticator_endpoint_gdtc",
"authentik.enterprise.stages.mtls",
"authentik.enterprise.stages.source",

View File

@@ -102,7 +102,7 @@ class MTLSStageView(ChallengeStageView):
return []
certs = []
for cert in ftcc_raw.split(","):
certs.extend(self.__parse_single_cert(cert, ParseOptions.UNQUOTE, ParseOptions.FORMAT))
certs.extend(self.__parse_single_cert(cert, ParseOptions.FORMAT))
return certs
def _parse_cert_outpost(self) -> list[Certificate]:

View File

@@ -53,7 +53,7 @@ class MTLSStageTests(FlowTestCase):
def _format_traefik(self, cert: str | None = None):
cert = cert if cert else self.client_cert
return quote_plus(cert.replace(PEM_HEADER, "").replace(PEM_FOOTER, "").replace("\n", ""))
return cert.replace(PEM_HEADER, "").replace(PEM_FOOTER, "").replace("\n", "")
def test_parse_xfcc(self):
"""Test authentik Proxy/Envoy's XFCC format"""

View File

@@ -11,6 +11,8 @@ from django.db.models.functions import TruncHour
from django.db.models.query_utils import Q
from django.utils.text import slugify
from django.utils.timezone import now
from djangoql.schema import DateTimeField as QLDateTimeFIeld
from djangoql.schema import IntField, StrField
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, extend_schema
from guardian.shortcuts import get_objects_for_user
@@ -27,6 +29,7 @@ from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from authentik.api.search.fields import ChoiceSearchField, JSONSearchField
from authentik.api.validation import validate
from authentik.core.api.object_types import TypeCreateSerializer
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
@@ -171,10 +174,6 @@ class EventViewSet(
filterset_class = EventsFilter
def get_ql_fields(self):
from djangoql.schema import DateTimeField, IntField, StrField
from authentik.enterprise.search.fields import ChoiceSearchField, JSONSearchField
return [
ChoiceSearchField(Event, "action"),
StrField(Event, "event_uuid"),
@@ -216,7 +215,7 @@ class EventViewSet(
),
),
),
DateTimeField(Event, "created", suggest_options=True),
QLDateTimeFIeld(Event, "created", suggest_options=True),
]
@extend_schema(

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.2.12 on 2026-04-09 16:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0017_notificationtransport_webhook_ca"),
]
operations = [
migrations.AddIndex(
model_name="event",
index=models.Index(models.F("user__pk"), name="authentik_e_user_pk__idx"),
),
]

View File

@@ -321,6 +321,10 @@ class Event(SerializerModel, ExpiringModel):
models.F("context__authorized_application"),
name="authentik_e_ctx_app__idx",
),
models.Index(
models.F("user__pk"),
name="authentik_e_user_pk__idx",
),
]
@@ -456,7 +460,7 @@ class NotificationTransport(TasksModel, SerializerModel):
response.raise_for_status()
except RequestException as exc:
raise NotificationTransportError(
exc.response.text if exc.response else str(exc)
exc.response.text if exc.response is not None else str(exc)
) from exc
return [
response.status_code,
@@ -519,7 +523,7 @@ class NotificationTransport(TasksModel, SerializerModel):
response = get_http_session().post(self.webhook_url, json=body)
response.raise_for_status()
except RequestException as exc:
text = exc.response.text if exc.response else str(exc)
text = exc.response.text if exc.response is not None else str(exc)
raise NotificationTransportError(text) from exc
return [
response.status_code,

View File

@@ -7,15 +7,18 @@ from django.utils.translation import gettext as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import BooleanField, FileField, ReadOnlyField, SerializerMethodField
from rest_framework.parsers import MultiPartParser
from rest_framework.fields import (
BooleanField,
FileField,
ReadOnlyField,
SerializerMethodField,
)
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from structlog.stdlib import get_logger
from authentik.blueprints.v1.exporter import FlowExporter
from authentik.blueprints.v1.importer import Importer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import (
CacheSerializer,
@@ -24,7 +27,6 @@ from authentik.core.api.utils import (
PassiveSerializer,
ThemedUrlsSerializer,
)
from authentik.events.logs import LogEventSerializer
from authentik.flows.api.flows_diagram import FlowDiagram, FlowDiagramSerializer
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import Flow
@@ -106,13 +108,6 @@ class FlowSetSerializer(FlowSerializer):
]
class FlowImportResultSerializer(PassiveSerializer):
"""Logs of an attempted flow import"""
logs = LogEventSerializer(many=True, read_only=True)
success = BooleanField(read_only=True)
class FlowViewSet(UsedByMixin, ModelViewSet):
"""Flow Viewset"""
@@ -146,59 +141,6 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
LOGGER.debug("Cleared flow cache", keys=len(keys))
return Response(status=204)
@permission_required(
None,
[
"authentik_flows.add_flow",
"authentik_flows.change_flow",
"authentik_flows.add_flowstagebinding",
"authentik_flows.change_flowstagebinding",
"authentik_flows.add_stage",
"authentik_flows.change_stage",
"authentik_policies.add_policy",
"authentik_policies.change_policy",
"authentik_policies.add_policybinding",
"authentik_policies.change_policybinding",
"authentik_stages_prompt.add_prompt",
"authentik_stages_prompt.change_prompt",
],
)
@extend_schema(
request={"multipart/form-data": FlowUploadSerializer},
responses={
204: FlowImportResultSerializer,
400: FlowImportResultSerializer,
},
)
@action(url_path="import", detail=False, methods=["POST"], parser_classes=(MultiPartParser,))
def import_flow(self, request: Request) -> Response:
"""Import flow from .yaml file"""
import_response = FlowImportResultSerializer(
data={
"logs": [],
"success": False,
}
)
import_response.is_valid()
file = request.FILES.get("file", None)
if not file:
return Response(data=import_response.initial_data, status=400)
importer = Importer.from_string(file.read().decode())
valid, logs = importer.validate()
import_response.initial_data["logs"] = [LogEventSerializer(log).data for log in logs]
import_response.initial_data["success"] = valid
import_response.is_valid()
if not valid:
return Response(data=import_response.initial_data, status=200)
successful = importer.apply()
import_response.initial_data["success"] = successful
import_response.is_valid()
if not successful:
return Response(data=import_response.initial_data, status=200)
return Response(data=import_response.initial_data, status=200)
@permission_required(
"authentik_flows.export_flow",
[

View File

@@ -15,6 +15,7 @@ from rest_framework.request import Request
from sentry_sdk import start_span
from structlog.stdlib import BoundLogger, get_logger
from authentik.common.oauth.constants import PLAN_CONTEXT_POST_LOGOUT_REDIRECT_URI
from authentik.core.models import Application, User
from authentik.flows.challenge import (
AccessDeniedChallenge,
@@ -300,7 +301,24 @@ class SessionEndStage(ChallengeStageView):
that the user is likely to take after signing out of a provider."""
def get_challenge(self, *args, **kwargs) -> Challenge:
# Check for OIDC post_logout_redirect_uri in context
post_logout_redirect_uri = self.executor.plan.context.get(
PLAN_CONTEXT_POST_LOGOUT_REDIRECT_URI
)
if post_logout_redirect_uri:
self.logger.debug(
"SessionEndStage redirecting to post_logout_redirect_uri",
redirect_url=post_logout_redirect_uri,
)
return RedirectChallenge(
data={
"to": post_logout_redirect_uri,
},
)
if not self.request.user.is_authenticated:
# User is logged out with no redirect URI - go to default
return RedirectChallenge(
data={
"to": reverse("authentik_core:root-redirect"),

View File

@@ -47,33 +47,23 @@
{% block body %}
<ak-skip-to-content></ak-skip-to-content>
<ak-message-container></ak-message-container>
<div class="pf-c-page__drawer">
<div class="pf-c-drawer pf-m-collapsed" id="flow-drawer">
<div class="pf-c-drawer__main">
<div class="pf-c-drawer__content">
<div class="pf-c-drawer__body">
<ak-flow-executor
slug="{{ flow.slug }}"
class="pf-c-login"
data-layout="{{ flow.layout|default:'stacked' }}"
loading
>
{% include "base/placeholder.html" %}
<ak-brand-links name="flow-links" slot="footer"></ak-brand-links>
</ak-flow-executor>
</div>
</div>
<ak-flow-inspector
id="flow-inspector"
data-registration="lazy"
class="pf-c-drawer__panel pf-m-width-33"
slug="{{ flow.slug }}"
></ak-flow-inspector>
</div>
</div>
</div>
</div>
<ak-drawer id="flow-drawer">
<ak-flow-executor
slug="{{ flow.slug }}"
class="pf-c-login"
data-layout="{{ flow.layout|default:'stacked' }}"
loading
>
{% include "base/placeholder.html" %}
<ak-brand-links name="flow-links" slot="footer"></ak-brand-links>
</ak-flow-executor>
<ak-flow-inspector
slot="panel"
id="flow-inspector"
data-registration="lazy"
slug="{{ flow.slug }}"
></ak-flow-inspector>
</ak-drawer>
{% endblock %}

View File

@@ -47,6 +47,7 @@ listen:
- "[::]:9300"
debug: 0.0.0.0:9900
debug_py: 0.0.0.0:9901
debug_tokio: "[::]:6669"
trusted_proxy_cidrs:
- 127.0.0.0/8
- 10.0.0.0/8
@@ -73,6 +74,19 @@ log_level: info
log:
http_headers:
- User-Agent
rust_log:
"console_subscriber": info
"h2": info
"hyper_util": warn
"mio": info
"notify": info
"reqwest": info
"runtime": info
"rustls": info
"sqlx": info
"sqlx_postgres": info
"tokio": info
"tungstenite": info
sessions:
unauthenticated_age: days=1

View File

@@ -4,12 +4,15 @@ from inspect import currentframe
from pathlib import Path
def load_fixture(path: str, **kwargs) -> str:
def load_fixture(path: str, path_only=False, **kwargs) -> str:
"""Load fixture, optionally formatting it with kwargs"""
current = currentframe()
parent = current.f_back
calling_file_path = parent.f_globals["__file__"]
with open(Path(calling_file_path).resolve().parent / Path(path), encoding="utf-8") as _fixture:
fixture_path = Path(calling_file_path).resolve().parent / Path(path)
if path_only:
return fixture_path
with open(fixture_path, encoding="utf-8") as _fixture:
fixture = _fixture.read()
try:
return fixture % kwargs

View File

@@ -57,9 +57,11 @@ class PolicyBindingSerializer(ModelSerializer):
required=True,
)
policy_obj = PolicySerializer(required=False, read_only=True, source="policy")
group_obj = PartialGroupSerializer(required=False, read_only=True, source="group")
user_obj = PartialUserSerializer(required=False, read_only=True, source="user")
policy_obj = PolicySerializer(required=False, allow_null=True, read_only=True, source="policy")
group_obj = PartialGroupSerializer(
required=False, allow_null=True, read_only=True, source="group"
)
user_obj = PartialUserSerializer(required=False, allow_null=True, read_only=True, source="user")
class Meta:
model = PolicyBinding

View File

@@ -27,6 +27,7 @@ from authentik.providers.oauth2.models import (
AccessToken,
OAuth2Provider,
RedirectURIMatchingMode,
RedirectURIType,
ScopeMapping,
)
from authentik.rbac.decorators import permission_required
@@ -37,6 +38,9 @@ class RedirectURISerializer(PassiveSerializer):
matching_mode = ChoiceField(choices=RedirectURIMatchingMode.choices)
url = CharField()
redirect_uri_type = ChoiceField(
choices=RedirectURIType.choices, default=RedirectURIType.AUTHORIZATION, required=False
)
class OAuth2ProviderSerializer(ProviderSerializer):

View File

@@ -97,6 +97,11 @@ class RedirectURIMatchingMode(models.TextChoices):
REGEX = "regex", _("Regular Expression URL matching")
class RedirectURIType(models.TextChoices):
AUTHORIZATION = "authorization", _("Authorization")
LOGOUT = "logout", _("Logout")
class OAuth2LogoutMethod(models.TextChoices):
"""OAuth2/OIDC Logout methods"""
@@ -110,6 +115,7 @@ class RedirectURI:
matching_mode: RedirectURIMatchingMode
url: str
redirect_uri_type: RedirectURIType = RedirectURIType.AUTHORIZATION
class ResponseTypes(models.TextChoices):
@@ -220,7 +226,6 @@ class OAuth2Provider(WebfingerProvider, Provider):
"Frontchannel uses iframes in your browser"
),
)
include_claims_in_id_token = models.BooleanField(
default=True,
verbose_name=_("Include claims in id_token"),
@@ -343,7 +348,12 @@ class OAuth2Provider(WebfingerProvider, Provider):
from_dict(
RedirectURI,
entry,
config=Config(type_hooks={RedirectURIMatchingMode: RedirectURIMatchingMode}),
config=Config(
type_hooks={
RedirectURIMatchingMode: RedirectURIMatchingMode,
RedirectURIType: RedirectURIType,
}
),
)
)
return uris
@@ -355,10 +365,24 @@ class OAuth2Provider(WebfingerProvider, Provider):
cleansed.append(asdict(entry))
self._redirect_uris = cleansed
@property
def authorization_redirect_uris(self) -> list[RedirectURI]:
return [
uri
for uri in self.redirect_uris
if uri.redirect_uri_type == RedirectURIType.AUTHORIZATION
]
@property
def post_logout_redirect_uris(self) -> list[RedirectURI]:
return [
uri for uri in self.redirect_uris if uri.redirect_uri_type == RedirectURIType.LOGOUT
]
@property
def launch_url(self) -> str | None:
"""Guess launch_url based on first redirect_uri"""
redirects = self.redirect_uris
redirects = self.authorization_redirect_uris
if len(redirects) < 1:
return None
main_url = redirects[0].url

View File

@@ -1,13 +1,13 @@
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from structlog.stdlib import get_logger
from authentik.common.oauth.constants import PLAN_CONTEXT_OIDC_LOGOUT_IFRAME_SESSIONS
from authentik.common.oauth.constants import (
OAUTH2_BINDING,
PLAN_CONTEXT_OIDC_LOGOUT_IFRAME_SESSIONS,
)
from authentik.core.models import AuthenticatedSession, User
from authentik.flows.models import in_memory_stage
from authentik.outposts.tasks import hash_session_key
from authentik.providers.iframe_logout import IframeLogoutStageView
from authentik.providers.oauth2.models import (
AccessToken,
@@ -16,6 +16,7 @@ from authentik.providers.oauth2.models import (
RefreshToken,
)
from authentik.providers.oauth2.tasks import backchannel_logout_notification_dispatch
from authentik.providers.oauth2.utils import build_frontchannel_logout_url
from authentik.stages.user_logout.models import UserLogoutStage
from authentik.stages.user_logout.stage import flow_pre_user_logout
@@ -51,43 +52,22 @@ def handle_flow_pre_user_logout(sender, request, user, executor, **kwargs):
LOGGER.debug("No sessions requiring IFrame frontchannel logout")
return
session_key = auth_session.session.session_key if auth_session.session else None
oidc_sessions = []
for token in oidc_access_tokens:
# Parse the logout URI and add query parameters
parsed_url = urlparse(token.provider.logout_uri)
query_params = {}
query_params["iss"] = token.provider.get_issuer(request)
if auth_session.session:
query_params["sid"] = hash_session_key(auth_session.session.session_key)
# Combine existing query params with new ones
if parsed_url.query:
existing_params = parse_qs(parsed_url.query, keep_blank_values=True)
for key, value in existing_params.items():
if key not in query_params:
query_params[key] = value[0] if len(value) == 1 else value
# Build the final URL with query parameters
logout_url = urlunparse(
(
parsed_url.scheme,
parsed_url.netloc,
parsed_url.path,
parsed_url.params,
urlencode(query_params),
parsed_url.fragment,
logout_url = build_frontchannel_logout_url(token.provider, request, session_key)
if logout_url:
oidc_sessions.append(
{
"url": logout_url,
"provider_name": token.provider.name,
"binding": OAUTH2_BINDING,
"provider_type": (
f"{token.provider._meta.app_label}.{token.provider._meta.model_name}"
),
}
)
)
logout_data = {
"url": logout_url,
"provider_name": token.provider.name,
"binding": "redirect",
"provider_type": "oidc",
}
oidc_sessions.append(logout_data)
if oidc_sessions:
executor.plan.context[PLAN_CONTEXT_OIDC_LOGOUT_IFRAME_SESSIONS] = oidc_sessions

View File

@@ -0,0 +1,263 @@
"""Test OAuth2 End Session (RP-Initiated Logout) implementation"""
from django.test import RequestFactory
from django.urls import reverse
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_brand, create_test_flow
from authentik.lib.generators import generate_id
from authentik.providers.oauth2.models import (
OAuth2Provider,
RedirectURI,
RedirectURIMatchingMode,
RedirectURIType,
)
from authentik.providers.oauth2.tests.utils import OAuthTestCase
from authentik.providers.oauth2.views.end_session import EndSessionView
class TestEndSessionView(OAuthTestCase):
"""Test EndSessionView validation"""
def setUp(self) -> None:
super().setUp()
self.user = create_test_admin_user()
self.invalidation_flow = create_test_flow()
self.app = Application.objects.create(name=generate_id(), slug="test-app")
self.provider = OAuth2Provider.objects.create(
name=generate_id(),
authorization_flow=create_test_flow(),
invalidation_flow=self.invalidation_flow,
redirect_uris=[
RedirectURI(
RedirectURIMatchingMode.STRICT,
"http://testserver/callback",
RedirectURIType.AUTHORIZATION,
),
RedirectURI(
RedirectURIMatchingMode.STRICT,
"http://testserver/logout",
RedirectURIType.LOGOUT,
),
RedirectURI(
RedirectURIMatchingMode.REGEX,
r"https://.*\.example\.com/logout",
RedirectURIType.LOGOUT,
),
],
)
self.app.provider = self.provider
self.app.save()
# Ensure brand has an invalidation flow
self.brand = create_test_brand()
self.brand.flow_invalidation = self.invalidation_flow
self.brand.save()
def test_post_logout_redirect_uri_strict_match(self):
"""Test strict URI matching redirects to flow"""
self.client.force_login(self.user)
response = self.client.get(
reverse(
"authentik_providers_oauth2:end-session",
kwargs={"application_slug": self.app.slug},
),
{"post_logout_redirect_uri": "http://testserver/logout"},
HTTP_HOST=self.brand.domain,
)
# Should redirect to the invalidation flow
self.assertEqual(response.status_code, 302)
self.assertIn(self.invalidation_flow.slug, response.url)
def test_post_logout_redirect_uri_strict_no_match(self):
"""Test strict URI not matching still proceeds with flow (no redirect URI in context)"""
self.client.force_login(self.user)
invalid_uri = "http://testserver/other"
response = self.client.get(
reverse(
"authentik_providers_oauth2:end-session",
kwargs={"application_slug": self.app.slug},
),
{"post_logout_redirect_uri": invalid_uri},
HTTP_HOST=self.brand.domain,
)
# Should still redirect to flow, but invalid URI should not be in response
self.assertEqual(response.status_code, 302)
self.assertNotIn(invalid_uri, response.url)
def test_post_logout_redirect_uri_regex_match(self):
"""Test regex URI matching redirects to flow"""
self.client.force_login(self.user)
response = self.client.get(
reverse(
"authentik_providers_oauth2:end-session",
kwargs={"application_slug": self.app.slug},
),
{"post_logout_redirect_uri": "https://app.example.com/logout"},
HTTP_HOST=self.brand.domain,
)
# Should redirect to the invalidation flow
self.assertEqual(response.status_code, 302)
self.assertIn(self.invalidation_flow.slug, response.url)
def test_post_logout_redirect_uri_regex_no_match(self):
"""Test regex URI not matching"""
self.client.force_login(self.user)
invalid_uri = "https://malicious.com/logout"
response = self.client.get(
reverse(
"authentik_providers_oauth2:end-session",
kwargs={"application_slug": self.app.slug},
),
{"post_logout_redirect_uri": invalid_uri},
HTTP_HOST=self.brand.domain,
)
# Should still proceed to flow, but invalid URI should not be in response
self.assertEqual(response.status_code, 302)
self.assertNotIn(invalid_uri, response.url)
def test_state_parameter_appended_to_uri(self):
"""Test state parameter is appended to validated redirect URI"""
factory = RequestFactory()
request = factory.get(
"/end-session/",
{
"post_logout_redirect_uri": "http://testserver/logout",
"state": "test-state-123",
},
)
request.user = self.user
request.brand = self.brand
view = EndSessionView()
view.request = request
view.kwargs = {"application_slug": self.app.slug}
view.resolve_provider_application()
self.assertIn("state=test-state-123", view.post_logout_redirect_uri)
def test_post_method(self):
"""Test POST requests work same as GET"""
self.client.force_login(self.user)
response = self.client.post(
reverse(
"authentik_providers_oauth2:end-session",
kwargs={"application_slug": self.app.slug},
),
{
"post_logout_redirect_uri": "http://testserver/logout",
"state": "xyz789",
},
HTTP_HOST=self.brand.domain,
)
self.assertEqual(response.status_code, 302)
class TestEndSessionAPI(OAuthTestCase):
"""Test End Session API functionality"""
def setUp(self) -> None:
super().setUp()
self.user = create_test_admin_user()
self.client.force_login(self.user)
def test_post_logout_redirect_uris_create(self):
"""Test creating provider with post_logout redirect_uris"""
response = self.client.post(
reverse("authentik_api:oauth2provider-list"),
data={
"name": generate_id(),
"authorization_flow": create_test_flow().pk,
"invalidation_flow": create_test_flow().pk,
"redirect_uris": [
{
"matching_mode": "strict",
"url": "http://testserver/callback",
"redirect_uri_type": "authorization",
},
{
"matching_mode": "strict",
"url": "http://testserver/logout",
"redirect_uri_type": "logout",
},
{
"matching_mode": "regex",
"url": "https://.*\\.example\\.com/logout",
"redirect_uri_type": "logout",
},
],
},
content_type="application/json",
)
self.assertEqual(response.status_code, 201)
provider_data = response.json()
post_logout_uris = [
u for u in provider_data["redirect_uris"] if u["redirect_uri_type"] == "logout"
]
self.assertEqual(len(post_logout_uris), 2)
def test_post_logout_redirect_uris_invalid_regex(self):
"""Test that invalid regex patterns are rejected"""
response = self.client.post(
reverse("authentik_api:oauth2provider-list"),
data={
"name": generate_id(),
"authorization_flow": create_test_flow().pk,
"invalidation_flow": create_test_flow().pk,
"redirect_uris": [
{
"matching_mode": "strict",
"url": "http://testserver/callback",
"redirect_uri_type": "authorization",
},
{
"matching_mode": "regex",
"url": "**invalid**",
"redirect_uri_type": "logout",
},
],
},
content_type="application/json",
)
self.assertEqual(response.status_code, 400)
self.assertIn("redirect_uris", response.json())
def test_post_logout_redirect_uris_update(self):
"""Test updating redirect_uris with logout type"""
# First create a provider
provider = OAuth2Provider.objects.create(
name=generate_id(),
authorization_flow=create_test_flow(),
redirect_uris=[
RedirectURI(
RedirectURIMatchingMode.STRICT,
"http://testserver/callback",
RedirectURIType.AUTHORIZATION,
),
],
)
# Update with post_logout redirect URIs
response = self.client.patch(
reverse("authentik_api:oauth2provider-detail", kwargs={"pk": provider.pk}),
data={
"redirect_uris": [
{
"matching_mode": "strict",
"url": "http://testserver/callback",
"redirect_uri_type": "authorization",
},
{
"matching_mode": "strict",
"url": "http://testserver/logout",
"redirect_uri_type": "logout",
},
],
},
content_type="application/json",
)
self.assertEqual(response.status_code, 200)
# Verify the update
provider.refresh_from_db()
self.assertEqual(len(provider.post_logout_redirect_uris), 1)
self.assertEqual(provider.post_logout_redirect_uris[0].url, "http://testserver/logout")

View File

@@ -7,7 +7,7 @@ from binascii import Error
from hashlib import sha256
from hmac import compare_digest
from typing import Any
from urllib.parse import unquote, urlparse
from urllib.parse import parse_qs, unquote, urlencode, urlparse, urlunparse
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.http.response import HttpResponseRedirect
@@ -267,3 +267,32 @@ def create_logout_token(
payload["sid"] = hash_session_key(session_key)
# Encode the token
return provider.encode(payload, jwt_type="logout+jwt")
def build_frontchannel_logout_url(
provider: OAuth2Provider,
request: HttpRequest,
session_key: str | None = None,
) -> str | None:
"""Build frontchannel logout URL with iss and sid parameters.
Returns None if provider doesn't have a logout_uri configured.
"""
if not provider.logout_uri:
return None
parsed_url = urlparse(provider.logout_uri)
query_params = {"iss": provider.get_issuer(request)}
if session_key:
query_params["sid"] = hash_session_key(session_key)
# Preserve existing query params
if parsed_url.query:
existing_params = parse_qs(parsed_url.query, keep_blank_values=True)
for key, value in existing_params.items():
if key not in query_params:
query_params[key] = value[0] if len(value) == 1 else value
parsed_url = parsed_url._replace(query=urlencode(query_params))
return urlunparse(parsed_url)

View File

@@ -18,6 +18,7 @@ from django.utils.translation import gettext as _
from structlog.stdlib import get_logger
from authentik.common.oauth.constants import (
FORBIDDEN_URI_SCHEMES,
PKCE_METHOD_PLAIN,
PKCE_METHOD_S256,
PROMPT_CONSENT,
@@ -60,6 +61,7 @@ from authentik.providers.oauth2.models import (
OAuth2Provider,
RedirectURI,
RedirectURIMatchingMode,
RedirectURIType,
ResponseMode,
ResponseTypes,
ScopeMapping,
@@ -78,7 +80,6 @@ PLAN_CONTEXT_PARAMS = "goauthentik.io/providers/oauth2/params"
SESSION_KEY_LAST_LOGIN_UID = "authentik/providers/oauth2/last_login_uid"
ALLOWED_PROMPT_PARAMS = {PROMPT_NONE, PROMPT_CONSENT, PROMPT_LOGIN}
FORBIDDEN_URI_SCHEMES = {"javascript", "data", "vbscript"}
@dataclass(slots=True)
@@ -191,7 +192,7 @@ class OAuthAuthorizationParams:
def check_redirect_uri(self):
"""Redirect URI validation."""
allowed_redirect_urls = self.provider.redirect_uris
allowed_redirect_urls = self.provider.authorization_redirect_uris
if not self.redirect_uri:
LOGGER.warning("Missing redirect uri.")
raise RedirectUriError("", allowed_redirect_urls).with_cause("redirect_uri_missing")
@@ -199,10 +200,14 @@ class OAuthAuthorizationParams:
if len(allowed_redirect_urls) < 1:
LOGGER.info("Setting redirect for blank redirect_uris", redirect=self.redirect_uri)
self.provider.redirect_uris = [
RedirectURI(RedirectURIMatchingMode.STRICT, self.redirect_uri)
RedirectURI(
RedirectURIMatchingMode.STRICT,
self.redirect_uri,
RedirectURIType.AUTHORIZATION,
)
]
self.provider.save()
allowed_redirect_urls = self.provider.redirect_uris
allowed_redirect_urls = self.provider.authorization_redirect_uris
match_found = False
for allowed in allowed_redirect_urls:

View File

@@ -1,20 +1,42 @@
"""oauth2 provider end_session Views"""
from re import fullmatch
from urllib.parse import quote, urlparse
from django.http import Http404, HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404
from authentik.core.models import Application
from authentik.common.oauth.constants import (
FORBIDDEN_URI_SCHEMES,
OAUTH2_BINDING,
PLAN_CONTEXT_OIDC_LOGOUT_IFRAME_SESSIONS,
PLAN_CONTEXT_POST_LOGOUT_REDIRECT_URI,
)
from authentik.core.models import Application, AuthenticatedSession
from authentik.flows.models import Flow, in_memory_stage
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, FlowPlanner
from authentik.flows.planner import (
PLAN_CONTEXT_APPLICATION,
FlowPlanner,
)
from authentik.flows.stage import SessionEndStage
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.policies.views import PolicyAccessView
from authentik.lib.views import bad_request_message
from authentik.policies.views import PolicyAccessView, RequestValidationError
from authentik.providers.iframe_logout import IframeLogoutStageView
from authentik.providers.oauth2.models import (
AccessToken,
OAuth2LogoutMethod,
RedirectURIMatchingMode,
)
from authentik.providers.oauth2.tasks import send_backchannel_logout_request
from authentik.providers.oauth2.utils import build_frontchannel_logout_url
class EndSessionView(PolicyAccessView):
"""Redirect to application's provider's invalidation flow"""
"""OIDC RP-Initiated Logout endpoint"""
flow: Flow
post_logout_redirect_uri: str | None
def resolve_provider_application(self):
self.application = get_object_or_404(Application, slug=self.kwargs["application_slug"])
@@ -25,6 +47,38 @@ class EndSessionView(PolicyAccessView):
if not self.flow:
raise Http404
# Parse end session parameters
query_dict = self.request.POST if self.request.method == "POST" else self.request.GET
state = query_dict.get("state")
request_redirect_uri = query_dict.get("post_logout_redirect_uri")
self.post_logout_redirect_uri = None
# Validate post_logout_redirect_uri against registered URIs
if request_redirect_uri:
if urlparse(request_redirect_uri).scheme in FORBIDDEN_URI_SCHEMES:
raise RequestValidationError(
bad_request_message(
self.request,
"Forbidden URI scheme in post_logout_redirect_uri",
)
)
for allowed in self.provider.post_logout_redirect_uris:
if allowed.matching_mode == RedirectURIMatchingMode.STRICT:
if request_redirect_uri == allowed.url:
self.post_logout_redirect_uri = request_redirect_uri
break
elif allowed.matching_mode == RedirectURIMatchingMode.REGEX:
if fullmatch(allowed.url, request_redirect_uri):
self.post_logout_redirect_uri = request_redirect_uri
break
# Append state to the redirect URI if both are present
if self.post_logout_redirect_uri and state:
separator = "&" if "?" in self.post_logout_redirect_uri else "?"
self.post_logout_redirect_uri = (
f"{self.post_logout_redirect_uri}{separator}state={quote(state, safe='')}"
)
# If IFrame provider logout happens when a saml provider has redirect
# logout enabled, the flow won't make it back without this dispatch
def dispatch(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
@@ -44,11 +98,80 @@ class EndSessionView(PolicyAccessView):
"""Dispatch the flow planner for the invalidation flow"""
planner = FlowPlanner(self.flow)
planner.allow_empty_flows = True
plan = planner.plan(
request,
{
PLAN_CONTEXT_APPLICATION: self.application,
},
# Build flow context with logout parameters
context = {
PLAN_CONTEXT_APPLICATION: self.application,
}
# Get session info for logout notifications and token invalidation
auth_session = AuthenticatedSession.from_request(request, request.user)
# Add validated redirect URI (with state appended) to context if available
if self.post_logout_redirect_uri:
context[PLAN_CONTEXT_POST_LOGOUT_REDIRECT_URI] = self.post_logout_redirect_uri
# Invalidate tokens for this provider/session (RP-initiated logout:
# user stays logged into authentik, only this provider's tokens are revoked)
if request.user.is_authenticated and auth_session:
AccessToken.objects.filter(
user=request.user,
provider=self.provider,
session=auth_session,
).delete()
session_key = (
auth_session.session.session_key if auth_session and auth_session.session else None
)
# Handle frontchannel logout
frontchannel_logout_url = None
if self.provider.logout_method == OAuth2LogoutMethod.FRONTCHANNEL:
frontchannel_logout_url = build_frontchannel_logout_url(
self.provider, request, session_key
)
# Handle backchannel logout
if (
self.provider.logout_method == OAuth2LogoutMethod.BACKCHANNEL
and self.provider.logout_uri
):
# Find access token to get iss and sub for the logout token
access_token = AccessToken.objects.filter(
user=request.user,
provider=self.provider,
session=auth_session,
).first()
if access_token and access_token.id_token:
send_backchannel_logout_request.send(
self.provider.pk,
access_token.id_token.iss,
access_token.id_token.sub,
session_key,
)
# Delete the token to prevent duplicate backchannel logout
# when UserLogoutStage triggers the session deletion signal
access_token.delete()
if frontchannel_logout_url:
context[PLAN_CONTEXT_OIDC_LOGOUT_IFRAME_SESSIONS] = [
{
"url": frontchannel_logout_url,
"provider_name": self.provider.name,
"binding": OAUTH2_BINDING,
"provider_type": (
f"{self.provider._meta.app_label}.{self.provider._meta.model_name}"
),
}
]
plan = planner.plan(request, context)
# Inject iframe logout stage if frontchannel logout is configured
if frontchannel_logout_url:
plan.insert_stage(in_memory_stage(IframeLogoutStageView))
plan.append_stage(in_memory_stage(SessionEndStage))
return plan.to_redirect(self.request, self.flow)
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Handle POST requests for logout (same as GET per OIDC spec)"""
return self.get(request, *args, **kwargs)

View File

@@ -74,6 +74,8 @@ class ProviderInfoView(View):
),
"backchannel_logout_supported": True,
"backchannel_logout_session_supported": True,
"frontchannel_logout_supported": True,
"frontchannel_logout_session_supported": True,
"response_types_supported": [
ResponseTypes.CODE,
ResponseTypes.ID_TOKEN,

View File

@@ -241,7 +241,7 @@ class TokenParams:
raise TokenError("invalid_grant")
def __check_redirect_uri(self, request: HttpRequest):
allowed_redirect_urls = self.provider.redirect_uris
allowed_redirect_urls = self.provider.authorization_redirect_uris
# At this point, no provider should have a blank redirect_uri, in case they do
# this will check an empty array and raise an error

View File

@@ -232,8 +232,8 @@ class SAMLProviderSerializer(ProviderSerializer):
class SAMLMetadataSerializer(PassiveSerializer):
"""SAML Provider Metadata serializer"""
metadata = CharField(read_only=True)
download_url = CharField(read_only=True, required=False)
metadata = CharField()
download_url = CharField(required=False, allow_null=True)
class SAMLProviderImportSerializer(PassiveSerializer):
@@ -315,7 +315,7 @@ class SAMLProviderViewSet(UsedByMixin, ModelViewSet):
return response
return Response({"metadata": metadata}, content_type="application/json")
except Provider.application.RelatedObjectDoesNotExist:
return Response({"metadata": ""}, content_type="application/json")
raise Http404 from None
@permission_required(
None,

View File

@@ -157,7 +157,7 @@ class TestSAMLProviderAPI(APITestCase):
response = self.client.get(
reverse("authentik_api:samlprovider-metadata", kwargs={"pk": provider.pk}),
)
self.assertEqual(200, response.status_code)
self.assertEqual(404, response.status_code)
response = self.client.get(
reverse("authentik_api:samlprovider-metadata", kwargs={"pk": "abc"}),
)

View File

@@ -5,6 +5,10 @@ from unittest.mock import Mock
from django.test import RequestFactory, TestCase
from authentik.common.oauth.constants import (
OAUTH2_BINDING,
PLAN_CONTEXT_OIDC_LOGOUT_IFRAME_SESSIONS,
)
from authentik.common.saml.constants import (
RSA_SHA256,
SAML_NAME_ID_FORMAT_EMAIL,
@@ -17,6 +21,7 @@ from authentik.providers.iframe_logout import (
IframeLogoutChallenge,
IframeLogoutStageView,
)
from authentik.providers.oauth2.models import OAuth2Provider
from authentik.providers.saml.models import SAMLLogoutMethods, SAMLProvider
from authentik.providers.saml.native_logout import (
NativeLogoutChallenge,
@@ -295,14 +300,14 @@ class TestIframeLogoutStageView(TestCase):
},
]
# OIDC sessions (pre-processed)
from authentik.common.oauth.constants import PLAN_CONTEXT_OIDC_LOGOUT_IFRAME_SESSIONS
plan.context[PLAN_CONTEXT_OIDC_LOGOUT_IFRAME_SESSIONS] = [
{
"url": "https://oidc.example.com/logout?iss=authentik&sid=abc123",
"provider_name": "oidc-provider",
"binding": "redirect",
"provider_type": "oidc",
"binding": OAUTH2_BINDING,
"provider_type": (
f"{OAuth2Provider._meta.app_label}" f".{OAuth2Provider._meta.model_name}"
),
},
]
stage_view = IframeLogoutStageView(

View File

@@ -93,32 +93,33 @@ class TestSPInitiatedSLOViews(TestCase):
self.assertEqual(logout_request.issuer, self.provider.issuer)
self.assertEqual(logout_request.session_index, "test-session-123")
def test_redirect_view_handles_logout_response_with_relay_state(self):
"""Test that redirect view handles logout response with RelayState"""
# Use raw URL (no encoding needed)
relay_state = "https://idp.example.com/flow/return"
def test_redirect_view_handles_logout_response_with_plan_context(self):
"""Test that redirect view always redirects to plan context URL, ignoring RelayState"""
plan_relay_state = "https://idp.example.com/flow/return"
# Create request with SAML logout response
request = self.factory.get(
f"/slo/redirect/{self.application.slug}/",
{
"SAMLResponse": "dummy-response",
"RelayState": relay_state,
"RelayState": "https://somewhere-else.example.com/return",
},
)
request.session = {}
plan = FlowPlan(flow_pk="test-flow")
plan.context[PLAN_CONTEXT_SAML_RELAY_STATE] = plan_relay_state
request.session = {SESSION_KEY_PLAN: plan}
request.brand = self.brand
view = SPInitiatedSLOBindingRedirectView()
view.setup(request, application_slug=self.application.slug)
response = view.dispatch(request, application_slug=self.application.slug)
# Should redirect to relay state URL
# Should redirect to plan context URL, not the request's RelayState
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, relay_state)
self.assertEqual(response.url, plan_relay_state)
def test_redirect_view_handles_logout_response_plain_relay_state(self):
"""Test that redirect view handles logout response with plain RelayState"""
def test_redirect_view_ignores_relay_state_without_plan(self):
"""Test that redirect view ignores RelayState and falls back to root when no plan context"""
relay_state = "https://sp.example.com/plain"
# Create request with SAML logout response
@@ -136,9 +137,9 @@ class TestSPInitiatedSLOViews(TestCase):
view.setup(request, application_slug=self.application.slug)
response = view.dispatch(request, application_slug=self.application.slug)
# Should redirect to plain relay state
# Should ignore relay_state and redirect to root (no plan context)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, relay_state)
self.assertEqual(response.url, reverse("authentik_core:root-redirect"))
def test_redirect_view_handles_logout_response_no_relay_state_with_plan_context(self):
"""Test that redirect view uses plan context fallback when no RelayState"""
@@ -230,29 +231,30 @@ class TestSPInitiatedSLOViews(TestCase):
self.assertEqual(logout_request.issuer, self.provider.issuer)
self.assertEqual(logout_request.session_index, "test-session-123")
def test_post_view_handles_logout_response_with_relay_state(self):
"""Test that POST view handles logout response with RelayState"""
# Use raw URL (no encoding needed)
relay_state = "https://idp.example.com/flow/return"
def test_post_view_handles_logout_response_with_plan_context(self):
"""Test that POST view always redirects to plan context URL, ignoring RelayState"""
plan_relay_state = "https://idp.example.com/flow/return"
# Create POST request with SAML logout response
request = self.factory.post(
f"/slo/post/{self.application.slug}/",
{
"SAMLResponse": "dummy-response",
"RelayState": relay_state,
"RelayState": "https://somewhere-else.example.com/return",
},
)
request.session = {}
plan = FlowPlan(flow_pk="test-flow")
plan.context[PLAN_CONTEXT_SAML_RELAY_STATE] = plan_relay_state
request.session = {SESSION_KEY_PLAN: plan}
request.brand = self.brand
view = SPInitiatedSLOBindingPOSTView()
view.setup(request, application_slug=self.application.slug)
response = view.dispatch(request, application_slug=self.application.slug)
# Should redirect to relay state URL
# Should redirect to plan context URL, not the request's RelayState
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, relay_state)
self.assertEqual(response.url, plan_relay_state)
def test_post_view_handles_logout_response_no_relay_state_with_plan_context(self):
"""Test that POST view uses plan context fallback when no RelayState"""
@@ -419,7 +421,7 @@ class TestSPInitiatedSLOViews(TestCase):
view.resolve_provider_application()
def test_relay_state_decoding_failure(self):
"""Test handling of RelayState that's a path"""
"""Test that arbitrary path RelayState is ignored and redirects to root"""
# Create request with relay state that is a path
request = self.factory.get(
f"/slo/redirect/{self.application.slug}/",
@@ -435,9 +437,73 @@ class TestSPInitiatedSLOViews(TestCase):
view.setup(request, application_slug=self.application.slug)
response = view.dispatch(request, application_slug=self.application.slug)
# Should treat it as plain URL and redirect to it
# Should ignore relay_state and redirect to root (no plan context)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, "/some/invalid/path")
self.assertEqual(response.url, reverse("authentik_core:root-redirect"))
def test_redirect_view_blocks_external_relay_state(self):
"""Test that redirect view ignores external malicious URL and redirects to root"""
request = self.factory.get(
f"/slo/redirect/{self.application.slug}/",
{
"SAMLResponse": "dummy-response",
"RelayState": "https://evil.com/phishing",
},
)
request.session = {}
request.brand = self.brand
view = SPInitiatedSLOBindingRedirectView()
view.setup(request, application_slug=self.application.slug)
response = view.dispatch(request, application_slug=self.application.slug)
# Should ignore relay_state and redirect to root (no plan context)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse("authentik_core:root-redirect"))
def test_redirect_view_ignores_relay_state_uses_plan_context(self):
"""Test that redirect view always uses plan context URL regardless of RelayState"""
plan_relay_state = "https://authentik.example.com/if/flow/logout/"
request = self.factory.get(
f"/slo/redirect/{self.application.slug}/",
{
"SAMLResponse": "dummy-response",
"RelayState": "https://evil.com/phishing",
},
)
plan = FlowPlan(flow_pk="test-flow")
plan.context[PLAN_CONTEXT_SAML_RELAY_STATE] = plan_relay_state
request.session = {SESSION_KEY_PLAN: plan}
request.brand = self.brand
view = SPInitiatedSLOBindingRedirectView()
view.setup(request, application_slug=self.application.slug)
response = view.dispatch(request, application_slug=self.application.slug)
# Should always use plan context value, ignoring malicious RelayState
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, plan_relay_state)
def test_post_view_ignores_external_relay_state(self):
"""Test that POST view ignores external RelayState and redirects to root"""
request = self.factory.post(
f"/slo/post/{self.application.slug}/",
{
"SAMLResponse": "dummy-response",
"RelayState": "https://evil.com/phishing",
},
)
request.session = {}
request.brand = self.brand
view = SPInitiatedSLOBindingPOSTView()
view.setup(request, application_slug=self.application.slug)
response = view.dispatch(request, application_slug=self.application.slug)
# Should ignore relay_state and redirect to root (no plan context)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse("authentik_core:root-redirect"))
class TestSPInitiatedSLOLogoutMethods(TestCase):

View File

@@ -41,6 +41,24 @@ from authentik.providers.saml.views.flows import (
LOGGER = get_logger()
def _get_redirect_url(request: HttpRequest, relay_state: str = "") -> str:
"""Get the safe redirect URL from the plan context, logging a warning if the
incoming relay_state doesn't match the stored value."""
stored_relay_state = ""
if SESSION_KEY_PLAN in request.session:
plan: FlowPlan = request.session[SESSION_KEY_PLAN]
stored_relay_state = plan.context.get(PLAN_CONTEXT_SAML_RELAY_STATE, "")
if relay_state and relay_state != stored_relay_state:
LOGGER.warning(
"SAML logout relay_state mismatch, possible open redirect attempt",
received_relay_state=relay_state,
stored_relay_state=stored_relay_state,
)
return stored_relay_state
class SPInitiatedSLOView(PolicyAccessView):
"""Handle SP-initiated SAML Single Logout requests"""
@@ -203,17 +221,9 @@ class SPInitiatedSLOBindingRedirectView(SPInitiatedSLOView):
# IDP SLO, so we want to redirect to our next provider
if REQUEST_KEY_SAML_RESPONSE in request.GET:
relay_state = request.GET.get(REQUEST_KEY_RELAY_STATE, "")
if relay_state:
return redirect(relay_state)
# No RelayState provided, try to get return URL from plan context
if SESSION_KEY_PLAN in request.session:
plan: FlowPlan = request.session[SESSION_KEY_PLAN]
relay_state = plan.context.get(PLAN_CONTEXT_SAML_RELAY_STATE)
if relay_state:
return redirect(relay_state)
# No relay state and no plan context - redirect to root
redirect_url = _get_redirect_url(request, relay_state)
if redirect_url:
return redirect(redirect_url)
return redirect("authentik_core:root-redirect")
# For SAML logout requests, use the parent dispatch with auth checks
@@ -254,17 +264,9 @@ class SPInitiatedSLOBindingPOSTView(SPInitiatedSLOView):
# IDP SLO, so we want to redirect to our next provider
if REQUEST_KEY_SAML_RESPONSE in request.POST:
relay_state = request.POST.get(REQUEST_KEY_RELAY_STATE, "")
if relay_state:
return redirect(relay_state)
# No RelayState provided, try to get return URL from plan context
if SESSION_KEY_PLAN in request.session:
plan: FlowPlan = request.session[SESSION_KEY_PLAN]
relay_state = plan.context.get(PLAN_CONTEXT_SAML_RELAY_STATE)
if relay_state:
return redirect(relay_state)
# No relay state and no plan context - redirect to root
redirect_url = _get_redirect_url(request, relay_state)
if redirect_url:
return redirect(redirect_url)
return redirect("authentik_core:root-redirect")
# For SAML logout requests, use the parent dispatch with auth checks

View File

@@ -49,6 +49,7 @@ class TestRBACPermissionRoles(APITestCase):
self.assertJSONEqual(
res.content,
{
"autocomplete": {},
"pagination": {
"next": 0,
"previous": 0,

View File

@@ -208,6 +208,7 @@ SPECTACULAR_SETTINGS = {
"authentik.api.v3.schema.response.postprocess_schema_responses",
"authentik.api.v3.schema.query.postprocess_schema_query_params",
"authentik.api.v3.schema.cleanup.postprocess_schema_remove_unused",
"authentik.api.v3.schema.search.postprocess_schema_search_autocomplete",
"authentik.api.v3.schema.enum.postprocess_schema_enums",
],
}
@@ -215,10 +216,10 @@ SPECTACULAR_SETTINGS = {
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS": "authentik.api.pagination.Pagination",
"DEFAULT_FILTER_BACKENDS": [
"authentik.api.search.ql.QLSearch",
"authentik.rbac.filters.ObjectFilter",
"django_filters.rest_framework.DjangoFilterBackend",
"rest_framework.filters.OrderingFilter",
"rest_framework.filters.SearchFilter",
],
"DEFAULT_PERMISSION_CLASSES": ("authentik.rbac.permissions.ObjectPermissions",),
"DEFAULT_AUTHENTICATION_CLASSES": (

View File

@@ -1,12 +1,15 @@
import math
from os import environ
from ssl import OPENSSL_VERSION
from time import monotonic
from typing import TextIO
import pytest
from cryptography.hazmat.backends.openssl.backend import backend
from pytest import Config, Item, TerminalReporter
from authentik import authentik_full_version
from tests.e2e.utils import get_local_ip
from tests.decorators import get_local_ip
IS_CI = "CI" in environ
@@ -29,7 +32,7 @@ def pytest_report_header(*_, **__):
]
def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None:
def pytest_collection_modifyitems(config: Config, items: list[Item]) -> None:
current_id = int(environ.get("CI_RUN_ID", "0")) - 1
total_ids = int(environ.get("CI_TOTAL_RUNS", "0"))
@@ -44,3 +47,38 @@ def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item
config.hook.pytest_deselected(items=deselected_items)
items[:] = items[start:end]
print(f" Executing {start} - {end} tests")
@pytest.hookimpl(trylast=True)
def pytest_configure(config: Config):
# Replace the default terminal reporter
reporter = config.pluginmanager.get_plugin("terminalreporter")
if reporter:
config.pluginmanager.unregister(reporter)
config.pluginmanager.register(
RelativeTimeTerminalReporter(config),
"terminalreporter",
)
class RelativeTimeTerminalReporter(TerminalReporter):
_start_time: None | float
def __init__(self, config: Config, file: TextIO | None = None):
super().__init__(config, file)
self._start_time = None
def pytest_runtest_logstart(self, nodeid, location):
# Set start time on the first test
if self._start_time is None:
self._start_time = monotonic()
super().pytest_runtest_logstart(nodeid, location)
def _locationline(self, nodeid, fspath, lineno, domain):
original = super()._locationline(nodeid, fspath, lineno, domain)
if self._start_time is None:
return original
elapsed = monotonic() - self._start_time
minutes, seconds = divmod(elapsed, 60)
timestamp = f"{int(minutes):02d}:{seconds:06.3f}"
return f"[+{timestamp}] {original}"

View File

@@ -69,8 +69,8 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover
# Test-specific configuration
test_config = {
"events.context_processors.geoip": "tests/GeoLite2-City-Test.mmdb",
"events.context_processors.asn": "tests/GeoLite2-ASN-Test.mmdb",
"events.context_processors.geoip": "tests/geoip/GeoLite2-City-Test.mmdb",
"events.context_processors.asn": "tests/geoip/GeoLite2-ASN-Test.mmdb",
"blueprints_dir": "./blueprints",
"outposts.container_image_base": f"ghcr.io/goauthentik/dev-%(type)s:{get_docker_tag()}",
"tenants.enabled": False,

View File

View File

@@ -0,0 +1,42 @@
"""Source API Views"""
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.groups import PartialUserSerializer
from authentik.core.api.sources import (
GroupSourceConnectionSerializer,
GroupSourceConnectionViewSet,
UserSourceConnectionSerializer,
UserSourceConnectionViewSet,
)
from authentik.core.api.users import PartialGroupSerializer
from authentik.sources.ldap.models import (
GroupLDAPSourceConnection,
UserLDAPSourceConnection,
)
class UserLDAPSourceConnectionSerializer(UserSourceConnectionSerializer):
user_obj = PartialUserSerializer(source="user", read_only=True)
class Meta(UserSourceConnectionSerializer.Meta):
model = UserLDAPSourceConnection
fields = UserSourceConnectionSerializer.Meta.fields + ["user_obj"]
class UserLDAPSourceConnectionViewSet(UserSourceConnectionViewSet, ModelViewSet):
queryset = UserLDAPSourceConnection.objects.all()
serializer_class = UserLDAPSourceConnectionSerializer
class GroupLDAPSourceConnectionSerializer(GroupSourceConnectionSerializer):
group_obj = PartialGroupSerializer(source="group", read_only=True)
class Meta(GroupSourceConnectionSerializer.Meta):
model = GroupLDAPSourceConnection
fields = GroupSourceConnectionSerializer.Meta.fields + ["group_obj"]
class GroupLDAPSourceConnectionViewSet(GroupSourceConnectionViewSet, ModelViewSet):
queryset = GroupLDAPSourceConnection.objects.all()
serializer_class = GroupLDAPSourceConnectionSerializer

View File

@@ -0,0 +1,32 @@
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.property_mappings import PropertyMappingFilterSet, PropertyMappingSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.sources.ldap.models import (
LDAPSourcePropertyMapping,
)
class LDAPSourcePropertyMappingSerializer(PropertyMappingSerializer):
"""LDAP PropertyMapping Serializer"""
class Meta:
model = LDAPSourcePropertyMapping
fields = PropertyMappingSerializer.Meta.fields
class LDAPSourcePropertyMappingFilter(PropertyMappingFilterSet):
"""Filter for LDAPSourcePropertyMapping"""
class Meta(PropertyMappingFilterSet.Meta):
model = LDAPSourcePropertyMapping
class LDAPSourcePropertyMappingViewSet(UsedByMixin, ModelViewSet):
"""LDAP PropertyMapping Viewset"""
queryset = LDAPSourcePropertyMapping.objects.all()
serializer_class = LDAPSourcePropertyMappingSerializer
filterset_class = LDAPSourcePropertyMappingFilter
search_fields = ["name"]
ordering = ["name"]

View File

@@ -13,23 +13,15 @@ from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.property_mappings import PropertyMappingFilterSet, PropertyMappingSerializer
from authentik.core.api.sources import (
GroupSourceConnectionSerializer,
GroupSourceConnectionViewSet,
SourceSerializer,
UserSourceConnectionSerializer,
UserSourceConnectionViewSet,
)
from authentik.core.api.used_by import UsedByMixin
from authentik.crypto.models import CertificateKeyPair
from authentik.lib.sync.api import SyncStatusSerializer
from authentik.rbac.filters import ObjectFilter
from authentik.sources.ldap.models import (
GroupLDAPSourceConnection,
LDAPSource,
LDAPSourcePropertyMapping,
UserLDAPSourceConnection,
)
from authentik.sources.ldap.tasks import CACHE_KEY_STATUS, SYNC_CLASSES, ldap_sync
from authentik.tasks.models import Task, TaskStatus
@@ -224,48 +216,3 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
obj.pop("raw_dn", None)
all_objects[class_name].append(obj)
return Response(data=all_objects)
class LDAPSourcePropertyMappingSerializer(PropertyMappingSerializer):
"""LDAP PropertyMapping Serializer"""
class Meta:
model = LDAPSourcePropertyMapping
fields = PropertyMappingSerializer.Meta.fields
class LDAPSourcePropertyMappingFilter(PropertyMappingFilterSet):
"""Filter for LDAPSourcePropertyMapping"""
class Meta(PropertyMappingFilterSet.Meta):
model = LDAPSourcePropertyMapping
class LDAPSourcePropertyMappingViewSet(UsedByMixin, ModelViewSet):
"""LDAP PropertyMapping Viewset"""
queryset = LDAPSourcePropertyMapping.objects.all()
serializer_class = LDAPSourcePropertyMappingSerializer
filterset_class = LDAPSourcePropertyMappingFilter
search_fields = ["name"]
ordering = ["name"]
class UserLDAPSourceConnectionSerializer(UserSourceConnectionSerializer):
class Meta(UserSourceConnectionSerializer.Meta):
model = UserLDAPSourceConnection
class UserLDAPSourceConnectionViewSet(UserSourceConnectionViewSet, ModelViewSet):
queryset = UserLDAPSourceConnection.objects.all()
serializer_class = UserLDAPSourceConnectionSerializer
class GroupLDAPSourceConnectionSerializer(GroupSourceConnectionSerializer):
class Meta(GroupSourceConnectionSerializer.Meta):
model = GroupLDAPSourceConnection
class GroupLDAPSourceConnectionViewSet(GroupSourceConnectionViewSet, ModelViewSet):
queryset = GroupLDAPSourceConnection.objects.all()
serializer_class = GroupLDAPSourceConnectionSerializer

View File

@@ -31,6 +31,7 @@ from authentik.tasks.schedules.common import ScheduleSpec
LDAP_TIMEOUT = 15
LDAP_UNIQUENESS = "ldap_uniq"
"""Deprecated, don't use"""
LDAP_DISTINGUISHED_NAME = "distinguishedName"
LOGGER = get_logger()
@@ -159,7 +160,7 @@ class LDAPSource(IncomingSyncSource):
@property
def serializer(self) -> type[Serializer]:
from authentik.sources.ldap.api import LDAPSourceSerializer
from authentik.sources.ldap.api.sources import LDAPSourceSerializer
return LDAPSourceSerializer
@@ -192,6 +193,7 @@ class LDAPSource(IncomingSyncSource):
def update_properties_with_uniqueness_field(self, properties, dn, ldap, **kwargs):
properties.setdefault("attributes", {})[LDAP_DISTINGUISHED_NAME] = dn
# TODO: Remove after 2026.5, still stored for legacy
if self.object_uniqueness_field in ldap:
properties["attributes"][LDAP_UNIQUENESS] = flatten(
ldap.get(self.object_uniqueness_field)
@@ -356,7 +358,7 @@ class LDAPSourcePropertyMapping(PropertyMapping):
@property
def serializer(self) -> type[Serializer]:
from authentik.sources.ldap.api import LDAPSourcePropertyMappingSerializer
from authentik.sources.ldap.api.property_mappings import LDAPSourcePropertyMappingSerializer
return LDAPSourcePropertyMappingSerializer
@@ -377,7 +379,7 @@ class UserLDAPSourceConnection(UserSourceConnection):
@property
def serializer(self) -> type[Serializer]:
from authentik.sources.ldap.api import (
from authentik.sources.ldap.api.connections import (
UserLDAPSourceConnectionSerializer,
)
@@ -400,7 +402,7 @@ class GroupLDAPSourceConnection(GroupSourceConnection):
@property
def serializer(self) -> type[Serializer]:
from authentik.sources.ldap.api import (
from authentik.sources.ldap.api.connections import (
GroupLDAPSourceConnectionSerializer,
)

View File

@@ -7,9 +7,15 @@ from ldap3 import DEREF_ALWAYS, SUBTREE, Connection
from structlog.stdlib import BoundLogger, get_logger
from authentik.core.sources.mapper import SourceMapper
from authentik.core.sources.matcher import SourceMatcher
from authentik.lib.config import CONFIG
from authentik.lib.sync.mapper import PropertyMappingManager
from authentik.sources.ldap.models import LDAPSource, flatten
from authentik.sources.ldap.models import (
GroupLDAPSourceConnection,
LDAPSource,
UserLDAPSourceConnection,
flatten,
)
from authentik.tasks.models import Task
@@ -28,6 +34,9 @@ class BaseLDAPSynchronizer:
self._task = task
self._connection = source.connection()
self._logger = get_logger().bind(source=source, syncer=self.__class__.__name__)
self.matcher = SourceMatcher(
self._source, UserLDAPSourceConnection, GroupLDAPSourceConnection
)
@staticmethod
def name() -> str:

View File

@@ -12,8 +12,10 @@ from authentik.core.expression.exceptions import (
)
from authentik.core.models import Group
from authentik.core.sources.mapper import SourceMapper
from authentik.core.sources.matcher import Action
from authentik.events.models import Event, EventAction
from authentik.lib.sync.outgoing.exceptions import StopSync
from authentik.lib.utils.errors import exception_to_dict
from authentik.sources.ldap.models import (
LDAP_UNIQUENESS,
GroupLDAPSourceConnection,
@@ -88,33 +90,55 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
if "users" in defaults:
del defaults["users"]
parent = defaults.pop("parent", None)
group, created = Group.update_or_create_attributes(
{
f"attributes__{LDAP_UNIQUENESS}": uniq,
},
defaults,
)
action, connection = self.matcher.get_group_action(uniq, defaults)
created = False
if action == Action.ENROLL:
# Legacy fallback, in case the group only has an `ldap_uniq` attribute set, but
# no source connection exists yet
legacy_group = Group.objects.filter(
**{
f"attributes__{LDAP_UNIQUENESS}": uniq,
}
).first()
if legacy_group and LDAP_UNIQUENESS in legacy_group.attributes:
connection = GroupLDAPSourceConnection(
source=self._source,
group=legacy_group,
identifier=legacy_group.attributes.get(LDAP_UNIQUENESS),
)
group = legacy_group
# Switch the action to update the attributes
action = Action.AUTH
else:
group = Group.objects.create(**defaults)
created = True
connection.group = group
connection.save()
if action in (Action.AUTH, Action.LINK):
group = connection.group
group.update_attributes(defaults)
elif action == Action.DENY:
continue
if parent:
group.parents.add(parent)
self._logger.debug("Created group with attributes", **defaults)
if not GroupLDAPSourceConnection.objects.filter(
source=self._source, identifier=uniq
):
GroupLDAPSourceConnection.objects.create(
source=self._source, group=group, identifier=uniq
)
except SkipObjectException:
continue
except PropertyMappingExpressionException as exc:
raise StopSync(exc, None, exc.mapping) from exc
except (IntegrityError, FieldError, TypeError, AttributeError) as exc:
self._logger.debug("failed to create group", exc=exc)
Event.new(
EventAction.CONFIGURATION_ERROR,
message=(
f"Failed to create group: {str(exc)} "
"To merge new group with existing group, set the groups's "
f"Attribute '{LDAP_UNIQUENESS}' to '{uniq}'"
"Failed to create group; "
"To merge new group with existing group, connect it via the LDAP Source's "
"'Synced Groups' tab."
),
exception=exception_to_dict(exc),
source=self._source,
dn=group_dn,
).save()

View File

@@ -8,7 +8,11 @@ from ldap3 import SUBTREE
from ldap3.utils.conv import escape_filter_chars
from authentik.core.models import Group, User
from authentik.sources.ldap.models import LDAP_DISTINGUISHED_NAME, LDAP_UNIQUENESS, LDAPSource
from authentik.sources.ldap.models import (
LDAP_DISTINGUISHED_NAME,
GroupLDAPSourceConnection,
LDAPSource,
)
from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer
from authentik.tasks.models import Task
@@ -104,7 +108,9 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
return None
group_uniq = group_uniq[0]
if group_uniq not in self.group_cache:
groups = Group.objects.filter(**{f"attributes__{LDAP_UNIQUENESS}": group_uniq})
groups = GroupLDAPSourceConnection.objects.filter(identifier=group_uniq).select_related(
"group"
)
if not groups.exists():
if self._source.sync_groups:
self._task.info(
@@ -112,5 +118,5 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
group=group_dn,
)
return None
self.group_cache[group_uniq] = groups.first()
self.group_cache[group_uniq] = groups.first().group
return self.group_cache[group_uniq]

View File

@@ -12,8 +12,10 @@ from authentik.core.expression.exceptions import (
)
from authentik.core.models import User
from authentik.core.sources.mapper import SourceMapper
from authentik.core.sources.matcher import Action
from authentik.events.models import Event, EventAction
from authentik.lib.sync.outgoing.exceptions import StopSync
from authentik.lib.utils.errors import exception_to_dict
from authentik.sources.ldap.models import (
LDAP_UNIQUENESS,
LDAPSource,
@@ -86,27 +88,50 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
self._logger.debug("Writing user with attributes", **defaults)
if "username" not in defaults:
raise IntegrityError("Username was not set by propertymappings")
ak_user, created = User.update_or_create_attributes(
{f"attributes__{LDAP_UNIQUENESS}": uniq}, defaults
)
if not UserLDAPSourceConnection.objects.filter(
source=self._source, identifier=uniq
):
UserLDAPSourceConnection.objects.create(
source=self._source, user=ak_user, identifier=uniq
)
action, connection = self.matcher.get_user_action(uniq, defaults)
created = False
if action == Action.ENROLL:
# Legacy fallback, in case the user only has an `ldap_uniq` attribute set, but
# no source connection exists yet
legacy_user = User.objects.filter(
**{
f"attributes__{LDAP_UNIQUENESS}": uniq,
}
).first()
if legacy_user and LDAP_UNIQUENESS in legacy_user.attributes:
connection = UserLDAPSourceConnection(
source=self._source,
user=legacy_user,
identifier=legacy_user.attributes.get(LDAP_UNIQUENESS),
)
ak_user = legacy_user
# Switch the action to update the attributes
action = Action.AUTH
else:
ak_user = User.objects.create(**defaults)
created = True
connection.user = ak_user
connection.save()
if action in (Action.AUTH, Action.LINK):
ak_user = connection.user
ak_user.update_attributes(defaults)
elif action == Action.DENY:
continue
except PropertyMappingExpressionException as exc:
raise StopSync(exc, None, exc.mapping) from exc
except SkipObjectException:
continue
except (IntegrityError, FieldError, TypeError, AttributeError) as exc:
self._logger.debug("failed to create user", exc=exc)
Event.new(
EventAction.CONFIGURATION_ERROR,
message=(
f"Failed to create user: {str(exc)} "
"To merge new user with existing user, set the user's "
f"Attribute '{LDAP_UNIQUENESS}' to '{uniq}'"
"Failed to create user; "
"To merge new user with existing user, connect it via the LDAP Source's "
"'Synced Users' tab."
),
exception=exception_to_dict(exc),
source=self._source,
dn=user_dn,
).save()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,154 @@
{
"raw": {
"altServer": [],
"configurationNamingContext": [
"CN=Configuration,DC=t,DC=goauthentik,DC=io"
],
"currentTime": [
"20260331161910.0Z"
],
"defaultNamingContext": [
"DC=t,DC=goauthentik,DC=io"
],
"dnsHostName": [
"ak-dc.t.goauthentik.io"
],
"domainControllerFunctionality": [
"10"
],
"domainFunctionality": [
"10"
],
"dsServiceName": [
"CN=NTDS Settings,CN=AK-DC,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=t,DC=goauthentik,DC=io"
],
"forestFunctionality": [
"10"
],
"highestCommittedUSN": [
"20594"
],
"isGlobalCatalogReady": [
"TRUE"
],
"isSynchronized": [
"TRUE"
],
"ldapServiceName": [
"t.goauthentik.io:ak-dc$@T.GOAUTHENTIK.IO"
],
"namingContexts": [
"DC=t,DC=goauthentik,DC=io",
"CN=Configuration,DC=t,DC=goauthentik,DC=io",
"CN=Schema,CN=Configuration,DC=t,DC=goauthentik,DC=io",
"DC=DomainDnsZones,DC=t,DC=goauthentik,DC=io",
"DC=ForestDnsZones,DC=t,DC=goauthentik,DC=io"
],
"rootDomainNamingContext": [
"DC=t,DC=goauthentik,DC=io"
],
"schemaNamingContext": [
"CN=Schema,CN=Configuration,DC=t,DC=goauthentik,DC=io"
],
"serverName": [
"CN=AK-DC,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=t,DC=goauthentik,DC=io"
],
"subschemaSubentry": [
"CN=Aggregate,CN=Schema,CN=Configuration,DC=t,DC=goauthentik,DC=io"
],
"supportedCapabilities": [
"1.2.840.113556.1.4.800",
"1.2.840.113556.1.4.1670",
"1.2.840.113556.1.4.1791",
"1.2.840.113556.1.4.1935",
"1.2.840.113556.1.4.2080",
"1.2.840.113556.1.4.2237"
],
"supportedControl": [
"1.2.840.113556.1.4.319",
"1.2.840.113556.1.4.801",
"1.2.840.113556.1.4.473",
"1.2.840.113556.1.4.528",
"1.2.840.113556.1.4.417",
"1.2.840.113556.1.4.619",
"1.2.840.113556.1.4.841",
"1.2.840.113556.1.4.529",
"1.2.840.113556.1.4.805",
"1.2.840.113556.1.4.521",
"1.2.840.113556.1.4.970",
"1.2.840.113556.1.4.1338",
"1.2.840.113556.1.4.474",
"1.2.840.113556.1.4.1339",
"1.2.840.113556.1.4.1340",
"1.2.840.113556.1.4.1413",
"2.16.840.1.113730.3.4.9",
"2.16.840.1.113730.3.4.10",
"1.2.840.113556.1.4.1504",
"1.2.840.113556.1.4.1852",
"1.2.840.113556.1.4.802",
"1.2.840.113556.1.4.1907",
"1.2.840.113556.1.4.1948",
"1.2.840.113556.1.4.1974",
"1.2.840.113556.1.4.1341",
"1.2.840.113556.1.4.2026",
"1.2.840.113556.1.4.2064",
"1.2.840.113556.1.4.2065",
"1.2.840.113556.1.4.2066",
"1.2.840.113556.1.4.2090",
"1.2.840.113556.1.4.2205",
"1.2.840.113556.1.4.2204",
"1.2.840.113556.1.4.2206",
"1.2.840.113556.1.4.2211",
"1.2.840.113556.1.4.2239",
"1.2.840.113556.1.4.2255",
"1.2.840.113556.1.4.2256",
"1.2.840.113556.1.4.2309",
"1.2.840.113556.1.4.2330",
"1.2.840.113556.1.4.2354"
],
"supportedExtension": [
"1.3.6.1.4.1.1466.20037",
"1.3.6.1.4.1.1466.101.119.1",
"1.2.840.113556.1.4.1781",
"1.3.6.1.4.1.4203.1.11.3",
"1.2.840.113556.1.4.2212"
],
"supportedFeatures": [],
"supportedLDAPPolicies": [
"MaxPoolThreads",
"MaxPercentDirSyncRequests",
"MaxDatagramRecv",
"MaxReceiveBuffer",
"InitRecvTimeout",
"MaxConnections",
"MaxConnIdleTime",
"MaxPageSize",
"MaxBatchReturnMessages",
"MaxQueryDuration",
"MaxDirSyncDuration",
"MaxTempTableSize",
"MaxResultSetSize",
"MinResultSets",
"MaxResultSetsPerConn",
"MaxNotificationPerConn",
"MaxValRange",
"MaxValRangeTransitive",
"ThreadMemoryLimit",
"SystemMemoryLimitPercent",
"SecurityDescriptorWarningSize"
],
"supportedLDAPVersion": [
"3",
"2"
],
"supportedSASLMechanisms": [
"GSSAPI",
"GSS-SPNEGO",
"EXTERNAL",
"DIGEST-MD5"
],
"vendorName": [],
"vendorVersion": []
},
"type": "DsaInfo"
}

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