Compare commits

...

68 Commits

Author SHA1 Message Date
Jens Langhammer
d3d9165e3d format
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-05-05 13:56:45 +02:00
Dewi Roberts
cd0440c40c Merge branch 'main' into api--set-AuthenticatedSessionUserAgent-nullable-properties 2026-05-05 12:47:09 +01:00
Dewi Roberts
138feeb116 Make gen 2026-05-05 14:42:22 +03:00
dependabot[bot]
38ae472f6c website: bump docusaurus-theme-openapi-docs from 5.0.1 to 5.0.2 in /website (#22052)
* website: bump docusaurus-theme-openapi-docs in /website

Bumps [docusaurus-theme-openapi-docs](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/tree/HEAD/packages/docusaurus-theme-openapi-docs) from 5.0.1 to 5.0.2.
- [Release notes](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/releases)
- [Changelog](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/commits/v5.0.2/packages/docusaurus-theme-openapi-docs)

---
updated-dependencies:
- dependency-name: docusaurus-theme-openapi-docs
  dependency-version: 5.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

* bump

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-05-05 13:36:39 +02:00
Dewi Roberts
922e9b6de2 Set properties to nullable and regenerate schema 2026-05-05 14:36:14 +03:00
dependabot[bot]
7d0656c6fa web: bump the storybook group across 1 directory with 5 updates (#22024)
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.5 to 10.3.6
- [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.6/code/addons/docs)

Updates `@storybook/addon-links` from 10.3.5 to 10.3.6
- [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.6/code/addons/links)

Updates `@storybook/web-components` from 10.3.5 to 10.3.6
- [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.6/code/renderers/web-components)

Updates `@storybook/web-components-vite` from 10.3.5 to 10.3.6
- [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.6/code/frameworks/web-components-vite)

Updates `storybook` from 10.3.5 to 10.3.6
- [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.6/code/core)

---
updated-dependencies:
- dependency-name: "@storybook/addon-docs"
  dependency-version: 10.3.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/addon-links"
  dependency-version: 10.3.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components"
  dependency-version: 10.3.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components-vite"
  dependency-version: 10.3.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: storybook
  dependency-version: 10.3.6
  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-05-05 13:18:52 +02:00
Teffen Ellis
0bbe415b5b revert: web: Consistent use of "User Dashboard" (#22038) (#22046)
Revert "web: Consistent use of "User Dashboard" (#22038)"

This reverts commit d69433b314.
2026-05-05 13:17:40 +02:00
dependabot[bot]
e52c1b2bdc core: bump metrics-exporter-prometheus from 0.18.1 to 0.18.3 (#22057)
Bumps [metrics-exporter-prometheus](https://github.com/metrics-rs/metrics) from 0.18.1 to 0.18.3.
- [Changelog](https://github.com/metrics-rs/metrics/blob/main/release.toml)
- [Commits](https://github.com/metrics-rs/metrics/compare/metrics-exporter-prometheus-v0.18.1...metrics-exporter-prometheus-v0.18.3)

---
updated-dependencies:
- dependency-name: metrics-exporter-prometheus
  dependency-version: 0.18.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-05-05 13:09:40 +02:00
authentik-automation[bot]
5064167f28 core, web: update translations (#22047)
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-05-05 13:08:29 +02:00
dependabot[bot]
bca0f51b53 core: bump cryptography from 47.0.0 to 48.0.0 (#22053)
Bumps [cryptography](https://github.com/pyca/cryptography) from 47.0.0 to 48.0.0.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/47.0.0...48.0.0)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 48.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-05-05 13:08:25 +02:00
dependabot[bot]
67c197e5a5 core: bump psycopg[c,pool] from 3.3.3 to 3.3.4 (#22054)
Bumps [psycopg[c,pool]](https://github.com/psycopg/psycopg) from 3.3.3 to 3.3.4.
- [Changelog](https://github.com/psycopg/psycopg/blob/master/docs/news.rst)
- [Commits](https://github.com/psycopg/psycopg/compare/3.3.3...3.3.4)

---
updated-dependencies:
- dependency-name: psycopg[c,pool]
  dependency-version: 3.3.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-05-05 13:08:21 +02:00
dependabot[bot]
32b17da699 ci: bump taiki-e/install-action from 2.75.28 to 2.75.29 in /.github/actions/setup (#22056)
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.28 to 2.75.29.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](51cd0b8c04...b5fddbb536)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.75.29
  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-05-05 13:08:16 +02:00
Dominic R
c75eed630a web: remove native fieldset borders from action groups (#21334)
* web: remove native fieldset borders from action groups

Refs:\n- https://authentiksecurity.slack.com/archives/C08C0SCU2JV/p1775085687040019\n- https://authentiksecurity.slack.com/archives/C08C0SCU2JV/p1774988472501059

* Use consistent naming.

* Fix up styles, selector specifics, compatibility mode.

* Fix field autocapitalization, keyboard behavior.

* Fix default height.

* Fix for mid-size tablet viewports.

- Helped with debugging on mobile.

* Fix linter warning.

---------

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2026-05-05 06:17:23 +02:00
Dominic R
9f17d6df96 website/docs: document blueprint import options (#21973)
* website/docs: document blueprint import options

Closes: https://github.com/goauthentik/authentik/issues/21204

* Update website/docs/customize/blueprints/index.mdx

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

* Update website/docs/customize/blueprints/working_with_blueprints.md

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

* Clarify blueprint docs

* Apply suggestions from code review

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

---------

Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-05-05 01:07:23 +00:00
Dominic R
13c8ad5c56 website/integrations: clarify Jellyfin LDAP bind permissions (#21975)
* website/integrations: clarify Jellyfin LDAP bind permissions

Closes: https://github.com/goauthentik/authentik/issues/9770

* website/docs: clarify jellyfin LDAP service account

* website/docs: link jellyfin LDAP setup steps

* Update index.md

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

---------

Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-05-05 00:58:10 +00:00
Marcelo Elizeche Landó
28209c03e2 docs: Improve docs on webauthn authenticator attachment (#22045)
Improve docs on webauthn authenticator attachment
2026-05-05 00:34:54 +00:00
Marcelo Elizeche Landó
f47cf08d8a website/docs: Add docs for webauthn hints feature (#20933)
* Add docs for webauthn hints feature

* remove accidentally added file

* Apply suggestions from code review

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

* Apply suggestions from code review

* Apply suggestions from code review

* Apply suggestions from code review

* point to our docs

---------

Signed-off-by: Marcelo Elizeche Landó <marce@melizeche.com>
Signed-off-by: Marcelo Elizeche Landó <marcelo@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-05-04 23:47:48 +00:00
Teffen Ellis
d69433b314 web: Consistent use of "User Dashboard" (#22038)
* Update app labels.

* Update docs.
2026-05-04 23:46:58 +02:00
Gianluca Ulivi
849a6053ad website/integrations: actual budget: add env var (#22036)
Update index.mdx

Set auth method to oauth2 to use correct JWT algorithm

Signed-off-by: Gianluca Ulivi <22895603+GianlucaUlivi@users.noreply.github.com>
2026-05-04 19:09:21 +00:00
Dominic R
abdbe0269f website/docs: add webhook mapping examples (#21971)
* website/docs: add webhook mapping examples

Document event fields for generic webhook payload mappings.

Closes: https://github.com/goauthentik/authentik/issues/19335

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

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

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

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

---------

Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-05-04 18:08:54 +00:00
Dominic R
55384c384a website/integrations: fix nextcloud LDAP group mapping (#21970)
Set Nextcloud's LDAP group-member association to member (AD).

Closes: https://github.com/goauthentik/authentik/issues/21696
2026-05-04 13:44:15 -04:00
Dominic R
06fd68f076 website/docs: preserve blueprint download filenames (#21969)
* website/docs: preserve blueprint download filenames

Use a shared DownloadLink component for bundled blueprint downloads.

Closes: https://github.com/goauthentik/authentik/issues/20089

* website/docs: use download link for lockdown blueprint
2026-05-04 13:41:44 -04:00
Teffen Ellis
d35ab99b2d web: Radio and Checkbox Input Revisions (#21792)
* Flesh out checkbox group and radio style alignment.

* Fix input order, phrasing.

* fix radio not selecting default value if default value is falsey

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

* align items in empty state primary

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

* fix required flag

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

* Fix casing.

* consistent casing

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-05-04 19:12:18 +02:00
Connor Peshek
a3b0180049 providers/oauth: make rp init logout oidc certification changes (#21815)
* providers/oauth: make rp init logout oidc certification changes

* update test

* slight rework

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

* fix tests

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

* add oidc certification tests

* test

* fix backchannel url

* make urls uniform

* update to main

* remove env bind

* cleanup patch

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

* fixup

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

* add traefik healthcheck

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

* fix healthcheck

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-05-04 19:11:59 +02:00
Dominic R
88a545f4fb website/docs: document SCIM custom attributes (#21980)
* website/docs: document SCIM custom attributes

Add a SCIM provider example for custom extension attributes.

Closes: https://github.com/goauthentik/authentik/issues/14202

* website/docs: clarify SCIM custom attributes mapping

* website/docs: link SCIM mapping setup guidance

* Update index.md

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

---------

Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-05-04 14:44:47 +00:00
Marc 'risson' Schmitt
ba62507fc2 root: introduce allinone mode (#21990) 2026-05-04 16:43:11 +02:00
Dominic R
82fc2e2c80 website/docs: add SAML source mapping guidance (#21978) 2026-05-04 10:14:25 -04:00
Marc 'risson' Schmitt
80b3739640 website/docs: fix misplaced AWS-LC clang warning (#22034) 2026-05-04 15:41:57 +02:00
Marc 'risson' Schmitt
1258e1eada lifecycle/worker_process: fix healthchecks and metrics not reloading db connections after a failure (#21992) 2026-05-04 15:06:30 +02:00
Marc 'risson' Schmitt
96ed17e760 root: add more logging to worker requests (#21989) 2026-05-04 15:06:28 +02:00
Marc 'risson' Schmitt
4b17468b6e root/channels: use group_send_blocking where possible (#21993) 2026-05-04 14:53:22 +02:00
authentik-automation[bot]
c834681251 core, web: update translations (#22014)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-05-04 14:41:55 +02:00
transifex-integration[bot]
9edd7cfbda translate: Updates for project authentik and language fr_FR (#22015)
translate: Translate web/xliff/en.xlf in fr_FR

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-05-04 14:24:04 +02:00
Jens L.
4851179522 enterprise/providers/ssf: more conformance fixes (#21521)
* enterprise/providers/ssf: more conformance fixes

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

* include request when possible

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

* remove null state

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

* t

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

* re-gen & format

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

* remove None state

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

* fix ci

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

* revert a thing

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

* fix tests

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

* fix ssf conformance test

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

* no subtest

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

* fix network

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

* add test for stream update

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-05-04 14:11:21 +02:00
Jens L.
685f920de2 web/flows: update flow background (#22032)
* web/flows: update flow background

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

* Optimised images with calibre/image-actions

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-05-04 14:11:10 +02:00
Dominic R
3b4d51b0c5 website/integrations: update NetBox OIDC config (#22018) 2026-05-04 07:17:13 -04:00
dependabot[bot]
a1098d00b7 web: bump @formatjs/intl-listformat from 8.3.2 to 8.3.4 in /web (#22026)
Bumps [@formatjs/intl-listformat](https://github.com/formatjs/formatjs) from 8.3.2 to 8.3.4.
- [Release notes](https://github.com/formatjs/formatjs/releases)
- [Commits](https://github.com/formatjs/formatjs/compare/@formatjs/intl-listformat@8.3.2...@formatjs/intl-listformat@8.3.4)

---
updated-dependencies:
- dependency-name: "@formatjs/intl-listformat"
  dependency-version: 8.3.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-05-04 12:22:15 +02:00
dependabot[bot]
0d4984b964 web: bump knip from 6.6.3 to 6.7.0 in /web (#22027)
Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 6.6.3 to 6.7.0.
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@6.7.0/packages/knip)

---
updated-dependencies:
- dependency-name: knip
  dependency-version: 6.7.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-05-04 12:22:05 +02:00
dependabot[bot]
38330df1f9 core: bump metrics from 0.24.3 to 0.24.5 (#22030)
Bumps [metrics](https://github.com/metrics-rs/metrics) from 0.24.3 to 0.24.5.
- [Changelog](https://github.com/metrics-rs/metrics/blob/main/release.toml)
- [Commits](https://github.com/metrics-rs/metrics/compare/metrics-v0.24.3...metrics-v0.24.5)

---
updated-dependencies:
- dependency-name: metrics
  dependency-version: 0.24.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-05-04 12:21:22 +02:00
dependabot[bot]
8b03c36d5a core: bump github.com/getsentry/sentry-go from 0.46.0 to 0.46.1 (#22019)
Bumps [github.com/getsentry/sentry-go](https://github.com/getsentry/sentry-go) from 0.46.0 to 0.46.1.
- [Release notes](https://github.com/getsentry/sentry-go/releases)
- [Changelog](https://github.com/getsentry/sentry-go/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-go/compare/v0.46.0...v0.46.1)

---
updated-dependencies:
- dependency-name: github.com/getsentry/sentry-go
  dependency-version: 0.46.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-05-04 12:13:02 +02:00
dependabot[bot]
07a53a101c website: bump the docusaurus group in /website with 10 updates (#22020)
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.10.0` | `3.10.1` |
| [@docusaurus/core](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus) | `3.10.0` | `3.10.1` |
| [@docusaurus/faster](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-faster) | `3.10.0` | `3.10.1` |
| [@docusaurus/module-type-aliases](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases) | `3.10.0` | `3.10.1` |
| [@docusaurus/plugin-client-redirects](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-client-redirects) | `3.10.0` | `3.10.1` |
| [@docusaurus/plugin-content-docs](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-content-docs) | `3.10.0` | `3.10.1` |
| [@docusaurus/theme-common](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-theme-common) | `3.10.0` | `3.10.1` |
| [@docusaurus/tsconfig](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-tsconfig) | `3.10.0` | `3.10.1` |
| [@docusaurus/types](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-types) | `3.10.0` | `3.10.1` |
| [@docusaurus/theme-mermaid](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-theme-mermaid) | `3.10.0` | `3.10.1` |


Updates `@docusaurus/preset-classic` from 3.10.0 to 3.10.1
- [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.1/packages/docusaurus-preset-classic)

Updates `@docusaurus/core` from 3.10.0 to 3.10.1
- [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.1/packages/docusaurus)

Updates `@docusaurus/faster` from 3.10.0 to 3.10.1
- [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.1/packages/docusaurus-faster)

Updates `@docusaurus/module-type-aliases` from 3.10.0 to 3.10.1
- [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.1/packages/docusaurus-module-type-aliases)

Updates `@docusaurus/plugin-client-redirects` from 3.10.0 to 3.10.1
- [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.1/packages/docusaurus-plugin-client-redirects)

Updates `@docusaurus/plugin-content-docs` from 3.10.0 to 3.10.1
- [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.1/packages/docusaurus-plugin-content-docs)

Updates `@docusaurus/theme-common` from 3.10.0 to 3.10.1
- [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.1/packages/docusaurus-theme-common)

Updates `@docusaurus/tsconfig` from 3.10.0 to 3.10.1
- [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.1/packages/docusaurus-tsconfig)

Updates `@docusaurus/types` from 3.10.0 to 3.10.1
- [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.1/packages/docusaurus-types)

Updates `@docusaurus/theme-mermaid` from 3.10.0 to 3.10.1
- [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.1/packages/docusaurus-theme-mermaid)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-04 12:12:58 +02:00
dependabot[bot]
a3db2ce6a3 core: bump packaging from 26.1 to 26.2 (#22021)
Bumps [packaging](https://github.com/pypa/packaging) from 26.1 to 26.2.
- [Release notes](https://github.com/pypa/packaging/releases)
- [Changelog](https://github.com/pypa/packaging/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pypa/packaging/compare/26.1...26.2)

---
updated-dependencies:
- dependency-name: packaging
  dependency-version: '26.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-05-04 12:12:53 +02:00
dependabot[bot]
5487cdb874 core: bump aws-cdk-lib from 2.250.0 to 2.251.0 (#22022)
Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk) from 2.250.0 to 2.251.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.250.0...v2.251.0)

---
updated-dependencies:
- dependency-name: aws-cdk-lib
  dependency-version: 2.251.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-05-04 12:12:49 +02:00
dependabot[bot]
2d5160d09b ci: bump int128/docker-manifest-create-action from 2.19.0 to 2.20.0 (#22025)
Bumps [int128/docker-manifest-create-action](https://github.com/int128/docker-manifest-create-action) from 2.19.0 to 2.20.0.
- [Release notes](https://github.com/int128/docker-manifest-create-action/releases)
- [Commits](7df7f9e221...fa55f72001)

---
updated-dependencies:
- dependency-name: int128/docker-manifest-create-action
  dependency-version: 2.20.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-05-04 12:12:45 +02:00
dependabot[bot]
973fe0bd65 web: bump dompurify from 3.4.1 to 3.4.2 in /web (#22028)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.4.1 to 3.4.2.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.4.1...3.4.2)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-version: 3.4.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-05-04 12:12:41 +02:00
dependabot[bot]
58b5e605de ci: bump taiki-e/install-action from 2.75.25 to 2.75.28 in /.github/actions/setup (#22029)
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.25 to 2.75.28.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](1329c298aa...51cd0b8c04)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.75.28
  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-05-04 12:12:37 +02:00
Chris
626e23b87a fix(rbac): ensure migration 0056 runs before 0010 removes group field (#21964)
fix(rbac): ensure migration 0056 runs before group field is removed

Migration 0010 removes the `group` FK from the Role model, but
migration 0056 (authentik_core) queries `group_id` on Role as part of
a data migration to move guardian permissions to RBAC roles.

When upgrading from 2025.x, Django's migration executor can schedule
0010 before 0056 because neither depends on the other — only 0056
depends on 0008. This causes a FieldError at runtime:

  Cannot resolve keyword 'group_id' into field.

Adding 0056 as a dependency of 0010 enforces the correct ordering:
the data migration that reads `group_id` must complete before the
schema migration that removes it.
2026-05-04 10:48:30 +02:00
Matthew
3559beba9c website/integrations: add OneUptime SAML integration guide (#21534)
* website/integrations: add OneUptime SAML integration guide

* website/integrations: populate OneUptime SAML integration guide

* wip

* remove link

* website/integrations: simplify OneUptime SAML setup

---------

Co-authored-by: Dominic R <dominic@sdko.org>
2026-05-04 03:03:53 +00:00
authentik-automation[bot]
0b6d3a2850 core, web: update translations (#22013)
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-05-02 18:26:22 +02:00
dependabot[bot]
56ca192391 website: bump the build group in /website with 6 updates (#22000)
Bumps the build group in /website with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@swc/core-darwin-arm64](https://github.com/swc-project/swc) | `1.15.30` | `1.15.32` |
| [@swc/core-linux-arm64-gnu](https://github.com/swc-project/swc) | `1.15.30` | `1.15.32` |
| [@swc/core-linux-x64-gnu](https://github.com/swc-project/swc) | `1.15.30` | `1.15.32` |
| [@swc/html-darwin-arm64](https://github.com/swc-project/swc) | `1.15.30` | `1.15.32` |
| [@swc/html-linux-arm64-gnu](https://github.com/swc-project/swc) | `1.15.30` | `1.15.32` |
| [@swc/html-linux-x64-gnu](https://github.com/swc-project/swc) | `1.15.30` | `1.15.32` |

Updates `@swc/core-darwin-arm64` from 1.15.30 to 1.15.32
- [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.30...v1.15.32)

Updates `@swc/core-linux-arm64-gnu` from 1.15.30 to 1.15.32
- [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.30...v1.15.32)

Updates `@swc/core-linux-x64-gnu` from 1.15.30 to 1.15.32
- [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.30...v1.15.32)

Updates `@swc/html-darwin-arm64` from 1.15.30 to 1.15.32
- [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.30...v1.15.32)

Updates `@swc/html-linux-arm64-gnu` from 1.15.30 to 1.15.32
- [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.30...v1.15.32)

Updates `@swc/html-linux-x64-gnu` from 1.15.30 to 1.15.32
- [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.30...v1.15.32)

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

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>
2026-05-02 18:25:36 +02:00
Ken Sternberg
6df62aaa2a web: fix a few visual nits reported after the latest release (#22012)
* ## What

         window.authentik.flow = {
             "layout": "{{ flow.layout }}",
    +        "background": "{{ flow.background }}",
    +        "title": "{{ flow.title }}",
         };

Amends the `flow.html` template and `GlobalAuthentik` parser to include new parameters, `background` and `title`, in the flow-specific part of the configuration written to the HTML `<head>` object, and to provide those parameters to client code.

## Why

The `layout` is start-up critical: it tells the Flow interface how the admin wants the Flow page to look, and allows the HTML and CSS to be pre-aligned to that condition. `layout` is determined on a per-Flow bases, not a per-Stage basis; Flows are derived from a tuple of `(Brand, Application?)`, where the opening policy *may* direct a user to a different flow if the user reached authentik via a redirect from a specific application, but will otherwise fall back to the default Flow for the Brand.

The `background` is a field that is required if the `Flow`’s layout is of type `frame_background`; in this case, the part of the viewport not dedicated to the FlowExecutor is reserved for an `<iframe>` that will be filled in with whatever the administrator specifies. Although this gives it the same priority as `layout` (whether it’s provided or undefined) for describing the [chrome](https://developer.mozilla.org/en-US/docs/Glossary/Chrome) around a challenge, it is currently not provided to the application in the start-up config; it is provided in the `challenge` and renders the IFrame as part of the initial challenge.

This patch fixes that; if `layout` is provided, `background` ought to be as well, even if it’s empty. The execution of a Challenge ought not have any influence over the look and feel of the Flow-defined appearance *around* that Challenge.

I have added `title` as well; with that, all of the current theme-and-appearance related configuration details are placed into `<head>` and can be removed from the FlowExecutor.

Server-side, `background` is currently specified: `background = FileField(blank=True, default="")` which is … interesting since we also appear to store URLs in it. I don’t see anything in the FlowSerializer that would change that from a client’s point of view.

This patch furthers the effort to separate flow execution from flow presentation.

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

* The status label was using HTML booleans incorrectly. It is impossible for a boolean to be null. The default red was alarming, so I chose a neutral grey for the 'not default' state.

* It is not enough to provide a blank cell to ensure the header is spaced correctly; if the table is empty, that will collapse to zero width.  Providing the classes that go with the 'this cell may contain a toggle' provides the correct spacing as well.

* Fix inconsistent wording between menu and page; make the 'select type' radiocard and radiolist interfaces flush with the top of the form container, removing a weird jagged visual line between the menu and the content.

* Document adding 'toggle' to Table classes.
2026-05-02 18:25:23 +02:00
transifex-integration[bot]
ca344a64c4 translate: Updates for project authentik and language fr_FR (#22008)
* translate: Translate django.po in fr_FR

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

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

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-05-02 18:07:19 +02:00
Jens L.
a0cdd81f71 tests: add mixin to launch traefik for tests requiring SSL (#22011)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-05-01 18:23:13 +02:00
Dominic R
8eff4c7e0b website/docs: document air-gapped upgrades (#21972)
* website/docs: document air-gapped upgrades

Explain how to prepare mirrored artifacts for air-gapped upgrades.

Closes: https://github.com/goauthentik/authentik/issues/21376

* Update website/docs/install-config/air-gapped.mdx

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

---------

Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2026-05-01 11:54:37 -04:00
Jens L.
d241a0e8f1 web/admin: use bindings form for app entitlements (#22007)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-05-01 16:28:46 +02:00
Simon Cinca
ebfc01fcda website/integrations: Add guide to integrate Technitium DNS with authentik (#21826)
Co-authored-by: Dominic R <dominic@sdko.org>
2026-05-01 15:12:24 +02:00
Dominic R
4b0e8a411b website/docs: clarify M2M scope requests (#21977) 2026-05-01 13:11:59 +00:00
Dominic R
9bf6595fc6 website/docs: clarify LDAP TLS verification (#21974) 2026-05-01 09:09:14 -04:00
Dominic R
5c07e845d2 website/docs: clarify blueprint identifiers (#21976)
Closes: https://github.com/goauthentik/authentik/issues/15601
2026-05-01 08:45:38 -04:00
Dominic R
4f76232e7c website/docs: document promoted sources (#21979)
Closes: https://discord.com/channels/809154715984199690/809154716507963434/1499225991778926612
2026-05-01 08:00:33 -04:00
dependabot[bot]
846f8a7e30 lifecycle/aws: bump aws-cdk from 2.1118.4 to 2.1119.0 in /lifecycle/aws (#22001)
Bumps [aws-cdk](https://github.com/aws/aws-cdk-cli/tree/HEAD/packages/aws-cdk) from 2.1118.4 to 2.1119.0.
- [Release notes](https://github.com/aws/aws-cdk-cli/releases)
- [Commits](https://github.com/aws/aws-cdk-cli/commits/aws-cdk@v2.1119.0/packages/aws-cdk)

---
updated-dependencies:
- dependency-name: aws-cdk
  dependency-version: 2.1119.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-05-01 12:49:17 +02:00
dependabot[bot]
fa1c3490c3 web: bump the swc group across 1 directory with 11 updates (#22004)
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.30 to 1.15.32
- [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.32/packages/core)

Updates `@swc/core-darwin-arm64` from 1.15.30 to 1.15.32
- [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.30...v1.15.32)

Updates `@swc/core-darwin-x64` from 1.15.30 to 1.15.32
- [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.30...v1.15.32)

Updates `@swc/core-linux-arm-gnueabihf` from 1.15.30 to 1.15.32
- [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.30...v1.15.32)

Updates `@swc/core-linux-arm64-gnu` from 1.15.30 to 1.15.32
- [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.30...v1.15.32)

Updates `@swc/core-linux-arm64-musl` from 1.15.30 to 1.15.32
- [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.30...v1.15.32)

Updates `@swc/core-linux-x64-gnu` from 1.15.30 to 1.15.32
- [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.30...v1.15.32)

Updates `@swc/core-linux-x64-musl` from 1.15.30 to 1.15.32
- [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.30...v1.15.32)

Updates `@swc/core-win32-arm64-msvc` from 1.15.30 to 1.15.32
- [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.30...v1.15.32)

Updates `@swc/core-win32-ia32-msvc` from 1.15.30 to 1.15.32
- [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.30...v1.15.32)

Updates `@swc/core-win32-x64-msvc` from 1.15.30 to 1.15.32
- [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.30...v1.15.32)

---
updated-dependencies:
- dependency-name: "@swc/core"
  dependency-version: 1.15.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-arm64"
  dependency-version: 1.15.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-x64"
  dependency-version: 1.15.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm-gnueabihf"
  dependency-version: 1.15.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-gnu"
  dependency-version: 1.15.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-musl"
  dependency-version: 1.15.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-gnu"
  dependency-version: 1.15.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-musl"
  dependency-version: 1.15.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-arm64-msvc"
  dependency-version: 1.15.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-ia32-msvc"
  dependency-version: 1.15.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-x64-msvc"
  dependency-version: 1.15.32
  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-05-01 12:49:14 +02:00
dependabot[bot]
a35edf7d0f core: bump uvicorn[standard] from 0.45.0 to 0.46.0 (#22002)
Bumps [uvicorn[standard]](https://github.com/Kludex/uvicorn) from 0.45.0 to 0.46.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.45.0...0.46.0)

---
updated-dependencies:
- dependency-name: uvicorn[standard]
  dependency-version: 0.46.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-05-01 12:49:12 +02:00
dependabot[bot]
9d4d5b7133 web: bump @sentry/browser from 10.49.0 to 10.50.0 in /web in the sentry group across 1 directory (#22003)
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.49.0 to 10.50.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.49.0...10.50.0)

---
updated-dependencies:
- dependency-name: "@sentry/browser"
  dependency-version: 10.50.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-05-01 12:49:08 +02:00
dependabot[bot]
8d91a76bc9 ci: bump taiki-e/install-action from 2.75.23 to 2.75.25 in /.github/actions/setup (#22005)
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.23 to 2.75.25.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](481c34c1cf...1329c298aa)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 12:49:04 +02:00
dependabot[bot]
6910428a93 core: bump reqwest from 0.13.2 to 0.13.3 (#22006)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.13.2 to 0.13.3.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.13.2...v0.13.3)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-version: 0.13.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-05-01 12:49:01 +02:00
authentik-automation[bot]
cb181d388a stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#21999)
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-05-01 12:48:51 +02:00
authentik-automation[bot]
aad4b6f925 core, web: update translations (#21998)
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-05-01 02:01:58 -03:00
178 changed files with 6374 additions and 3061 deletions

View File

@@ -64,7 +64,7 @@ runs:
rustflags: "" rustflags: ""
- name: Setup rust dependencies - name: Setup rust dependencies
if: ${{ contains(inputs.dependencies, 'rust') }} if: ${{ contains(inputs.dependencies, 'rust') }}
uses: taiki-e/install-action@481c34c1cf3a84c68b5e46f4eccfc82af798415a # v2 uses: taiki-e/install-action@b5fddbb5361bce8a06fb168c9d403a6cc552b084 # v2
with: with:
tool: cargo-deny cargo-machete cargo-llvm-cov nextest tool: cargo-deny cargo-machete cargo-llvm-cov nextest
- name: Setup node (web) - name: Setup node (web)

View File

@@ -90,7 +90,7 @@ jobs:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- uses: int128/docker-manifest-create-action@7df7f9e221d927eaadf87db231ddf728047308a4 # v2 - uses: int128/docker-manifest-create-action@fa55f72001a6c74b0f4997dca65c70d334905180 # v2
id: build id: build
with: with:
tags: ${{ matrix.tag }} tags: ${{ matrix.tag }}

View File

@@ -282,10 +282,18 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
job: job:
- name: basic - name: oidc_basic
glob: tests/openid_conformance/test_basic.py glob: tests/openid_conformance/test_oidc_basic.py
- name: implicit - name: oidc_implicit
glob: tests/openid_conformance/test_implicit.py glob: tests/openid_conformance/test_oidc_implicit.py
- name: oidc_rp-initiated
glob: tests/openid_conformance/test_oidc_rp_initiated.py
- name: oidc_frontchannel
glob: tests/openid_conformance/test_oidc_frontchannel.py
- name: oidc_backchannel
glob: tests/openid_conformance/test_oidc_backchannel.py
- name: ssf_transmitter
glob: tests/openid_conformance/test_ssf_transmitter.py
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- name: Setup authentik env - name: Setup authentik env

5
.gitignore vendored
View File

@@ -229,6 +229,11 @@ source_docs/
### Golang ### ### Golang ###
/vendor/ /vendor/
server
proxy
ldap
rac
radius
### Docker ### ### Docker ###
tests/openid_conformance/exports/*.zip tests/openid_conformance/exports/*.zip

108
Cargo.lock generated
View File

@@ -17,18 +17,6 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.4" version = "1.1.4"
@@ -203,6 +191,7 @@ dependencies = [
"tokio", "tokio",
"tracing", "tracing",
"uuid", "uuid",
"which",
] ]
[[package]] [[package]]
@@ -1014,6 +1003,17 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "evmap"
version = "11.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8874945f036109c72242964c1174cf99434e30cfa45bf45fedc983f50046f8"
dependencies = [
"hashbag",
"left-right",
"smallvec",
]
[[package]] [[package]]
name = "eyre" name = "eyre"
version = "0.6.12" version = "0.6.12"
@@ -1230,6 +1230,21 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "generator"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9"
dependencies = [
"cc",
"cfg-if",
"libc",
"log",
"rustversion",
"windows-link",
"windows-result",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.7" version = "0.14.7"
@@ -1311,6 +1326,12 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "hashbag"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7040a10f52cba493ddb09926e15d10a9d8a28043708a405931fe4c6f19fac064"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.5" version = "0.15.5"
@@ -1868,6 +1889,17 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "left-right"
version = "0.11.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f0c21e4c8ff95f487fb34e6f9182875f42c84cef966d29216bf115d9bba835a"
dependencies = [
"crossbeam-utils",
"loom",
"slab",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.183" version = "0.2.183"
@@ -1939,6 +1971,19 @@ version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "loom"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"
dependencies = [
"cfg-if",
"generator",
"scoped-tls",
"tracing",
"tracing-subscriber",
]
[[package]] [[package]]
name = "lru-slab" name = "lru-slab"
version = "0.1.2" version = "0.1.2"
@@ -1978,21 +2023,22 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]] [[package]]
name = "metrics" name = "metrics"
version = "0.24.3" version = "0.24.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" checksum = "ff56c2e7dce6bd462e3b8919986a617027481b1dcc703175b58cf9dd98a2f071"
dependencies = [ dependencies = [
"ahash",
"portable-atomic", "portable-atomic",
"rapidhash",
] ]
[[package]] [[package]]
name = "metrics-exporter-prometheus" name = "metrics-exporter-prometheus"
version = "0.18.1" version = "0.18.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3589659543c04c7dc5526ec858591015b87cd8746583b51b48ef4353f99dbcda" checksum = "1db0d8f1fc9e62caebd0319e11eaec5822b0186c171568f0480b46a0137f9108"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"evmap",
"indexmap", "indexmap",
"metrics", "metrics",
"metrics-util", "metrics-util",
@@ -2813,6 +2859,15 @@ dependencies = [
"rand_core 0.9.5", "rand_core 0.9.5",
] ]
[[package]]
name = "rapidhash"
version = "4.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59"
dependencies = [
"rustversion",
]
[[package]] [[package]]
name = "raw-cpuid" name = "raw-cpuid"
version = "11.6.0" version = "11.6.0"
@@ -2871,9 +2926,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.13.2" version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
@@ -3105,6 +3160,12 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
@@ -4515,6 +4576,15 @@ dependencies = [
"rustls-pki-types", "rustls-pki-types",
] ]
[[package]]
name = "which"
version = "8.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81995fafaaaf6ae47a7d0cc83c67caf92aeb7e5331650ae6ff856f7c0c60c459"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "whoami" name = "whoami"
version = "1.6.1" version = "1.6.1"

View File

@@ -43,15 +43,15 @@ hyper-unix-socket = "= 0.6.1"
hyper-util = "= 0.1.20" hyper-util = "= 0.1.20"
ipnet = { version = "= 2.12.0", features = ["serde"] } ipnet = { version = "= 2.12.0", features = ["serde"] }
json-subscriber = "= 0.2.8" json-subscriber = "= 0.2.8"
metrics = "= 0.24.3" metrics = "= 0.24.5"
metrics-exporter-prometheus = { version = "= 0.18.1", default-features = false } metrics-exporter-prometheus = { version = "= 0.18.3", default-features = false }
nix = { version = "= 0.31.2", features = ["hostname", "signal"] } nix = { version = "= 0.31.2", features = ["hostname", "signal"] }
notify = "= 8.2.0" notify = "= 8.2.0"
pin-project-lite = "= 0.2.17" pin-project-lite = "= 0.2.17"
pyo3 = "= 0.28.3" pyo3 = "= 0.28.3"
pyo3-build-config = "= 0.28.3" pyo3-build-config = "= 0.28.3"
regex = "= 1.12.3" regex = "= 1.12.3"
reqwest = { version = "= 0.13.2", features = [ reqwest = { version = "= 0.13.3", features = [
"form", "form",
"json", "json",
"multipart", "multipart",
@@ -113,6 +113,7 @@ tracing-subscriber = { version = "= 0.3.23", features = [
] } ] }
url = "= 2.5.8" url = "= 2.5.8"
uuid = { version = "= 1.23.1", features = ["serde", "v4"] } uuid = { version = "= 1.23.1", features = ["serde", "v4"] }
which = "= 8.0.2"
ak-axum = { package = "authentik-axum", version = "2026.5.0-rc1", path = "./packages/ak-axum" } ak-axum = { package = "authentik-axum", version = "2026.5.0-rc1", path = "./packages/ak-axum" }
ak-client = { package = "authentik-client", version = "2026.5.0-rc1", path = "./packages/client-rust" } ak-client = { package = "authentik-client", version = "2026.5.0-rc1", path = "./packages/client-rust" }
@@ -282,6 +283,7 @@ sqlx = { workspace = true, optional = true }
tokio.workspace = true tokio.workspace = true
tracing.workspace = true tracing.workspace = true
uuid.workspace = true uuid.workspace = true
which.workspace = true
[lints] [lints]
workspace = true workspace = true

View File

@@ -109,14 +109,11 @@ i18n-extract: core-i18n-extract web-i18n-extract ## Extract strings that requir
aws-cfn: aws-cfn:
cd lifecycle/aws && npm i && $(UV) run npm run aws-cfn cd lifecycle/aws && npm i && $(UV) run npm run aws-cfn
run-server: ## Run the main authentik server process run: ## Run the main authentik server and worker processes
$(UV) run ak server $(UV) run ak allinone
run-worker: ## Run the main authentik worker process run-watch: ## Run the authentik server and worker, with auto reloading
$(UV) run ak worker watchexec --on-busy-update=restart --stop-signal=SIGINT --exts py,rs,go --no-meta --notify -- $(UV) run ak allinone
run-worker-watch: ## Run the authentik worker, with auto reloading
watchexec --on-busy-update=restart --stop-signal=SIGINT --exts py,rs --no-meta --notify -- $(UV) run ak worker
core-i18n-extract: core-i18n-extract:
$(UV) run ak makemessages \ $(UV) run ak makemessages \

View File

@@ -32,19 +32,19 @@ from authentik.rbac.decorators import permission_required
class UserAgentDeviceDict(TypedDict): class UserAgentDeviceDict(TypedDict):
"""User agent device""" """User agent device"""
brand: str brand: str | None = None
family: str family: str
model: str model: str | None = None
class UserAgentOSDict(TypedDict): class UserAgentOSDict(TypedDict):
"""User agent os""" """User agent os"""
family: str family: str
major: str major: str | None = None
minor: str minor: str | None = None
patch: str patch: str | None = None
patch_minor: str patch_minor: str | None = None
class UserAgentBrowserDict(TypedDict): class UserAgentBrowserDict(TypedDict):

View File

@@ -1,6 +1,5 @@
"""authentik core signals""" """authentik core signals"""
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer from channels.layers import get_channel_layer
from django.contrib.auth.signals import user_logged_in from django.contrib.auth.signals import user_logged_in
from django.core.cache import cache from django.core.cache import cache
@@ -59,7 +58,7 @@ def user_logged_in_session(sender, request: HttpRequest, user: User, **_):
layer = get_channel_layer() layer = get_channel_layer()
device_cookie = request.COOKIES.get("authentik_device") device_cookie = request.COOKIES.get("authentik_device")
if device_cookie: if device_cookie:
async_to_sync(layer.group_send)( layer.group_send_blocking(
build_device_group(device_cookie), build_device_group(device_cookie),
{"type": "event.session.authenticated"}, {"type": "event.session.authenticated"},
) )

View File

@@ -1,6 +1,7 @@
# Generated by Django 5.2.12 on 2026-04-04 16:58 # Generated by Django 5.2.12 on 2026-04-04 16:58
from django.db import migrations, models from django.db import migrations, models
import django.contrib.postgres.fields
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -40,4 +41,109 @@ class Migration(migrations.Migration):
] ]
), ),
), ),
migrations.AlterField(
model_name="stream",
name="events_requested",
field=django.contrib.postgres.fields.ArrayField(
base_field=models.TextField(
choices=[
(
"https://schemas.openid.net/secevent/caep/event-type/session-revoked",
"Caep Session Revoked",
),
(
"https://schemas.openid.net/secevent/caep/event-type/token-claims-change",
"Caep Token Claims Change",
),
(
"https://schemas.openid.net/secevent/caep/event-type/credential-change",
"Caep Credential Change",
),
(
"https://schemas.openid.net/secevent/caep/event-type/assurance-level-change",
"Caep Assurance Level Change",
),
(
"https://schemas.openid.net/secevent/caep/event-type/device-compliance-change",
"Caep Device Compliance Change",
),
(
"https://schemas.openid.net/secevent/caep/event-type/session-established",
"Caep Session Established",
),
(
"https://schemas.openid.net/secevent/caep/event-type/session-presented",
"Caep Session Presented",
),
(
"https://schemas.openid.net/secevent/caep/event-type/risk-level-change",
"Caep Risk Level Change",
),
(
"https://schemas.openid.net/secevent/ssf/event-type/verification",
"Set Verification",
),
]
),
default=list,
size=None,
),
),
migrations.AlterField(
model_name="stream",
name="status",
field=models.TextField(
choices=[
("enabled", "Enabled"),
("paused", "Paused"),
("disabled", "Disabled"),
("disabled_deleted", "Disabled Deleted"),
],
default="enabled",
),
),
migrations.AlterField(
model_name="streamevent",
name="type",
field=models.TextField(
choices=[
(
"https://schemas.openid.net/secevent/caep/event-type/session-revoked",
"Caep Session Revoked",
),
(
"https://schemas.openid.net/secevent/caep/event-type/token-claims-change",
"Caep Token Claims Change",
),
(
"https://schemas.openid.net/secevent/caep/event-type/credential-change",
"Caep Credential Change",
),
(
"https://schemas.openid.net/secevent/caep/event-type/assurance-level-change",
"Caep Assurance Level Change",
),
(
"https://schemas.openid.net/secevent/caep/event-type/device-compliance-change",
"Caep Device Compliance Change",
),
(
"https://schemas.openid.net/secevent/caep/event-type/session-established",
"Caep Session Established",
),
(
"https://schemas.openid.net/secevent/caep/event-type/session-presented",
"Caep Session Presented",
),
(
"https://schemas.openid.net/secevent/caep/event-type/risk-level-change",
"Caep Risk Level Change",
),
(
"https://schemas.openid.net/secevent/ssf/event-type/verification",
"Set Verification",
),
]
),
),
] ]

View File

@@ -24,8 +24,31 @@ class EventTypes(models.TextChoices):
"""SSF Event types supported by authentik""" """SSF Event types supported by authentik"""
CAEP_SESSION_REVOKED = "https://schemas.openid.net/secevent/caep/event-type/session-revoked" CAEP_SESSION_REVOKED = "https://schemas.openid.net/secevent/caep/event-type/session-revoked"
"""https://openid.net/specs/openid-caep-1_0-final.html#section-3.1"""
CAEP_TOKEN_CLAIMS_CHANGE = (
"https://schemas.openid.net/secevent/caep/event-type/token-claims-change"
)
"""https://openid.net/specs/openid-caep-1_0-final.html#section-3.2"""
CAEP_CREDENTIAL_CHANGE = "https://schemas.openid.net/secevent/caep/event-type/credential-change" CAEP_CREDENTIAL_CHANGE = "https://schemas.openid.net/secevent/caep/event-type/credential-change"
"""https://openid.net/specs/openid-caep-1_0-final.html#section-3.3"""
CAEP_ASSURANCE_LEVEL_CHANGE = (
"https://schemas.openid.net/secevent/caep/event-type/assurance-level-change"
)
"""https://openid.net/specs/openid-caep-1_0-final.html#section-3.4"""
CAEP_DEVICE_COMPLIANCE_CHANGE = (
"https://schemas.openid.net/secevent/caep/event-type/device-compliance-change"
)
"""https://openid.net/specs/openid-caep-1_0-final.html#section-3.5"""
CAEP_SESSION_ESTABLISHED = (
"https://schemas.openid.net/secevent/caep/event-type/session-established"
)
"""https://openid.net/specs/openid-caep-1_0-final.html#section-3.6"""
CAEP_SESSION_PRESENTED = "https://schemas.openid.net/secevent/caep/event-type/session-presented"
"""https://openid.net/specs/openid-caep-1_0-final.html#section-3.7"""
CAEP_RISK_LEVEL_CHANGE = "https://schemas.openid.net/secevent/caep/event-type/risk-level-change"
"""https://openid.net/specs/openid-caep-1_0-final.html#section-3.8"""
SET_VERIFICATION = "https://schemas.openid.net/secevent/ssf/event-type/verification" SET_VERIFICATION = "https://schemas.openid.net/secevent/ssf/event-type/verification"
"""https://openid.net/specs/openid-sharedsignals-framework-1_0.html#section-8.1.4.1"""
class DeliveryMethods(models.TextChoices): class DeliveryMethods(models.TextChoices):
@@ -46,10 +69,12 @@ class SSFEventStatus(models.TextChoices):
class StreamStatus(models.TextChoices): class StreamStatus(models.TextChoices):
"""SSF Stream status"""
ENABLED = "enabled" ENABLED = "enabled"
PAUSED = "paused" PAUSED = "paused"
DISABLED = "disabled" DISABLED = "disabled"
DISABLED_DELETED = "disabled_deleted"
class SSFProvider(TasksModel, BackchannelProvider): class SSFProvider(TasksModel, BackchannelProvider):

View File

@@ -108,13 +108,13 @@ def send_ssf_event(stream_uuid: UUID, event_data: dict[str, Any]):
event.save() event.save()
self.info("Event successfully sent", status=response.status_code) 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 # 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 # (status=StreamStatus.DISABLED_DELETED), then we can delete the stream
if ( if (
not StreamEvent.objects.filter( not StreamEvent.objects.filter(
stream=stream, stream=stream,
status__in=[SSFEventStatus.PENDING_FAILED, SSFEventStatus.PENDING_NEW], status__in=[SSFEventStatus.PENDING_FAILED, SSFEventStatus.PENDING_NEW],
).exists() ).exists()
and stream.status == StreamStatus.DISABLED and stream.status == StreamStatus.DISABLED_DELETED
): ):
LOGGER.info( LOGGER.info(
"Deleting inactive stream as all pending messages were sent.", stream=stream "Deleting inactive stream as all pending messages were sent.", stream=stream

View File

@@ -62,7 +62,7 @@ class TestSSFAuth(APITestCase):
self.assertEqual(event.status, SSFEventStatus.PENDING_FAILED) self.assertEqual(event.status, SSFEventStatus.PENDING_FAILED)
self.assertEqual( self.assertEqual(
event.payload["events"], event.payload["events"],
{"https://schemas.openid.net/secevent/ssf/event-type/verification": {"state": None}}, {"https://schemas.openid.net/secevent/ssf/event-type/verification": {}},
) )
def test_stream_add_oidc(self): def test_stream_add_oidc(self):
@@ -115,7 +115,7 @@ class TestSSFAuth(APITestCase):
self.assertEqual(event.status, SSFEventStatus.PENDING_FAILED) self.assertEqual(event.status, SSFEventStatus.PENDING_FAILED)
self.assertEqual( self.assertEqual(
event.payload["events"], event.payload["events"],
{"https://schemas.openid.net/secevent/ssf/event-type/verification": {"state": None}}, {"https://schemas.openid.net/secevent/ssf/event-type/verification": {}},
) )
def test_token_invalid(self): def test_token_invalid(self):

View File

@@ -54,7 +54,7 @@ class TestStream(APITestCase):
self.assertEqual(event.status, SSFEventStatus.PENDING_FAILED) self.assertEqual(event.status, SSFEventStatus.PENDING_FAILED)
self.assertEqual( self.assertEqual(
event.payload["events"], event.payload["events"],
{"https://schemas.openid.net/secevent/ssf/event-type/verification": {"state": None}}, {"https://schemas.openid.net/secevent/ssf/event-type/verification": {}},
) )
def test_stream_add_poll(self): def test_stream_add_poll(self):
@@ -96,7 +96,7 @@ class TestStream(APITestCase):
) )
self.assertEqual(res.status_code, 204) self.assertEqual(res.status_code, 204)
stream.refresh_from_db() stream.refresh_from_db()
self.assertEqual(stream.status, StreamStatus.DISABLED) self.assertEqual(stream.status, StreamStatus.DISABLED_DELETED)
def test_stream_get(self): def test_stream_get(self):
"""get stream""" """get stream"""
@@ -225,3 +225,26 @@ class TestStream(APITestCase):
HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}", HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}",
) )
self.assertEqual(res.status_code, 404) self.assertEqual(res.status_code, 404)
def test_stream_status_update(self):
stream = Stream.objects.create(provider=self.provider)
res = self.client.post(
reverse(
"authentik_providers_ssf:stream-status",
kwargs={"application_slug": self.application.slug},
),
data={
"stream_id": str(stream.pk),
"status": StreamStatus.DISABLED,
},
HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}",
)
self.assertEqual(res.status_code, 200)
stream.refresh_from_db()
self.assertJSONEqual(
res.content,
{
"stream_id": str(stream.pk),
"status": str(stream.status),
},
)

View File

@@ -33,7 +33,7 @@ class TestTasks(APITestCase):
) )
event_data = stream.prepare_event_payload( event_data = stream.prepare_event_payload(
EventTypes.SET_VERIFICATION, EventTypes.SET_VERIFICATION,
{"state": None}, {},
sub_id={"format": "opaque", "id": str(stream.uuid)}, sub_id={"format": "opaque", "id": str(stream.uuid)},
) )
with Mocker() as mocker: with Mocker() as mocker:
@@ -46,7 +46,7 @@ class TestTasks(APITestCase):
) )
jwt = decode_complete(mocker.request_history[0].body, options={"verify_signature": False}) jwt = decode_complete(mocker.request_history[0].body, options={"verify_signature": False})
self.assertEqual(jwt["header"]["typ"], "secevent+jwt") self.assertEqual(jwt["header"]["typ"], "secevent+jwt")
self.assertIsNone(jwt["payload"]["events"][EventTypes.SET_VERIFICATION]["state"]) self.assertEqual(jwt["payload"]["events"][EventTypes.SET_VERIFICATION], {})
def test_push_auth(self): def test_push_auth(self):
auth = generate_id() auth = generate_id()
@@ -58,7 +58,7 @@ class TestTasks(APITestCase):
) )
event_data = stream.prepare_event_payload( event_data = stream.prepare_event_payload(
EventTypes.SET_VERIFICATION, EventTypes.SET_VERIFICATION,
{"state": None}, {},
sub_id={"format": "opaque", "id": str(stream.uuid)}, sub_id={"format": "opaque", "id": str(stream.uuid)},
) )
with Mocker() as mocker: with Mocker() as mocker:
@@ -72,7 +72,7 @@ class TestTasks(APITestCase):
) )
jwt = decode_complete(mocker.request_history[0].body, options={"verify_signature": False}) jwt = decode_complete(mocker.request_history[0].body, options={"verify_signature": False})
self.assertEqual(jwt["header"]["typ"], "secevent+jwt") self.assertEqual(jwt["header"]["typ"], "secevent+jwt")
self.assertIsNone(jwt["payload"]["events"][EventTypes.SET_VERIFICATION]["state"]) self.assertEqual(jwt["payload"]["events"][EventTypes.SET_VERIFICATION], {})
def test_push_stream_disable(self): def test_push_stream_disable(self):
auth = generate_id() auth = generate_id()
@@ -81,11 +81,11 @@ class TestTasks(APITestCase):
delivery_method=DeliveryMethods.RFC_PUSH, delivery_method=DeliveryMethods.RFC_PUSH,
endpoint_url="http://localhost/ssf-push", endpoint_url="http://localhost/ssf-push",
authorization_header=auth, authorization_header=auth,
status=StreamStatus.DISABLED, status=StreamStatus.DISABLED_DELETED,
) )
event_data = stream.prepare_event_payload( event_data = stream.prepare_event_payload(
EventTypes.SET_VERIFICATION, EventTypes.SET_VERIFICATION,
{"state": None}, {},
sub_id={"format": "opaque", "id": str(stream.uuid)}, sub_id={"format": "opaque", "id": str(stream.uuid)},
) )
with Mocker() as mocker: with Mocker() as mocker:
@@ -95,7 +95,7 @@ class TestTasks(APITestCase):
).get_result(block=True, timeout=1) ).get_result(block=True, timeout=1)
jwt = decode_complete(mocker.request_history[0].body, options={"verify_signature": False}) jwt = decode_complete(mocker.request_history[0].body, options={"verify_signature": False})
self.assertEqual(jwt["header"]["typ"], "secevent+jwt") self.assertEqual(jwt["header"]["typ"], "secevent+jwt")
self.assertIsNone(jwt["payload"]["events"][EventTypes.SET_VERIFICATION]["state"]) self.assertEqual(jwt["payload"]["events"][EventTypes.SET_VERIFICATION], {})
self.assertFalse(Stream.objects.filter(pk=stream.pk).exists()) self.assertFalse(Stream.objects.filter(pk=stream.pk).exists())
def test_push_error(self): def test_push_error(self):
@@ -106,7 +106,7 @@ class TestTasks(APITestCase):
) )
event_data = stream.prepare_event_payload( event_data = stream.prepare_event_payload(
EventTypes.SET_VERIFICATION, EventTypes.SET_VERIFICATION,
{"state": None}, {},
sub_id={"format": "opaque", "id": str(stream.uuid)}, sub_id={"format": "opaque", "id": str(stream.uuid)},
) )
with Mocker() as mocker: with Mocker() as mocker:

View File

@@ -24,10 +24,10 @@ class SSFView(APIView):
class SSFStreamView(SSFView): class SSFStreamView(SSFView):
def get_object(self, any_status=False) -> Stream: def get_object(self) -> Stream:
streams = Stream.objects.filter(provider=self.provider) streams = Stream.objects.filter(provider=self.provider).exclude(
if not any_status: status=StreamStatus.DISABLED_DELETED
streams = streams.filter(status__in=[StreamStatus.ENABLED, StreamStatus.PAUSED]) )
if "stream_id" in self.request.query_params: if "stream_id" in self.request.query_params:
streams = streams.filter(pk=self.request.query_params["stream_id"]) streams = streams.filter(pk=self.request.query_params["stream_id"])
if "stream_id" in self.request.data: if "stream_id" in self.request.data:

View File

@@ -1,6 +1,6 @@
from uuid import uuid4 from uuid import uuid4
from django.http import HttpRequest from django.http import Http404, HttpRequest
from django.urls import reverse from django.urls import reverse
from rest_framework.exceptions import PermissionDenied, ValidationError from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.fields import CharField, ChoiceField, ListField, SerializerMethodField from rest_framework.fields import CharField, ChoiceField, ListField, SerializerMethodField
@@ -106,7 +106,11 @@ class StreamResponseSerializer(PassiveSerializer):
} }
def get_events_supported(self, instance: Stream) -> list[str]: def get_events_supported(self, instance: Stream) -> list[str]:
return [x.value for x in EventTypes] return [
EventTypes.CAEP_SESSION_REVOKED,
EventTypes.CAEP_CREDENTIAL_CHANGE,
EventTypes.SET_VERIFICATION,
]
class StreamView(SSFStreamView): class StreamView(SSFStreamView):
@@ -128,10 +132,9 @@ class StreamView(SSFStreamView):
LOGGER.info("Sending verification event", stream=instance) LOGGER.info("Sending verification event", stream=instance)
send_ssf_events( send_ssf_events(
EventTypes.SET_VERIFICATION, EventTypes.SET_VERIFICATION,
{ {},
"state": None,
},
stream_filter={"pk": instance.uuid}, stream_filter={"pk": instance.uuid},
request=request,
sub_id={"format": "opaque", "id": str(instance.uuid)}, sub_id={"format": "opaque", "id": str(instance.uuid)},
) )
response = StreamResponseSerializer(instance=instance, context={"request": request}).data response = StreamResponseSerializer(instance=instance, context={"request": request}).data
@@ -159,7 +162,9 @@ class StreamView(SSFStreamView):
def delete(self, request: Request, *args, **kwargs) -> Response: def delete(self, request: Request, *args, **kwargs) -> Response:
stream = self.get_object() stream = self.get_object()
stream.status = StreamStatus.DISABLED if stream.status == StreamStatus.DISABLED_DELETED:
raise Http404
stream.status = StreamStatus.DISABLED_DELETED
stream.save() stream.save()
return Response(status=204) return Response(status=204)
@@ -175,6 +180,7 @@ class StreamVerifyView(SSFStreamView):
"state": state, "state": state,
}, },
stream_filter={"pk": stream.uuid}, stream_filter={"pk": stream.uuid},
request=request,
sub_id={"format": "opaque", "id": str(stream.uuid)}, sub_id={"format": "opaque", "id": str(stream.uuid)},
) )
return Response(status=204) return Response(status=204)
@@ -182,8 +188,25 @@ class StreamVerifyView(SSFStreamView):
class StreamStatusView(SSFStreamView): class StreamStatusView(SSFStreamView):
class StreamStatusSerializer(PassiveSerializer):
stream_id = CharField()
status = ChoiceField(choices=StreamStatus.choices)
def get(self, request: Request, *args, **kwargs): def get(self, request: Request, *args, **kwargs):
stream = self.get_object(any_status=True) stream = self.get_object()
return Response(
{
"stream_id": str(stream.pk),
"status": str(stream.status),
}
)
def post(self, request: Request, *args, **kwargs):
stream = self.get_object()
serializer = self.StreamStatusSerializer(stream, data=request.data)
serializer.is_valid(raise_exception=True)
stream.status = serializer.validated_data["status"]
stream.save()
return Response( return Response(
{ {
"stream_id": str(stream.pk), "stream_id": str(stream.pk),

View File

@@ -8,7 +8,6 @@ from inspect import currentframe
from typing import Any from typing import Any
from uuid import uuid4 from uuid import uuid4
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer from channels.layers import get_channel_layer
from django.apps import apps from django.apps import apps
from django.db import models from django.db import models
@@ -410,7 +409,7 @@ class NotificationTransport(TasksModel, SerializerModel):
) )
notification.save() notification.save()
layer = get_channel_layer() layer = get_channel_layer()
async_to_sync(layer.group_send)( layer.group_send_blocking(
build_user_group(notification.user), build_user_group(notification.user),
{ {
"type": "event.notification", "type": "event.notification",

View File

@@ -53,6 +53,16 @@ class TestEndSessionView(OAuthTestCase):
self.brand.flow_invalidation = self.invalidation_flow self.brand.flow_invalidation = self.invalidation_flow
self.brand.save() self.brand.save()
def _id_token_hint(self, host: str) -> str:
"""Issue a valid id_token_hint for the test provider under the given host."""
return self.provider.encode(
{
"iss": f"http://{host}/application/o/{self.app.slug}/",
"aud": self.provider.client_id,
"sub": str(self.user.pk),
}
)
def test_post_logout_redirect_uri_strict_match(self): def test_post_logout_redirect_uri_strict_match(self):
"""Test strict URI matching redirects to flow""" """Test strict URI matching redirects to flow"""
self.client.force_login(self.user) self.client.force_login(self.user)
@@ -61,7 +71,10 @@ class TestEndSessionView(OAuthTestCase):
"authentik_providers_oauth2:end-session", "authentik_providers_oauth2:end-session",
kwargs={"application_slug": self.app.slug}, kwargs={"application_slug": self.app.slug},
), ),
{"post_logout_redirect_uri": "http://testserver/logout"}, {
"post_logout_redirect_uri": "http://testserver/logout",
"id_token_hint": self._id_token_hint(self.brand.domain),
},
HTTP_HOST=self.brand.domain, HTTP_HOST=self.brand.domain,
) )
# Should redirect to the invalidation flow # Should redirect to the invalidation flow
@@ -69,7 +82,12 @@ class TestEndSessionView(OAuthTestCase):
self.assertIn(self.invalidation_flow.slug, response.url) self.assertIn(self.invalidation_flow.slug, response.url)
def test_post_logout_redirect_uri_strict_no_match(self): def test_post_logout_redirect_uri_strict_no_match(self):
"""Test strict URI not matching still proceeds with flow (no redirect URI in context)""" """Test strict URI not matching returns an error and does not start logout flow.
Required by OIDC RP-Initiated Logout 1.0: on an unregistered
post_logout_redirect_uri, the OP MUST NOT redirect and MUST NOT proceed with
logout that targets the RP.
"""
self.client.force_login(self.user) self.client.force_login(self.user)
invalid_uri = "http://testserver/other" invalid_uri = "http://testserver/other"
response = self.client.get( response = self.client.get(
@@ -77,12 +95,14 @@ class TestEndSessionView(OAuthTestCase):
"authentik_providers_oauth2:end-session", "authentik_providers_oauth2:end-session",
kwargs={"application_slug": self.app.slug}, kwargs={"application_slug": self.app.slug},
), ),
{"post_logout_redirect_uri": invalid_uri}, {
"post_logout_redirect_uri": invalid_uri,
"id_token_hint": self._id_token_hint(self.brand.domain),
},
HTTP_HOST=self.brand.domain, HTTP_HOST=self.brand.domain,
) )
# Should still redirect to flow, but invalid URI should not be in response self.assertEqual(response.status_code, 400)
self.assertEqual(response.status_code, 302) self.assertNotIn(invalid_uri, response.content.decode())
self.assertNotIn(invalid_uri, response.url)
def test_post_logout_redirect_uri_regex_match(self): def test_post_logout_redirect_uri_regex_match(self):
"""Test regex URI matching redirects to flow""" """Test regex URI matching redirects to flow"""
@@ -92,7 +112,10 @@ class TestEndSessionView(OAuthTestCase):
"authentik_providers_oauth2:end-session", "authentik_providers_oauth2:end-session",
kwargs={"application_slug": self.app.slug}, kwargs={"application_slug": self.app.slug},
), ),
{"post_logout_redirect_uri": "https://app.example.com/logout"}, {
"post_logout_redirect_uri": "https://app.example.com/logout",
"id_token_hint": self._id_token_hint(self.brand.domain),
},
HTTP_HOST=self.brand.domain, HTTP_HOST=self.brand.domain,
) )
# Should redirect to the invalidation flow # Should redirect to the invalidation flow
@@ -100,7 +123,7 @@ class TestEndSessionView(OAuthTestCase):
self.assertIn(self.invalidation_flow.slug, response.url) self.assertIn(self.invalidation_flow.slug, response.url)
def test_post_logout_redirect_uri_regex_no_match(self): def test_post_logout_redirect_uri_regex_no_match(self):
"""Test regex URI not matching""" """Test regex URI not matching returns an error and does not start logout flow."""
self.client.force_login(self.user) self.client.force_login(self.user)
invalid_uri = "https://malicious.com/logout" invalid_uri = "https://malicious.com/logout"
response = self.client.get( response = self.client.get(
@@ -108,12 +131,14 @@ class TestEndSessionView(OAuthTestCase):
"authentik_providers_oauth2:end-session", "authentik_providers_oauth2:end-session",
kwargs={"application_slug": self.app.slug}, kwargs={"application_slug": self.app.slug},
), ),
{"post_logout_redirect_uri": invalid_uri}, {
"post_logout_redirect_uri": invalid_uri,
"id_token_hint": self._id_token_hint(self.brand.domain),
},
HTTP_HOST=self.brand.domain, HTTP_HOST=self.brand.domain,
) )
# Should still proceed to flow, but invalid URI should not be in response self.assertEqual(response.status_code, 400)
self.assertEqual(response.status_code, 302) self.assertNotIn(invalid_uri, response.content.decode())
self.assertNotIn(invalid_uri, response.url)
def test_state_parameter_appended_to_uri(self): def test_state_parameter_appended_to_uri(self):
"""Test state parameter is appended to validated redirect URI""" """Test state parameter is appended to validated redirect URI"""
@@ -123,6 +148,7 @@ class TestEndSessionView(OAuthTestCase):
{ {
"post_logout_redirect_uri": "http://testserver/logout", "post_logout_redirect_uri": "http://testserver/logout",
"state": "test-state-123", "state": "test-state-123",
"id_token_hint": self._id_token_hint("testserver"),
}, },
) )
request.user = self.user request.user = self.user
@@ -132,6 +158,7 @@ class TestEndSessionView(OAuthTestCase):
view.request = request view.request = request
view.kwargs = {"application_slug": self.app.slug} view.kwargs = {"application_slug": self.app.slug}
view.resolve_provider_application() view.resolve_provider_application()
view.validate()
self.assertIn("state=test-state-123", view.post_logout_redirect_uri) self.assertIn("state=test-state-123", view.post_logout_redirect_uri)
@@ -146,6 +173,7 @@ class TestEndSessionView(OAuthTestCase):
{ {
"post_logout_redirect_uri": "http://testserver/logout", "post_logout_redirect_uri": "http://testserver/logout",
"state": "xyz789", "state": "xyz789",
"id_token_hint": self._id_token_hint(self.brand.domain),
}, },
HTTP_HOST=self.brand.domain, HTTP_HOST=self.brand.domain,
) )

View File

@@ -5,6 +5,8 @@ from urllib.parse import quote, urlparse
from django.http import Http404, HttpRequest, HttpResponse from django.http import Http404, HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from jwt import PyJWTError
from jwt import decode as jwt_decode
from authentik.common.oauth.constants import ( from authentik.common.oauth.constants import (
FORBIDDEN_URI_SCHEMES, FORBIDDEN_URI_SCHEMES,
@@ -21,11 +23,14 @@ from authentik.flows.planner import (
from authentik.flows.stage import SessionEndStage from authentik.flows.stage import SessionEndStage
from authentik.flows.views.executor import SESSION_KEY_PLAN from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.views import bad_request_message from authentik.lib.views import bad_request_message
from authentik.policies.views import PolicyAccessView, RequestValidationError from authentik.policies.views import PolicyAccessView
from authentik.providers.iframe_logout import IframeLogoutStageView from authentik.providers.iframe_logout import IframeLogoutStageView
from authentik.providers.oauth2.errors import TokenError
from authentik.providers.oauth2.models import ( from authentik.providers.oauth2.models import (
AccessToken, AccessToken,
JWTAlgorithms,
OAuth2LogoutMethod, OAuth2LogoutMethod,
OAuth2Provider,
RedirectURIMatchingMode, RedirectURIMatchingMode,
) )
from authentik.providers.oauth2.tasks import send_backchannel_logout_request from authentik.providers.oauth2.tasks import send_backchannel_logout_request
@@ -47,21 +52,45 @@ class EndSessionView(PolicyAccessView):
if not self.flow: if not self.flow:
raise Http404 raise Http404
def validate(self):
# Parse end session parameters # Parse end session parameters
query_dict = self.request.POST if self.request.method == "POST" else self.request.GET query_dict = self.request.POST if self.request.method == "POST" else self.request.GET
state = query_dict.get("state") state = query_dict.get("state")
request_redirect_uri = query_dict.get("post_logout_redirect_uri") request_redirect_uri = query_dict.get("post_logout_redirect_uri")
id_token_hint = query_dict.get("id_token_hint")
self.post_logout_redirect_uri = None self.post_logout_redirect_uri = None
# OIDC Certification: Verify id_token_hint. If invalid or missing, throw an error
if id_token_hint:
# Load a fresh provider instance that's not part of the flow
# since it'll have the cryptography Certificate that can't be pickled
provider = OAuth2Provider.objects.get(pk=self.provider.pk)
key, alg = provider.jwt_key
if alg != JWTAlgorithms.HS256:
key = provider.signing_key.public_key
try:
jwt_decode(
id_token_hint,
key,
algorithms=[alg],
audience=provider.client_id,
issuer=provider.get_issuer(self.request),
# ID Tokens are short-lived; a logout request arriving
# after expiry is still legitimate and must succeed.
options={"verify_exp": False},
)
except PyJWTError:
raise TokenError("invalid_request").with_cause(
"id_token_hint_decode_failed"
) from None
# Validate post_logout_redirect_uri against registered URIs # Validate post_logout_redirect_uri against registered URIs
if request_redirect_uri: if request_redirect_uri:
# OIDC Certification: id_token_hint required with post_logout_redirect_uri
if not id_token_hint:
raise TokenError("invalid_request").with_cause("id_token_hint_missing")
if urlparse(request_redirect_uri).scheme in FORBIDDEN_URI_SCHEMES: if urlparse(request_redirect_uri).scheme in FORBIDDEN_URI_SCHEMES:
raise RequestValidationError( raise TokenError("invalid_request").with_cause("post_logout_redirect_uri")
bad_request_message(
self.request,
"Forbidden URI scheme in post_logout_redirect_uri",
)
)
for allowed in self.provider.post_logout_redirect_uris: for allowed in self.provider.post_logout_redirect_uris:
if allowed.matching_mode == RedirectURIMatchingMode.STRICT: if allowed.matching_mode == RedirectURIMatchingMode.STRICT:
if request_redirect_uri == allowed.url: if request_redirect_uri == allowed.url:
@@ -71,6 +100,10 @@ class EndSessionView(PolicyAccessView):
if fullmatch(allowed.url, request_redirect_uri): if fullmatch(allowed.url, request_redirect_uri):
self.post_logout_redirect_uri = request_redirect_uri self.post_logout_redirect_uri = request_redirect_uri
break break
# OIDC Certification: OP MUST NOT perform post-logout redirection
# if the supplied URI does not exactly match a registered one
if self.post_logout_redirect_uri is None:
raise TokenError("invalid_request").with_cause("invalid_post_logout_redirect_uri")
# Append state to the redirect URI if both are present # Append state to the redirect URI if both are present
if self.post_logout_redirect_uri and state: if self.post_logout_redirect_uri and state:
@@ -91,50 +124,43 @@ class EndSessionView(PolicyAccessView):
"<html><body>Logout successful</body></html>", content_type="text/html", status=200 "<html><body>Logout successful</body></html>", content_type="text/html", status=200
) )
# Otherwise, continue with normal policy checks
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Dispatch the flow planner for the invalidation flow""" """Dispatch the flow planner for the invalidation flow"""
try:
self.validate()
except TokenError as exc:
return bad_request_message(
self.request,
exc.description,
)
planner = FlowPlanner(self.flow) planner = FlowPlanner(self.flow)
planner.allow_empty_flows = True planner.allow_empty_flows = True
# Build flow context with logout parameters
context = { context = {
PLAN_CONTEXT_APPLICATION: self.application, PLAN_CONTEXT_APPLICATION: self.application,
} }
# Get session info for logout notifications and token invalidation
auth_session = AuthenticatedSession.from_request(request, request.user) 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: if self.post_logout_redirect_uri:
context[PLAN_CONTEXT_POST_LOGOUT_REDIRECT_URI] = 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 = ( session_key = (
auth_session.session.session_key if auth_session and auth_session.session else None auth_session.session.session_key if auth_session and auth_session.session else None
) )
# Handle frontchannel logout
frontchannel_logout_url = None frontchannel_logout_url = None
if self.provider.logout_method == OAuth2LogoutMethod.FRONTCHANNEL: if self.provider.logout_method == OAuth2LogoutMethod.FRONTCHANNEL:
frontchannel_logout_url = build_frontchannel_logout_url( frontchannel_logout_url = build_frontchannel_logout_url(
self.provider, request, session_key self.provider, request, session_key
) )
# Handle backchannel logout
if ( if (
self.provider.logout_method == OAuth2LogoutMethod.BACKCHANNEL self.provider.logout_method == OAuth2LogoutMethod.BACKCHANNEL
and self.provider.logout_uri and self.provider.logout_uri
): ):
# Find access token to get iss and sub for the logout token
access_token = AccessToken.objects.filter( access_token = AccessToken.objects.filter(
user=request.user, user=request.user,
provider=self.provider, provider=self.provider,
@@ -163,9 +189,16 @@ class EndSessionView(PolicyAccessView):
} }
] ]
access_tokens = AccessToken.objects.filter(
user=request.user,
provider=self.provider,
)
if auth_session:
access_tokens = access_tokens.filter(session=auth_session)
access_tokens.delete()
plan = planner.plan(request, context) plan = planner.plan(request, context)
# Inject iframe logout stage if frontchannel logout is configured
if frontchannel_logout_url: if frontchannel_logout_url:
plan.insert_stage(in_memory_stage(IframeLogoutStageView)) plan.insert_stage(in_memory_stage(IframeLogoutStageView))

View File

@@ -1,6 +1,5 @@
"""RAC Signals""" """RAC Signals"""
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer from channels.layers import get_channel_layer
from django.core.cache import cache from django.core.cache import cache
from django.db.models.signals import post_delete, post_save, pre_delete from django.db.models.signals import post_delete, post_save, pre_delete
@@ -18,7 +17,7 @@ from authentik.providers.rac.models import ConnectionToken, Endpoint
@receiver(pre_delete, sender=AuthenticatedSession) @receiver(pre_delete, sender=AuthenticatedSession)
def user_session_deleted(sender, instance: AuthenticatedSession, **_): def user_session_deleted(sender, instance: AuthenticatedSession, **_):
layer = get_channel_layer() layer = get_channel_layer()
async_to_sync(layer.group_send)( layer.group_send_blocking(
build_rac_client_group_session(instance.session.session_key), build_rac_client_group_session(instance.session.session_key),
{"type": "event.disconnect", "reason": "session_logout"}, {"type": "event.disconnect", "reason": "session_logout"},
) )
@@ -28,7 +27,7 @@ def user_session_deleted(sender, instance: AuthenticatedSession, **_):
def pre_delete_connection_token_disconnect(sender, instance: ConnectionToken, **_): def pre_delete_connection_token_disconnect(sender, instance: ConnectionToken, **_):
"""Disconnect session when connection token is deleted""" """Disconnect session when connection token is deleted"""
layer = get_channel_layer() layer = get_channel_layer()
async_to_sync(layer.group_send)( layer.group_send_blocking(
build_rac_client_group_token(instance.token), build_rac_client_group_token(instance.token),
{"type": "event.disconnect", "reason": "token_delete"}, {"type": "event.disconnect", "reason": "token_delete"},
) )

View File

@@ -6,6 +6,7 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("authentik_core", "0056_user_roles"), # must run before group field is removed
("authentik_rbac", "0009_remove_initialpermissions_mode"), ("authentik_rbac", "0009_remove_initialpermissions_mode"),
] ]

File diff suppressed because one or more lines are too long

View File

@@ -73,8 +73,16 @@ entries:
redirect_uris: redirect_uris:
- matching_mode: strict - matching_mode: strict
url: https://localhost:8443/test/a/authentik/callback url: https://localhost:8443/test/a/authentik/callback
redirect_uri_type: authorization
- matching_mode: strict - matching_mode: strict
url: https://host.docker.internal:8443/test/a/authentik/callback url: https://host.docker.internal:8443/test/a/authentik/callback
redirect_uri_type: authorization
- matching_mode: strict
url: https://localhost:8443/test/a/authentik/post_logout_redirect
redirect_uri_type: logout
- matching_mode: strict
url: https://host.docker.internal:8443/test/a/authentik/post_logout_redirect
redirect_uri_type: logout
grant_types: grant_types:
- authorization_code - authorization_code
- implicit - implicit
@@ -108,8 +116,16 @@ entries:
redirect_uris: redirect_uris:
- matching_mode: strict - matching_mode: strict
url: https://localhost:8443/test/a/authentik/callback url: https://localhost:8443/test/a/authentik/callback
redirect_uri_type: authorization
- matching_mode: strict - matching_mode: strict
url: https://host.docker.internal:8443/test/a/authentik/callback url: https://host.docker.internal:8443/test/a/authentik/callback
redirect_uri_type: authorization
- matching_mode: strict
url: https://localhost:8443/test/a/authentik/post_logout_redirect
redirect_uri_type: logout
- matching_mode: strict
url: https://host.docker.internal:8443/test/a/authentik/post_logout_redirect
redirect_uri_type: logout
grant_types: grant_types:
- authorization_code - authorization_code
- implicit - implicit

View File

@@ -26,6 +26,8 @@ var healthcheckCmd = &cobra.Command{
exitCode := 1 exitCode := 1
log.WithField("mode", mode).Debug("checking health") log.WithField("mode", mode).Debug("checking health")
switch strings.ToLower(mode) { switch strings.ToLower(mode) {
case "allinone":
fallthrough
case "server": case "server":
exitCode = check(fmt.Sprintf("http://localhost%s-/health/live/", config.Get().Web.Path)) exitCode = check(fmt.Sprintf("http://localhost%s-/health/live/", config.Get().Web.Path))
case "worker": case "worker":

2
go.mod
View File

@@ -7,7 +7,7 @@ require (
beryju.io/radius-eap v0.1.0 beryju.io/radius-eap v0.1.0
github.com/avast/retry-go/v4 v4.7.0 github.com/avast/retry-go/v4 v4.7.0
github.com/coreos/go-oidc/v3 v3.18.0 github.com/coreos/go-oidc/v3 v3.18.0
github.com/getsentry/sentry-go v0.46.0 github.com/getsentry/sentry-go v0.46.1
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1 github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-ldap/ldap/v3 v3.4.13 github.com/go-ldap/ldap/v3 v3.4.13
github.com/go-openapi/runtime v0.29.4 github.com/go-openapi/runtime v0.29.4

4
go.sum
View File

@@ -20,8 +20,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/getsentry/sentry-go v0.46.0 h1:mbdDaarbUdOt9X+dx6kDdntkShLEX3/+KyOsVDTPDj0= github.com/getsentry/sentry-go v0.46.1 h1:mZyQFaQYkPxAdDG4HR8gDg6j4CnKYVWt4TF92N7i3XY=
github.com/getsentry/sentry-go v0.46.0/go.mod h1:evVbw2qotNUdYG8KxXbAdjOQWWvWIwKxpjdZZIvcIPw= github.com/getsentry/sentry-go v0.46.1/go.mod h1:evVbw2qotNUdYG8KxXbAdjOQWWvWIwKxpjdZZIvcIPw=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=

View File

@@ -31,7 +31,7 @@ function run_authentik {
echo go run ./cmd/server "$@" echo go run ./cmd/server "$@"
fi fi
;; ;;
worker) allinone | worker)
if [[ -x "$(command -v authentik)" ]]; then if [[ -x "$(command -v authentik)" ]]; then
echo authentik "$@" echo authentik "$@"
else else
@@ -105,7 +105,7 @@ elif [[ "$1" == "test-all" ]]; then
prepare_debug prepare_debug
chmod 777 /root chmod 777 /root
check_if_root_and_run manage test authentik check_if_root_and_run manage test authentik
elif [[ "$1" == "server" ]] || [[ "$1" == "worker" ]]; then elif [[ "$1" == "allinone" ]] || [[ "$1" == "server" ]] || [[ "$1" == "worker" ]]; then
wait_for_db wait_for_db
check_if_root_and_run "$@" check_if_root_and_run "$@"
elif [[ "$1" == "healthcheck" ]]; then elif [[ "$1" == "healthcheck" ]]; then

View File

@@ -9,7 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"aws-cdk": "^2.1118.4", "aws-cdk": "^2.1119.0",
"cross-env": "^10.1.0" "cross-env": "^10.1.0"
}, },
"engines": { "engines": {
@@ -25,9 +25,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/aws-cdk": { "node_modules/aws-cdk": {
"version": "2.1118.4", "version": "2.1119.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1118.4.tgz", "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1119.0.tgz",
"integrity": "sha512-wJfRQdvb+FJ2cni059mYdmjhfwhMskP+PAB59BL9jhon+jYtjy8X3pbj3uzHgAOJwNhh6jGkP8xq36Cffccbbw==", "integrity": "sha512-XBxZEKH3BY4M1EX6x0qBkmOAj8viErjpww14iH6Z3z6nI0YzjZeJ05eEl7eJwzUgv7NTGagWBS9m/eDJW5+dAg==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {

View File

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

View File

@@ -28,20 +28,45 @@ class HttpHandler(BaseHTTPRequestHandler):
_ = db_conn.cursor() _ = db_conn.cursor()
def do_GET(self): def do_GET(self):
if self.path == "/-/metrics/": from django.db import (
from authentik.root.monitoring import monitoring_set DatabaseError,
InterfaceError,
OperationalError,
connections,
)
from psycopg.errors import AdminShutdown
monitoring_set.send_robust(self) from authentik.root.monitoring import monitoring_set
self.send_response(200)
DATABASE_ERRORS = (
AdminShutdown,
InterfaceError,
DatabaseError,
ConnectionError,
OperationalError,
)
if self.path == "/-/metrics/":
try:
monitoring_set.send(self)
except DATABASE_ERRORS as exc:
LOGGER.warning("failed to send monitoring_set", exc=exc)
for db_conn in connections.all():
db_conn.close()
self.send_response(503)
else:
self.send_response(200)
self.end_headers() self.end_headers()
elif self.path == "/-/health/ready/": elif self.path == "/-/health/ready/":
from django.db.utils import OperationalError
try: try:
self.check_db() self.check_db()
except OperationalError: except DATABASE_ERRORS as exc:
LOGGER.warning("failed to check database health", exc=exc)
for db_conn in connections.all():
db_conn.close()
self.send_response(503) self.send_response(503)
self.send_response(200) else:
self.send_response(200)
self.end_headers() self.end_headers()
else: else:
self.send_response(200) self.send_response(200)

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-04-30 00:27+0000\n" "POT-Creation-Date: 2026-05-01 03:47+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -872,10 +872,6 @@ msgstr ""
msgid "Grace period must be shorter than the interval." msgid "Grace period must be shorter than the interval."
msgstr "" msgstr ""
#: authentik/enterprise/lifecycle/api/rules.py
msgid "Only one type-wide rule for each object type is allowed."
msgstr ""
#: authentik/enterprise/lifecycle/models.py #: authentik/enterprise/lifecycle/models.py
msgid "" msgid ""
"Select which transports should be used to notify the reviewers. If none are " "Select which transports should be used to notify the reviewers. If none are "
@@ -903,7 +899,8 @@ msgid "Go to {self._get_model_name()}"
msgstr "" msgstr ""
#: authentik/enterprise/lifecycle/models.py #: authentik/enterprise/lifecycle/models.py
msgid "Access review is due for {self.content_type.name} {str(self.object)}" msgid ""
"Access review is due for {self.content_type.name.lower()} {object_label}"
msgstr "" msgstr ""
#: authentik/enterprise/lifecycle/models.py #: authentik/enterprise/lifecycle/models.py
@@ -916,7 +913,7 @@ msgid "Access review completed for {self.content_type.name} {str(self.object)}"
msgstr "" msgstr ""
#: authentik/enterprise/lifecycle/tasks.py #: authentik/enterprise/lifecycle/tasks.py
msgid "Dispatch tasks to validate lifecycle rules." msgid "Dispatch tasks to apply lifecycle rules."
msgstr "" msgstr ""
#: authentik/enterprise/lifecycle/tasks.py #: authentik/enterprise/lifecycle/tasks.py
@@ -1229,6 +1226,78 @@ msgstr ""
msgid "Generate data export." msgid "Generate data export."
msgstr "" msgstr ""
#: authentik/enterprise/stages/account_lockdown/api.py
msgid "User to lock. If omitted, locks the current user (self-service)."
msgstr ""
#: authentik/enterprise/stages/account_lockdown/api.py
msgid "No lockdown flow configured."
msgstr ""
#: authentik/enterprise/stages/account_lockdown/api.py
msgid "Lockdown flow is not applicable."
msgstr ""
#: authentik/enterprise/stages/account_lockdown/api.py
msgid "Choose the target account, then return a flow link."
msgstr ""
#: authentik/enterprise/stages/account_lockdown/api.py
msgid "No lockdown flow configured or the flow is not applicable"
msgstr ""
#: authentik/enterprise/stages/account_lockdown/api.py
msgid "Permission denied (when targeting another user)"
msgstr ""
#: authentik/enterprise/stages/account_lockdown/models.py
msgid "Deactivate the user account (set is_active to False)"
msgstr ""
#: authentik/enterprise/stages/account_lockdown/models.py
msgid "Set an unusable password for the user"
msgstr ""
#: authentik/enterprise/stages/account_lockdown/models.py
msgid "Delete all active sessions for the user"
msgstr ""
#: authentik/enterprise/stages/account_lockdown/models.py
msgid ""
"Revoke all tokens for the user (API, app password, recovery, verification, "
"OAuth)"
msgstr ""
#: authentik/enterprise/stages/account_lockdown/models.py
msgid ""
"Flow to redirect users to after self-service lockdown. This flow should not "
"require authentication since the user's session is deleted."
msgstr ""
#: authentik/enterprise/stages/account_lockdown/models.py
msgid "Account Lockdown Stage"
msgstr ""
#: authentik/enterprise/stages/account_lockdown/models.py
msgid "Account Lockdown Stages"
msgstr ""
#: authentik/enterprise/stages/account_lockdown/stage.py
msgid "No target user specified for account lockdown"
msgstr ""
#: authentik/enterprise/stages/account_lockdown/stage.py
msgid "You do not have permission to lock down this account."
msgstr ""
#: authentik/enterprise/stages/account_lockdown/stage.py
msgid "Account lockdown failed for this account."
msgstr ""
#: authentik/enterprise/stages/account_lockdown/stage.py
msgid "Self-service account lockdown requires a completion flow."
msgstr ""
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py #: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
msgid "Endpoint Authenticator Google Device Trust Connector Stage" msgid "Endpoint Authenticator Google Device Trust Connector Stage"
msgstr "" msgstr ""
@@ -4456,6 +4525,18 @@ msgstr ""
msgid "Static: Static value, displayed as-is." msgid "Static: Static value, displayed as-is."
msgstr "" msgstr ""
#: authentik/stages/prompt/models.py
msgid "Alert (Info): Static alert box with info styling"
msgstr ""
#: authentik/stages/prompt/models.py
msgid "Alert (Warning): Static alert box with warning styling"
msgstr ""
#: authentik/stages/prompt/models.py
msgid "Alert (Danger): Static alert box with danger styling"
msgstr ""
#: authentik/stages/prompt/models.py #: authentik/stages/prompt/models.py
msgid "authentik: Selection of locales authentik supports" msgid "authentik: Selection of locales authentik supports"
msgstr "" msgstr ""

View File

@@ -53,6 +53,7 @@ Relatedly
Sidero Sidero
snipeit snipeit
sonarqube sonarqube
Technitium
Terrakube Terrakube
Ueberauth Ueberauth
Veeam Veeam

Binary file not shown.

View File

@@ -15,7 +15,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-04-08 00:28+0000\n" "POT-Creation-Date: 2026-05-01 03:47+0000\n"
"PO-Revision-Date: 2025-12-01 19:09+0000\n" "PO-Revision-Date: 2025-12-01 19:09+0000\n"
"Last-Translator: Sp P, 2026\n" "Last-Translator: Sp P, 2026\n"
"Language-Team: French (France) (https://app.transifex.com/authentik/teams/119923/fr_FR/)\n" "Language-Team: French (France) (https://app.transifex.com/authentik/teams/119923/fr_FR/)\n"
@@ -263,6 +263,18 @@ msgstr ""
"fournisseurs backchannels sont retournés. Si faux, les fournisseurs " "fournisseurs backchannels sont retournés. Si faux, les fournisseurs "
"backchannels sont exclus" "backchannels sont exclus"
#: authentik/core/api/users.py
msgid "Invalid password hash format. Must be a valid Django password hash."
msgstr ""
"Format de hachage de mot de passe invalide. Cela doit être un hachage de mot"
" de passe Django valide."
#: authentik/core/api/users.py
msgid "Cannot set both password and password_hash. Use only one."
msgstr ""
"Impossible de définir à la fois password (mot de passe) et password_hash "
"(hachage de mot de passe). N'en utiliser qu'un seul."
#: authentik/core/api/users.py #: authentik/core/api/users.py
msgid "No leading or trailing slashes allowed." msgid "No leading or trailing slashes allowed."
msgstr "" msgstr ""
@@ -443,6 +455,11 @@ msgid "Open launch URL in a new browser tab or window."
msgstr "" msgstr ""
"Ouvrir l'URL de lancement dans une nouvelle fenêtre ou un nouvel onglet." "Ouvrir l'URL de lancement dans une nouvelle fenêtre ou un nouvel onglet."
#: authentik/core/models.py
msgid "Hide this application from the user's My applications page."
msgstr ""
"Masquer cette application dans la page Mes applications de l'utilisateur."
#: authentik/core/models.py #: authentik/core/models.py
msgid "Application" msgid "Application"
msgstr "Application" msgstr "Application"
@@ -810,6 +827,14 @@ msgstr "Nonce Apple"
msgid "Apple Nonces" msgid "Apple Nonces"
msgstr "Nonces Apple" msgstr "Nonces Apple"
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Independent Secure Enclave"
msgstr "Secure Enclave indépendante d'Apple"
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Independent Secure Enclaves"
msgstr "Secure Enclaves indépendantes d'Apple"
#: authentik/endpoints/facts.py #: authentik/endpoints/facts.py
msgid "Operating System name, such as 'Server 2022' or 'Ubuntu'" msgid "Operating System name, such as 'Server 2022' or 'Ubuntu'"
msgstr "Nom du système d'exploitation, comme 'Server 2022' ou 'Ubuntu'" msgstr "Nom du système d'exploitation, comme 'Server 2022' ou 'Ubuntu'"
@@ -936,12 +961,6 @@ msgstr "Soit un groupe de réviseurs soit un réviseur doit être défini."
msgid "Grace period must be shorter than the interval." msgid "Grace period must be shorter than the interval."
msgstr "La période de grâce doit être plus courte que l'intervalle." msgstr "La période de grâce doit être plus courte que l'intervalle."
#: authentik/enterprise/lifecycle/api/rules.py
msgid "Only one type-wide rule for each object type is allowed."
msgstr ""
"Une seule règle pour l'ensemble du type est autorisée pour chaque type "
"d'objet."
#: authentik/enterprise/lifecycle/models.py #: authentik/enterprise/lifecycle/models.py
msgid "" msgid ""
"Select which transports should be used to notify the reviewers. If none are " "Select which transports should be used to notify the reviewers. If none are "
@@ -972,10 +991,11 @@ msgid "Go to {self._get_model_name()}"
msgstr "Aller à {self._get_model_name()}" msgstr "Aller à {self._get_model_name()}"
#: authentik/enterprise/lifecycle/models.py #: authentik/enterprise/lifecycle/models.py
msgid "Access review is due for {self.content_type.name} {str(self.object)}" msgid ""
"Access review is due for {self.content_type.name.lower()} {object_label}"
msgstr "" msgstr ""
"La révision d'accès est attendue pour {self.content_type.name} " "La révision de l'accès doit être effectuée pour "
"{str(self.object)}" "{self.content_type.name.lower()} {object_label}"
#: authentik/enterprise/lifecycle/models.py #: authentik/enterprise/lifecycle/models.py
msgid "" msgid ""
@@ -992,8 +1012,8 @@ msgstr ""
"{str(self.object)}" "{str(self.object)}"
#: authentik/enterprise/lifecycle/tasks.py #: authentik/enterprise/lifecycle/tasks.py
msgid "Dispatch tasks to validate lifecycle rules." msgid "Dispatch tasks to apply lifecycle rules."
msgstr "Déclenche les tâches pour valider les règles de cycle de vie" msgstr "Déclencher les tâches pour appliquer les règles de cycle de vie"
#: authentik/enterprise/lifecycle/tasks.py #: authentik/enterprise/lifecycle/tasks.py
msgid "Apply lifecycle rule." msgid "Apply lifecycle rule."
@@ -1336,6 +1356,86 @@ msgstr "Télécharger"
msgid "Generate data export." msgid "Generate data export."
msgstr "Générer un export de données." msgstr "Générer un export de données."
#: authentik/enterprise/stages/account_lockdown/api.py
msgid "User to lock. If omitted, locks the current user (self-service)."
msgstr ""
"Utilisateur à bloquer. Si non renseigné, bloque l'utilisateur actuel (libre "
"service)."
#: authentik/enterprise/stages/account_lockdown/api.py
msgid "No lockdown flow configured."
msgstr "Aucun flux de blocage configuré."
#: authentik/enterprise/stages/account_lockdown/api.py
msgid "Lockdown flow is not applicable."
msgstr "Le flux de blocage n'est pas applicable."
#: authentik/enterprise/stages/account_lockdown/api.py
msgid "Choose the target account, then return a flow link."
msgstr "Choisit le compte cible, puis renvoie un lien de flux."
#: authentik/enterprise/stages/account_lockdown/api.py
msgid "No lockdown flow configured or the flow is not applicable"
msgstr "Aucun flux de blocage configuré, ou le flux n'est pas applicable"
#: authentik/enterprise/stages/account_lockdown/api.py
msgid "Permission denied (when targeting another user)"
msgstr "Permission refusée (lors du ciblage d'un autre utilisateur)"
#: authentik/enterprise/stages/account_lockdown/models.py
msgid "Deactivate the user account (set is_active to False)"
msgstr "Désactiver le compte de l'utilisateur (définir is_active à False)."
#: authentik/enterprise/stages/account_lockdown/models.py
msgid "Set an unusable password for the user"
msgstr "Définit un mot de passe inutilisable pour cet utilisateur."
#: authentik/enterprise/stages/account_lockdown/models.py
msgid "Delete all active sessions for the user"
msgstr "Supprimer toutes les sessions actives pour cet utilisateur."
#: authentik/enterprise/stages/account_lockdown/models.py
msgid ""
"Revoke all tokens for the user (API, app password, recovery, verification, "
"OAuth)"
msgstr ""
"Révoquer tous les jetons pour cet utilisateur (API, mot de passe applicatif,"
" récupération, vérification, OAuth)"
#: authentik/enterprise/stages/account_lockdown/models.py
msgid ""
"Flow to redirect users to after self-service lockdown. This flow should not "
"require authentication since the user's session is deleted."
msgstr ""
"Flux vers lequel rediriger les utilisateurs après le blocage en libre "
"service. Ce flux ne doit pas nécessiter d'authentification car la session "
"utilisateur est supprimée."
#: authentik/enterprise/stages/account_lockdown/models.py
msgid "Account Lockdown Stage"
msgstr "Etape de blocage de compte"
#: authentik/enterprise/stages/account_lockdown/models.py
msgid "Account Lockdown Stages"
msgstr "Etapes de blocage de compte"
#: authentik/enterprise/stages/account_lockdown/stage.py
msgid "No target user specified for account lockdown"
msgstr "Aucun utilisateur ciblé défini pour le blocage de compte"
#: authentik/enterprise/stages/account_lockdown/stage.py
msgid "You do not have permission to lock down this account."
msgstr "Vous n'avez pas la permission de bloquer ce compte."
#: authentik/enterprise/stages/account_lockdown/stage.py
msgid "Account lockdown failed for this account."
msgstr "Echec du blocage de compte pour ce compte."
#: authentik/enterprise/stages/account_lockdown/stage.py
msgid "Self-service account lockdown requires a completion flow."
msgstr ""
"Le blocage de compte en libre service nécessite un flux de finalisation."
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py #: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
msgid "Endpoint Authenticator Google Device Trust Connector Stage" msgid "Endpoint Authenticator Google Device Trust Connector Stage"
msgstr "" msgstr ""
@@ -1469,11 +1569,11 @@ msgstr "Évènement utilisateur"
#: authentik/events/models.py #: authentik/events/models.py
msgid "Notification Transport" msgid "Notification Transport"
msgstr "Transport de Notification" msgstr "Transport de notification"
#: authentik/events/models.py #: authentik/events/models.py
msgid "Notification Transports" msgid "Notification Transports"
msgstr "Transports de notification" msgstr "Transports de notifications"
#: authentik/events/models.py #: authentik/events/models.py
msgid "Notice" msgid "Notice"
@@ -1745,6 +1845,10 @@ msgstr "Jeton du flux"
msgid "Flow Tokens" msgid "Flow Tokens"
msgstr "Jetons du flux" msgstr "Jetons du flux"
#: authentik/flows/planner.py
msgid "This link is invalid or has expired. Please request a new one."
msgstr "Ce lien est invalide ou a expiré. Veuillez un demander un nouveau."
#: authentik/flows/views/executor.py #: authentik/flows/views/executor.py
msgid "Invalid next URL" msgid "Invalid next URL"
msgstr "URL suivante invalide" msgstr "URL suivante invalide"
@@ -2772,8 +2876,12 @@ msgstr ""
"restriction d'audience ne sera ajoutée." "restriction d'audience ne sera ajoutée."
#: authentik/providers/saml/models.py #: authentik/providers/saml/models.py
msgid "Also known as EntityID" msgid ""
msgstr "Aussi appelé EntityID" "Also known as EntityID. Providing a value overrides the default issuer "
"generated by authentik."
msgstr ""
"Aussi appelé EntityID. Fournir une valeur remplace l'émetteur par défaut "
"généré par authentik."
#: authentik/providers/saml/models.py #: authentik/providers/saml/models.py
msgid "SLS URL" msgid "SLS URL"
@@ -2994,6 +3102,10 @@ msgstr "SAML NameID pour cette session"
msgid "SAML NameID format" msgid "SAML NameID format"
msgstr "Format SAML NameID" msgstr "Format SAML NameID"
#: authentik/providers/saml/models.py
msgid "SAML Issuer used for this session"
msgstr "Émetteur SAML utilisé pour cette session"
#: authentik/providers/saml/models.py #: authentik/providers/saml/models.py
msgid "SAML Session" msgid "SAML Session"
msgstr "Session SAML" msgstr "Session SAML"
@@ -3026,6 +3138,10 @@ msgstr "Salesforce"
msgid "Webex" msgid "Webex"
msgstr "Webex" msgstr "Webex"
#: authentik/providers/scim/models.py
msgid "vCenter"
msgstr "vCenter"
#: authentik/providers/scim/models.py #: authentik/providers/scim/models.py
msgid "Group filters used to define sync-scope for groups." msgid "Group filters used to define sync-scope for groups."
msgstr "" msgstr ""
@@ -3749,8 +3865,8 @@ msgid ""
"Which servers a user has to be a member of to be granted access. Empty list " "Which servers a user has to be a member of to be granted access. Empty list "
"allows every server." "allows every server."
msgstr "" msgstr ""
"De quels serveurs un utilisateur doit être membre afin d'être autorisé. Une " "De quels serveurs un utilisateur doit être membre afin d'obtenir l'accès. "
"liste vide autorise tous les serveurs." "Une liste vide autorise tous les serveurs."
#: authentik/sources/plex/models.py #: authentik/sources/plex/models.py
msgid "Allow friends to authenticate, even if you don't share a server." msgid "Allow friends to authenticate, even if you don't share a server."
@@ -4455,11 +4571,11 @@ msgstr "Activer les utilisateurs à la complétion de l'étape."
#: authentik/stages/email/models.py #: authentik/stages/email/models.py
msgid "Email Stage" msgid "Email Stage"
msgstr "Étape Courriel" msgstr "Étape de Courriel"
#: authentik/stages/email/models.py #: authentik/stages/email/models.py
msgid "Email Stages" msgid "Email Stages"
msgstr "Étapes Courriel" msgstr "Étapes de Courriel"
#: authentik/stages/email/stage.py #: authentik/stages/email/stage.py
msgid "Successfully verified Email." msgid "Successfully verified Email."
@@ -4933,6 +5049,19 @@ msgstr ""
msgid "Static: Static value, displayed as-is." msgid "Static: Static value, displayed as-is."
msgstr "Statique : valeur statique, affichée comme telle." msgstr "Statique : valeur statique, affichée comme telle."
#: authentik/stages/prompt/models.py
msgid "Alert (Info): Static alert box with info styling"
msgstr "Alerte (Info) : message d'alerte statique au format information"
#: authentik/stages/prompt/models.py
msgid "Alert (Warning): Static alert box with warning styling"
msgstr ""
"Alerte (Avertissement) : message d'alerte statique au format avertissement"
#: authentik/stages/prompt/models.py
msgid "Alert (Danger): Static alert box with danger styling"
msgstr "Alerte (Danger) : message d'alerte statique au format danger"
#: authentik/stages/prompt/models.py #: authentik/stages/prompt/models.py
msgid "authentik: Selection of locales authentik supports" msgid "authentik: Selection of locales authentik supports"
msgstr "authentik : sélection des locales prises en charges par authentik" msgstr "authentik : sélection des locales prises en charges par authentik"

View File

@@ -1,6 +1,6 @@
//! Utilities to run an axum server. //! Utilities to run an axum server.
use std::{net, os::unix}; use std::{net, os::unix, path::PathBuf};
use ak_common::arbiter::{Arbiter, Tasks}; use ak_common::arbiter::{Arbiter, Tasks};
use axum::Router; use axum::Router;
@@ -21,26 +21,20 @@ async fn run_plain(
name: &str, name: &str,
router: Router, router: Router,
addr: net::SocketAddr, addr: net::SocketAddr,
allow_failure: bool,
) -> Result<()> { ) -> Result<()> {
info!(addr = addr.to_string(), "starting {name} server"); info!(addr = addr.to_string(), "starting {name} server");
let handle = Handle::new(); let handle = Handle::new();
arbiter.add_net_handle(handle.clone()).await; arbiter.add_net_handle(handle.clone()).await;
let res = axum_server::Server::bind(addr) axum_server::Server::bind(addr)
.acceptor(CatchPanicAcceptor::new( .acceptor(CatchPanicAcceptor::new(
ProxyProtocolAcceptor::new().acceptor(DefaultAcceptor::new()), ProxyProtocolAcceptor::new().acceptor(DefaultAcceptor::new()),
arbiter.clone(), arbiter.clone(),
)) ))
.handle(handle) .handle(handle)
.serve(router.into_make_service_with_connect_info::<net::SocketAddr>()) .serve(router.into_make_service_with_connect_info::<net::SocketAddr>())
.await; .await?;
if res.is_err() && allow_failure {
arbiter.shutdown().await;
return Ok(());
}
res?;
Ok(()) Ok(())
} }
@@ -49,60 +43,59 @@ async fn run_plain(
/// ///
/// `name` is only used for observability purposes and should describe which module is starting the /// `name` is only used for observability purposes and should describe which module is starting the
/// server. /// server.
///
/// `allow_failure` allows the server to fail silently.
pub fn start_plain( pub fn start_plain(
tasks: &mut Tasks, tasks: &mut Tasks,
name: &'static str, name: &'static str,
router: Router, router: Router,
addr: net::SocketAddr, addr: net::SocketAddr,
allow_failure: bool,
) -> Result<()> { ) -> Result<()> {
let arbiter = tasks.arbiter(); let arbiter = tasks.arbiter();
tasks tasks
.build_task() .build_task()
.name(&format!("{}::run_plain({name}, {addr})", module_path!())) .name(&format!("{}::run_plain({name}, {addr})", module_path!()))
.spawn(run_plain(arbiter, name, router, addr, allow_failure))?; .spawn(run_plain(arbiter, name, router, addr))?;
Ok(()) Ok(())
} }
struct UnixSocketGuard(PathBuf);
impl Drop for UnixSocketGuard {
fn drop(&mut self) {
trace!(path = ?self.0, "removing socket");
if let Err(err) = std::fs::remove_file(&self.0) {
trace!(?err, "failed to remove socket, ignoring");
}
}
}
pub(crate) async fn run_unix( pub(crate) async fn run_unix(
arbiter: Arbiter, arbiter: Arbiter,
name: &str, name: &str,
router: Router, router: Router,
addr: unix::net::SocketAddr, addr: unix::net::SocketAddr,
allow_failure: bool,
) -> Result<()> { ) -> Result<()> {
info!(?addr, "starting {name} server"); info!(?addr, "starting {name} server");
let handle = Handle::new(); let handle = Handle::new();
arbiter.add_unix_handle(handle.clone()).await; arbiter.add_unix_handle(handle.clone()).await;
if !allow_failure && let Some(path) = addr.as_pathname() { let _guard = if let Some(path) = addr.as_pathname() {
trace!(?addr, "removing socket"); trace!(?addr, "removing socket");
if let Err(err) = std::fs::remove_file(path) { if let Err(err) = std::fs::remove_file(path) {
trace!(?err, "failed to remove socket, ignoring"); trace!(?err, "failed to remove socket, ignoring");
} }
} Some(UnixSocketGuard(path.to_owned()))
let res = axum_server::Server::bind(addr.clone()) } else {
None
};
axum_server::Server::bind(addr.clone())
.acceptor(CatchPanicAcceptor::new( .acceptor(CatchPanicAcceptor::new(
DefaultAcceptor::new(), DefaultAcceptor::new(),
arbiter.clone(), arbiter.clone(),
)) ))
.handle(handle) .handle(handle)
.serve(router.into_make_service()) .serve(router.into_make_service())
.await; .await?;
if !allow_failure && let Some(path) = addr.as_pathname() {
trace!(?addr, "removing socket");
if let Err(err) = std::fs::remove_file(path) {
trace!(?err, "failed to remove socket, ignoring");
}
}
if res.is_err() && allow_failure {
arbiter.shutdown().await;
return Ok(());
}
res?;
Ok(()) Ok(())
} }
@@ -111,20 +104,17 @@ pub(crate) async fn run_unix(
/// ///
/// `name` is only used for observability purposes and should describe which module is starting the /// `name` is only used for observability purposes and should describe which module is starting the
/// server. /// server.
///
/// `allow_failure` allows the server to fail silently.
pub fn start_unix( pub fn start_unix(
tasks: &mut Tasks, tasks: &mut Tasks,
name: &'static str, name: &'static str,
router: Router, router: Router,
addr: unix::net::SocketAddr, addr: unix::net::SocketAddr,
allow_failure: bool,
) -> Result<()> { ) -> Result<()> {
let arbiter = tasks.arbiter(); let arbiter = tasks.arbiter();
tasks tasks
.build_task() .build_task()
.name(&format!("{}::run_unix({name}, {addr:?})", module_path!())) .name(&format!("{}::run_unix({name}, {addr:?})", module_path!()))
.spawn(run_unix(arbiter, name, router, addr, allow_failure))?; .spawn(run_unix(arbiter, name, router, addr))?;
Ok(()) Ok(())
} }

View File

@@ -23,7 +23,7 @@ export interface AuthenticatedSessionUserAgentDevice {
* @type {string} * @type {string}
* @memberof AuthenticatedSessionUserAgentDevice * @memberof AuthenticatedSessionUserAgentDevice
*/ */
brand: string; brand: string | null;
/** /**
* *
* @type {string} * @type {string}
@@ -35,7 +35,7 @@ export interface AuthenticatedSessionUserAgentDevice {
* @type {string} * @type {string}
* @memberof AuthenticatedSessionUserAgentDevice * @memberof AuthenticatedSessionUserAgentDevice
*/ */
model: string; model: string | null;
} }
/** /**

View File

@@ -29,25 +29,25 @@ export interface AuthenticatedSessionUserAgentOs {
* @type {string} * @type {string}
* @memberof AuthenticatedSessionUserAgentOs * @memberof AuthenticatedSessionUserAgentOs
*/ */
major: string; major: string | null;
/** /**
* *
* @type {string} * @type {string}
* @memberof AuthenticatedSessionUserAgentOs * @memberof AuthenticatedSessionUserAgentOs
*/ */
minor: string; minor: string | null;
/** /**
* *
* @type {string} * @type {string}
* @memberof AuthenticatedSessionUserAgentOs * @memberof AuthenticatedSessionUserAgentOs
*/ */
patch: string; patch: string | null;
/** /**
* *
* @type {string} * @type {string}
* @memberof AuthenticatedSessionUserAgentOs * @memberof AuthenticatedSessionUserAgentOs
*/ */
patchMinor: string; patchMinor: string | null;
} }
/** /**

View File

@@ -19,8 +19,20 @@
export const EventsRequestedEnum = { export const EventsRequestedEnum = {
HttpsSchemasOpenidNetSeceventCaepEventTypeSessionRevoked: HttpsSchemasOpenidNetSeceventCaepEventTypeSessionRevoked:
"https://schemas.openid.net/secevent/caep/event-type/session-revoked", "https://schemas.openid.net/secevent/caep/event-type/session-revoked",
HttpsSchemasOpenidNetSeceventCaepEventTypeTokenClaimsChange:
"https://schemas.openid.net/secevent/caep/event-type/token-claims-change",
HttpsSchemasOpenidNetSeceventCaepEventTypeCredentialChange: HttpsSchemasOpenidNetSeceventCaepEventTypeCredentialChange:
"https://schemas.openid.net/secevent/caep/event-type/credential-change", "https://schemas.openid.net/secevent/caep/event-type/credential-change",
HttpsSchemasOpenidNetSeceventCaepEventTypeAssuranceLevelChange:
"https://schemas.openid.net/secevent/caep/event-type/assurance-level-change",
HttpsSchemasOpenidNetSeceventCaepEventTypeDeviceComplianceChange:
"https://schemas.openid.net/secevent/caep/event-type/device-compliance-change",
HttpsSchemasOpenidNetSeceventCaepEventTypeSessionEstablished:
"https://schemas.openid.net/secevent/caep/event-type/session-established",
HttpsSchemasOpenidNetSeceventCaepEventTypeSessionPresented:
"https://schemas.openid.net/secevent/caep/event-type/session-presented",
HttpsSchemasOpenidNetSeceventCaepEventTypeRiskLevelChange:
"https://schemas.openid.net/secevent/caep/event-type/risk-level-change",
HttpsSchemasOpenidNetSeceventSsfEventTypeVerification: HttpsSchemasOpenidNetSeceventSsfEventTypeVerification:
"https://schemas.openid.net/secevent/ssf/event-type/verification", "https://schemas.openid.net/secevent/ssf/event-type/verification",
UnknownDefaultOpenApi: "11184809", UnknownDefaultOpenApi: "11184809",

View File

@@ -20,6 +20,7 @@ export const SSFStreamStatusEnum = {
Enabled: "enabled", Enabled: "enabled",
Paused: "paused", Paused: "paused",
Disabled: "disabled", Disabled: "disabled",
DisabledDeleted: "disabled_deleted",
UnknownDefaultOpenApi: "11184809", UnknownDefaultOpenApi: "11184809",
} as const; } as const;
export type SSFStreamStatusEnum = (typeof SSFStreamStatusEnum)[keyof typeof SSFStreamStatusEnum]; export type SSFStreamStatusEnum = (typeof SSFStreamStatusEnum)[keyof typeof SSFStreamStatusEnum];

View File

@@ -9,7 +9,7 @@ dependencies = [
"argon2-cffi==25.1.0", "argon2-cffi==25.1.0",
"cachetools==7.0.6", "cachetools==7.0.6",
"channels==4.3.2", "channels==4.3.2",
"cryptography==47.0.0", "cryptography==48.0.0",
"dacite==1.9.2", "dacite==1.9.2",
"deepmerge==2.0", "deepmerge==2.0",
"defusedxml==0.7.1", "defusedxml==0.7.1",
@@ -46,9 +46,9 @@ dependencies = [
"lxml==6.1.0", "lxml==6.1.0",
"msgraph-sdk==1.56.0", "msgraph-sdk==1.56.0",
"opencontainers==0.0.15", "opencontainers==0.0.15",
"packaging==26.1", "packaging==26.2",
"paramiko==4.0.0", "paramiko==4.0.0",
"psycopg[c,pool]==3.3.3", "psycopg[c,pool]==3.3.4",
"pydantic-scim==0.0.8", "pydantic-scim==0.0.8",
"pydantic==2.13.3", "pydantic==2.13.3",
"pyjwt==2.11.0", "pyjwt==2.11.0",
@@ -66,7 +66,7 @@ dependencies = [
"ua-parser==1.0.2", "ua-parser==1.0.2",
"unidecode==1.4.0", "unidecode==1.4.0",
"urllib3<3", "urllib3<3",
"uvicorn[standard]==0.45.0", "uvicorn[standard]==0.46.0",
"watchdog==6.0.0", "watchdog==6.0.0",
"webauthn==2.7.1", "webauthn==2.7.1",
"wsproto==1.3.2", "wsproto==1.3.2",
@@ -76,7 +76,7 @@ dependencies = [
[dependency-groups] [dependency-groups]
dev = [ dev = [
"aws-cdk-lib==2.250.0", "aws-cdk-lib==2.251.0",
"bandit==1.9.4", "bandit==1.9.4",
"black==26.3.1", "black==26.3.1",
"bpython==0.26", "bpython==0.26",

View File

@@ -34608,10 +34608,12 @@ components:
properties: properties:
brand: brand:
type: string type: string
nullable: true
family: family:
type: string type: string
model: model:
type: string type: string
nullable: true
required: required:
- brand - brand
- family - family
@@ -34624,12 +34626,16 @@ components:
type: string type: string
major: major:
type: string type: string
nullable: true
minor: minor:
type: string type: string
nullable: true
patch: patch:
type: string type: string
nullable: true
patch_minor: patch_minor:
type: string type: string
nullable: true
required: required:
- family - family
- major - major
@@ -39031,7 +39037,13 @@ components:
EventsRequestedEnum: EventsRequestedEnum:
enum: enum:
- https://schemas.openid.net/secevent/caep/event-type/session-revoked - https://schemas.openid.net/secevent/caep/event-type/session-revoked
- https://schemas.openid.net/secevent/caep/event-type/token-claims-change
- https://schemas.openid.net/secevent/caep/event-type/credential-change - https://schemas.openid.net/secevent/caep/event-type/credential-change
- https://schemas.openid.net/secevent/caep/event-type/assurance-level-change
- https://schemas.openid.net/secevent/caep/event-type/device-compliance-change
- https://schemas.openid.net/secevent/caep/event-type/session-established
- https://schemas.openid.net/secevent/caep/event-type/session-presented
- https://schemas.openid.net/secevent/caep/event-type/risk-level-change
- https://schemas.openid.net/secevent/ssf/event-type/verification - https://schemas.openid.net/secevent/ssf/event-type/verification
type: string type: string
ExpiringBaseGrantModel: ExpiringBaseGrantModel:
@@ -55618,6 +55630,7 @@ components:
- enabled - enabled
- paused - paused
- disabled - disabled
- disabled_deleted
type: string type: string
Schedule: Schedule:
type: object type: object

View File

@@ -23,10 +23,23 @@ struct Cli {
#[derive(Debug, FromArgs, PartialEq)] #[derive(Debug, FromArgs, PartialEq)]
#[argh(subcommand)] #[argh(subcommand)]
enum Command { enum Command {
#[cfg(feature = "core")]
AllInOne(AllInOne),
#[cfg(feature = "core")]
Server(server::Cli),
#[cfg(feature = "core")] #[cfg(feature = "core")]
Worker(worker::Cli), Worker(worker::Cli),
} }
#[derive(Debug, FromArgs, PartialEq)]
/// Run both the authentik server and worker.
#[argh(subcommand, name = "allinone")]
#[expect(
clippy::empty_structs_with_brackets,
reason = "argh doesn't support unit structs"
)]
pub(crate) struct AllInOne {}
fn main() -> Result<()> { fn main() -> Result<()> {
let tracing_crude = ak_tracing::install_crude(); let tracing_crude = ak_tracing::install_crude();
info!(version = authentik_full_version(), "authentik is starting"); info!(version = authentik_full_version(), "authentik is starting");
@@ -34,6 +47,10 @@ fn main() -> Result<()> {
let cli: Cli = argh::from_env(); let cli: Cli = argh::from_env();
match &cli.command { match &cli.command {
#[cfg(feature = "core")]
Command::AllInOne(_) => Mode::set(Mode::AllInOne)?,
#[cfg(feature = "core")]
Command::Server(_) => Mode::set(Mode::Server)?,
#[cfg(feature = "core")] #[cfg(feature = "core")]
Command::Worker(_) => Mode::set(Mode::Worker)?, Command::Worker(_) => Mode::set(Mode::Worker)?,
} }
@@ -76,6 +93,16 @@ fn main() -> Result<()> {
} }
match cli.command { match cli.command {
#[cfg(feature = "core")]
Command::AllInOne(_) => {
server::start(server::Cli::default(), &mut tasks).await?;
let workers = worker::start(worker::Cli::default(), &mut tasks)?;
metrics.workers.store(Some(workers));
}
#[cfg(feature = "core")]
Command::Server(args) => {
server::start(args, &mut tasks).await?;
}
#[cfg(feature = "core")] #[cfg(feature = "core")]
Command::Worker(args) => { Command::Worker(args) => {
let workers = worker::start(args, &mut tasks)?; let workers = worker::start(args, &mut tasks)?;

View File

@@ -2,6 +2,7 @@ use std::{env::temp_dir, os::unix, path::PathBuf, sync::Arc};
use ak_axum::{router::wrap_router, server}; use ak_axum::{router::wrap_router, server};
use ak_common::{ use ak_common::{
Mode,
arbiter::{Arbiter, Tasks}, arbiter::{Arbiter, Tasks},
config, config,
}; };
@@ -77,25 +78,20 @@ pub(crate) fn start(tasks: &mut Tasks) -> Result<Arc<Metrics>> {
.name(&format!("{}::run_upkeep", module_path!())) .name(&format!("{}::run_upkeep", module_path!()))
.spawn(run_upkeep(arbiter, Arc::clone(&metrics)))?; .spawn(run_upkeep(arbiter, Arc::clone(&metrics)))?;
for addr in config::get().listen.metrics.iter().copied() { // Only run HTTP server in worker mode, in server or allinone mode, they're handled by the
server::start_plain( // server.
if Mode::get() == Mode::Worker {
for addr in config::get().listen.metrics.iter().copied() {
server::start_plain(tasks, "metrics", router.clone(), addr)?;
}
server::start_unix(
tasks, tasks,
"metrics", "metrics",
router.clone(), router,
addr, unix::net::SocketAddr::from_pathname(socket_path())?,
config::get().debug, /* Allow failure in case the server is running on the same
* machine, like in dev */
)?; )?;
} }
server::start_unix(
tasks,
"metrics",
router,
unix::net::SocketAddr::from_pathname(socket_path())?,
config::get().debug, /* Allow failure in case the server is running on the same machine,
* like in dev */
)?;
Ok(metrics) Ok(metrics)
} }

View File

@@ -1,5 +1,124 @@
use std::{env::temp_dir, path::PathBuf}; use std::{env::temp_dir, path::PathBuf, process::Stdio, sync::Arc};
use ak_common::{Arbiter, Tasks, config};
use argh::FromArgs;
use eyre::{Result, eyre};
use nix::{
sys::signal::{Signal, kill},
unistd::Pid,
};
use tokio::{
process::{Child, Command},
sync::Mutex,
time::{Duration, sleep, timeout},
};
use tracing::{info, warn};
#[derive(Debug, Default, FromArgs, PartialEq, Eq)]
/// Run the authentik server.
#[argh(subcommand, name = "server")]
#[expect(
clippy::empty_structs_with_brackets,
reason = "argh doesn't support unit structs"
)]
pub(crate) struct Cli {}
pub(crate) fn socket_path() -> PathBuf { pub(crate) fn socket_path() -> PathBuf {
temp_dir().join("authentik.sock") temp_dir().join("authentik.sock")
} }
pub(crate) struct Server {
server: Mutex<Child>,
}
impl Server {
async fn new() -> Result<Self> {
info!("starting server");
let server = if config::get().debug && which::which("authentik-server").is_err() {
let build_status = Command::new("go")
.args(["build", "-o", "server", "./cmd/server"])
.stdin(Stdio::null())
.status()
.await?;
if !build_status.success() {
return Err(eyre!("golang server failed to compile"));
}
Command::new("./server")
.kill_on_drop(true)
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?
} else {
Command::new("authentik-server")
.kill_on_drop(true)
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?
};
Ok(Self {
server: Mutex::new(server),
})
}
async fn shutdown(&self) -> Result<()> {
info!("shutting down server");
let mut server = self.server.lock().await;
if let Some(id) = server.id() {
kill(Pid::from_raw(id.cast_signed()), Signal::SIGINT)?;
}
timeout(Duration::from_secs(1), server.wait()).await??;
Ok(())
}
async fn is_alive(&self) -> bool {
let try_wait = self.server.lock().await.try_wait();
match try_wait {
Ok(Some(code)) => {
warn!(?code, "server has exited");
false
}
Ok(None) => true,
Err(err) => {
warn!(
?err,
"failed to check the status of server process, ignoring"
);
true
}
}
}
}
async fn watch_server(arbiter: Arbiter, server: Arc<Server>) -> Result<()> {
info!("starting server watcher");
loop {
tokio::select! {
() = sleep(Duration::from_secs(5)) => {
if !server.is_alive().await {
return Err(eyre!("server has exited unexpectedly"));
}
}
() = arbiter.shutdown() => {
server.shutdown().await?;
return Ok(());
}
}
}
}
pub(crate) async fn start(_cli: Cli, tasks: &mut Tasks) -> Result<Arc<Server>> {
let arbiter = tasks.arbiter();
let server = Arc::new(Server::new().await?);
tasks
.build_task()
.name(&format!("{}::watch_server", module_path!()))
.spawn(watch_server(arbiter.clone(), Arc::clone(&server)))?;
Ok(server)
}

View File

@@ -54,6 +54,7 @@ const INITIAL_WORKER_ID: usize = 1000;
static INITIAL_WORKER_READY: AtomicBool = AtomicBool::new(false); static INITIAL_WORKER_READY: AtomicBool = AtomicBool::new(false);
pub(crate) struct Worker { pub(crate) struct Worker {
worker_id: usize,
worker: Child, worker: Child,
client: Client<UnixSocketConnector<PathBuf>, Body>, client: Client<UnixSocketConnector<PathBuf>, Body>,
socket_path: PathBuf, socket_path: PathBuf,
@@ -75,6 +76,7 @@ impl Worker {
.build(UnixSocketConnector::new(socket_path.clone())); .build(UnixSocketConnector::new(socket_path.clone()));
Ok(Self { Ok(Self {
worker_id,
worker: cmd worker: cmd
.kill_on_drop(true) .kill_on_drop(true)
.stdin(Stdio::null()) .stdin(Stdio::null())
@@ -108,7 +110,7 @@ impl Worker {
self.shutdown(Signal::SIGINT).await self.shutdown(Signal::SIGINT).await
} }
#[instrument(skip_all)] #[instrument(skip(self), fields(worker_id = self.worker_id))]
fn is_alive(&mut self) -> bool { fn is_alive(&mut self) -> bool {
let try_wait = self.worker.try_wait(); let try_wait = self.worker.try_wait();
match try_wait { match try_wait {
@@ -133,34 +135,52 @@ impl Worker {
result.is_ok() result.is_ok()
} }
#[instrument(skip_all)] #[instrument(skip(self), fields(worker_id = self.worker_id))]
async fn health_live(&self) -> Result<bool> { async fn health_live(&self) -> Result<bool> {
trace!("sending health live request to worker");
let req = Request::builder() let req = Request::builder()
.method("GET") .method("GET")
.uri("http://localhost:8000/-/health/live/") .uri("http://localhost:8000/-/health/live/")
.header(HOST, "localhost") .header(HOST, "localhost")
.body(Body::from(""))?; .body(Body::from(""))?;
Ok(self.client.request(req).await?.status().is_success()) Ok(self
.client
.request(req)
.await
.inspect_err(|err| warn!(?err, "failed to send health live request to worker"))?
.status()
.is_success())
} }
#[instrument(skip_all)] #[instrument(skip(self), fields(worker_id = self.worker_id))]
async fn health_ready(&self) -> Result<bool> { async fn health_ready(&self) -> Result<bool> {
trace!("sending health ready request to worker");
let req = Request::builder() let req = Request::builder()
.method("GET") .method("GET")
.uri("http://localhost:8000/-/health/ready/") .uri("http://localhost:8000/-/health/ready/")
.header(HOST, "localhost") .header(HOST, "localhost")
.body(Body::from(""))?; .body(Body::from(""))?;
Ok(self.client.request(req).await?.status().is_success()) Ok(self
.client
.request(req)
.await
.inspect_err(|err| warn!(?err, "failed to send health ready request to worker"))?
.status()
.is_success())
} }
#[instrument(skip_all)] #[instrument(skip(self), fields(worker_id = self.worker_id))]
async fn notify_metrics(&self) -> Result<()> { async fn notify_metrics(&self) -> Result<()> {
trace!("sending metrics request to worker");
let req = Request::builder() let req = Request::builder()
.method("GET") .method("GET")
.uri("http://localhost:8000/-/metrics/") .uri("http://localhost:8000/-/metrics/")
.header(HOST, "localhost") .header(HOST, "localhost")
.body(Body::from(""))?; .body(Body::from(""))?;
self.client.request(req).await?; self.client
.request(req)
.await
.inspect_err(|err| warn!(?err, "failed to send metrics request to worker"))?;
Ok(()) Ok(())
} }
} }
@@ -323,14 +343,7 @@ pub(crate) fn start(_cli: Cli, tasks: &mut Tasks) -> Result<Arc<Workers>> {
let router = healthcheck::build_router(Arc::clone(&workers)); let router = healthcheck::build_router(Arc::clone(&workers));
for addr in config::get().listen.http.iter().copied() { for addr in config::get().listen.http.iter().copied() {
ak_axum::server::start_plain( ak_axum::server::start_plain(tasks, "worker", router.clone(), addr)?;
tasks,
"worker",
router.clone(),
addr,
config::get().debug, /* Allow failure in case the server is running on the same
* machine, like in dev. */
)?;
} }
ak_axum::server::start_unix( ak_axum::server::start_unix(
@@ -338,8 +351,6 @@ pub(crate) fn start(_cli: Cli, tasks: &mut Tasks) -> Result<Arc<Workers>> {
"worker", "worker",
router, router,
unix::net::SocketAddr::from_pathname(socket_path())?, unix::net::SocketAddr::from_pathname(socket_path())?,
config::get().debug, /* Allow failure in case the server is running on the same
* machine, like in dev. */
)?; )?;
} }

View File

@@ -1,10 +1,16 @@
from os import unlink, write
from sys import stderr from sys import stderr
from tempfile import mkstemp
from urllib.parse import urlencode
from channels.testing import ChannelsLiveServerTestCase from channels.testing import ChannelsLiveServerTestCase
from django.apps import apps from django.apps import apps
from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.urls import reverse
from docker.types import Healthcheck
from dramatiq import get_broker from dramatiq import get_broker
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from yaml import safe_dump
from authentik.core.apps import Setup from authentik.core.apps import Setup
from authentik.core.models import User from authentik.core.models import User
@@ -47,6 +53,84 @@ class E2ETestMixin(DockerTestCase):
print("::endgroup::", file=stderr) print("::endgroup::", file=stderr)
super().tearDown() super().tearDown()
def url(self, view: str, query: dict | None = None, **kwargs) -> str:
"""reverse `view` with `**kwargs` into full URL using live_server_url"""
url = self.live_server_url + reverse(view, kwargs=kwargs)
if query:
return url + "?" + urlencode(query)
return url
class SSLLiveMixin(DockerTestCase):
"""Mixin to provide an SSL-enabled webserver for integration/e2e tests that require it.
Overrides `live_server_url` and as such other all usual helper functions will return an HTTPS
URL. Certificate is self-signed and random on each run."""
def setUp(self):
super().setUp()
self._setup_traefik()
def tearDown(self):
super().tearDown()
unlink(self._traefik_config)
@property
def live_server_url(self):
return f"https://{self.host}:{self._traefik_port}"
def _setup_traefik(self):
config = {
"http": {
"routers": {
"authentik": {
"rule": "PathPrefix(`/`)",
"entryPoints": ["websecure"],
"service": "authentik",
"tls": {},
}
},
"services": {
"authentik": {"loadBalancer": {"servers": [{"url": super().live_server_url}]}}
},
}
}
fd, self._traefik_config = mkstemp()
write(fd, safe_dump(config).encode())
traefik = self.run_container(
image="docker.io/library/traefik:3.1",
command=[
"--providers.file.filename=/etc/traefik/dynamic.yml",
"--providers.file.watch=true",
"--entrypoints.websecure.address=:9443",
"--log.level=DEBUG",
"--api=true",
"--api.dashboard=true",
"--api.insecure=true",
"--ping=true",
],
healthcheck=Healthcheck(
test=["CMD", "traefik", "healthcheck", "--ping"],
interval=5 * 1_000 * 1_000_000,
start_period=1 * 1_000 * 1_000_000,
),
ports={
"9443": None,
},
volumes={
self._traefik_config: {
"bind": "/etc/traefik/dynamic.yml",
}
},
)
# {
# "8443/tcp": [
# {"HostIp": "0.0.0.0", "HostPort": "8443"},
# {"HostIp": "::", "HostPort": "8443"},
# ],
# }
self._traefik_port = traefik.ports["9443/tcp"][0]["HostPort"]
class E2ETestCase(E2ETestMixin, StaticLiveServerTestCase): class E2ETestCase(E2ETestMixin, StaticLiveServerTestCase):
"""E2E Test case with django static live server""" """E2E Test case with django static live server"""

View File

@@ -1,17 +1,19 @@
from os import makedirs from os import makedirs
from pathlib import Path from pathlib import Path
from time import sleep from time import sleep
from typing import Any
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support import expected_conditions as ec
from authentik.blueprints.tests import apply_blueprint, reconcile_app from authentik.blueprints.tests import apply_blueprint, reconcile_app
from authentik.providers.oauth2.models import OAuth2Provider from authentik.providers.oauth2.models import OAuth2Provider
from tests.live import SSLLiveMixin
from tests.openid_conformance.conformance import Conformance from tests.openid_conformance.conformance import Conformance
from tests.selenium import SeleniumTestCase from tests.selenium import SeleniumTestCase
class TestOpenIDConformance(SeleniumTestCase): class TestOpenIDConformance(SSLLiveMixin, SeleniumTestCase):
conformance: Conformance conformance: Conformance
@@ -59,32 +61,28 @@ class TestOpenIDConformance(SeleniumTestCase):
}, },
"consent": {}, "consent": {},
} }
self.test_variant = {
"server_metadata": "discovery",
"client_registration": "static_client",
}
def run_test(self, test_name: str, test_plan_config: dict): def run_test(
# Create a Conformance instance... self, test_name: str, test_plan_config: dict[str, Any], test_variant: dict[str, Any]
):
self.conformance = Conformance(f"https://{self.host}:8443/", None, verify_ssl=False) self.conformance = Conformance(f"https://{self.host}:8443/", None, verify_ssl=False)
test_plan = self.conformance.create_test_plan( test_plan = self.conformance.create_test_plan(
test_name, test_name,
test_plan_config, test_plan_config,
self.test_variant, test_variant,
) )
plan_id = test_plan["id"] plan_id = test_plan["id"]
for test in test_plan["modules"]: for test in test_plan["modules"]:
with self.subTest(test["testModule"], **test["variant"]): # Fetch name and variant of the next test to run
# Fetch name and variant of the next test to run module_name = test["testModule"]
module_name = test["testModule"] variant = test["variant"]
variant = test["variant"] module_instance = self.conformance.create_test_from_plan_with_variant(
module_instance = self.conformance.create_test_from_plan_with_variant( plan_id, module_name, variant
plan_id, module_name, variant )
) module_id = module_instance["id"]
module_id = module_instance["id"] self.run_single_test(module_id)
self.run_single_test(module_id) self.conformance.wait_for_state(module_id, ["FINISHED"], timeout=self.wait_timeout)
self.conformance.wait_for_state(module_id, ["FINISHED"], timeout=self.wait_timeout)
sleep(2) sleep(2)
self.conformance.export_html(plan_id, Path(__file__).parent / "exports") self.conformance.export_html(plan_id, Path(__file__).parent / "exports")

View File

@@ -2,14 +2,14 @@ services:
mongodb: mongodb:
image: mongo:6.0.13 image: mongo:6.0.13
nginx: nginx:
image: ghcr.io/beryju/oidc-conformance-suite-nginx:v5.1.41 image: ghcr.io/beryju/oidc-conformance-suite-nginx:v5.1.43
ports: ports:
- "8443:8443" - "8443:8443"
- "8444:8444" - "8444:8444"
depends_on: depends_on:
- server - server
server: server:
image: ghcr.io/beryju/oidc-conformance-suite-server:v5.1.41 image: ghcr.io/beryju/oidc-conformance-suite-server:v5.1.43
ports: ports:
- "9999:9999" - "9999:9999"
extra_hosts: extra_hosts:
@@ -19,8 +19,8 @@ services:
-Xdebug -Xrunjdwp:transport=dt_socket,address=*:9999,server=y,suspend=n -Xdebug -Xrunjdwp:transport=dt_socket,address=*:9999,server=y,suspend=n
-jar /server/fapi-test-suite.jar -jar /server/fapi-test-suite.jar
-Djdk.tls.maxHandshakeMessageSize=65536 -Djdk.tls.maxHandshakeMessageSize=65536
--fintechlabs.base_url=https://host.docker.internal:8443 --fintechlabs.base_url=https://localhost:8443
--fintechlabs.base_mtls_url=https://host.docker.internal:8444 --fintechlabs.base_mtls_url=https://localhost:8444
--fintechlabs.devmode=true --fintechlabs.devmode=true
--fintechlabs.startredir=true --fintechlabs.startredir=true
links: links:

View File

@@ -1,10 +0,0 @@
from tests.decorators import retry
from tests.openid_conformance.base import TestOpenIDConformance
class TestOpenIDConformanceBasic(TestOpenIDConformance):
@retry()
def test_oidcc_basic_certification_test(self):
test_plan_name = "oidcc-basic-certification-test-plan"
self.run_test(test_plan_name, self.test_plan_config)

View File

@@ -1,10 +0,0 @@
from tests.decorators import retry
from tests.openid_conformance.base import TestOpenIDConformance
class TestOpenIDConformanceImplicit(TestOpenIDConformance):
@retry()
def test_oidcc_implicit_certification_test_plan(self):
test_plan_name = "oidcc-implicit-certification-test-plan"
self.run_test(test_plan_name, self.test_plan_config)

View File

@@ -0,0 +1,39 @@
from unittest.mock import patch
import urllib3
from authentik.flows.models import Flow
from authentik.lib.utils.http import get_http_session as real_get_http_session
from authentik.providers.oauth2.models import OAuth2LogoutMethod, OAuth2Provider
from tests.decorators import retry
from tests.openid_conformance.base import TestOpenIDConformance
def _insecure_http_session():
session = real_get_http_session()
session.verify = False
return session
@patch("authentik.providers.oauth2.tasks.get_http_session", _insecure_http_session)
class TestOpenIDConformanceBackchannel(TestOpenIDConformance):
def setUp(self):
super().setUp()
OAuth2Provider.objects.filter(name__startswith="oidc-conformance-").update(
invalidation_flow=Flow.objects.get(slug="default-invalidation-flow"),
logout_method=OAuth2LogoutMethod.BACKCHANNEL,
logout_uri="https://localhost:8443/test/a/authentik/backchannel_logout",
)
# We are unable to use https for this at the current time
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
@retry()
def test_oidcc_backchannel_logout_certification_test_plan(self):
self.run_test(
"oidcc-backchannel-rp-initiated-logout-certification-test-plan",
self.test_plan_config,
{
"client_registration": "static_client",
"response_type": "code",
},
)

View File

@@ -0,0 +1,16 @@
from tests.decorators import retry
from tests.openid_conformance.base import TestOpenIDConformance
class TestOpenIDConformanceBasic(TestOpenIDConformance):
@retry()
def test_oidcc_basic_certification_test(self):
self.run_test(
"oidcc-basic-certification-test-plan",
self.test_plan_config,
{
"server_metadata": "discovery",
"client_registration": "static_client",
},
)

View File

@@ -0,0 +1,26 @@
from authentik.flows.models import Flow
from authentik.providers.oauth2.models import OAuth2LogoutMethod, OAuth2Provider
from tests.decorators import retry
from tests.openid_conformance.base import TestOpenIDConformance
class TestOpenIDConformanceFrontchannel(TestOpenIDConformance):
def setUp(self):
super().setUp()
OAuth2Provider.objects.filter(name__startswith="oidc-conformance-").update(
invalidation_flow=Flow.objects.get(slug="default-invalidation-flow"),
logout_method=OAuth2LogoutMethod.FRONTCHANNEL,
logout_uri="https://localhost:8443/test/a/authentik/frontchannel_logout",
)
@retry()
def test_oidcc_frontchannel_logout_certification_test_plan(self):
self.run_test(
"oidcc-frontchannel-rp-initiated-logout-certification-test-plan",
self.test_plan_config,
{
"client_registration": "static_client",
"response_type": "code",
},
)

View File

@@ -0,0 +1,16 @@
from tests.decorators import retry
from tests.openid_conformance.base import TestOpenIDConformance
class TestOpenIDConformanceImplicit(TestOpenIDConformance):
@retry()
def test_oidcc_implicit_certification_test_plan(self):
self.run_test(
"oidcc-implicit-certification-test-plan",
self.test_plan_config,
{
"server_metadata": "discovery",
"client_registration": "static_client",
},
)

View File

@@ -0,0 +1,24 @@
from authentik.flows.models import Flow
from authentik.providers.oauth2.models import OAuth2Provider
from tests.decorators import retry
from tests.openid_conformance.base import TestOpenIDConformance
class TestOpenIDConformanceRPInitiated(TestOpenIDConformance):
def setUp(self):
super().setUp()
OAuth2Provider.objects.filter(name__startswith="oidc-conformance-").update(
invalidation_flow=Flow.objects.get(slug="default-invalidation-flow"),
)
@retry()
def test_oidcc_rp_initiated_certification_test_plan(self):
self.run_test(
"oidcc-rp-initiated-logout-certification-test-plan",
self.test_plan_config,
{
"client_registration": "static_client",
"response_type": "code",
},
)

View File

@@ -0,0 +1,49 @@
from authentik.core.models import Application
from authentik.crypto.models import CertificateKeyPair
from authentik.enterprise.providers.ssf.models import SSFProvider
from authentik.lib.generators import generate_id
from tests.decorators import retry
from tests.live import SSLLiveMixin
from tests.openid_conformance.base import TestOpenIDConformance
class TestOpenIDConformanceSSFTransmitter(TestOpenIDConformance, SSLLiveMixin):
def setUp(self):
super().setUp()
self.provider = SSFProvider.objects.create(
name=generate_id(),
signing_key=CertificateKeyPair.objects.get(name="authentik Self-signed Certificate"),
backchannel_application=Application.objects.get(slug="oidc-conformance-1"),
push_verify_certificates=False,
)
@retry()
def test_openid_ssf_transmitter_test_plan(self):
iss = self.url(
"authentik_providers_ssf:configuration",
application_slug="oidc-conformance-1",
)
self.run_test(
"openid-ssf-transmitter-test-plan",
{
"alias": "authentik",
"description": "authentik",
"ssf": {
"transmitter": {
"issuer": iss,
"configuration_metadata_endpoint": iss,
"access_token": self.provider.token.key,
}
},
},
test_variant={
"client_auth_type": "client_secret_post",
"ssf_server_metadata": "static",
"server_metadata": "static",
"ssf_auth_mode": "static",
"ssf_delivery_mode": "push",
"ssf_profile": "caep_interop",
"client_registration": "static_client",
},
)

View File

@@ -5,10 +5,8 @@ from json import JSONDecodeError, dumps, loads
from pathlib import Path from pathlib import Path
from tempfile import gettempdir from tempfile import gettempdir
from time import sleep from time import sleep
from urllib.parse import urlencode
from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.urls import reverse
from docker.models.containers import Container from docker.models.containers import Container
from requests import RequestException from requests import RequestException
from selenium import webdriver from selenium import webdriver
@@ -131,13 +129,6 @@ class SeleniumTestMixin(E2ETestMixin):
f"HTML: {self.driver.page_source[:1000]}" f"HTML: {self.driver.page_source[:1000]}"
) from exc ) from exc
def url(self, view: str, query: dict | None = None, **kwargs) -> str:
"""reverse `view` with `**kwargs` into full URL using live_server_url"""
url = self.live_server_url + reverse(view, kwargs=kwargs)
if query:
return url + "?" + urlencode(query)
return url
def if_user_url(self, path: str | None = None) -> str: def if_user_url(self, path: str | None = None) -> str:
"""same as self.url() but show URL in shell""" """same as self.url() but show URL in shell"""
url = self.url("authentik_core:if-user") url = self.url("authentik_core:if-user")

138
uv.lock generated
View File

@@ -318,7 +318,7 @@ requires-dist = [
{ name = "argon2-cffi", specifier = "==25.1.0" }, { name = "argon2-cffi", specifier = "==25.1.0" },
{ name = "cachetools", specifier = "==7.0.6" }, { name = "cachetools", specifier = "==7.0.6" },
{ name = "channels", specifier = "==4.3.2" }, { name = "channels", specifier = "==4.3.2" },
{ name = "cryptography", specifier = "==47.0.0" }, { name = "cryptography", specifier = "==48.0.0" },
{ name = "dacite", specifier = "==1.9.2" }, { name = "dacite", specifier = "==1.9.2" },
{ name = "deepmerge", specifier = "==2.0" }, { name = "deepmerge", specifier = "==2.0" },
{ name = "defusedxml", specifier = "==0.7.1" }, { name = "defusedxml", specifier = "==0.7.1" },
@@ -355,9 +355,9 @@ requires-dist = [
{ name = "lxml", specifier = "==6.1.0" }, { name = "lxml", specifier = "==6.1.0" },
{ name = "msgraph-sdk", specifier = "==1.56.0" }, { name = "msgraph-sdk", specifier = "==1.56.0" },
{ name = "opencontainers", git = "https://github.com/vsoch/oci-python?rev=ceb4fcc090851717a3069d78e85ceb1e86c2740c" }, { name = "opencontainers", git = "https://github.com/vsoch/oci-python?rev=ceb4fcc090851717a3069d78e85ceb1e86c2740c" },
{ name = "packaging", specifier = "==26.1" }, { name = "packaging", specifier = "==26.2" },
{ name = "paramiko", specifier = "==4.0.0" }, { name = "paramiko", specifier = "==4.0.0" },
{ name = "psycopg", extras = ["c", "pool"], specifier = "==3.3.3" }, { name = "psycopg", extras = ["c", "pool"], specifier = "==3.3.4" },
{ name = "pydantic", specifier = "==2.13.3" }, { name = "pydantic", specifier = "==2.13.3" },
{ name = "pydantic-scim", specifier = "==0.0.8" }, { name = "pydantic-scim", specifier = "==0.0.8" },
{ name = "pyjwt", specifier = "==2.11.0" }, { name = "pyjwt", specifier = "==2.11.0" },
@@ -375,7 +375,7 @@ requires-dist = [
{ name = "ua-parser", specifier = "==1.0.2" }, { name = "ua-parser", specifier = "==1.0.2" },
{ name = "unidecode", specifier = "==1.4.0" }, { name = "unidecode", specifier = "==1.4.0" },
{ name = "urllib3", specifier = "<3" }, { name = "urllib3", specifier = "<3" },
{ name = "uvicorn", extras = ["standard"], specifier = "==0.45.0" }, { name = "uvicorn", extras = ["standard"], specifier = "==0.46.0" },
{ name = "watchdog", specifier = "==6.0.0" }, { name = "watchdog", specifier = "==6.0.0" },
{ name = "webauthn", specifier = "==2.7.1" }, { name = "webauthn", specifier = "==2.7.1" },
{ name = "wsproto", specifier = "==1.3.2" }, { name = "wsproto", specifier = "==1.3.2" },
@@ -385,7 +385,7 @@ requires-dist = [
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "aws-cdk-lib", specifier = "==2.250.0" }, { name = "aws-cdk-lib", specifier = "==2.251.0" },
{ name = "bandit", specifier = "==1.9.4" }, { name = "bandit", specifier = "==1.9.4" },
{ name = "black", specifier = "==26.3.1" }, { name = "black", specifier = "==26.3.1" },
{ name = "bpython", specifier = "==0.26" }, { name = "bpython", specifier = "==0.26" },
@@ -481,21 +481,21 @@ wheels = [
[[package]] [[package]]
name = "aws-cdk-cloud-assembly-schema" name = "aws-cdk-cloud-assembly-schema"
version = "53.9.0" version = "53.20.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "jsii" }, { name = "jsii" },
{ name = "publication" }, { name = "publication" },
{ name = "typeguard" }, { name = "typeguard" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/7c/95/afd7bd310b7eb64bfa51700e283a297a57692d8ca5a3a289d9bfe6ca92f6/aws_cdk_cloud_assembly_schema-53.9.0.tar.gz", hash = "sha256:0a9a537c6cdfebbf3e97f250aaff92d811a6be94394a4673784a50660889a3cb", size = 210905, upload-time = "2026-03-26T18:44:01.266Z" } sdist = { url = "https://files.pythonhosted.org/packages/bf/8a/3b94fbba0d8ca4123eb015ea12a1c8fc5193a1eddcc4d69d31b9575546c8/aws_cdk_cloud_assembly_schema-53.20.0.tar.gz", hash = "sha256:c5d884f7211fd18cc0ce8c4349902ab6a6b3cd8f3c2259c56616a59218c221eb", size = 212292, upload-time = "2026-04-30T11:34:29.29Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/6e/d0/4dbd2020f8fa6991f6469ed1851981f3160965393ea4cef19a59bf8c938b/aws_cdk_cloud_assembly_schema-53.9.0-py3-none-any.whl", hash = "sha256:41e75541d3ea3d46edbe67ade491d974f0472ac62c3856596e3029fe384a9a44", size = 210649, upload-time = "2026-03-26T18:43:59.951Z" }, { url = "https://files.pythonhosted.org/packages/b5/eb/7bf100ad3603d5fbbebb49b3d48add58abda59c3fa44a4a7eae40da5b8d0/aws_cdk_cloud_assembly_schema-53.20.0-py3-none-any.whl", hash = "sha256:b68ea0754ec830751d4a375ebe84d4c077dc488a2498c3607a2f998bc7e91d73", size = 212140, upload-time = "2026-04-30T11:34:27.044Z" },
] ]
[[package]] [[package]]
name = "aws-cdk-lib" name = "aws-cdk-lib"
version = "2.250.0" version = "2.251.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "aws-cdk-asset-awscli-v1" }, { name = "aws-cdk-asset-awscli-v1" },
@@ -506,9 +506,9 @@ dependencies = [
{ name = "publication" }, { name = "publication" },
{ name = "typeguard" }, { name = "typeguard" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/d5/7b/98c66b6ea6d4f84b70d86ec391bbcd2856a82b384a97adc223f36b36dfd6/aws_cdk_lib-2.250.0.tar.gz", hash = "sha256:6e5cb8def9208a45cede1376a81d7508b3889879ccc7e9cddaa4fd807da0b144", size = 49146123, upload-time = "2026-04-14T21:43:07.309Z" } sdist = { url = "https://files.pythonhosted.org/packages/b8/6c/d60d96e1848aabf1882e6a1d30a27de4a592affc9437d6918848f0e06497/aws_cdk_lib-2.251.0.tar.gz", hash = "sha256:ed69e7ea6896c62ac2ce01857083601baf541d5d875370bee6d213d641e8921e", size = 49353237, upload-time = "2026-04-24T23:21:04.805Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/e7/b64389b59215a42d0d7538a1d9a8006edb2eefb297ccbdcd4c55ff2cffba/aws_cdk_lib-2.250.0-py3-none-any.whl", hash = "sha256:427c9a062f350c16e301326fd6ca0440428f9cc0e85421aaa69a9afa57228acc", size = 49827287, upload-time = "2026-04-14T21:42:21.21Z" }, { url = "https://files.pythonhosted.org/packages/d2/fb/ab682b518e3ca5d18b23b252832e0fade4e6617a2c0f2b0ae0d8d2e74312/aws_cdk_lib-2.251.0-py3-none-any.whl", hash = "sha256:a684f3461d096443ac688adbf559abe1af2d50dd5c8e0fa7dbf4a5f361702db8", size = 50035969, upload-time = "2026-04-24T23:20:18.952Z" },
] ]
[[package]] [[package]]
@@ -917,55 +917,55 @@ wheels = [
[[package]] [[package]]
name = "cryptography" name = "cryptography"
version = "47.0.0" version = "48.0.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/ef/b2/7ffa7fe8207a8c42147ffe70c3e360b228160c1d85dc3faff16aaa3244c0/cryptography-47.0.0.tar.gz", hash = "sha256:9f8e55fe4e63613a5e1cc5819030f27b97742d720203a087802ce4ce9ceb52bb", size = 830863, upload-time = "2026-04-24T19:54:57.056Z" } sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/a4/98/40dfe932134bdcae4f6ab5927c87488754bf9eb79297d7e0070b78dd58e9/cryptography-47.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:160ad728f128972d362e714054f6ba0067cab7fb350c5202a9ae8ae4ce3ef1a0", size = 7912214, upload-time = "2026-04-24T19:53:03.864Z" }, { url = "https://files.pythonhosted.org/packages/df/3d/01f6dd9190170a5a241e0e98c2d04be3664a9e6f5b9b872cde63aff1c3dd/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6", size = 8001587, upload-time = "2026-05-04T22:57:36.803Z" },
{ url = "https://files.pythonhosted.org/packages/34/c6/2733531243fba725f58611b918056b277692f1033373dcc8bd01af1c05d4/cryptography-47.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b9a8943e359b7615db1a3ba587994618e094ff3d6fa5a390c73d079ce18b3973", size = 4644617, upload-time = "2026-04-24T19:53:06.909Z" }, { url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" },
{ url = "https://files.pythonhosted.org/packages/00/e3/b27be1a670a9b87f855d211cf0e1174a5d721216b7616bd52d8581d912ed/cryptography-47.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5c15764f261394b22aef6b00252f5195f46f2ca300bec57149474e2538b31f8", size = 4668186, upload-time = "2026-04-24T19:53:09.053Z" }, { url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" },
{ url = "https://files.pythonhosted.org/packages/81/b9/8443cfe5d17d482d348cee7048acf502bb89a51b6382f06240fd290d4ca3/cryptography-47.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9c59ab0e0fa3a180a5a9c59f3a5abe3ef90d474bc56d7fadfbe80359491b615b", size = 4651244, upload-time = "2026-04-24T19:53:11.217Z" }, { url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" },
{ url = "https://files.pythonhosted.org/packages/5d/5e/13ed0cdd0eb88ba159d6dd5ebfece8cb901dbcf1ae5ac4072e28b55d3153/cryptography-47.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:34b4358b925a5ea3e14384ca781a2c0ef7ac219b57bb9eacc4457078e2b19f92", size = 5252906, upload-time = "2026-04-24T19:53:13.532Z" }, { url = "https://files.pythonhosted.org/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" },
{ url = "https://files.pythonhosted.org/packages/64/16/ed058e1df0f33d440217cd120d41d5dda9dd215a80b8187f68483185af82/cryptography-47.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0024b87d47ae2399165a6bfb20d24888881eeab83ae2566d62467c5ff0030ce7", size = 4701842, upload-time = "2026-04-24T19:53:15.618Z" }, { url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" },
{ url = "https://files.pythonhosted.org/packages/02/e0/3d30986b30fdbd9e969abbdf8ba00ed0618615144341faeb57f395a084fe/cryptography-47.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:1e47422b5557bb82d3fff997e8d92cff4e28b9789576984f08c248d2b3535d93", size = 4289313, upload-time = "2026-04-24T19:53:17.755Z" }, { url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" },
{ url = "https://files.pythonhosted.org/packages/df/fd/32db38e3ad0cb331f0691cb4c7a8a6f176f679124dee746b3af6633db4d9/cryptography-47.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6f29f36582e6151d9686235e586dd35bb67491f024767d10b842e520dc6a07ac", size = 4650964, upload-time = "2026-04-24T19:53:20.062Z" }, { url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" },
{ url = "https://files.pythonhosted.org/packages/86/53/5395d944dfd48cb1f67917f533c609c34347185ef15eb4308024c876f274/cryptography-47.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a9b761f012a943b7de0e828843c5688d0de94a0578d44d6c85a1bae32f87791f", size = 5207817, upload-time = "2026-04-24T19:53:22.498Z" }, { url = "https://files.pythonhosted.org/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" },
{ url = "https://files.pythonhosted.org/packages/34/4f/e5711b28e1901f7d480a2b1b688b645aa4c77c73f10731ed17e7f7db3f0d/cryptography-47.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4e1de79e047e25d6e9f8cea71c86b4a53aced64134f0f003bbcbf3655fd172c8", size = 4701544, upload-time = "2026-04-24T19:53:24.356Z" }, { url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" },
{ url = "https://files.pythonhosted.org/packages/22/22/c8ddc25de3010fc8da447648f5a092c40e7a8fadf01dd6d255d9c0b9373d/cryptography-47.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef6b3634087f18d2155b1e8ce264e5345a753da2c5fa9815e7d41315c90f8318", size = 4783536, upload-time = "2026-04-24T19:53:26.665Z" }, { url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" },
{ url = "https://files.pythonhosted.org/packages/66/b6/d4a68f4ea999c6d89e8498579cba1c5fcba4276284de7773b17e4fa69293/cryptography-47.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:11dbb9f50a0f1bb9757b3d8c27c1101780efb8f0bdecfb12439c22a74d64c001", size = 4926106, upload-time = "2026-04-24T19:53:28.686Z" }, { url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" },
{ url = "https://files.pythonhosted.org/packages/54/ed/5f524db1fade9c013aa618e1c99c6ed05e8ffc9ceee6cda22fed22dda3f4/cryptography-47.0.0-cp311-abi3-win32.whl", hash = "sha256:7fda2f02c9015db3f42bb8a22324a454516ed10a8c29ca6ece6cdbb5efe2a203", size = 3258581, upload-time = "2026-04-24T19:53:31.058Z" }, { url = "https://files.pythonhosted.org/packages/04/70/e5a1b41d325f797f39427aa44ef8baf0be500065ab6d8e10369d850d4a4f/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4", size = 3294868, upload-time = "2026-05-04T22:58:06.467Z" },
{ url = "https://files.pythonhosted.org/packages/b2/dc/1b901990b174786569029f67542b3edf72ac068b6c3c8683c17e6a2f5363/cryptography-47.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:f5c3296dab66202f1b18a91fa266be93d6aa0c2806ea3d67762c69f60adc71aa", size = 3775309, upload-time = "2026-04-24T19:53:33.054Z" }, { url = "https://files.pythonhosted.org/packages/f4/ac/8ac51b4a5fc5932eb7ee5c517ba7dc8cd834f0048962b6b352f00f41ebf9/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7", size = 3817107, upload-time = "2026-05-04T22:58:08.845Z" },
{ url = "https://files.pythonhosted.org/packages/14/88/7aa18ad9c11bc87689affa5ce4368d884b517502d75739d475fc6f4a03c7/cryptography-47.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:be12cb6a204f77ed968bcefe68086eb061695b540a3dd05edac507a3111b25f0", size = 7904299, upload-time = "2026-04-24T19:53:35.003Z" }, { url = "https://files.pythonhosted.org/packages/6b/84/70e3feea9feea87fd7cbe77efb2712ae1e3e6edf10749dc6e95f4e60e455/cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec", size = 7986556, upload-time = "2026-05-04T22:58:11.172Z" },
{ url = "https://files.pythonhosted.org/packages/07/55/c18f75724544872f234678fdedc871391722cb34a2aee19faa9f63100bb2/cryptography-47.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2ebd84adf0728c039a3be2700289378e1c164afc6748df1a5ed456767bef9ba7", size = 4631180, upload-time = "2026-04-24T19:53:37.517Z" }, { url = "https://files.pythonhosted.org/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" },
{ url = "https://files.pythonhosted.org/packages/ee/65/31a5cc0eaca99cec5bafffe155d407115d96136bb161e8b49e0ef73f09a7/cryptography-47.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f68d6fbc7fbbcfb0939fea72c3b96a9f9a6edfc0e1b1d29778a2066030418b1", size = 4653529, upload-time = "2026-04-24T19:53:39.775Z" }, { url = "https://files.pythonhosted.org/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" },
{ url = "https://files.pythonhosted.org/packages/e5/bc/641c0519a495f3bfd0421b48d7cd325c4336578523ccd76ea322b6c29c7a/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:6651d32eff255423503aa276739da98c30f26c40cbeffcc6048e0d54ef704c0c", size = 4638570, upload-time = "2026-04-24T19:53:42.129Z" }, { url = "https://files.pythonhosted.org/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" },
{ url = "https://files.pythonhosted.org/packages/2b/f2/300327b0a47f6dc94dd8b71b57052aefe178bb51745073d73d80604f11ab/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3fb8fa48075fad7193f2e5496135c6a76ac4b2aa5a38433df0a539296b377829", size = 5238019, upload-time = "2026-04-24T19:53:44.577Z" }, { url = "https://files.pythonhosted.org/packages/ca/b4/fc334ed8cfd705aca282fe4d8f5ae64a8e0f74932e9feecb344610cf6e4d/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c", size = 5282526, upload-time = "2026-05-04T22:58:20.75Z" },
{ url = "https://files.pythonhosted.org/packages/e9/5a/5b5cf994391d4bf9d9c7efd4c66aabe4d95227256627f8fea6cff7dfadbd/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11438c7518132d95f354fa01a4aa2f806d172a061a7bed18cf18cbdacdb204d7", size = 4686832, upload-time = "2026-04-24T19:53:47.015Z" }, { url = "https://files.pythonhosted.org/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" },
{ url = "https://files.pythonhosted.org/packages/dc/2c/ae950e28fd6475c852fc21a44db3e6b5bcc1261d1e370f2b6e42fa800fef/cryptography-47.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8c1a736bbb3288005796c3f7ccb9453360d7fed483b13b9f468aea5171432923", size = 4269301, upload-time = "2026-04-24T19:53:48.97Z" }, { url = "https://files.pythonhosted.org/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" },
{ url = "https://files.pythonhosted.org/packages/67/fb/6a39782e150ffe5cc1b0018cb6ddc48bf7ca62b498d7539ffc8a758e977d/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:f1557695e5c2b86e204f6ce9470497848634100787935ab7adc5397c54abd7ab", size = 4638110, upload-time = "2026-04-24T19:53:51.011Z" }, { url = "https://files.pythonhosted.org/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" },
{ url = "https://files.pythonhosted.org/packages/8e/d7/0b3c71090a76e5c203164a47688b697635ece006dcd2499ab3a4dbd3f0bd/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:f9a034b642b960767fb343766ae5ba6ad653f2e890ddd82955aef288ffea8736", size = 5194988, upload-time = "2026-04-24T19:53:52.962Z" }, { url = "https://files.pythonhosted.org/packages/dd/a6/825010a291b4438aecc1f568bc428189fc1175515223632477c07dc0a6df/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336", size = 5237657, upload-time = "2026-05-04T22:58:29.848Z" },
{ url = "https://files.pythonhosted.org/packages/63/33/63a961498a9df51721ab578c5a2622661411fc520e00bd83b0cc64eb20c4/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:b1c76fca783aa7698eb21eb14f9c4aa09452248ee54a627d125025a43f83e7a7", size = 4686563, upload-time = "2026-04-24T19:53:55.274Z" }, { url = "https://files.pythonhosted.org/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" },
{ url = "https://files.pythonhosted.org/packages/b7/bf/5ee5b145248f92250de86145d1c1d6edebbd57a7fe7caa4dedb5d4cf06a1/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4f7722c97826770bab8ae92959a2e7b20a5e9e9bf4deae68fd86c3ca457bab52", size = 4770094, upload-time = "2026-04-24T19:53:57.753Z" }, { url = "https://files.pythonhosted.org/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" },
{ url = "https://files.pythonhosted.org/packages/92/43/21d220b2da5d517773894dacdcdb5c682c28d3fffce65548cb06e87d5501/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:09f6d7bf6724f8db8b32f11eccf23efc8e759924bc5603800335cf8859a3ddbd", size = 4913811, upload-time = "2026-04-24T19:54:00.236Z" }, { url = "https://files.pythonhosted.org/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" },
{ url = "https://files.pythonhosted.org/packages/31/98/dc4ad376ac5f1a1a7d4a83f7b0c6f2bcad36b5d2d8f30aeb482d3a7d9582/cryptography-47.0.0-cp314-cp314t-win32.whl", hash = "sha256:6eebcaf0df1d21ce1f90605c9b432dd2c4f4ab665ac29a40d5e3fc68f51b5e63", size = 3237158, upload-time = "2026-04-24T19:54:02.606Z" }, { url = "https://files.pythonhosted.org/packages/75/54/cc6d0f3deac3e81c7f847e8a189a12b6cdd65059b43dad25d4316abd849a/cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f", size = 3270954, upload-time = "2026-05-04T22:58:38.791Z" },
{ url = "https://files.pythonhosted.org/packages/bc/da/97f62d18306b5133468bc3f8cc73a3111e8cdc8cf8d3e69474d6e5fd2d1b/cryptography-47.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:51c9313e90bd1690ec5a75ed047c27c0b8e6c570029712943d6116ef9a90620b", size = 3758706, upload-time = "2026-04-24T19:54:04.433Z" }, { url = "https://files.pythonhosted.org/packages/49/67/cc947e288c0758a4e5473d1dcb743037ab7785541265a969240b8885441a/cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12", size = 3797313, upload-time = "2026-05-04T22:58:40.746Z" },
{ url = "https://files.pythonhosted.org/packages/e0/34/a4fae8ae7c3bc227460c9ae43f56abf1b911da0ec29e0ebac53bb0a4b6b7/cryptography-47.0.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:14432c8a9bcb37009784f9594a62fae211a2ae9543e96c92b2a8e4c3cd5cd0c4", size = 7904072, upload-time = "2026-04-24T19:54:06.411Z" }, { url = "https://files.pythonhosted.org/packages/f2/63/61d4a4e1c6b6bab6ce1e213cd36a24c415d90e76d78c5eb8577c5541d2e8/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86", size = 7983482, upload-time = "2026-05-04T22:58:43.769Z" },
{ url = "https://files.pythonhosted.org/packages/01/64/d7b1e54fdb69f22d24a64bb3e88dc718b31c7fb10ef0b9691a3cf7eeea6e/cryptography-47.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:07efe86201817e7d3c18781ca9770bc0db04e1e48c994be384e4602bc38f8f27", size = 4635767, upload-time = "2026-04-24T19:54:08.519Z" }, { url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" },
{ url = "https://files.pythonhosted.org/packages/8b/7b/cca826391fb2a94efdcdfe4631eb69306ee1cff0b22f664a412c90713877/cryptography-47.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b45761c6ec22b7c726d6a829558777e32d0f1c8be7c3f3480f9c912d5ee8a10", size = 4654350, upload-time = "2026-04-24T19:54:10.795Z" }, { url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" },
{ url = "https://files.pythonhosted.org/packages/4c/65/4b57bcc823f42a991627c51c2f68c9fd6eb1393c1756aac876cba2accae2/cryptography-47.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:edd4da498015da5b9f26d38d3bfc2e90257bfa9cbed1f6767c282a0025ae649b", size = 4643394, upload-time = "2026-04-24T19:54:13.275Z" }, { url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" },
{ url = "https://files.pythonhosted.org/packages/f4/c4/2c5fbeea70adbbca2bbae865e1d605d6a4a7f8dbd9d33eaf69645087f06c/cryptography-47.0.0-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9af828c0d5a65c70ec729cd7495a4bf1a67ecb66417b8f02ff125ab8a6326a74", size = 5225777, upload-time = "2026-04-24T19:54:15.18Z" }, { url = "https://files.pythonhosted.org/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" },
{ url = "https://files.pythonhosted.org/packages/7e/b8/ac57107ef32749d2b244e36069bb688792a363aaaa3acc9e3cf84c130315/cryptography-47.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:256d07c78a04d6b276f5df935a9923275f53bd1522f214447fdf365494e2d515", size = 4688771, upload-time = "2026-04-24T19:54:17.835Z" }, { url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" },
{ url = "https://files.pythonhosted.org/packages/56/fc/9f1de22ff8be99d991f240a46863c52d475404c408886c5a38d2b5c3bb26/cryptography-47.0.0-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:5d0e362ff51041b0c0d219cc7d6924d7b8996f57ce5712bdcef71eb3c65a59cc", size = 4270753, upload-time = "2026-04-24T19:54:19.963Z" }, { url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" },
{ url = "https://files.pythonhosted.org/packages/00/68/d70c852797aa68e8e48d12e5a87170c43f67bb4a59403627259dd57d15de/cryptography-47.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:1581aef4219f7ca2849d0250edaa3866212fb74bf5667284f46aa92f9e65c1ca", size = 4642911, upload-time = "2026-04-24T19:54:21.818Z" }, { url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" },
{ url = "https://files.pythonhosted.org/packages/a5/51/661cbee74f594c5d97ff82d34f10d5551c085ca4668645f4606ebd22bd5d/cryptography-47.0.0-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a49a3eb5341b9503fa3000a9a0db033161db90d47285291f53c2a9d2cd1b7f76", size = 5181411, upload-time = "2026-04-24T19:54:24.376Z" }, { url = "https://files.pythonhosted.org/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" },
{ url = "https://files.pythonhosted.org/packages/94/87/f2b6c374a82cf076cfa1416992ac8e8ec94d79facc37aec87c1a5cb72352/cryptography-47.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2207a498b03275d0051589e326b79d4cf59985c99031b05bb292ac52631c37fe", size = 4688262, upload-time = "2026-04-24T19:54:26.946Z" }, { url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" },
{ url = "https://files.pythonhosted.org/packages/14/e2/8b7462f4acf21ec509616f0245018bb197194ab0b65c2ea21a0bdd53c0eb/cryptography-47.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a02675e2fabd0c0fc04c868b8781863cbf1967691543c22f5470500ff840b31", size = 4775506, upload-time = "2026-04-24T19:54:28.926Z" }, { url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" },
{ url = "https://files.pythonhosted.org/packages/70/75/158e494e4c08dc05e039da5bb48553826bd26c23930cf8d3cd5f21fa8921/cryptography-47.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80887c5cbd1774683cb126f0ab4184567f080071d5acf62205acb354b4b753b7", size = 4912060, upload-time = "2026-04-24T19:54:30.869Z" }, { url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" },
{ url = "https://files.pythonhosted.org/packages/06/bd/0a9d3edbf5eadbac926d7b9b3cd0c4be584eeeae4a003d24d9eda4affbbd/cryptography-47.0.0-cp38-abi3-win32.whl", hash = "sha256:ed67ea4e0cfb5faa5bc7ecb6e2b8838f3807a03758eec239d6c21c8769355310", size = 3248487, upload-time = "2026-04-24T19:54:33.494Z" }, { url = "https://files.pythonhosted.org/packages/b8/23/6e6f32143ab5d8b36ca848a502c4bcd477ae75b9e1677e3530d669062578/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd", size = 3279117, upload-time = "2026-05-04T22:59:12.019Z" },
{ url = "https://files.pythonhosted.org/packages/60/80/5681af756d0da3a599b7bdb586fac5a1540f1bcefd2717a20e611ddade45/cryptography-47.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:835d2d7f47cdc53b3224e90810fb1d36ca94ea29cc1801fb4c1bc43876735769", size = 3755737, upload-time = "2026-04-24T19:54:35.408Z" }, { url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" },
] ]
[[package]] [[package]]
@@ -2583,11 +2583,11 @@ wheels = [
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "26.1" version = "26.2"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" } sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" }, { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
] ]
[[package]] [[package]]
@@ -2733,14 +2733,14 @@ wheels = [
[[package]] [[package]]
name = "psycopg" name = "psycopg"
version = "3.3.3" version = "3.3.4"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "tzdata", marker = "sys_platform == 'win32'" }, { name = "tzdata", marker = "sys_platform == 'win32'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/d3/b6/379d0a960f8f435ec78720462fd94c4863e7a31237cf81bf76d0af5883bf/psycopg-3.3.3.tar.gz", hash = "sha256:5e9a47458b3c1583326513b2556a2a9473a1001a56c9efe9e587245b43148dd9", size = 165624, upload-time = "2026-02-18T16:52:16.546Z" } sdist = { url = "https://files.pythonhosted.org/packages/db/2f/cb91e5502ec9de1de6f1b76cfbf69531932725361168bb06963620c77e2e/psycopg-3.3.4.tar.gz", hash = "sha256:e21207764952cff81b6b8bdacad9a3939f2793367fdac2987b3aac36a651b5bc", size = 165799, upload-time = "2026-05-01T23:31:55.179Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/5b/181e2e3becb7672b502f0ed7f16ed7352aca7c109cfb94cf3878a9186db9/psycopg-3.3.3-py3-none-any.whl", hash = "sha256:f96525a72bcfade6584ab17e89de415ff360748c766f0106959144dcbb38c698", size = 212768, upload-time = "2026-02-18T16:46:27.365Z" }, { url = "https://files.pythonhosted.org/packages/5c/e0/7b3dee031daae7743609ce3c746565d4a3ed7c2c186479eb48e34e838c64/psycopg-3.3.4-py3-none-any.whl", hash = "sha256:b6bbc25ccf05c8fad3b061d9db2ef0909a555171b84b07f29458a447253d679a", size = 213001, upload-time = "2026-05-01T23:20:50.816Z" },
] ]
[package.optional-dependencies] [package.optional-dependencies]
@@ -2753,9 +2753,9 @@ pool = [
[[package]] [[package]]
name = "psycopg-c" name = "psycopg-c"
version = "3.3.3" version = "3.3.4"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cb/a0/8feb0ca8c7c20a8b9ac4d46b335ddd57e48e593b714262f006880f34fee5/psycopg_c-3.3.3.tar.gz", hash = "sha256:86ef6f4424348247828e83fb0882c9f8acb33e64d0a5ce66c1b4a5107ee73edd", size = 631965, upload-time = "2026-02-18T16:52:18.084Z" } sdist = { url = "https://files.pythonhosted.org/packages/21/7c/c08364f2eab2913e4068b3b955d963e7a3491986a85429990969525def30/psycopg_c-3.3.4.tar.gz", hash = "sha256:ed8106128b2d04359c185fc9641b4409abfce4d0b6fb1d1ff6800646e27f1a22", size = 647111, upload-time = "2026-05-01T23:31:58.032Z" }
[[package]] [[package]]
name = "psycopg-pool" name = "psycopg-pool"
@@ -2947,14 +2947,14 @@ wheels = [
[[package]] [[package]]
name = "pyopenssl" name = "pyopenssl"
version = "26.1.0" version = "26.2.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "cryptography" }, { name = "cryptography" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/8c/a8/26d36401e3ab8eed9030ad33f381da7856fcfad5691780fccd1b019718fc/pyopenssl-26.1.0.tar.gz", hash = "sha256:737f0a2275c5bc54f3b02137687e1a765931fb3949b9a92a825e4d33b9eec08b", size = 186181, upload-time = "2026-04-24T20:23:48.115Z" } sdist = { url = "https://files.pythonhosted.org/packages/1a/51/27a5ad5f939d08f690a326ef9582cda7140555180db71695f6fb747d6a36/pyopenssl-26.2.0.tar.gz", hash = "sha256:8c6fcecd1183a7fc897548dfe388b0cdb7f37e018200d8409cf33959dbe35387", size = 182195, upload-time = "2026-05-04T23:06:09.72Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/a8/41/52f3a3e812b816a91e89aa504199d8bf989a1f873192b10762be66cf2009/pyopenssl-26.1.0-py3-none-any.whl", hash = "sha256:115563879b2c8ccb207975705d3e491434d8c9d7c79667c902ecbf5f3bbd2ece", size = 58109, upload-time = "2026-04-24T20:23:46.273Z" }, { url = "https://files.pythonhosted.org/packages/73/b8/a0e2790ae249d6f38c9f66de7a211621a7ab2650217bcd04e1262f578a56/pyopenssl-26.2.0-py3-none-any.whl", hash = "sha256:4f9d971bc5298b8bc1fab282803da04bf000c755d4ad9d99b52de2569ca19a70", size = 55823, upload-time = "2026-05-04T23:06:08.395Z" },
] ]
[[package]] [[package]]
@@ -3808,15 +3808,15 @@ socks = [
[[package]] [[package]]
name = "uvicorn" name = "uvicorn"
version = "0.45.0" version = "0.46.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "click" }, { name = "click" },
{ name = "h11" }, { name = "h11" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/62b0d9a2cfc8b4de6771322dae30f2db76c66dae9ec32e94e176a44ad563/uvicorn-0.45.0.tar.gz", hash = "sha256:3fe650df136c5bd2b9b06efc5980636344a2fbb840e9ddd86437d53144fa335d", size = 87818, upload-time = "2026-04-21T10:43:46.815Z" } sdist = { url = "https://files.pythonhosted.org/packages/1f/93/041fca8274050e40e6791f267d82e0e2e27dd165627bd640d3e0e378d877/uvicorn-0.46.0.tar.gz", hash = "sha256:fb9da0926999cc6cb22dc7cd71a94a632f078e6ae47ff683c5c420750fb7413d", size = 88758, upload-time = "2026-04-23T07:16:00.151Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/c1/88/d0f7512465b166a4e931ccf7e77792be60fb88466a43964c7566cbaff752/uvicorn-0.45.0-py3-none-any.whl", hash = "sha256:2db26f588131aeec7439de00f2dd52d5f210710c1f01e407a52c90b880d1fd4f", size = 69838, upload-time = "2026-04-21T10:43:45.029Z" }, { url = "https://files.pythonhosted.org/packages/31/a3/5b1562db76a5a488274b2332a97199b32d0442aca0ed193697fd47786316/uvicorn-0.46.0-py3-none-any.whl", hash = "sha256:bbebbcbed972d162afca128605223022bedd345b7bc7855ce66deb31487a9048", size = 70926, upload-time = "2026-04-23T07:15:58.355Z" },
] ]
[package.optional-dependencies] [package.optional-dependencies]

290
web/package-lock.json generated
View File

@@ -21,7 +21,7 @@
"@codemirror/theme-one-dark": "^6.1.3", "@codemirror/theme-one-dark": "^6.1.3",
"@eslint/js": "^9.39.3", "@eslint/js": "^9.39.3",
"@floating-ui/dom": "^1.7.6", "@floating-ui/dom": "^1.7.6",
"@formatjs/intl-listformat": "^8.3.2", "@formatjs/intl-listformat": "^8.3.4",
"@fortawesome/fontawesome-free": "^7.2.0", "@fortawesome/fontawesome-free": "^7.2.0",
"@goauthentik/api": "0.0.0", "@goauthentik/api": "0.0.0",
"@goauthentik/core": "^1.0.0", "@goauthentik/core": "^1.0.0",
@@ -43,11 +43,11 @@
"@patternfly/elements": "^4.4.0", "@patternfly/elements": "^4.4.0",
"@patternfly/patternfly": "^4.224.2", "@patternfly/patternfly": "^4.224.2",
"@playwright/test": "^1.59.1", "@playwright/test": "^1.59.1",
"@sentry/browser": "^10.49.0", "@sentry/browser": "^10.50.0",
"@storybook/addon-docs": "^10.3.5", "@storybook/addon-docs": "^10.3.6",
"@storybook/addon-links": "^10.3.5", "@storybook/addon-links": "^10.3.6",
"@storybook/web-components": "^10.3.5", "@storybook/web-components": "^10.3.6",
"@storybook/web-components-vite": "^10.3.5", "@storybook/web-components-vite": "^10.3.6",
"@types/codemirror": "^5.60.17", "@types/codemirror": "^5.60.17",
"@types/grecaptcha": "^3.0.9", "@types/grecaptcha": "^3.0.9",
"@types/guacamole-common-js": "^1.5.5", "@types/guacamole-common-js": "^1.5.5",
@@ -69,7 +69,7 @@
"country-flag-icons": "^1.6.16", "country-flag-icons": "^1.6.16",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"deepmerge-ts": "^7.1.5", "deepmerge-ts": "^7.1.5",
"dompurify": "^3.4.1", "dompurify": "^3.4.2",
"esbuild": "^0.28.0", "esbuild": "^0.28.0",
"eslint": "^9.39.3", "eslint": "^9.39.3",
"eslint-plugin-lit": "^2.2.1", "eslint-plugin-lit": "^2.2.1",
@@ -78,7 +78,7 @@
"globals": "^17.5.0", "globals": "^17.5.0",
"guacamole-common-js": "^1.5.0", "guacamole-common-js": "^1.5.0",
"hastscript": "^9.0.1", "hastscript": "^9.0.1",
"knip": "^6.6.3", "knip": "^6.7.0",
"lex": "^2025.11.0", "lex": "^2025.11.0",
"lit": "^3.3.2", "lit": "^3.3.2",
"lit-analyzer": "^2.0.3", "lit-analyzer": "^2.0.3",
@@ -1305,27 +1305,27 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@formatjs/fast-memoize": { "node_modules/@formatjs/fast-memoize": {
"version": "3.1.2", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.2.tgz", "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.3.tgz",
"integrity": "sha512-vPnriihkfK0lzoQGaXq+qXH23VsYyansRTkTgo2aTG0k1NjLFyZimFVdfj4C9JkSE5dm7CEngcQ5TTc1yAyBfQ==", "integrity": "sha512-Ocd1vPuD68rW6BJDuAOtnnc1GPeVepY5kZXML1psGVFQ+1Q8CfkftT3Tnam+Mxx97Pz08jIEDCotl/GV+Naccg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@formatjs/intl-listformat": { "node_modules/@formatjs/intl-listformat": {
"version": "8.3.2", "version": "8.3.4",
"resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-8.3.2.tgz", "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-8.3.4.tgz",
"integrity": "sha512-PH8V1YVSm+AcrQ1D9Th5wxWo74vDs1V1rRGz9PCFm1HTBtZLVMN3P/+tDcpizQBvvdGNwUqHBOdxN5ZZtlq2bQ==", "integrity": "sha512-q7WskvO6C/Cyq7ryyM9maDL2FJzt6u39MMBrxmTHZtpTMZukG5Lw0kl9sZaCOR9tYP34xOdWp4JNUrfrkdLGXQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@formatjs/intl-localematcher": "0.8.3" "@formatjs/intl-localematcher": "0.8.5"
} }
}, },
"node_modules/@formatjs/intl-localematcher": { "node_modules/@formatjs/intl-localematcher": {
"version": "0.8.3", "version": "0.8.5",
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.3.tgz", "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.5.tgz",
"integrity": "sha512-pHUjWb9NuhnMs8+PxQdzBtZRFJHlGhrURGAbm6Ltwl82BFajeuiIR3jblSa7ia3r62rXe/0YtVpUG3xWr5bFCA==", "integrity": "sha512-TEW/NR367c3PcQ2AXfkNig9jC740+qbkM0LgKl7UCE7Xtv7C5Uk1mvlu86MjQZBmscUai8HSWjcEETpwaVvJ6A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@formatjs/fast-memoize": "3.1.2" "@formatjs/fast-memoize": "3.1.3"
} }
}, },
"node_modules/@fortawesome/fontawesome-free": { "node_modules/@fortawesome/fontawesome-free": {
@@ -3593,75 +3593,75 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@sentry-internal/browser-utils": { "node_modules/@sentry-internal/browser-utils": {
"version": "10.49.0", "version": "10.50.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.49.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.50.0.tgz",
"integrity": "sha512-n0QRx0Ysx6mPfIydTkz7VP0FmwM+/EqMZiRqdsU3aTYsngE9GmEDV0OL1bAy6a8N/C1xf9vntkuAtj6N/8Z51w==", "integrity": "sha512-42bxyRTxnCmYlWnvz4CxikuQNanw8UNma2WJrtxJ0f1MAJV2GhQGSHDLnA+lvFlmiz6qct3pfen/NXGyOTegTA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/core": "10.49.0" "@sentry/core": "10.50.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry-internal/feedback": { "node_modules/@sentry-internal/feedback": {
"version": "10.49.0", "version": "10.50.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.49.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.50.0.tgz",
"integrity": "sha512-JNsUBGv0faCFE7MeZUH99Y9lU9qq3LBALbLxpE1x7ngNrQnVYRlcFgdqaD/btNBKr8awjYL8gmcSkHBWskGqLQ==", "integrity": "sha512-0k9XZF0wn86f77mIO2U3gNNyDZooy139CnEanRzHinrN106vVzvBZ6TUEQoHtoO1fqQxr+nWWVrqV/PXUqk47w==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/core": "10.49.0" "@sentry/core": "10.50.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry-internal/replay": { "node_modules/@sentry-internal/replay": {
"version": "10.49.0", "version": "10.50.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.49.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.50.0.tgz",
"integrity": "sha512-IEy4lwHVMiRE3JAcn+kFKjsTgalDOCSTf20SoFd+nkt6rN/k1RDyr4xpdfF//Kj3UdeTmbuibYjK5H/FLhhnGg==", "integrity": "sha512-51FYNfnvVLAWw1rrEWPFfwHuMRb9mkVCFGA4J9/un7SpeGBsQDziGB0Di4fsCxI7+EdSBpfLHPF0csKtCCw0oQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry-internal/browser-utils": "10.49.0", "@sentry-internal/browser-utils": "10.50.0",
"@sentry/core": "10.49.0" "@sentry/core": "10.50.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry-internal/replay-canvas": { "node_modules/@sentry-internal/replay-canvas": {
"version": "10.49.0", "version": "10.50.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.49.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.50.0.tgz",
"integrity": "sha512-7D/NrgH1Qwx5trDYaaTSSJmCb1yVQQLqFG4G/S9x2ltzl9876lSGJL8UeW8ReNQgF3CDAcwbmm/9aXaVSBUNZA==", "integrity": "sha512-jx6RKBmcJSWdI92qDGS/sBv1w+7Cww879Z/moX7bw7ipHa/Ts3iDcB3rgZwvhmi17U+mvYsbJeL2DXkPo3TjPw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry-internal/replay": "10.49.0", "@sentry-internal/replay": "10.50.0",
"@sentry/core": "10.49.0" "@sentry/core": "10.50.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry/browser": { "node_modules/@sentry/browser": {
"version": "10.49.0", "version": "10.50.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.49.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.50.0.tgz",
"integrity": "sha512-bGCHc+wK2Dx67YoSbmtlt04alqWfQ+dasD/GVipVOq50gvw/BBIDHTEWRJEjACl+LrvszeY54V+24p8z4IgysA==", "integrity": "sha512-1f6rAvET6myiTaSeYqvaaBwvq1LfxqWjAPIoAW/NVC9bPMkeEcuvgDajHrnZMrBeWoJ81NMyoLkyX+iOc7MoFA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry-internal/browser-utils": "10.49.0", "@sentry-internal/browser-utils": "10.50.0",
"@sentry-internal/feedback": "10.49.0", "@sentry-internal/feedback": "10.50.0",
"@sentry-internal/replay": "10.49.0", "@sentry-internal/replay": "10.50.0",
"@sentry-internal/replay-canvas": "10.49.0", "@sentry-internal/replay-canvas": "10.50.0",
"@sentry/core": "10.49.0" "@sentry/core": "10.50.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry/core": { "node_modules/@sentry/core": {
"version": "10.49.0", "version": "10.50.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.49.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.50.0.tgz",
"integrity": "sha512-UaFeum3LUM1mB0d67jvKnqId1yWQjyqmaDV6kWngG03x+jqXb08tJdGpSoxjXZe13jFBbiBL/wKDDYIK7rCK4g==", "integrity": "sha512-J4A+vzUO3adl0TkFCjaN1+4miamrjHiEIYuLHiuu1lmAjq5WIVw32ObvAh4yMwNtxyaEMosTrrh5M6f12XSJFg==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@@ -3698,15 +3698,15 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@storybook/addon-docs": { "node_modules/@storybook/addon-docs": {
"version": "10.3.5", "version": "10.3.6",
"resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.3.5.tgz", "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.3.6.tgz",
"integrity": "sha512-WuHbxia/o5TX4Rg/IFD0641K5qId/Nk0dxhmAUNoFs5L0+yfZUwh65XOBbzXqrkYmYmcVID4v7cgDRmzstQNkA==", "integrity": "sha512-TvIdADVPtauxW0LzXIpIv7X6GxwetorhyNh+6+7MHC27XSBCWVxxRUwL63YeLlHTuXsIk0quG3b1xgwVRzWOJA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@mdx-js/react": "^3.0.0", "@mdx-js/react": "^3.0.0",
"@storybook/csf-plugin": "10.3.5", "@storybook/csf-plugin": "10.3.6",
"@storybook/icons": "^2.0.1", "@storybook/icons": "^2.0.1",
"@storybook/react-dom-shim": "10.3.5", "@storybook/react-dom-shim": "10.3.6",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"ts-dedent": "^2.0.0" "ts-dedent": "^2.0.0"
@@ -3716,13 +3716,13 @@
"url": "https://opencollective.com/storybook" "url": "https://opencollective.com/storybook"
}, },
"peerDependencies": { "peerDependencies": {
"storybook": "^10.3.5" "storybook": "^10.3.6"
} }
}, },
"node_modules/@storybook/addon-links": { "node_modules/@storybook/addon-links": {
"version": "10.3.5", "version": "10.3.6",
"resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-10.3.5.tgz", "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-10.3.6.tgz",
"integrity": "sha512-Xe2wCGZ+hpZ0cDqAIBHk+kPc8nODNbu585ghd5bLrlYJMDVXoNM/fIlkrLgjIDVbfpgeJLUEg7vldJrn+FyOLw==", "integrity": "sha512-tv9Xd68qRGBAvEubaxNo3FuFq4GwuMiBriD+gLGuFK0+/u3cnkuA264aoR1v6YCH3sT3er3+MBimuyKM3jLDxg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@storybook/global": "^5.0.0" "@storybook/global": "^5.0.0"
@@ -3733,7 +3733,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"storybook": "^10.3.5" "storybook": "^10.3.6"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"react": { "react": {
@@ -3742,12 +3742,12 @@
} }
}, },
"node_modules/@storybook/builder-vite": { "node_modules/@storybook/builder-vite": {
"version": "10.3.5", "version": "10.3.6",
"resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.3.5.tgz", "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.3.6.tgz",
"integrity": "sha512-i4KwCOKbhtlbQIbhm53+Kk7bMnxa0cwTn1pxmtA/x5wm1Qu7FrrBQV0V0DNjkUqzcSKo1CjspASJV/HlY0zYlw==", "integrity": "sha512-gpvR/sE4BcrFtmQZ+Ker7zD23oQzoVeqD9nF6cK6yzY+Q0svJXyX2EPmFG4y+EwygD5/vNzDpP84gGMut8VRwg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@storybook/csf-plugin": "10.3.5", "@storybook/csf-plugin": "10.3.6",
"ts-dedent": "^2.0.0" "ts-dedent": "^2.0.0"
}, },
"funding": { "funding": {
@@ -3755,14 +3755,14 @@
"url": "https://opencollective.com/storybook" "url": "https://opencollective.com/storybook"
}, },
"peerDependencies": { "peerDependencies": {
"storybook": "^10.3.5", "storybook": "^10.3.6",
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
} }
}, },
"node_modules/@storybook/csf-plugin": { "node_modules/@storybook/csf-plugin": {
"version": "10.3.5", "version": "10.3.6",
"resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.3.5.tgz", "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.3.6.tgz",
"integrity": "sha512-qlEzNKxOjq86pvrbuMwiGD/bylnsXk1dg7ve0j77YFjEEchqtl7qTlrXvFdNaLA89GhW6D/EV6eOCu/eobPDgw==", "integrity": "sha512-9kBf7VRdRqTSIYo+rPtVn5yjYYyK8kP2QhEYx3oiXvfwy4RexmbJnhk/tXa/lNiTqukA1TqaWQ2+5MqF4fu6YQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"unplugin": "^2.3.5" "unplugin": "^2.3.5"
@@ -3774,7 +3774,7 @@
"peerDependencies": { "peerDependencies": {
"esbuild": "*", "esbuild": "*",
"rollup": "*", "rollup": "*",
"storybook": "^10.3.5", "storybook": "^10.3.6",
"vite": "*", "vite": "*",
"webpack": "*" "webpack": "*"
}, },
@@ -3810,9 +3810,9 @@
} }
}, },
"node_modules/@storybook/react-dom-shim": { "node_modules/@storybook/react-dom-shim": {
"version": "10.3.5", "version": "10.3.6",
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.3.5.tgz", "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.3.6.tgz",
"integrity": "sha512-Gw8R7XZm0zSUH0XAuxlQJhmizsLzyD6x00KOlP6l7oW9eQHXGfxg3seNDG3WrSAcW07iP1/P422kuiriQlOv7g==", "integrity": "sha512-/Tu1gPu+Fw+zOnAGmxRmOD30FX3a04LxcTAKflEtdpmtIMVR5bA3qpjy+f5YhoyDCecbXyKmL1OeIU2FIIZHqQ==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@@ -3821,13 +3821,13 @@
"peerDependencies": { "peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"storybook": "^10.3.5" "storybook": "^10.3.6"
} }
}, },
"node_modules/@storybook/web-components": { "node_modules/@storybook/web-components": {
"version": "10.3.5", "version": "10.3.6",
"resolved": "https://registry.npmjs.org/@storybook/web-components/-/web-components-10.3.5.tgz", "resolved": "https://registry.npmjs.org/@storybook/web-components/-/web-components-10.3.6.tgz",
"integrity": "sha512-tSppZagFCeZ+bWsaHUvdiw17ATWgfGDBz0mFicgEj0/eNuxQH2OvXyRIQUXY39b/55TBwSGeoIX3tOW91WIqpw==", "integrity": "sha512-femDZGYBGQDckL7F6ZCl2S+dNNBjvd9lp6rQrwBdbNprjctLd6d3EB4HyNM502QxtdEo7laq8y1goDu8KwIV3A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@storybook/global": "^5.0.0", "@storybook/global": "^5.0.0",
@@ -3840,24 +3840,24 @@
}, },
"peerDependencies": { "peerDependencies": {
"lit": "^2.0.0 || ^3.0.0", "lit": "^2.0.0 || ^3.0.0",
"storybook": "^10.3.5" "storybook": "^10.3.6"
} }
}, },
"node_modules/@storybook/web-components-vite": { "node_modules/@storybook/web-components-vite": {
"version": "10.3.5", "version": "10.3.6",
"resolved": "https://registry.npmjs.org/@storybook/web-components-vite/-/web-components-vite-10.3.5.tgz", "resolved": "https://registry.npmjs.org/@storybook/web-components-vite/-/web-components-vite-10.3.6.tgz",
"integrity": "sha512-6uAw6KAUXFsAPzp8KchcMp3gatEnEAd8ylIvzoMzvsIMiHmzXwvDNmoFZnAJ2tmsQGvF4dZRDCBg7PvWdTx8Rg==", "integrity": "sha512-VeDEAJuOOQV6VAqEF0pilXucS6kp+1ILJVkI+ets6Ku2D+RKeu167YrQAzh1NwzRTv0e5H0anDDNke+sWvg2dg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@storybook/builder-vite": "10.3.5", "@storybook/builder-vite": "10.3.6",
"@storybook/web-components": "10.3.5" "@storybook/web-components": "10.3.6"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/storybook" "url": "https://opencollective.com/storybook"
}, },
"peerDependencies": { "peerDependencies": {
"storybook": "^10.3.5" "storybook": "^10.3.6"
} }
}, },
"node_modules/@swagger-api/apidom-ast": { "node_modules/@swagger-api/apidom-ast": {
@@ -4556,9 +4556,9 @@
} }
}, },
"node_modules/@swc/core": { "node_modules/@swc/core": {
"version": "1.15.30", "version": "1.15.32",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.30.tgz", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.32.tgz",
"integrity": "sha512-R8VQbQY1BZcbIF2p3gjlTCwAQzx1A194ugWfwld5y+WgVVWqVKm7eURGGOVbQVubgKWzidP2agomBbg96rZilQ==", "integrity": "sha512-/eWL0n43D64QWEUHLtTE+jDqjkJhyidjkDhv6f0uJohOUAhywxQ9wXYp845DNNds0JpCdI4Uo0a9bl+vbXf+ew==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -4573,18 +4573,18 @@
"url": "https://opencollective.com/swc" "url": "https://opencollective.com/swc"
}, },
"optionalDependencies": { "optionalDependencies": {
"@swc/core-darwin-arm64": "1.15.30", "@swc/core-darwin-arm64": "1.15.32",
"@swc/core-darwin-x64": "1.15.30", "@swc/core-darwin-x64": "1.15.32",
"@swc/core-linux-arm-gnueabihf": "1.15.30", "@swc/core-linux-arm-gnueabihf": "1.15.32",
"@swc/core-linux-arm64-gnu": "1.15.30", "@swc/core-linux-arm64-gnu": "1.15.32",
"@swc/core-linux-arm64-musl": "1.15.30", "@swc/core-linux-arm64-musl": "1.15.32",
"@swc/core-linux-ppc64-gnu": "1.15.30", "@swc/core-linux-ppc64-gnu": "1.15.32",
"@swc/core-linux-s390x-gnu": "1.15.30", "@swc/core-linux-s390x-gnu": "1.15.32",
"@swc/core-linux-x64-gnu": "1.15.30", "@swc/core-linux-x64-gnu": "1.15.32",
"@swc/core-linux-x64-musl": "1.15.30", "@swc/core-linux-x64-musl": "1.15.32",
"@swc/core-win32-arm64-msvc": "1.15.30", "@swc/core-win32-arm64-msvc": "1.15.32",
"@swc/core-win32-ia32-msvc": "1.15.30", "@swc/core-win32-ia32-msvc": "1.15.32",
"@swc/core-win32-x64-msvc": "1.15.30" "@swc/core-win32-x64-msvc": "1.15.32"
}, },
"peerDependencies": { "peerDependencies": {
"@swc/helpers": ">=0.5.17" "@swc/helpers": ">=0.5.17"
@@ -4596,9 +4596,9 @@
} }
}, },
"node_modules/@swc/core-darwin-arm64": { "node_modules/@swc/core-darwin-arm64": {
"version": "1.15.30", "version": "1.15.32",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.30.tgz", "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.32.tgz",
"integrity": "sha512-VvpP+vq08HmGYewMWvrdsxh9s2lthz/808zXm8Yu5kaqeR8Yia2b0eYXleHQ3VAjoStUDk6LzTheBW9KXYQdMA==", "integrity": "sha512-/YWMvJDPu+AAwuUsM2G+DNQ/7zhodURGzdQyewEqcvgklAdDHs3LwQmLLnyn6SJl8DT8UOxkbzK+D1PmPeelRg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -4612,9 +4612,9 @@
} }
}, },
"node_modules/@swc/core-darwin-x64": { "node_modules/@swc/core-darwin-x64": {
"version": "1.15.30", "version": "1.15.32",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.30.tgz", "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.32.tgz",
"integrity": "sha512-WiJA0hiZI3nwQAO6mu5RqigtWGDtth4Hiq6rbZxAaQyhIcqKIg5IoMRc1Y071lrNJn29eEDMC86Rq58xgUxlDg==", "integrity": "sha512-KOTXJXdAhWL+hZ77MYP3z+4pcMFaQhQ74yqyN1uz093q0YnbxpqMtYpPISbYvMHzVRNNx5kN+9RZAXEaadhWVA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -4628,9 +4628,9 @@
} }
}, },
"node_modules/@swc/core-linux-arm-gnueabihf": { "node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.15.30", "version": "1.15.32",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.30.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.32.tgz",
"integrity": "sha512-YANuFUo48kIT6plJgCD0keae9HFXfjxsbvsgevqc0hr/07X/p7sAWTFOGYEc2SXcASaK7UvuQqzlbW8pr7R79g==", "integrity": "sha512-oOoxLweljlc0A4X8ybsgxV7cVaYTwBOg2iMDJcFR3Sr48C+lsv9VzSmqdK/IVIXF4W4GjLc3VqTAdSMXlfVLuQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -4644,9 +4644,9 @@
} }
}, },
"node_modules/@swc/core-linux-arm64-gnu": { "node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.15.30", "version": "1.15.32",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.30.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.32.tgz",
"integrity": "sha512-VndG8jaR4ugY6u+iVOT0Q+d2fZd7sLgjPgN8W/Le+3EbZKl+cRfFxV7Eoz4gfLqhmneZPdcIzf9T3LkgkmqNLg==", "integrity": "sha512-oDzEkdl6D6BAWdMtU5KGO7y3HR5fJcvByNLyEk9+ugj8nP5Ovb7P4kBcStBXc4MPExFGQryehiINMlmY8HlclA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -4660,9 +4660,9 @@
} }
}, },
"node_modules/@swc/core-linux-arm64-musl": { "node_modules/@swc/core-linux-arm64-musl": {
"version": "1.15.30", "version": "1.15.32",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.30.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.32.tgz",
"integrity": "sha512-1SYGs2l0Yyyi0pR/P/NKz/x0kqxkoiw+BXeJjLUdecSk/KasncWlJrc6hOvFSgKHOBrzgM5jwuluKtlT8dnrcA==", "integrity": "sha512-omcqjoZP/b8D8PuczVoRwJieC6ibj7qIxTftNYokz4/aSmKFHvsd7nIFfPk5ZvtzncbH4AY7+Dkr/Lp2gWxYeA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -4676,9 +4676,9 @@
} }
}, },
"node_modules/@swc/core-linux-ppc64-gnu": { "node_modules/@swc/core-linux-ppc64-gnu": {
"version": "1.15.30", "version": "1.15.32",
"resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.30.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.32.tgz",
"integrity": "sha512-TXREtiXeRhbfDFbmhnkIsXpKfzbfT73YkV2ZF6w0sfxgjC5zI2ZAbaCOq25qxvegofj2K93DtOpm9RLaBgqR2g==", "integrity": "sha512-KGkTMyz/Tbn3PBNu0AVZ4GTDFKnICrYcTiNPZq8DrvK42pnFsf3GNDrIG9E5AtQlTmC0YigkWKmu0eMcfTrmgA==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@@ -4692,9 +4692,9 @@
} }
}, },
"node_modules/@swc/core-linux-s390x-gnu": { "node_modules/@swc/core-linux-s390x-gnu": {
"version": "1.15.30", "version": "1.15.32",
"resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.30.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.32.tgz",
"integrity": "sha512-DCR2YYeyd6DQE4OuDhImouuNcjXEiEdnn1Y0DyGteugPEDvVuvYk8Xddi+4o2SgWH6jiW8/I+3emZvbep1NC+g==", "integrity": "sha512-G3Aa4tVS/3OGZBkoNIwUF9F6RAy+Osb4GOlo62SinLmDiErz/ykmM7KH0wkz6l9kM8jJq1HyAM6atJTUEbBk7g==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@@ -4708,9 +4708,9 @@
} }
}, },
"node_modules/@swc/core-linux-x64-gnu": { "node_modules/@swc/core-linux-x64-gnu": {
"version": "1.15.30", "version": "1.15.32",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.30.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.32.tgz",
"integrity": "sha512-5Pizw3NgfOJ5BJOBK8TIRa59xFW2avESTOBDPTAYwZYa1JNDs+KMF9lUfjJiJLM5HiMs/wPheA9eiT0q9m2AoA==", "integrity": "sha512-ERsjfGcj6CBmj3vJnGDO8m8rTvw6RqMcWo1dogOtNx3/+/0+NNpJiXDobJrr1GwInI/BHAEkvSFIH6d2LqPcUQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -4724,9 +4724,9 @@
} }
}, },
"node_modules/@swc/core-linux-x64-musl": { "node_modules/@swc/core-linux-x64-musl": {
"version": "1.15.30", "version": "1.15.32",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.30.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.32.tgz",
"integrity": "sha512-qyqydP/wyH8alcIP4a2hnGSjHLJjm9H7yDFup+CPy9oTahFgLLwnNcv5UHXqO2Qs3AIND+cls5f/Bb6hqpxdgA==", "integrity": "sha512-N4Ggahe/8SUbTX50P6EdhbW9YWcgbZVb52R4cq6MK+zsoMjRq7rGvV5ztA05QnbaCYqMYx8rTY7KAIA3Crdo4Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -4740,9 +4740,9 @@
} }
}, },
"node_modules/@swc/core-win32-arm64-msvc": { "node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.15.30", "version": "1.15.32",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.30.tgz", "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.32.tgz",
"integrity": "sha512-CaQENgDHVGOg1mSF5sQVgvfFHG9kjMor2rkLMLeLOkfZYNj13ppnJ9+lfaBZLZUMMbnlGQnavCJb8PVBUOso7Q==", "integrity": "sha512-01yN0o9jvo8xBTP12aPK2wW8b41jmOlGbDDlAnoynotc4pO6xA0zby9f1z6j++qXDpGBttLySq1omgVrlQKYcw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -4756,9 +4756,9 @@
} }
}, },
"node_modules/@swc/core-win32-ia32-msvc": { "node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.15.30", "version": "1.15.32",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.30.tgz", "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.32.tgz",
"integrity": "sha512-30VdLeGk6fugiUs/kUdJ/pAg7z/zpvVbR11RH60jZ0Z42WIeIniYx0rLEWN7h/pKJ3CopqsQ3RsogCAkRKiA2g==", "integrity": "sha512-fLagI9XZYNpTcmlqAcp3KBtmj7E19WCmYD80Jxj1Kn5tGNa7yxNLd3NNdWxuZGUPl5iC0/KqZru7g08gF6Fsrw==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -4772,9 +4772,9 @@
} }
}, },
"node_modules/@swc/core-win32-x64-msvc": { "node_modules/@swc/core-win32-x64-msvc": {
"version": "1.15.30", "version": "1.15.32",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.30.tgz", "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.32.tgz",
"integrity": "sha512-4iObHPR+Q4oDY110EF5SF5eIaaVJNpMdG9C0q3Q92BsJ5y467uHz7sYQhP60WYlLFsLQ1el2YrIPUItUAQGOKg==", "integrity": "sha512-gbc2bQ/T2CiR+w0OvcVKwLOFAcPZBvmWmolbwpg1E8UrpeC03DGtyMUApOHNXNYWA3SHFrYXCQtosrcMza1YFg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -8380,9 +8380,9 @@
"peer": true "peer": true
}, },
"node_modules/dompurify": { "node_modules/dompurify": {
"version": "3.4.1", "version": "3.4.2",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.1.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.2.tgz",
"integrity": "sha512-JahakDAIg1gyOm7dlgWSDjV4n7Ip2PKR55NIT6jrMfIgLFgWo81vdr1/QGqWtFNRqXP9UV71oVePtjqS2ebnPw==", "integrity": "sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==",
"license": "(MPL-2.0 OR Apache-2.0)", "license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": { "optionalDependencies": {
"@types/trusted-types": "^2.0.7" "@types/trusted-types": "^2.0.7"
@@ -11569,9 +11569,9 @@
} }
}, },
"node_modules/knip": { "node_modules/knip": {
"version": "6.6.3", "version": "6.7.0",
"resolved": "https://registry.npmjs.org/knip/-/knip-6.6.3.tgz", "resolved": "https://registry.npmjs.org/knip/-/knip-6.7.0.tgz",
"integrity": "sha512-7HSf5bLx6r66+sjXwSvSiDEE9RjRzHuAkrEFLE6XXHqaPDY97tdzNvyRVF9DeusbiV72kStAFiNnhj72rxJNGQ==", "integrity": "sha512-ckL51NDH1YJxnv1kNB0iUdDngB4f/e9Igz8uIqYfmNDoyOFmmk1V0WFv3LQ7/hzC63b2Z9X41gGUE9eOWrZpaA==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -16598,9 +16598,9 @@
} }
}, },
"node_modules/storybook": { "node_modules/storybook": {
"version": "10.3.5", "version": "10.3.6",
"resolved": "https://registry.npmjs.org/storybook/-/storybook-10.3.5.tgz", "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.3.6.tgz",
"integrity": "sha512-uBSZu/GZa9aEIW3QMGvdQPMZWhGxSe4dyRWU8B3/Vd47Gy/XLC7tsBxRr13txmmPOEDHZR94uLuq0H50fvuqBw==", "integrity": "sha512-vbSz7g/1rGMC1uAULqMZjALkIuLu2QABqfhRYhyr/11kzyesi+vAmwyJLukZP1FfecxGOgMwOh6GS0YsGpHAvQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@storybook/global": "^5.0.0", "@storybook/global": "^5.0.0",
@@ -16625,11 +16625,15 @@
"url": "https://opencollective.com/storybook" "url": "https://opencollective.com/storybook"
}, },
"peerDependencies": { "peerDependencies": {
"prettier": "^2 || ^3" "prettier": "^2 || ^3",
"vite-plus": "^0.1.15"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"prettier": { "prettier": {
"optional": true "optional": true
},
"vite-plus": {
"optional": true
} }
} }
}, },
@@ -19248,7 +19252,7 @@
"@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-swc": "^0.4.0", "@rollup/plugin-swc": "^0.4.0",
"@swc/cli": "^0.8.1", "@swc/cli": "^0.8.1",
"@swc/core": "^1.15.30", "@swc/core": "^1.15.32",
"@webcomponents/template": "^1.5.1", "@webcomponents/template": "^1.5.1",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"core-js": "^3.49.0", "core-js": "^3.49.0",

View File

@@ -97,7 +97,7 @@
"@codemirror/theme-one-dark": "^6.1.3", "@codemirror/theme-one-dark": "^6.1.3",
"@eslint/js": "^9.39.3", "@eslint/js": "^9.39.3",
"@floating-ui/dom": "^1.7.6", "@floating-ui/dom": "^1.7.6",
"@formatjs/intl-listformat": "^8.3.2", "@formatjs/intl-listformat": "^8.3.4",
"@fortawesome/fontawesome-free": "^7.2.0", "@fortawesome/fontawesome-free": "^7.2.0",
"@goauthentik/api": "0.0.0", "@goauthentik/api": "0.0.0",
"@goauthentik/core": "^1.0.0", "@goauthentik/core": "^1.0.0",
@@ -119,11 +119,11 @@
"@patternfly/elements": "^4.4.0", "@patternfly/elements": "^4.4.0",
"@patternfly/patternfly": "^4.224.2", "@patternfly/patternfly": "^4.224.2",
"@playwright/test": "^1.59.1", "@playwright/test": "^1.59.1",
"@sentry/browser": "^10.49.0", "@sentry/browser": "^10.50.0",
"@storybook/addon-docs": "^10.3.5", "@storybook/addon-docs": "^10.3.6",
"@storybook/addon-links": "^10.3.5", "@storybook/addon-links": "^10.3.6",
"@storybook/web-components": "^10.3.5", "@storybook/web-components": "^10.3.6",
"@storybook/web-components-vite": "^10.3.5", "@storybook/web-components-vite": "^10.3.6",
"@types/codemirror": "^5.60.17", "@types/codemirror": "^5.60.17",
"@types/grecaptcha": "^3.0.9", "@types/grecaptcha": "^3.0.9",
"@types/guacamole-common-js": "^1.5.5", "@types/guacamole-common-js": "^1.5.5",
@@ -145,7 +145,7 @@
"country-flag-icons": "^1.6.16", "country-flag-icons": "^1.6.16",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"deepmerge-ts": "^7.1.5", "deepmerge-ts": "^7.1.5",
"dompurify": "^3.4.1", "dompurify": "^3.4.2",
"esbuild": "^0.28.0", "esbuild": "^0.28.0",
"eslint": "^9.39.3", "eslint": "^9.39.3",
"eslint-plugin-lit": "^2.2.1", "eslint-plugin-lit": "^2.2.1",
@@ -154,7 +154,7 @@
"globals": "^17.5.0", "globals": "^17.5.0",
"guacamole-common-js": "^1.5.0", "guacamole-common-js": "^1.5.0",
"hastscript": "^9.0.1", "hastscript": "^9.0.1",
"knip": "^6.6.3", "knip": "^6.7.0",
"lex": "^2025.11.0", "lex": "^2025.11.0",
"lit": "^3.3.2", "lit": "^3.3.2",
"lit-analyzer": "^2.0.3", "lit-analyzer": "^2.0.3",

View File

@@ -20,7 +20,7 @@
"@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-swc": "^0.4.0", "@rollup/plugin-swc": "^0.4.0",
"@swc/cli": "^0.8.1", "@swc/cli": "^0.8.1",
"@swc/core": "^1.15.30", "@swc/core": "^1.15.32",
"@webcomponents/template": "^1.5.1", "@webcomponents/template": "^1.5.1",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"core-js": "^3.49.0", "core-js": "^3.49.0",

View File

@@ -52,10 +52,6 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(AKModal)) {
...AKModal.styles, ...AKModal.styles,
PFAbout, PFAbout,
css` css`
:host {
height: 100%;
}
.pf-c-about-modal-box { .pf-c-about-modal-box {
--pf-c-about-modal-box--BackgroundColor: var(--ak-c-dialog--BackgroundColor); --pf-c-about-modal-box--BackgroundColor: var(--ak-c-dialog--BackgroundColor);
width: unset; width: unset;

View File

@@ -135,7 +135,7 @@ export class AdminInterface extends WithCapabilitiesConfig(
WebsocketClient.connect(); WebsocketClient.connect();
this.#sidebarMatcher = window.matchMedia("(width >= 1200px)"); this.#sidebarMatcher = window.matchMedia("(width > 1210px)");
this.sidebarOpen = this.#sidebarMatcher.matches; this.sidebarOpen = this.#sidebarMatcher.matches;
} }

View File

@@ -322,30 +322,6 @@ export class ApplicationViewPage extends WithLicenseSummary(AKElement) {
id="page-app-entitlements" id="page-app-entitlements"
aria-label="${msg("Application entitlements")}" aria-label="${msg("Application entitlements")}"
> >
<div
slot="header"
class="pf-c-banner pf-m-info"
role="status"
aria-live="polite"
>
<div class="pf-l-flex pf-m-space-items-sm">
<div class="pf-l-flex__item">
<i class="fas fa-info-circle" aria-hidden="true"></i>
</div>
<div class="pf-l-flex__item">
${msg("Application entitlements are in preview.", {
id: "application.entitlements.preview.info",
})}
</div>
<div class="pf-l-flex__item">
<a href="mailto:hello+feature/app-ent@goauthentik.io"
>${msg("Send us feedback!", {
id: "preview.send-us-feedback",
})}</a
>
</div>
</div>
</div>
<div class="pf-c-page__main-section pf-m-no-padding-mobile"> <div class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card"> <div class="pf-c-card">
<div class="pf-c-card__title"> <div class="pf-c-card__title">

View File

@@ -201,7 +201,7 @@ export class ApplicationWizardEditBindingStep extends ApplicationWizardStep<Poli
<ak-switch-input <ak-switch-input
name="negate" name="negate"
?checked=${instance?.negate ?? false} ?checked=${instance?.negate ?? false}
label=${msg("Negate result")} label=${msg("Negate Result")}
help=${msg("Negates the outcome of the binding. Messages are unaffected.")} help=${msg("Negates the outcome of the binding. Messages are unaffected.")}
></ak-switch-input> ></ak-switch-input>
<ak-number-input <ak-number-input
@@ -218,8 +218,9 @@ export class ApplicationWizardEditBindingStep extends ApplicationWizardStep<Poli
></ak-number-input> ></ak-number-input>
<ak-radio-input <ak-radio-input
name="failureResult" name="failureResult"
label=${msg("Failure result")} label=${msg("Failure Result")}
.options=${createPassFailOptions} .options=${createPassFailOptions}
required
></ak-radio-input> ></ak-radio-input>
</form>`; </form>`;
} }

View File

@@ -381,7 +381,7 @@ export class ApplicationWizardSubmitStep extends CustomEmitterElement(Applicatio
return html`<h2 class="pf-c-wizard__main-title"> return html`<h2 class="pf-c-wizard__main-title">
${msg("Review the Application and Provider")} ${msg("Review the Application and Provider")}
</h2> </h2>
<fieldset> <fieldset class="ak-c-fieldset" name="application-details">
<legend>${msg("Application Details")}</legend> <legend>${msg("Application Details")}</legend>
<dl class="pf-c-description-list"> <dl class="pf-c-description-list">
<div class="pf-c-description-list__group"> <div class="pf-c-description-list__group">
@@ -419,7 +419,7 @@ export class ApplicationWizardSubmitStep extends CustomEmitterElement(Applicatio
${ ${
renderer renderer
? html`<fieldset> ? html`<fieldset class="ak-c-fieldset" name="provider-details">
<legend>${msg("Provider Details")}</legend> <legend>${msg("Provider Details")}</legend>
${renderer(provider)} ${renderer(provider)}
</fieldset>` </fieldset>`

View File

@@ -80,7 +80,7 @@ export class BrandListPage extends TablePage<Brand> {
return [ return [
item.domain, item.domain,
item.brandingTitle || msg("-"), item.brandingTitle || msg("-"),
html`<ak-status-label ?good=${item._default}></ak-status-label>`, html`<ak-status-label ?good=${item._default} type="neutral"></ak-status-label>`,
html`<div class="ak-c-table__actions"> html`<div class="ak-c-table__actions">
${IconEditButton(BrandForm, item.brandUuid, item.brandingTitle)} ${IconEditButton(BrandForm, item.brandUuid, item.brandingTitle)}

View File

@@ -86,7 +86,7 @@ export class ConfigModal extends ModalButton {
></ak-codemirror> ></ak-codemirror>
</ak-expand> </ak-expand>
</div> </div>
<fieldset class="pf-c-modal-box__footer"> <fieldset class="ak-c-fieldset pf-c-modal-box__footer">
<legend class="sr-only">${msg("Form actions")}</legend> <legend class="sr-only">${msg("Form actions")}</legend>
<button <button
class="pf-c-button pf-m-plain" class="pf-c-button pf-m-plain"

View File

@@ -65,7 +65,7 @@ export class DeviceAddHowTo extends ModalButton {
})} })}
</ak-tabs>`} </ak-tabs>`}
</div> </div>
<fieldset class="pf-c-modal-box__footer"> <fieldset class="ak-c-fieldset pf-c-modal-box__footer">
<legend class="sr-only">${msg("Form actions")}</legend> <legend class="sr-only">${msg("Form actions")}</legend>
<button <button
class="pf-c-button pf-m-primary" class="pf-c-button pf-m-primary"

View File

@@ -175,6 +175,21 @@ export class BoundPoliciesList<T extends PolicyBinding = PolicyBinding> extends
} }
protected renderNewPolicyButton(): SlottedTemplateResult { protected renderNewPolicyButton(): SlottedTemplateResult {
if (!this.allowedTypes.includes(PolicyBindingCheckTarget.Policy)) {
return html`<button
type="button"
class="pf-c-button pf-m-primary"
${modalInvoker(() => {
return StrictUnsafe<PolicyBindingForm>(this.bindingEditForm, {
allowedTypes: this.allowedTypes,
typeNotices: this.typeNotices,
targetPk: this.target || "",
});
})}
>
${msg("Bind existing group/user")}
</button>`;
}
return html`<button return html`<button
class="pf-c-button pf-m-primary" class="pf-c-button pf-m-primary"
type="button" type="button"

View File

@@ -272,7 +272,7 @@ export class PolicyBindingForm<T extends PolicyBinding = PolicyBinding> extends
</ak-switch-input> </ak-switch-input>
<ak-switch-input <ak-switch-input
name="negate" name="negate"
label=${msg("Negate result")} label=${msg("Negate Result")}
?checked=${this.instance?.negate ?? false} ?checked=${this.instance?.negate ?? false}
help=${msg("Negates the outcome of the binding. Messages are unaffected.")} help=${msg("Negates the outcome of the binding. Messages are unaffected.")}
> >
@@ -293,7 +293,11 @@ export class PolicyBindingForm<T extends PolicyBinding = PolicyBinding> extends
required required
/> />
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal name="failureResult" label=${msg("Failure result")}> <ak-form-element-horizontal
name="failureResult"
label=${msg("Failure Result")}
required
>
<ak-radio .options=${createPassFailOptions} .value=${this.instance?.failureResult}> <ak-radio .options=${createPassFailOptions} .value=${this.instance?.failureResult}>
</ak-radio> </ak-radio>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">

View File

@@ -46,7 +46,7 @@ export class PolicyWizard extends CreateWizard {
@property() @property()
public bindingTarget: string | null = null; public bindingTarget: string | null = null;
public override groupLabel = msg("Bind New Policy"); public override groupLabel = msg("Choose Policy Type");
public override groupDescription = msg("Select the type of policy you want to create."); public override groupDescription = msg("Select the type of policy you want to create.");
public override initialSteps = this.showBindingPage public override initialSteps = this.showBindingPage

View File

@@ -174,7 +174,7 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
></ak-crypto-certificate-search> ></ak-crypto-certificate-search>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${msg( ${msg(
"When connecting to an LDAP Server with TLS, certificates are not checked by default. Specify a keypair to validate the remote certificate.", "Leave empty to skip certificate validation, or select a certificate/keypair containing the LDAP server CA chain to validate the remote certificate.",
)} )}
</p> </p>
</ak-form-element-horizontal> </ak-form-element-horizontal>

View File

@@ -18,6 +18,8 @@ import { DEFAULT_CONFIG } from "#common/api/config";
import { DataProvision, DualSelectPair } from "#elements/ak-dual-select/types"; import { DataProvision, DualSelectPair } from "#elements/ak-dual-select/types";
import { AKLabel } from "#components/ak-label";
import { deviceTypeRestrictionPair } from "#admin/stages/authenticator_webauthn/utils"; import { deviceTypeRestrictionPair } from "#admin/stages/authenticator_webauthn/utils";
import { BaseStageForm } from "#admin/stages/BaseStageForm"; import { BaseStageForm } from "#admin/stages/BaseStageForm";
@@ -114,11 +116,20 @@ export class AuthenticatorValidateStageForm extends BaseStageForm<AuthenticatorV
></ak-text-input> ></ak-text-input>
<ak-form-group open label="${msg("Stage-specific settings")}"> <ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form"> <div class="pf-c-form">
<ak-form-element-horizontal <ak-form-element-horizontal required name="deviceClasses">
label=${msg("Device classes")} ${AKLabel(
required {
name="deviceClasses" slot: "label",
> className: "pf-c-form__group-label",
htmlFor: "deviceClasses",
required: true,
},
msg("Device Classes"),
)}
<p class="pf-c-form__helper-text">
${msg("Device classes which can be used to authenticate.")}
</p>
<ak-checkbox-group <ak-checkbox-group
name="users" name="users"
class="user-field-select" class="user-field-select"
@@ -129,9 +140,6 @@ export class AuthenticatorValidateStageForm extends BaseStageForm<AuthenticatorV
this.isDeviceClassSelected(name as DeviceClassesEnum), this.isDeviceClassSelected(name as DeviceClassesEnum),
)} )}
></ak-checkbox-group> ></ak-checkbox-group>
<p class="pf-c-form__helper-text">
${msg("Device classes which can be used to authenticate.")}
</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal <ak-form-element-horizontal
label=${msg("Last validation threshold")} label=${msg("Last validation threshold")}

View File

@@ -11,6 +11,8 @@ import { sourcesProvider, sourcesSelector } from "./IdentificationStageFormHelpe
import { DEFAULT_CONFIG } from "#common/api/config"; import { DEFAULT_CONFIG } from "#common/api/config";
import { groupBy } from "#common/utils"; import { groupBy } from "#common/utils";
import { AKLabel } from "#components/ak-label";
import { BaseStageForm } from "#admin/stages/BaseStageForm"; import { BaseStageForm } from "#admin/stages/BaseStageForm";
import { import {
@@ -87,7 +89,22 @@ export class IdentificationStageForm extends BaseStageForm<IdentificationStage>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-group open label="${msg("Stage-specific settings")}"> <ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form"> <div class="pf-c-form">
<ak-form-element-horizontal label=${msg("User fields")} name="userFields"> <ak-form-element-horizontal name="userFields">
${AKLabel(
{
slot: "label",
className: "pf-c-form__group-label",
htmlFor: "userFields",
},
msg("User Fields"),
)}
<p class="pf-c-form__helper-text">
${msg(
"Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources.",
)}
</p>
<ak-checkbox-group <ak-checkbox-group
class="user-field-select" class="user-field-select"
.options=${userSelectFields} .options=${userSelectFields}
@@ -95,11 +112,6 @@ export class IdentificationStageForm extends BaseStageForm<IdentificationStage>
.map(({ name }) => name) .map(({ name }) => name)
.filter((name) => this.isUserFieldSelected(name))} .filter((name) => this.isUserFieldSelected(name))}
></ak-checkbox-group> ></ak-checkbox-group>
<p class="pf-c-form__helper-text">
${msg(
"Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources.",
)}
</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Password stage")} name="passwordStage"> <ak-form-element-horizontal label=${msg("Password stage")} name="passwordStage">
<ak-search-select <ak-search-select

View File

@@ -6,6 +6,8 @@ import "#elements/forms/SearchSelect/index";
import { DEFAULT_CONFIG } from "#common/api/config"; import { DEFAULT_CONFIG } from "#common/api/config";
import { AKLabel } from "#components/ak-label";
import { RenderFlowOption } from "#admin/flows/utils"; import { RenderFlowOption } from "#admin/flows/utils";
import { BaseStageForm } from "#admin/stages/BaseStageForm"; import { BaseStageForm } from "#admin/stages/BaseStageForm";
@@ -87,7 +89,20 @@ export class PasswordStageForm extends BaseStageForm<PasswordStage> {
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-group open label="${msg("Stage-specific settings")}"> <ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form"> <div class="pf-c-form">
<ak-form-element-horizontal label=${msg("Backends")} required name="backends"> <ak-form-element-horizontal required name="backends">
${AKLabel(
{
slot: "label",
className: "pf-c-form__group-label",
htmlFor: "backends",
required: true,
},
msg("Backends"),
)}
<p class="pf-c-form__helper-text">
${msg("Selection of backends to test the password against.")}
</p>
<ak-checkbox-group <ak-checkbox-group
class="user-field-select" class="user-field-select"
.options=${backends} .options=${backends}
@@ -95,9 +110,6 @@ export class PasswordStageForm extends BaseStageForm<PasswordStage> {
.map(({ name }) => name) .map(({ name }) => name)
.filter((name) => this.isBackendSelected(name))} .filter((name) => this.isBackendSelected(name))}
></ak-checkbox-group> ></ak-checkbox-group>
<p class="pf-c-form__helper-text">
${msg("Selection of backends to test the password against.")}
</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal <ak-form-element-horizontal
label=${msg("Configuration flow")} label=${msg("Configuration flow")}

View File

@@ -3,10 +3,13 @@ import "#elements/forms/FormGroup";
import "#elements/forms/HorizontalFormElement"; import "#elements/forms/HorizontalFormElement";
import "#elements/forms/Radio"; import "#elements/forms/Radio";
import "#components/ak-text-input"; import "#components/ak-text-input";
import "#components/ak-radio-input";
import "#elements/forms/SearchSelect/index"; import "#elements/forms/SearchSelect/index";
import { DEFAULT_CONFIG } from "#common/api/config"; import { DEFAULT_CONFIG } from "#common/api/config";
import { RadioOption } from "#elements/forms/Radio";
import { AKLabel } from "#components/ak-label"; import { AKLabel } from "#components/ak-label";
import { BaseStageForm } from "#admin/stages/BaseStageForm"; import { BaseStageForm } from "#admin/stages/BaseStageForm";
@@ -109,39 +112,37 @@ export class UserWriteStageForm extends BaseStageForm<UserWriteStage> {
?checked=${this.instance?.createUsersAsInactive ?? true} ?checked=${this.instance?.createUsersAsInactive ?? true}
help=${msg("Mark newly created users as inactive.")} help=${msg("Mark newly created users as inactive.")}
></ak-switch-input> ></ak-switch-input>
<ak-form-element-horizontal label=${msg("User type")} name="userType"> <ak-radio-input
<ak-radio label=${msg("User type")}
.options=${[ name="userType"
{ help=${msg("User type used for newly created users.")}
label: msg("Internal"), .options=${[
value: UserTypeEnum.Internal, {
default: true, label: msg("Internal"),
description: html`${msg( value: UserTypeEnum.Internal,
"Internal users might be users such as company employees, which will get access to the full Enterprise feature set.", default: true,
)}`, description: html`${msg(
}, "Internal users might be users such as company employees, which will get access to the full Enterprise feature set.",
{ )}`,
label: msg("External"), },
value: UserTypeEnum.External, {
description: html`${msg( label: msg("External"),
"External users might be external consultants or B2C customers. These users don't get access to enterprise features.", value: UserTypeEnum.External,
)}`, description: html`${msg(
}, "External users might be external consultants or B2C customers. These users don't get access to enterprise features.",
{ )}`,
label: msg("Service account"), },
value: UserTypeEnum.ServiceAccount, {
description: html`${msg( label: msg("Service account"),
"Service accounts should be used for machine-to-machine authentication or other automations.", value: UserTypeEnum.ServiceAccount,
)}`, description: html`${msg(
}, "Service accounts should be used for machine-to-machine authentication or other automations.",
]} )}`,
.value=${this.instance?.userType} },
> ] satisfies RadioOption<UserTypeEnum>[]}
</ak-radio> .value=${this.instance?.userType}
<p class="pf-c-form__helper-text"> >
${msg("User type used for newly created users.")} </ak-radio-input>
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal <ak-form-element-horizontal
label=${msg("User path template")} label=${msg("User path template")}
name="userPathTemplate" name="userPathTemplate"

View File

@@ -159,7 +159,7 @@ export class UserBulkRevokeSessionsForm extends ModalButton {
> >
</ak-user-bulk-revoke-sessions-table> </ak-user-bulk-revoke-sessions-table>
</section> </section>
<fieldset class="pf-c-modal-box__footer"> <fieldset class="ak-c-fieldset pf-c-modal-box__footer">
<legend class="sr-only">${msg("Form actions")}</legend> <legend class="sr-only">${msg("Form actions")}</legend>
<ak-spinner-button <ak-spinner-button
.callAction=${async () => { .callAction=${async () => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 907 KiB

After

Width:  |  Height:  |  Size: 900 KiB

View File

@@ -48,7 +48,7 @@
width: 100%; width: 100%;
} }
@media (width < 1200px) { @media (width <= 1210px) {
column-gap: calc(var(--pf-global--spacer--md) / 2); column-gap: calc(var(--pf-global--spacer--md) / 2);
} }
} }
@@ -137,7 +137,7 @@
display: none; display: none;
} }
@media (width < 1200px) { @media (width <= 1210px) {
display: none; display: none;
} }
} }
@@ -164,7 +164,7 @@
grid-area: toggle; grid-area: toggle;
} }
@media (width >= 1200px) { @media (width > 1210px) {
slot[name="toggle"] { slot[name="toggle"] {
display: none; display: none;
} }

View File

@@ -45,8 +45,8 @@ const statusToDetails = new Map<P4Disposition, [string, string]>([
export class AkStatusLabel extends AKElement { export class AkStatusLabel extends AKElement {
static styles = [PFLabel, Styles]; static styles = [PFLabel, Styles];
@property({ type: Boolean }) @property({ type: Boolean, reflect: true })
public good: boolean | null = null; public good: boolean = false;
@property({ type: String, attribute: "good-label" }) @property({ type: String, attribute: "good-label" })
public goodLabel = msg("Yes"); public goodLabel = msg("Yes");

View File

@@ -0,0 +1,113 @@
.pf-c-form__group-control {
padding-block: calc(var(--pf-c-form--m-horizontal__group-label--md--PaddingTop) * 1.3);
--pf-c-grid__group-control--m-stack--Gap: 0;
}
label.pf-c-check {
--pf-c-check--checkmark--BorderColor: var(--pf-global--BorderColor--100);
--pf-c-check--checkmark--Color: transparent;
--pf-c-check--BorderColor: transparent;
--pf-c-check--checked--BackgroundColor: transparent;
--pf-c-check--checked--BorderColor: var(--pf-global--active-color--300);
--pf-c-check--checked--Hover--BorderColor: var(--pf-global--active-color--400);
--pf-c-check--hover--BorderColor: var(--pf-global--active-color--200);
--pf-c-check--disabled--BackgroundColor: var(--pf-global--BackgroundColor--150);
transition:
border-color 0.2s,
color 0.2s;
padding-inline: var(--pf-global--spacer--form-element);
padding-block: var(--pf-global--spacer--sm);
cursor: pointer;
background-color: var(--pf-c-check--BackgroundColor, transparent);
border-radius: var(--pf-global--BorderRadius--sm);
position: relative;
user-select: none;
align-content: center;
display: grid;
grid-template-areas: "input .";
grid-template-columns: auto 1fr;
column-gap: var(--pf-global--spacer--md);
row-gap: 0;
.pf-c-check__input {
grid-area: input;
align-self: center;
margin-block: 0;
appearance: none;
content: none;
width: 1.5em;
height: 1.5em;
padding: 0.25em;
border: 0.5px solid;
color: var(--pf-c-check--checkmark--BorderColor, transparent);
border-radius: 3px;
display: flex;
justify-content: center;
align-items: center;
z-index: 1;
background: color-mix(var(--pf-c-check--checkmark--BorderColor), transparent 84%);
box-shadow: inset 0 0 1px
color-mix(var(--pf-c-check--checkmark--BorderColor), var(--pf-global--BorderColor--200));
&::after {
font-family: "Font Awesome 5 Free";
font-weight: 900;
content: "\f00c";
display: block;
line-height: 1;
color: var(--pf-c-check--checkmark--Color);
transition: color 0.2s;
filter: drop-shadow(color-mix(currentColor, transparent 50%) 0 0 1px);
}
}
&:hover:has(.pf-c-check__input:not(:disabled)) .pf-c-check__label {
text-decoration: underline;
text-decoration-color: var(--pf-c-check--checked--BorderColor);
text-decoration-thickness: 1px;
}
&:has(.pf-c-check__input:checked:not(:disabled)) {
--pf-c-check--BackgroundColor: var(--pf-c-check--checked--BackgroundColor);
--pf-c-check--BorderColor: var(--pf-c-check--checked--BorderColor);
--pf-c-check--checkmark--BorderColor: var(--pf-global--active-color--300);
--pf-c-check--checkmark--Color: var(--pf-c-check--checked--BorderColor);
}
&:hover:has(.pf-c-check__input:checked:not(:disabled)) {
--pf-c-check--checkmark--BorderColor: var(--pf-c-check--checked--Hover--BorderColor);
--pf-c-check--checkmark--Color: var(--pf-c-check--checked--Hover--BorderColor);
}
&:hover:not(:has(.pf-c-check__input:checked)):not(:has(.pf-c-check__input:disabled)) {
--pf-c-check--checkmark--BorderColor: var(--pf-c-check--hover--BorderColor);
--pf-c-check--checkmark--Color: color-mix(
var(--pf-c-check--hover--BorderColor),
transparent 75%
);
}
&:has(.pf-c-check__input:disabled) {
cursor: not-allowed;
--pf-c-check--BackgroundColor: var(--pf-c-check--disabled--BackgroundColor) !important;
}
.pf-c-check__label {
align-self: center;
font-weight: var(--pf-global--FontWeight--overpass--semi-bold);
font-family: var(--pf-global--FontFamily--redhat-updated--heading--sans-serif);
}
&:has(.pf-c-check__input:last-child) .pf-c-check__label {
grid-row: 1 / -1;
}
}

View File

@@ -1,9 +1,11 @@
import Styles from "#elements/ak-checkbox-group/ak-checkbox-group.css";
import { AKControlElement } from "#elements/ControlElement"; import { AKControlElement } from "#elements/ControlElement";
import { SlottedTemplateResult } from "#elements/types";
import { CustomEmitterElement } from "#elements/utils/eventEmitter"; import { CustomEmitterElement } from "#elements/utils/eventEmitter";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { PropertyValues } from "@lit/reactive-element"; import { PropertyValues } from "@lit/reactive-element";
import { css, html, TemplateResult } from "lit"; import { html, TemplateResult } from "lit";
import { customElement, property, queryAll, state } from "lit/decorators.js"; import { customElement, property, queryAll, state } from "lit/decorators.js";
import { map } from "lit/directives/map.js"; import { map } from "lit/directives/map.js";
@@ -14,11 +16,9 @@ type CheckboxKv = { name: string; label: string | TemplateResult };
type CheckboxPr = [string, string | TemplateResult]; type CheckboxPr = [string, string | TemplateResult];
export type CheckboxPair = CheckboxKv | CheckboxPr; export type CheckboxPair = CheckboxKv | CheckboxPr;
// eslint-disable-next-line @typescript-eslint/no-explicit-any function* kvToPairs(items: Iterable<CheckboxPair>): Iterable<CheckboxPr> {
const isCheckboxPr = (t: any): t is CheckboxPr => Array.isArray(t);
function* kvToPairs(items: CheckboxPair[]): Iterable<CheckboxPr> {
for (const item of items) { for (const item of items) {
yield isCheckboxPr(item) ? item : [item.name, item.label]; yield Array.isArray(item) ? item : [item.name, item.label];
} }
} }
@@ -75,51 +75,40 @@ const AkElementWithCustomEvents = CustomEmitterElement(AKControlElement);
* protocol. * protocol.
* *
*/ */
@customElement("ak-checkbox-group") @customElement("ak-checkbox-group")
export class CheckboxGroup extends AkElementWithCustomEvents { export class CheckboxGroup extends AkElementWithCustomEvents {
static styles = [ static styles = [PFForm, PFCheck, Styles];
PFForm,
PFCheck,
css`
.pf-c-form__group-control {
padding-top: calc(
var(--pf-c-form--m-horizontal__group-label--md--PaddingTop) * 1.3
);
}
`,
];
static get formAssociated() { static get formAssociated() {
return true; return true;
} }
@property({ type: Array }) @property({ type: Array })
options: CheckboxPair[] = []; public options: CheckboxPair[] = [];
@property({ type: Array }) @property({ type: Array })
value: string[] = []; public value: string[] = [];
@property({ type: String }) @property({ type: String })
public name: string | null = null; public name: string | null = null;
@property({ type: Boolean }) @property({ type: Boolean })
required = false; public required = false;
@queryAll('input[type="checkbox"]') @queryAll('input[type="checkbox"]')
checkboxes!: NodeListOf<HTMLInputElement>; protected checkboxes!: NodeListOf<HTMLInputElement>;
@state() @state()
values: string[] = []; protected values: string[] = [];
internals?: ElementInternals; protected internals?: ElementInternals;
doneFirstUpdate = false; protected doneFirstUpdate = false;
toJSON() { public toJSON() {
return this.values; return this.values;
} }
private get formValue() { protected get formValue() {
if (typeof this.name !== "string") { if (typeof this.name !== "string") {
throw new Error("This cannot be called without having the name set."); throw new Error("This cannot be called without having the name set.");
} }
@@ -129,18 +118,16 @@ export class CheckboxGroup extends AkElementWithCustomEvents {
return entries; return entries;
} }
constructor() { protected clickListener = (ev: Event) => {
super();
this.onClick = this.onClick.bind(this);
}
onClick(ev: Event) {
ev.stopPropagation(); ev.stopPropagation();
this.values = Array.from(this.checkboxes) this.values = Array.from(this.checkboxes)
.filter((checkbox) => checkbox.checked) .filter((checkbox) => checkbox.checked)
.map((checkbox) => checkbox.name); .map((checkbox) => checkbox.name);
this.dispatchCustomEvent("change", this.values); this.dispatchCustomEvent("change", this.values);
this.dispatchCustomEvent("input", this.values); this.dispatchCustomEvent("input", this.values);
if (this.internals) { if (this.internals) {
this.internals.setValidity({}); this.internals.setValidity({});
if (this.required && this.values.length === 0) { if (this.required && this.values.length === 0) {
@@ -154,19 +141,20 @@ export class CheckboxGroup extends AkElementWithCustomEvents {
} }
this.internals.setFormValue(this.formValue); this.internals.setFormValue(this.formValue);
} }
// Doing a write-back so anyone examining the checkbox.value field will get something // Doing a write-back so anyone examining the checkbox.value field will get something
// meaningful. Doesn't do anything for anyone, usually, but it's nice to have. // meaningful. Doesn't do anything for anyone, usually, but it's nice to have.
this.value = this.values; this.value = this.values;
} };
willUpdate(changed: PropertyValues<this>) { protected override willUpdate(changed: PropertyValues<this>) {
if (changed.has("value") && !this.doneFirstUpdate) { if (changed.has("value") && !this.doneFirstUpdate) {
this.doneFirstUpdate = true; this.doneFirstUpdate = true;
this.values = this.value; this.values = this.value;
} }
} }
connectedCallback() { public override connectedCallback() {
super.connectedCallback(); super.connectedCallback();
this.dataset.akControl = "true"; this.dataset.akControl = "true";
if (this.name && !this.internals) { if (this.name && !this.internals) {
@@ -192,32 +180,35 @@ export class CheckboxGroup extends AkElementWithCustomEvents {
}); });
} }
render() { protected renderCheckbox = ([name, label]: CheckboxPr): SlottedTemplateResult => {
const renderOne = ([name, label]: CheckboxPr) => { const selected = this.values.includes(name);
const selected = this.values.includes(name); const blockFwd = (e: Event) => {
const blockFwd = (e: Event) => { e.stopImmediatePropagation();
e.stopImmediatePropagation();
};
return html` <div part="checkbox" class="pf-c-check" @click=${this.onClick}>
<input
part="input"
@change=${blockFwd}
@input=${blockFwd}
name="${name}"
class="pf-c-check__input"
type="checkbox"
?checked=${selected}
id="ak-check-${name}"
/>
<label part="label" class="pf-c-check__label" for="ak-check-${name}"
>${label}</label
>
</div>`;
}; };
return html`<label
part="label"
for="ak-check-${name}"
class="pf-c-check"
@click=${this.clickListener}
>
<input
part="input"
@change=${blockFwd}
@input=${blockFwd}
name=${name}
class="pf-c-check__input"
type="checkbox"
?checked=${selected}
id="ak-check-${name}"
/>
<div class="pf-c-check__label">${label}</div>
</label>`;
};
protected override render(): SlottedTemplateResult {
return html`<div part="checkbox-group" class="pf-c-form__group-control pf-m-stack"> return html`<div part="checkbox-group" class="pf-c-form__group-control pf-m-stack">
${map(kvToPairs(this.options), renderOne)} ${map(kvToPairs(this.options), this.renderCheckbox)}
</div>`; </div>`;
} }
} }

View File

@@ -22,11 +22,12 @@ function resolvePath(...args: string[]): string {
* - Intercepts local links and scrolls to the target element. * - Intercepts local links and scrolls to the target element.
*/ */
export const MDXAnchor = ({ export const MDXAnchor = ({
href, href: initialHref,
children, children,
...props ...props
}: React.AnchorHTMLAttributes<HTMLAnchorElement>) => { }: React.AnchorHTMLAttributes<HTMLAnchorElement>) => {
const { publicDirectory } = useMDXModule(); const { publicDirectory } = useMDXModule();
let href = initialHref;
if (href?.startsWith(".") && publicDirectory) { if (href?.startsWith(".") && publicDirectory) {
const nextPathname = resolvePath(publicDirectory, href); const nextPathname = resolvePath(publicDirectory, href);

View File

@@ -12,7 +12,7 @@
); );
--ak-c-command-palette__group--Color: var(--pf-global--palette--purple-100); --ak-c-command-palette__group--Color: var(--pf-global--palette--purple-100);
--ak-fieldset--BorderColor: transparent; --ak-c-fieldset--BorderColor: transparent;
--ak-c-command-palette__item--BackgroundColor: transparent; --ak-c-command-palette__item--BackgroundColor: transparent;
--ak-c-command-palette__item--Color: var(--pf-global--palette--purple-50); --ak-c-command-palette__item--Color: var(--pf-global--palette--purple-50);
@@ -37,7 +37,7 @@
transform: translate3d(0, 0, 0); /* Fixes rendering artifacts. */ transform: translate3d(0, 0, 0); /* Fixes rendering artifacts. */
@media (prefers-contrast: more) { @media (prefers-contrast: more) {
--ak-fieldset--BorderColor: var(--pf-global--palette--purple-500); --ak-c-fieldset--BorderColor: var(--pf-global--palette--purple-500);
} }
} }
@@ -109,7 +109,7 @@
transition-duration: 0.2s; transition-duration: 0.2s;
legend { legend {
--ak-fieldset__legend--PaddingInlineBase: var(--pf-global--spacer--md); --ak-c-fieldset__legend--PaddingInlineBase: var(--pf-global--spacer--md);
cursor: pointer; cursor: pointer;
color: var(--ak-c-command-palette__group--Color); color: var(--ak-c-command-palette__group--Color);

View File

@@ -524,7 +524,7 @@ export class AKCommandPaletteModal extends AKModal {
Object.entries(grouped), Object.entries(grouped),
(_group, groupIdx) => `group-${groupIdx}`, (_group, groupIdx) => `group-${groupIdx}`,
([groupLabel, commands], groupIdx) => html` ([groupLabel, commands], groupIdx) => html`
<fieldset part="results-group"> <fieldset class="ak-c-fieldset" part="results-group">
<legend <legend
class="${!groupLabel ? "sr-only more-contrast-only" : ""}" class="${!groupLabel ? "sr-only more-contrast-only" : ""}"
data-label=${ifPresent(groupLabel)} data-label=${ifPresent(groupLabel)}

View File

@@ -115,8 +115,8 @@
/* #region Footer */ /* #region Footer */
fieldset.ak-c-dialog__footer { fieldset.ak-c-dialog__footer {
--ak-fieldset__legend--PaddingInlineBase: var(--pf-global--spacer--md); --ak-c-fieldset__legend--PaddingInlineBase: var(--pf-global--spacer--md);
padding-block: calc(var(--ak-fieldset__legend--PaddingInlineBase) / 2); padding-block: calc(var(--ak-c-fieldset__legend--PaddingInlineBase) / 2);
border-inline: none; border-inline: none;
border-block-end: none; border-block-end: none;

View File

@@ -77,7 +77,7 @@ export class ConfirmationForm extends ModalButton {
<slot class="pf-c-content" name="body"></slot> <slot class="pf-c-content" name="body"></slot>
</form> </form>
</section> </section>
<fieldset class="pf-c-modal-box__footer"> <fieldset class="ak-c-fieldset pf-c-modal-box__footer">
<legend class="sr-only">${msg("Form actions")}</legend> <legend class="sr-only">${msg("Form actions")}</legend>
<ak-spinner-button <ak-spinner-button
.callAction=${async () => { .callAction=${async () => {

View File

@@ -119,7 +119,7 @@ export class DeleteBulkForm<T> extends ModalButton {
> >
</ak-used-by-table> </ak-used-by-table>
</section> </section>
<fieldset class="pf-c-modal-box__footer"> <fieldset class="ak-c-fieldset pf-c-modal-box__footer">
<legend class="sr-only">${msg("Form actions")}</legend> <legend class="sr-only">${msg("Form actions")}</legend>
<ak-spinner-button <ak-spinner-button
.callAction=${async () => { .callAction=${async () => {

View File

@@ -218,7 +218,7 @@ export class ModalForm extends ModalButton {
} }
protected renderActions(): SlottedTemplateResult { protected renderActions(): SlottedTemplateResult {
return html`<fieldset class="pf-c-modal-box__footer"> return html`<fieldset class="ak-c-fieldset pf-c-modal-box__footer">
<legend class="sr-only">${msg("Form actions")}</legend> <legend class="sr-only">${msg("Form actions")}</legend>
<button <button
type="button" type="button"

View File

@@ -51,7 +51,7 @@ export class Radio<T extends Jsonifiable = never> extends FormAssociatedElement<
@property() @property()
public set value(nextValue: T) { public set value(nextValue: T) {
if (!nextValue) { if (nextValue === null) {
return; return;
} }

View File

@@ -1054,7 +1054,12 @@ export abstract class Table<T extends object, D = T>
<thead aria-label=${msg("Column actions")}> <thead aria-label=${msg("Column actions")}>
<tr class="pf-c-table__header-row"> <tr class="pf-c-table__header-row">
${this.checkbox ? this.renderAllOnThisPageCheckbox() : nothing} ${this.checkbox ? this.renderAllOnThisPageCheckbox() : nothing}
${this.expandable ? html`<td aria-hidden="true"></td>` : nothing} ${this.expandable
? html`<th
class="pf-c-table__toggle pf-m-pressable"
aria-hidden="true"
></th>`
: nothing}
${this.columns.map((column, idx) => { ${this.columns.map((column, idx) => {
const [label, orderBy, ariaLabel] = column; const [label, orderBy, ariaLabel] = column;
const columnID = this.#columnIDs.get(column) ?? `column-${idx}`; const columnID = this.#columnIDs.get(column) ?? `column-${idx}`;

View File

@@ -18,4 +18,5 @@
.empty-state-primary { .empty-state-primary {
display: flex; display: flex;
gap: var(--pf-global--spacer--sm); gap: var(--pf-global--spacer--sm);
justify-content: center;
} }

View File

@@ -263,7 +263,7 @@ export class TypeCreateWizardPage extends WithLicenseSummary(WizardPage) {
html`<form html`<form
${ref(this.formRef)} ${ref(this.formRef)}
part="form type-create list" part="form type-create list"
class="pf-c-form pf-m-horizontal ak-m-content-center" class="pf-c-form pf-m-horizontal"
role="radiogroup" role="radiogroup"
aria-label=${ifPresent(this.headline)} aria-label=${ifPresent(this.headline)}
> >

View File

@@ -126,7 +126,7 @@ export class FlowInspector extends AKElement {
protected renderNextStage({ currentPlan, isCompleted }: FlowInspection): TemplateResult { protected renderNextStage({ currentPlan, isCompleted }: FlowInspection): TemplateResult {
return html`<div class="pf-c-card"> return html`<div class="pf-c-card">
<fieldset> <fieldset class="ak-c-fieldset">
<legend class="pf-c-card__title">${msg("Next stage")}</legend> <legend class="pf-c-card__title">${msg("Next stage")}</legend>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
<dl class="pf-c-description-list"> <dl class="pf-c-description-list">
@@ -184,7 +184,7 @@ ${stringify(this.getStage(currentPlan?.nextPlannedStage?.stageObj))}</pre
currentPlan, currentPlan,
}: FlowInspection): TemplateResult { }: FlowInspection): TemplateResult {
return html`<div class="pf-c-card"> return html`<div class="pf-c-card">
<fieldset> <fieldset class="ak-c-fieldset">
<legend class="pf-c-card__title">${msg("Plan history")}</legend> <legend class="pf-c-card__title">${msg("Plan history")}</legend>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
<ol class="pf-c-progress-stepper pf-m-vertical"> <ol class="pf-c-progress-stepper pf-m-vertical">
@@ -248,7 +248,7 @@ ${stringify(this.getStage(currentPlan?.nextPlannedStage?.stageObj))}</pre
protected renderCurrentPlan({ currentPlan }: FlowInspection): TemplateResult { protected renderCurrentPlan({ currentPlan }: FlowInspection): TemplateResult {
return html`<div class="pf-c-card"> return html`<div class="pf-c-card">
<fieldset> <fieldset class="ak-c-fieldset">
<legend class="pf-c-card__title">${msg("Current plan context")}</legend> <legend class="pf-c-card__title">${msg("Current plan context")}</legend>
<pre class="pf-c-card__body"><code>${stringify( <pre class="pf-c-card__body"><code>${stringify(
currentPlan?.planContext, currentPlan?.planContext,
@@ -259,7 +259,7 @@ ${stringify(this.getStage(currentPlan?.nextPlannedStage?.stageObj))}</pre
protected renderSession({ currentPlan }: FlowInspection): TemplateResult { protected renderSession({ currentPlan }: FlowInspection): TemplateResult {
return html`<div class="pf-c-card"> return html`<div class="pf-c-card">
<fieldset> <fieldset class="ak-c-fieldset">
<legend class="pf-c-card__title">${msg("Session ID")}</legend> <legend class="pf-c-card__title">${msg("Session ID")}</legend>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
<code class="break"> ${currentPlan?.sessionId} </code> <code class="break"> ${currentPlan?.sessionId} </code>

View File

@@ -118,7 +118,7 @@ export class RedirectStage extends BaseStage<RedirectChallenge, FlowChallengeRes
<p>${msg("You're about to be redirected to the following URL.")}</p> <p>${msg("You're about to be redirected to the following URL.")}</p>
<code>${this.getURL()}</code> <code>${this.getURL()}</code>
</div> </div>
<fieldset class="pf-c-form__group pf-m-action"> <fieldset class="ak-c-fieldset pf-c-form__group pf-m-action">
<legend class="sr-only">${msg("Form actions")}</legend> <legend class="sr-only">${msg("Form actions")}</legend>
<a <a
type="submit" type="submit"

View File

@@ -38,7 +38,7 @@ export class AccessDeniedStage extends BaseStage<
: nothing} : nothing}
</ak-empty-state> </ak-empty-state>
${this.challenge?.flowInfo?.cancelUrl ${this.challenge?.flowInfo?.cancelUrl
? html`<fieldset class="pf-c-form__group pf-m-action"> ? html`<fieldset class="ak-c-fieldset pf-c-form__group pf-m-action">
<legend class="sr-only">${msg("Form actions")}</legend> <legend class="sr-only">${msg("Form actions")}</legend>
<a <a
class="pf-c-button pf-m-primary pf-m-block" class="pf-c-button pf-m-primary pf-m-block"

View File

@@ -87,7 +87,7 @@ export class AuthenticatorDuoStage extends BaseStage<
</p> </p>
<a href=${this.challenge.activationCode}>${msg("Duo activation")}</a> <a href=${this.challenge.activationCode}>${msg("Duo activation")}</a>
<fieldset class="pf-c-form__group pf-m-action"> <fieldset class="ak-c-fieldset pf-c-form__group pf-m-action">
<legend class="sr-only">${msg("Form actions")}</legend> <legend class="sr-only">${msg("Form actions")}</legend>
<button <button
type="button" type="button"

View File

@@ -64,7 +64,7 @@ export class AuthenticatorEmailStage extends BaseStage<
${AKFormErrors({ errors: this.challenge?.responseErrors?.email })} ${AKFormErrors({ errors: this.challenge?.responseErrors?.email })}
</div> </div>
${this.renderNonFieldErrors()} ${this.renderNonFieldErrors()}
<fieldset class="pf-c-form__group pf-m-action"> <fieldset class="ak-c-fieldset pf-c-form__group pf-m-action">
<legend class="sr-only">${msg("Form actions")}</legend> <legend class="sr-only">${msg("Form actions")}</legend>
<button <button
name="continue" name="continue"
@@ -120,7 +120,7 @@ export class AuthenticatorEmailStage extends BaseStage<
${AKFormErrors({ errors: this.challenge.responseErrors?.code })} ${AKFormErrors({ errors: this.challenge.responseErrors?.code })}
</div> </div>
${this.renderNonFieldErrors()} ${this.renderNonFieldErrors()}
<fieldset class="pf-c-form__group pf-m-action"> <fieldset class="ak-c-fieldset pf-c-form__group pf-m-action">
<legend class="sr-only">${msg("Form actions")}</legend> <legend class="sr-only">${msg("Form actions")}</legend>
<button <button
name="continue" name="continue"

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