Compare commits

..

120 Commits

Author SHA1 Message Date
Jens Langhammer
9213f8b06f web: use codecov bundle analysis
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-08-09 21:13:58 +01:00
Tom Neuber
7ed3fed5c3 outpost/proxyv2: add session cleanup for filesystem session store (#15798)
* proxyv2/filesystemstore: add persistent filesystem store to implement a session cleanup job

* proxyv2: add session cleanup for filesystem session store
2025-08-09 20:13:39 +01:00
Bethuel Mmbaga
8eecc537fa website/integrations: Update netbird redirect URIs settings (#16001)
update netbird integration redirect URIs and add auth variables
2025-08-09 20:00:01 +01:00
authentik-automation[bot]
c91c97178f core, web: update translations (#16046)
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>
2025-08-09 13:50:04 +01:00
Teffen Ellis
9ced8b948a web: Clean up ak-mdx linter warnings. (#16039) 2025-08-09 00:11:08 +02:00
dependabot[bot]
5a9cec6c61 web: bump format-imports from 4.0.7 to 4.0.8 in /packages/prettier-config (#15950)
web: bump format-imports in /packages/prettier-config

Bumps [format-imports](https://github.com/daidodo/format-imports) from 4.0.7 to 4.0.8.
- [Changelog](https://github.com/daidodo/format-imports/blob/main/CHANGELOG.md)
- [Commits](https://github.com/daidodo/format-imports/commits)

---
updated-dependencies:
- dependency-name: format-imports
  dependency-version: 4.0.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-08 17:17:39 +01:00
dependabot[bot]
9caca181cb web: bump typedoc from 0.28.8 to 0.28.9 in /packages/esbuild-plugin-live-reload (#15951)
web: bump typedoc in /packages/esbuild-plugin-live-reload

Bumps [typedoc](https://github.com/TypeStrong/TypeDoc) from 0.28.8 to 0.28.9.
- [Release notes](https://github.com/TypeStrong/TypeDoc/releases)
- [Changelog](https://github.com/TypeStrong/typedoc/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TypeStrong/TypeDoc/compare/v0.28.8...v0.28.9)

---
updated-dependencies:
- dependency-name: typedoc
  dependency-version: 0.28.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-08 17:17:20 +01:00
dependabot[bot]
43343adfd8 web: bump react-dom and @types/react-dom in /packages/docusaurus-config (#15864)
Bumps [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) and [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom). These dependencies needed to be updated together.

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

Updates `@types/react-dom` from 19.1.6 to 19.1.7
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

---
updated-dependencies:
- dependency-name: react-dom
  dependency-version: 19.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: "@types/react-dom"
  dependency-version: 19.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-08 15:32:19 +01:00
dependabot[bot]
f38c472eec website: bump @types/node from 24.1.0 to 24.2.0 in /website (#15970)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.1.0 to 24.2.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-08 15:31:35 +01:00
dependabot[bot]
9913345670 website: bump typescript from 5.8.3 to 5.9.2 in /website (#15969)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.8.3 to 5.9.2.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.8.3...v5.9.2)

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 5.9.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>
2025-08-08 15:31:18 +01:00
dependabot[bot]
c705eaabca web: bump react-dom and @types/react-dom in /web (#15867)
Bumps [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) and [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom). These dependencies needed to be updated together.

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

Updates `@types/react-dom` from 19.1.6 to 19.1.7
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

---
updated-dependencies:
- dependency-name: react-dom
  dependency-version: 19.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: "@types/react-dom"
  dependency-version: 19.1.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-08 14:52:02 +01:00
Marc 'risson' Schmitt
87eadea92b ci: readable workflow names (#16036) 2025-08-08 14:51:18 +01:00
dependabot[bot]
35c478d045 web: bump the eslint group across 2 directories with 3 updates (#15964)
Bumps the eslint group with 1 update in the /packages/eslint-config directory: [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).
Bumps the eslint group with 1 update in the /web directory: [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


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

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

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

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

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

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

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.39.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.39.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.39.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: typescript-eslint
  dependency-version: 8.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: eslint
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-08 14:49:44 +01:00
dependabot[bot]
c0e0fcdcb1 web: bump @types/node from 24.1.0 to 24.2.0 in /packages/esbuild-plugin-live-reload (#15967)
web: bump @types/node in /packages/esbuild-plugin-live-reload

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

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.2.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>
2025-08-08 14:49:37 +01:00
dependabot[bot]
3d97120cc2 web: bump the storybook group across 1 directory with 5 updates (#15968)
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 9.1.0 to 9.1.1
- [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/v9.1.1/code/addons/docs)

Updates `@storybook/addon-links` from 9.1.0 to 9.1.1
- [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/v9.1.1/code/addons/links)

Updates `@storybook/web-components` from 9.1.0 to 9.1.1
- [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/v9.1.1/code/renderers/web-components)

Updates `@storybook/web-components-vite` from 9.1.0 to 9.1.1
- [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/v9.1.1/code/frameworks/web-components-vite)

Updates `storybook` from 9.1.0 to 9.1.1
- [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/v9.1.1/code/core)

---
updated-dependencies:
- dependency-name: "@storybook/addon-docs"
  dependency-version: 9.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/addon-links"
  dependency-version: 9.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components"
  dependency-version: 9.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components-vite"
  dependency-version: 9.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: storybook
  dependency-version: 9.1.1
  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>
2025-08-08 14:49:27 +01:00
dependabot[bot]
29047ecf7a web: bump @types/node from 22.15.19 to 24.2.0 in /web (#15971)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.15.19 to 24.2.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.2.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>
2025-08-08 14:49:14 +01:00
dependabot[bot]
6cd4916d82 web: bump @types/node from 24.1.0 to 24.2.0 in /packages/prettier-config (#15972)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.1.0 to 24.2.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.2.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>
2025-08-08 14:49:04 +01:00
dependabot[bot]
5a24a6eef4 web: bump tmp from 0.2.3 to 0.2.4 in /packages/esbuild-plugin-live-reload (#16008)
web: bump tmp in /packages/esbuild-plugin-live-reload

Bumps [tmp](https://github.com/raszi/node-tmp) from 0.2.3 to 0.2.4.
- [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/raszi/node-tmp/compare/v0.2.3...v0.2.4)

---
updated-dependencies:
- dependency-name: tmp
  dependency-version: 0.2.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-08 14:48:47 +01:00
dependabot[bot]
f0b9cbd517 website: bump the eslint group in /website with 3 updates (#15977)
Bumps the eslint group in /website with 3 updates: [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin), [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


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

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

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

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: typescript-eslint
  dependency-version: 8.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: eslint
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-08 14:47:59 +01:00
dependabot[bot]
137223f7a0 website: bump tmp from 0.2.3 to 0.2.4 in /website (#16012)
* website: bump tmp from 0.2.3 to 0.2.4 in /website

Bumps [tmp](https://github.com/raszi/node-tmp) from 0.2.3 to 0.2.4.
- [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/raszi/node-tmp/compare/v0.2.3...v0.2.4)

---
updated-dependencies:
- dependency-name: tmp
  dependency-version: 0.2.4
  dependency-type: indirect
...

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

* use codecov oidc

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

* fix perms

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>
2025-08-08 14:46:20 +01:00
Teffen Ellis
6b9810ebe4 web: Fix cursor using pointer in modals. (#16009) 2025-08-08 14:46:13 +01:00
Teffen Ellis
b873bd7c80 web: Fix wide inputs, label alignment (#16042)
* web: Remove extra-wide field groups.

* web: Fix input alignment, types.
2025-08-08 14:45:52 +01:00
Marc 'risson' Schmitt
a34cd14ad7 ci: revert: main, outpost: ignore website changes (#16037) (#16040) 2025-08-08 13:06:19 +00:00
dependabot[bot]
2cd7e9f5ad web: bump the sentry group across 1 directory with 2 updates (#16023)
Bumps the sentry group with 2 updates in the /web directory: [@sentry/browser](https://github.com/getsentry/sentry-javascript) and @spotlightjs/spotlight.


Updates `@sentry/browser` from 10.0.0 to 10.2.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.0.0...10.2.0)

Updates `@spotlightjs/spotlight` from 3.0.1 to 3.0.2

---
updated-dependencies:
- dependency-name: "@sentry/browser"
  dependency-version: 10.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: sentry
- dependency-name: "@spotlightjs/spotlight"
  dependency-version: 3.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: sentry
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-08 13:35:51 +01:00
dependabot[bot]
8a5f4c3f63 core: bump astral-sh/uv from 0.8.5 to 0.8.6 (#16033)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.8.5 to 0.8.6.
- [Release notes](https://github.com/astral-sh/uv/releases)
- [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/uv/compare/0.8.5...0.8.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-08 13:35:39 +01:00
Marc 'risson' Schmitt
c0feb98635 ci: main, outpost: ignore website changes (#16037) 2025-08-08 12:32:59 +00:00
dependabot[bot]
745b7621ab lifecycle/aws: bump aws-cdk from 2.1023.0 to 2.1024.0 in /lifecycle/aws (#16025)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-08 12:27:39 +00:00
transifex-integration[bot]
2de0649898 translate: Updates for file web/xliff/en.xlf in fr (#16035)
Translate web/xliff/en.xlf in fr

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

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-08-08 11:03:35 +00:00
transifex-integration[bot]
a80b18cabe translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#16034)
Translate locale/en/LC_MESSAGES/django.po in fr

100% translated source file: 'locale/en/LC_MESSAGES/django.po'
on 'fr'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-08-08 11:01:59 +00:00
authentik-automation[bot]
f054af82dd core, web: update translations (#16032)
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>
2025-08-08 10:30:02 +00:00
Dominic R
6c176e0ba5 web: providers/rac: Fix wizard confirmation (#16013)
* providers/rac: Fix wizard confirmation

* lint

* Update web/src/admin/applications/wizard/steps/SubmitStepOverviewRenderers.ts

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Signed-off-by: Dominic R <dominic@sdko.org>

---------

Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2025-08-08 06:15:29 +02:00
YapWC
2cdc741b0b website/integrations: fix outline subject mode (#16022)
* Update index.md for Configure the Provider

Added Subject Mode which is required by Outline, default by username.

Signed-off-by: YapWC <34093361+YapWC@users.noreply.github.com>

* Update website/integrations/documentation/outline/index.md

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

---------

Signed-off-by: YapWC <34093361+YapWC@users.noreply.github.com>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-08-07 15:25:29 -05:00
Teffen Ellis
331732ce94 web: Form error rendering (#15874)
web: Use snake case helper.
2025-08-07 21:46:23 +02:00
Teffen Ellis
7e66f0ad94 web: Fix issue where aria-owns attribute triggers Chrome crash. (#16003)
- Tidy theme colors.
2025-08-07 16:37:24 +00:00
Teffen Ellis
fa5e8f993e web/a11y: Form Errors (#15940)
* web: Clarify client errors.

* web: Flesh out component type.

* web: Normalize field errors.
2025-08-07 12:33:53 -04:00
Ken Sternberg
b9a2722ef7 core: limit User search_field to pure text only (#16020) 2025-08-07 16:33:09 +00:00
Teffen Ellis
c3101fdf64 web: Bump package-lock. (#16018) 2025-08-07 09:15:56 -07:00
Teffen Ellis
97a2e02dcb web: Fix initial browser color scheme. (#16028) 2025-08-07 16:15:40 +00:00
Marcelo Elizeche Landó
336aa7f5e9 website/docs: Add docs for oauth2 back-channel logout (#15845)
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: dewi-tik <dewi@goauthentik.io>
2025-08-07 18:13:07 +02:00
Teffen Ellis
bfe26a8b23 website: Fix Markdown link. (#16029) 2025-08-07 17:35:10 +02:00
dependabot[bot]
2392ccb945 core: bump goauthentik.io/api/v3 from 3.2025064.6 to 3.2025064.7 (#16024)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2025064.6 to 3.2025064.7.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Changelog](https://github.com/goauthentik/client-go/blob/main/model_version_history.go)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2025064.6...v3.2025064.7)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  dependency-version: 3.2025064.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-07 12:40:25 +00:00
authentik-automation[bot]
7e46a7defc core, web: update translations (#16021)
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>
2025-08-07 14:25:59 +02:00
Dominic R
2d5247f1bc ci: move images from beryju/* to authentik/* (#15321) 2025-08-06 16:04:15 +00:00
authentik-automation[bot]
2c2d7c38d2 core, web: update translations (#15985)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-08-06 15:38:53 +00:00
Marcelo Elizeche Landó
d3963172cc core: bump cattrs from 24.1.3 to v25.1.1 (#15981) 2025-08-06 15:38:38 +00:00
authentik-automation[bot]
08abe34b81 web: bump API Client version (#16002)
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>
2025-08-06 15:30:54 +00:00
dependabot[bot]
47263aef3f ci: bump actions/download-artifact from 4 to 5 (#15995)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-06 15:29:24 +00:00
Marcelo Elizeche Landó
162934679d core: bump certifi from 2025.7.14 to v2025.8.3 (#15982) 2025-08-06 17:21:07 +02:00
Marcelo Elizeche Landó
1903c35ee0 core: bump anyio from 4.9.0 to v4.10.0 (#15979) 2025-08-06 17:20:33 +02:00
Marcelo Elizeche Landó
b97635f710 core: bump boto3 from 1.40.1 to v1.40.2 (#15980) 2025-08-06 17:20:20 +02:00
dependabot[bot]
fd1f65eefc core: bump astral-sh/uv from 0.8.4 to 0.8.5 (#15998)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-06 17:16:39 +02:00
dependabot[bot]
1e8eff4cb2 core: bump goauthentik.io/api/v3 from 3.2025064.5 to 3.2025064.6 (#15997)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-06 17:16:04 +02:00
dechen-authentik
cfe113b36a stages/email: implement rate limiting for account verification (#15531)
Co-authored-by: Marcelo Elizeche Landó <marcelo@goauthentik.io>
2025-08-06 16:44:35 +02:00
Teffen Ellis
90274b357a web: Fix stale application slug, missing error state. (#15941)
* web: Fix issue where application slug is outdated.

* web: Show error if application does not load.

* web: Tidy policy styling.
2025-08-05 20:10:38 +02:00
Dewi Roberts
9848e4fbe0 website/docs: change azure ad to entra id (#15691)
* Update sidebar, update doc and files

* Update website/docs/users-sources/sources/social-logins/entra-id/index.mdx

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

* Update website/docs/users-sources/sources/social-logins/entra-id/index.mdx

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

* Update website/docs/users-sources/sources/social-logins/entra-id/index.mdx

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

* Update sidebar, update doc and files

* Update website/docs/users-sources/sources/social-logins/entra-id/index.mdx

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

* Update website/docs/users-sources/sources/social-logins/entra-id/index.mdx

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

* Update website/docs/users-sources/sources/social-logins/entra-id/index.mdx

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

* Applied suggestions

---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2025-08-05 16:24:30 +01:00
Tana M Berry
a9deefe481 website/docs: add tips for image optimization (#15978)
* add new section for image optimization

* tweaks

---------

Co-authored-by: Tana M Berry <tana@goauthentik.io>
2025-08-05 10:19:25 -05:00
authentik-automation[bot]
d29896cfe1 web: bump API Client version (#15976)
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>
2025-08-05 10:59:51 -04:00
Marcelo Elizeche Landó
30670bb547 providers/oauth2: backchannel logout (#15401)
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-08-05 14:16:02 +02:00
authentik-automation[bot]
0f64471115 web: bump API Client version (#15953)
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>
2025-08-05 14:09:50 +02:00
transifex-integration[bot]
249b22963a translate: Updates for file web/xliff/en.xlf in fr (#15974)
Translate web/xliff/en.xlf in fr

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

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-08-05 12:01:13 +00:00
transifex-integration[bot]
b3a513273b translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#15973)
Translate locale/en/LC_MESSAGES/django.po in fr

100% translated source file: 'locale/en/LC_MESSAGES/django.po'
on 'fr'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-08-05 11:55:23 +00:00
authentik-automation[bot]
7ca013d527 core, web: update translations (#15962)
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>
2025-08-05 12:39:11 +02:00
dependabot[bot]
2e65e307fe core: bump goauthentik.io/api/v3 from 3.2025064.3 to 3.2025064.5 (#15965)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2025064.3 to 3.2025064.5.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Changelog](https://github.com/goauthentik/client-go/blob/main/model_version_history.go)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2025064.3...v3.2025064.5)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  dependency-version: 3.2025064.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>
2025-08-05 12:37:33 +02:00
Tana M Berry
0c07bad6f6 website/docs: reword Warning in Docker install docs (#15960)
* try again

* tweak

---------

Co-authored-by: Tana M Berry <tana@goauthentik.io>
2025-08-04 17:45:37 -05:00
Teffen Ellis
eb1c56dbeb web: Fix property name mismatch. (#15961) 2025-08-04 22:32:40 +00:00
Mike
766a294e55 website/docs: update instructions for Cloudflare Turnstile setup (#15918)
* Update index.md

Expanded on the instructions to setup Cloudflare Turnstile captcha

Signed-off-by: Mike <mike@cxi.nz>

* Update website/docs/add-secure-apps/flows-stages/stages/captcha/index.md

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

* Added period

---------

Signed-off-by: Mike <mike@cxi.nz>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-08-04 22:19:42 +00:00
Dominic R
db84a29ad7 website/integrations: home assistant: fix typo (#15958)
Update index.md

Signed-off-by: Dominic R <dominic@sdko.org>
2025-08-04 15:49:20 -05:00
Teffen Ellis
58e65e4612 web: Fix scroll-event induced tab crash (#15939)
web: Fix issue where native scroll event doesn't trigger before element
expands.
2025-08-04 20:25:05 +00:00
Dominic R
95b2d15476 website/integrations: actual budget: add info about first login fails (#15931)
* Import from PR

* wip

* wip
2025-08-04 15:24:10 -05:00
Dominic R
2bdc5ef8b1 website/integrations: mattermost (#15922)
* Import from PR

* wip

* wip
2025-08-04 15:19:06 -05:00
Dominic R
83cae926f7 website/integrations: fix build (#15957)
* Update index.md

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

* Delete website/integrations/services/home-assistant/index.mdx

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

* bump build

* frustrating

---------

Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Tana M Berry <tana@goauthentik.io>
2025-08-04 15:02:40 -05:00
Simonyi Gergő
213cf44928 root: enhance custom middleware experience (#15919)
* enable custom middleware positioning

Users can now set up their middleware to come before or after other
middleware.

Comes with the added benefit that prometheus middlewares are ensured
to be the very first and very last to run.

* stop treating authentik.enterprise exceptionally in settings

This is the singular case where more apps are added.

* stop treating authentik.core exceptionally in settings

Uhh, fingers crossed? This has history, it goes back to 80d90b91e8
2025-08-04 21:05:05 +02:00
Timo Christeleit
3c97b081b0 website/integrations: add hass-openid instructions (#14672)
* add instructions

* Added tabs for each configuration method, changed some wording, and ran prettier.

* Changed proxy section formatting and some language

* Update website/integrations/services/home-assistant/index.mdx

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Timo Christeleit <timo.christeleit@cavefire.net>

* Update website/integrations/services/home-assistant/index.mdx

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Timo Christeleit <timo.christeleit@cavefire.net>

* Update website/integrations/services/home-assistant/index.mdx

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Timo Christeleit <timo.christeleit@cavefire.net>

* Update website/integrations/services/home-assistant/index.mdx

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

---------

Signed-off-by: Timo Christeleit <timo.christeleit@cavefire.net>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-08-04 09:39:37 -05:00
Jose D. Gomez R.
ba725365ec core: add updated_at field to user (#15571)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-08-04 13:36:09 +00:00
Dominic R
e5e9708ec2 root: Add more opencontainer labels to Dockerfiles (#15923)
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-08-04 13:08:09 +00:00
dependabot[bot]
6a604e42ca core: bump goauthentik.io/api/v3 from 3.2025064.2 to 3.2025064.3 (#15949)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-04 12:55:18 +00:00
Daniel Adu-Gyan
ab1f87cfd6 core, providers/ldap: add parent/child groups to api and ldap results (#14974) 2025-08-04 14:29:16 +02:00
Teffen Ellis
de9b795c97 web: Make Webdriver optional during install. (#15952) 2025-08-04 12:24:34 +00:00
authentik-automation[bot]
0377e3593e core, web: update translations (#15945)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-08-04 13:22:17 +02:00
Simonyi Gergő
951c24dab5 packages/django-dramatiq-postgres: fix typo (#15932)
* fix typo

* fix typo
2025-08-04 13:05:57 +02:00
authentik-automation[bot]
707eca883e web: bump API Client version (#15942)
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>
2025-08-03 18:29:37 +01:00
Jens L.
8bc64ea478 core: fix flow planner checking against wrong user when creating recovery link (#15390)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-08-03 18:24:37 +01:00
Jens L.
8b1240ff0b providers/saml: configuration for default NameID Policy (#15109)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-08-03 18:24:12 +01:00
Marcelo Elizeche Landó
56ff8b1f97 core: bump boto3 from 1.39.15 to v1.40.1 (#15926) 2025-08-03 12:22:54 +02:00
Marcelo Elizeche Landó
cf26aace7b core: bump jsii from 1.112.0 to v1.113.0 (#15927) 2025-08-03 12:22:39 +02:00
Marcelo Elizeche Landó
46021e904a core: bump argon2-cffi-bindings from 21.2.0 to v25.1.0 (#15925) 2025-08-03 12:22:24 +02:00
Marcelo Elizeche Landó
a47196776d core: bump aiohttp from 3.12.14 to v3.12.15 (#15924) 2025-08-03 12:22:12 +02:00
Marcelo Elizeche Landó
58ce20c840 core: bump opentelemetry-api from 1.35.0 to v1.36.0 (#15928) 2025-08-03 12:22:06 +02:00
Simonyi Gergő
29b0177235 web/admin: fix variable name (#15934)
fix variable name
2025-08-02 16:26:20 +01:00
Simonyi Gergő
f15ddfcccd policies: fix typo (#15933)
fix typo
2025-08-02 16:23:05 +01:00
dependabot[bot]
7e4bdac093 web: bump @sentry/browser from 9.43.0 to 10.0.0 in /web in the sentry group across 1 directory (#15911)
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 9.43.0 to 10.0.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/9.43.0...10.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-01 12:42:54 +02:00
dependabot[bot]
5f16ea4718 core: bump github.com/prometheus/client_golang from 1.22.0 to 1.23.0 (#15908)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-01 10:22:50 +00:00
dependabot[bot]
d50a266d74 website: bump the build group in /website with 3 updates (#15910)
Bumps the build group in /website with 3 updates: [@rspack/binding-darwin-arm64](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack), [@rspack/binding-linux-arm64-gnu](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack) and [@rspack/binding-linux-x64-gnu](https://github.com/web-infra-dev/rspack/tree/HEAD/packages/rspack).


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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-01 12:14:49 +02:00
dependabot[bot]
41ebfa24da web: bump the storybook group across 1 directory with 5 updates (#15912)
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 9.0.18 to 9.1.0
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v9.1.0/code/addons/docs)

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

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

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

Updates `storybook` from 9.0.18 to 9.1.0
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v9.1.0/code/core)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-01 12:14:23 +02:00
dependabot[bot]
8b7cc18988 web: bump typescript from 5.8.3 to 5.9.2 in /packages/docusaurus-config (#15913)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.8.3 to 5.9.2.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.8.3...v5.9.2)

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 5.9.2
  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>
2025-08-01 12:14:08 +02:00
dependabot[bot]
e780b7d519 web: bump typescript from 5.8.3 to 5.9.2 in /packages/esbuild-plugin-live-reload (#15914)
web: bump typescript in /packages/esbuild-plugin-live-reload

Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.8.3 to 5.9.2.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.8.3...v5.9.2)

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 5.9.2
  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>
2025-08-01 12:13:57 +02:00
dependabot[bot]
25894592ae web: bump typescript from 5.8.3 to 5.9.2 in /packages/eslint-config (#15915)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.8.3 to 5.9.2.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.8.3...v5.9.2)

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 5.9.2
  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>
2025-08-01 12:13:48 +02:00
dependabot[bot]
5021d08c69 web: bump typescript from 5.8.3 to 5.9.2 in /packages/prettier-config (#15916)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.8.3 to 5.9.2.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.8.3...v5.9.2)

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 5.9.2
  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>
2025-08-01 12:13:35 +02:00
authentik-automation[bot]
cb74b47674 stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#15906)
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>
2025-08-01 12:06:19 +02:00
dependabot[bot]
aafd81ca09 core: bump github.com/getsentry/sentry-go from 0.34.1 to 0.35.0 (#15909)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-01 12:05:29 +02:00
Dominic R
a4f8e15f91 website/integrations: kimai: fix order and var (#15907)
Signed-off-by: Dominic R <dominic@sdko.org>
2025-08-01 08:30:31 +01:00
Jens L.
0c20169739 web/common: fix form element alignment (#15904)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-31 18:18:01 +02:00
Jens L.
24ca89c439 web/flows: fix flow inspector button always showing (#15893)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-31 17:19:35 +02:00
dependabot[bot]
0352d31af0 web: bump @floating-ui/dom from 1.7.2 to 1.7.3 in /web (#15899)
Bumps [@floating-ui/dom](https://github.com/floating-ui/floating-ui/tree/HEAD/packages/dom) from 1.7.2 to 1.7.3.
- [Release notes](https://github.com/floating-ui/floating-ui/releases)
- [Changelog](https://github.com/floating-ui/floating-ui/blob/master/packages/dom/CHANGELOG.md)
- [Commits](https://github.com/floating-ui/floating-ui/commits/@floating-ui/dom@1.7.3/packages/dom)

---
updated-dependencies:
- dependency-name: "@floating-ui/dom"
  dependency-version: 1.7.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>
2025-07-31 15:54:06 +02:00
dependabot[bot]
5bdbf06351 web: bump chromedriver from 138.0.4 to 138.0.5 in /web (#15900)
Bumps [chromedriver](https://github.com/giggio/node-chromedriver) from 138.0.4 to 138.0.5.
- [Commits](https://github.com/giggio/node-chromedriver/compare/138.0.4...138.0.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-31 15:53:55 +02:00
dependabot[bot]
b3f1e7b1a2 core: bump github.com/golang-jwt/jwt/v5 from 5.2.3 to 5.3.0 (#15896)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-31 14:18:18 +02:00
dependabot[bot]
2dfda8833d core: bump github.com/coreos/go-oidc/v3 from 3.14.1 to 3.15.0 (#15897)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-31 14:18:01 +02:00
dependabot[bot]
9094b30860 core: bump astral-sh/uv from 0.8.3 to 0.8.4 (#15898)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-31 14:16:39 +02:00
authentik-automation[bot]
7e52e932fc core, web: update translations (#15895)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-07-31 14:15:25 +02:00
Teffen Ellis
b5fc28a3fd website: Ignore legacy API docs. (#15891) 2025-07-31 11:24:27 +00:00
dependabot[bot]
df49dd4ec8 web: bump the rollup group across 1 directory with 4 updates (#15884)
Bumps the rollup group with 4 updates in the /web directory: [@rollup/rollup-darwin-arm64](https://github.com/rollup/rollup), [@rollup/rollup-linux-arm64-gnu](https://github.com/rollup/rollup), [@rollup/rollup-linux-x64-gnu](https://github.com/rollup/rollup) and [rollup](https://github.com/rollup/rollup).


Updates `@rollup/rollup-darwin-arm64` from 4.46.1 to 4.46.2
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.46.1...v4.46.2)

Updates `@rollup/rollup-linux-arm64-gnu` from 4.46.1 to 4.46.2
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.46.1...v4.46.2)

Updates `@rollup/rollup-linux-x64-gnu` from 4.46.1 to 4.46.2
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.46.1...v4.46.2)

Updates `rollup` from 4.46.1 to 4.46.2
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.46.1...v4.46.2)

---
updated-dependencies:
- dependency-name: "@rollup/rollup-darwin-arm64"
  dependency-version: 4.46.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rollup
- dependency-name: "@rollup/rollup-linux-arm64-gnu"
  dependency-version: 4.46.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rollup
- dependency-name: "@rollup/rollup-linux-x64-gnu"
  dependency-version: 4.46.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rollup
- dependency-name: rollup
  dependency-version: 4.46.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rollup
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-31 01:37:58 +02:00
dependabot[bot]
3f4c58a05b lifecycle/aws: bump aws-cdk from 2.1022.0 to 2.1023.0 in /lifecycle/aws (#15881)
Bumps [aws-cdk](https://github.com/aws/aws-cdk-cli/tree/HEAD/packages/aws-cdk) from 2.1022.0 to 2.1023.0.
- [Release notes](https://github.com/aws/aws-cdk-cli/releases)
- [Commits](https://github.com/aws/aws-cdk-cli/commits/aws-cdk@v2.1023.0/packages/aws-cdk)

---
updated-dependencies:
- dependency-name: aws-cdk
  dependency-version: 2.1023.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>
2025-07-31 01:37:50 +02:00
dependabot[bot]
46c9bfb0aa website: bump the build group in /website with 2 updates (#15882)
Bumps the build group in /website with 2 updates: [@swc/core-darwin-arm64](https://github.com/swc-project/swc) and [@swc/core-linux-arm64-gnu](https://github.com/swc-project/swc).


Updates `@swc/core-darwin-arm64` from 1.13.2 to 1.13.3
- [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.13.2...v1.13.3)

Updates `@swc/core-linux-arm64-gnu` from 1.13.2 to 1.13.3
- [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.13.2...v1.13.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-31 01:37:39 +02:00
dependabot[bot]
6d325d566c web: bump @sentry/browser from 9.42.1 to 9.43.0 in /web in the sentry group across 1 directory (#15883)
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 9.42.1 to 9.43.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/9.43.0/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/9.42.1...9.43.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-31 01:37:21 +02:00
dependabot[bot]
5689336f61 web: bump typedoc-plugin-markdown from 4.7.1 to 4.8.0 in /packages/esbuild-plugin-live-reload (#15885)
web: bump typedoc-plugin-markdown

Bumps [typedoc-plugin-markdown](https://github.com/typedoc2md/typedoc-plugin-markdown/tree/HEAD/packages/typedoc-plugin-markdown) from 4.7.1 to 4.8.0.
- [Release notes](https://github.com/typedoc2md/typedoc-plugin-markdown/releases)
- [Changelog](https://github.com/typedoc2md/typedoc-plugin-markdown/blob/main/packages/typedoc-plugin-markdown/CHANGELOG.md)
- [Commits](https://github.com/typedoc2md/typedoc-plugin-markdown/commits/typedoc-plugin-markdown@4.8.0/packages/typedoc-plugin-markdown)

---
updated-dependencies:
- dependency-name: typedoc-plugin-markdown
  dependency-version: 4.8.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>
2025-07-31 01:37:07 +02:00
Teffen Ellis
e04ca70cb2 web/a11y: Tables & Modals (#15877)
web: Prep for a11y, tables, modals.
2025-07-30 19:27:11 -04:00
Teffen Ellis
6a5342f621 web/a11y: Form Inputs (#15878)
web: Prep form inputs for a11y.
2025-07-30 19:27:03 -04:00
Teffen Ellis
e250c8f514 web/a11y: Navigation Banner (#15880)
web: Prepare navbar for a11y.
2025-07-30 19:26:34 -04:00
Teffen Ellis
a4e7aa0adc web/a11y: License notice ARIA attributes. (#15872)
web: Add ARIA attributes.
2025-07-30 19:26:23 -04:00
authentik-automation[bot]
ac79acd2bc core, web: update translations (#15873)
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>
2025-07-30 10:34:47 +02:00
234 changed files with 8437 additions and 6235 deletions

View File

@@ -4,7 +4,7 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
GITHUB_OUTPUT=/dev/stdout \
GITHUB_REF=ref \
GITHUB_SHA=sha \
IMAGE_NAME=ghcr.io/goauthentik/server,beryju/authentik \
IMAGE_NAME=ghcr.io/goauthentik/server,authentik/server \
GITHUB_REPOSITORY=goauthentik/authentik \
python $SCRIPT_DIR/push_vars.py
@@ -12,7 +12,7 @@ GITHUB_OUTPUT=/dev/stdout \
GITHUB_OUTPUT=/dev/stdout \
GITHUB_REF=ref \
GITHUB_SHA=sha \
IMAGE_NAME=ghcr.io/goauthentik/server,beryju/authentik \
IMAGE_NAME=ghcr.io/goauthentik/server,authentik/server \
GITHUB_REPOSITORY=goauthentik/authentik \
DOCKER_USERNAME=foo \
python $SCRIPT_DIR/push_vars.py

View File

@@ -1,5 +1,6 @@
---
# Re-usable workflow for a single-architecture build
name: Single-arch Container build
name: Reusable - Single-arch Container build
on:
workflow_call:

View File

@@ -1,5 +1,6 @@
---
# Re-usable workflow for a multi-architecture build
name: Multi-arch container build
name: Reusable - Multi-arch container build
on:
workflow_call:

View File

@@ -1,10 +1,13 @@
name: authentik-api-py-publish
---
name: API - Publish Python client
on:
push:
branches: [main]
paths:
- "schema.yml"
workflow_dispatch:
jobs:
build:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}

View File

@@ -1,10 +1,13 @@
name: authentik-api-ts-publish
---
name: API - Publish Typescript client
on:
push:
branches: [main]
paths:
- "schema.yml"
workflow_dispatch:
jobs:
build:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}

View File

@@ -1,4 +1,5 @@
name: authentik-ci-api-docs
---
name: CI - API Docs
on:
push:
@@ -66,7 +67,7 @@ jobs:
- build
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v5
with:
name: api-docs
path: website/api/build

View File

@@ -1,4 +1,5 @@
name: authentik-ci-aws-cfn
---
name: CI - AWS cfn
on:
push:

View File

@@ -1,4 +1,5 @@
name: authentik-publish-source-docs
---
name: CI - Source code docs
on:
push:

View File

@@ -1,4 +1,5 @@
name: authentik-ci-docs
---
name: CI - Docs
on:
push:

View File

@@ -1,5 +1,5 @@
---
name: authentik-ci-main-daily
name: CI - Main daily
on:
workflow_dispatch:

View File

@@ -1,5 +1,5 @@
---
name: authentik-ci-main
name: CI - Main
on:
push:
@@ -17,6 +17,12 @@ env:
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
permissions:
# Needed for checkout
contents: read
# Needed for codecov OIDC token
id-token: write
jobs:
lint:
strategy:
@@ -136,13 +142,13 @@ jobs:
uses: codecov/codecov-action@v5
with:
flags: unit
token: ${{ secrets.CODECOV_TOKEN }}
use_oidc: true
- if: ${{ !cancelled() }}
uses: codecov/test-results-action@v1
with:
flags: unit
file: unittest.xml
token: ${{ secrets.CODECOV_TOKEN }}
use_oidc: true
test-integration:
runs-on: ubuntu-latest
timeout-minutes: 30
@@ -160,13 +166,13 @@ jobs:
uses: codecov/codecov-action@v5
with:
flags: integration
token: ${{ secrets.CODECOV_TOKEN }}
use_oidc: true
- if: ${{ !cancelled() }}
uses: codecov/test-results-action@v1
with:
flags: integration
file: unittest.xml
token: ${{ secrets.CODECOV_TOKEN }}
use_oidc: true
test-e2e:
name: test-e2e (${{ matrix.job.name }})
runs-on: ubuntu-latest
@@ -219,13 +225,13 @@ jobs:
uses: codecov/codecov-action@v5
with:
flags: e2e
token: ${{ secrets.CODECOV_TOKEN }}
use_oidc: true
- if: ${{ !cancelled() }}
uses: codecov/test-results-action@v1
with:
flags: e2e
file: unittest.xml
token: ${{ secrets.CODECOV_TOKEN }}
use_oidc: true
ci-core-mark:
if: always()
needs:

View File

@@ -1,5 +1,5 @@
---
name: authentik-ci-outpost
name: CI - Outpost
on:
push:

View File

@@ -1,4 +1,5 @@
name: authentik-ci-web
---
name: CI - Web
on:
push:

View File

@@ -1,5 +1,5 @@
---
name: authentik-compress-images
name: Gen - Compress images
on:
push:

View File

@@ -1,4 +1,6 @@
name: authentik-gen-update-webauthn-mds
---
name: Gen - Webauthn MDS
on:
workflow_dispatch:
schedule:

View File

@@ -1,6 +1,7 @@
---
# See https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries
name: Cleanup cache after PR is closed
name: GH - Cleanup actions cache after PR is closed
on:
pull_request:
types:

View File

@@ -1,4 +1,5 @@
name: ghcr-retention
---
name: GH - GHCR retention
on:
# schedule:

View File

@@ -1,4 +1,6 @@
name: authentik-packages-npm-publish
---
name: Packages - Publish NPM packages
on:
push:
branches: [main]
@@ -9,6 +11,7 @@ on:
- packages/tsconfig/**
- packages/esbuild-plugin-live-reload/**
workflow_dispatch:
jobs:
publish:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}

View File

@@ -1,4 +1,5 @@
name: "CodeQL"
---
name: QA - CodeQL
on:
push:

View File

@@ -1,4 +1,6 @@
name: authentik-semgrep
---
name: QA - Semgrep
on:
workflow_dispatch: {}
pull_request: {}
@@ -7,10 +9,11 @@ on:
- main
- master
paths:
- .github/workflows/semgrep.yml
- .github/workflows/qa-semgrep.yml
schedule:
# random HH:MM to avoid a load spike on GitHub Actions at 00:00
- cron: '12 15 * * *'
jobs:
semgrep:
name: semgrep/ci

View File

@@ -1,4 +1,5 @@
name: authentik-on-release-next-branch
---
name: Release - Update next branch
on:
schedule:

View File

@@ -1,5 +1,5 @@
---
name: authentik-on-release
name: Release - On publish
on:
release:
@@ -16,7 +16,7 @@ jobs:
id-token: write
attestations: write
with:
image_name: ghcr.io/goauthentik/server,beryju/authentik
image_name: ghcr.io/goauthentik/server,authentik/server
release: true
registry_dockerhub: true
registry_ghcr: true
@@ -38,7 +38,7 @@ jobs:
uses: ./.github/actions/docker-push-variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
with:
image-name: ghcr.io/goauthentik/docs
- name: Login to GitHub Container Registry
@@ -92,9 +92,9 @@ jobs:
uses: ./.github/actions/docker-push-variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
with:
image-name: ghcr.io/goauthentik/${{ matrix.type }},beryju/authentik-${{ matrix.type }}
image-name: ghcr.io/goauthentik/${{ matrix.type }},authentik/${{ matrix.type }}
- name: make empty clients
run: |
mkdir -p ./gen-ts-api
@@ -102,8 +102,8 @@ jobs:
- name: Docker Login Registry
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
username: ${{ secrets.DOCKER_CORP_USERNAME }}
password: ${{ secrets.DOCKER_CORP_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
@@ -220,7 +220,7 @@ jobs:
uses: ./.github/actions/docker-push-variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
with:
image-name: ghcr.io/goauthentik/server
- name: Get static files from docker image

View File

@@ -1,5 +1,5 @@
---
name: authentik-on-tag
name: Release - On tag
on:
push:

View File

@@ -1,4 +1,5 @@
name: "authentik-repo-mirror-cleanup"
---
name: Repo - Cleanup internal mirror
on:
workflow_dispatch:

View File

@@ -1,4 +1,5 @@
name: "authentik-repo-mirror"
---
name: Repo - Mirror to internal
on: [push, delete]

View File

@@ -1,4 +1,5 @@
name: "authentik-repo-stale"
---
name: Repo - Mark and close stale issues
on:
schedule:

View File

@@ -1,4 +1,5 @@
name: authentik-translation-advice
---
name: Translation - Post advice
on:
pull_request:

View File

@@ -1,5 +1,6 @@
---
name: authentik-translate-extract-compile
name: Translation - Extract and compile
on:
schedule:
- cron: "0 0 * * *" # every day at midnight

View File

@@ -1,6 +1,7 @@
---
# Rename transifex pull requests to have a correct naming
# Also enables auto squash-merge
name: authentik-translation-transifex-rename
name: Translation - Auto-rename Transifex PRs
on:
pull_request:

View File

@@ -76,7 +76,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
# Stage 4: Download uv
FROM ghcr.io/astral-sh/uv:0.8.3 AS uv
FROM ghcr.io/astral-sh/uv:0.8.6 AS uv
# Stage 5: Base python image
FROM ghcr.io/goauthentik/fips-python:3.13.5-slim-bookworm-fips AS python-base
@@ -134,11 +134,16 @@ ARG VERSION
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
LABEL org.opencontainers.image.url=https://goauthentik.io
LABEL org.opencontainers.image.description="goauthentik.io Main server image, see https://goauthentik.io for more info."
LABEL org.opencontainers.image.source=https://github.com/goauthentik/authentik
LABEL org.opencontainers.image.version=${VERSION}
LABEL org.opencontainers.image.revision=${GIT_BUILD_HASH}
LABEL org.opencontainers.image.authors="Authentik Security Inc." \
org.opencontainers.image.description="goauthentik.io Main server image, see https://goauthentik.io for more info." \
org.opencontainers.image.documentation="https://docs.goauthentik.io" \
org.opencontainers.image.licenses="https://github.com/goauthentik/authentik/blob/main/LICENSE" \
org.opencontainers.image.revision=${GIT_BUILD_HASH} \
org.opencontainers.image.source="https://github.com/goauthentik/authentik" \
org.opencontainers.image.title="authentik server image" \
org.opencontainers.image.url="https://goauthentik.io" \
org.opencontainers.image.vendor="Authentik Security Inc." \
org.opencontainers.image.version=${VERSION}
WORKDIR /

View File

@@ -9,8 +9,8 @@
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/goauthentik/authentik/ci-outpost.yml?branch=main&label=outpost%20build&style=for-the-badge)](https://github.com/goauthentik/authentik/actions/workflows/ci-outpost.yml)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/goauthentik/authentik/ci-web.yml?branch=main&label=web%20build&style=for-the-badge)](https://github.com/goauthentik/authentik/actions/workflows/ci-web.yml)
[![Code Coverage](https://img.shields.io/codecov/c/gh/goauthentik/authentik?style=for-the-badge)](https://codecov.io/gh/goauthentik/authentik)
![Docker pulls](https://img.shields.io/docker/pulls/beryju/authentik.svg?style=for-the-badge)
![Latest version](https://img.shields.io/docker/v/beryju/authentik?sort=semver&style=for-the-badge)
![Docker pulls](https://img.shields.io/docker/pulls/authentik/server.svg?style=for-the-badge)
![Latest version](https://img.shields.io/docker/v/authentik/server?sort=semver&style=for-the-badge)
[![](https://img.shields.io/badge/Help%20translate-transifex-blue?style=for-the-badge)](https://www.transifex.com/authentik/authentik/)
## What is authentik?

View File

@@ -8,8 +8,6 @@ API Browser - {{ brand.branding_title }}
{% block head %}
<script src="{% versioned_script 'dist/standalone/api-browser/index-%v.js' %}" type="module"></script>
<meta name="theme-color" content="#151515" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#151515" media="(prefers-color-scheme: dark)">
{% endblock %}
{% block body %}

View File

@@ -49,11 +49,28 @@ class GroupMemberSerializer(ModelSerializer):
]
class GroupChildSerializer(ModelSerializer):
"""Stripped down group serializer to show relevant children for groups"""
attributes = JSONDictField(required=False)
class Meta:
model = Group
fields = [
"pk",
"name",
"is_superuser",
"attributes",
"group_uuid",
]
class GroupSerializer(ModelSerializer):
"""Group Serializer"""
attributes = JSONDictField(required=False)
users_obj = SerializerMethodField(allow_null=True)
children_obj = SerializerMethodField(allow_null=True)
roles_obj = ListSerializer(
child=RoleSerializer(),
read_only=True,
@@ -61,7 +78,6 @@ class GroupSerializer(ModelSerializer):
required=False,
)
parent_name = CharField(source="parent.name", read_only=True, allow_null=True)
num_pk = IntegerField(read_only=True)
@property
@@ -71,12 +87,25 @@ class GroupSerializer(ModelSerializer):
return True
return str(request.query_params.get("include_users", "true")).lower() == "true"
@property
def _should_include_children(self) -> bool:
request: Request = self.context.get("request", None)
if not request:
return True
return str(request.query_params.get("include_children", "false")).lower() == "true"
@extend_schema_field(GroupMemberSerializer(many=True))
def get_users_obj(self, instance: Group) -> list[GroupMemberSerializer] | None:
if not self._should_include_users:
return None
return GroupMemberSerializer(instance.users, many=True).data
@extend_schema_field(GroupChildSerializer(many=True))
def get_children_obj(self, instance: Group) -> list[GroupChildSerializer] | None:
if not self._should_include_children:
return None
return GroupChildSerializer(instance.children, many=True).data
def validate_parent(self, parent: Group | None):
"""Validate group parent (if set), ensuring the parent isn't itself"""
if not self.instance or not parent:
@@ -126,11 +155,17 @@ class GroupSerializer(ModelSerializer):
"attributes",
"roles",
"roles_obj",
"children",
"children_obj",
]
extra_kwargs = {
"users": {
"default": list,
},
"children": {
"required": False,
"default": list,
},
# TODO: This field isn't unique on the database which is hard to backport
# hence we just validate the uniqueness here
"name": {"validators": [UniqueValidator(Group.objects.all())]},
@@ -203,11 +238,15 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
Prefetch("users", queryset=User.objects.all().only("id"))
)
if self.serializer_class(context={"request": self.request})._should_include_children:
base_qs = base_qs.prefetch_related("children")
return base_qs
@extend_schema(
parameters=[
OpenApiParameter("include_users", bool, default=True),
OpenApiParameter("include_children", bool, default=False),
]
)
def list(self, request, *args, **kwargs):
@@ -216,6 +255,7 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
@extend_schema(
parameters=[
OpenApiParameter("include_users", bool, default=True),
OpenApiParameter("include_children", bool, default=False),
]
)
def retrieve(self, request, *args, **kwargs):

View File

@@ -5,7 +5,7 @@ from json import loads
from typing import Any
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.models import Permission
from django.contrib.auth.models import AnonymousUser, Permission
from django.db.transaction import atomic
from django.db.utils import IntegrityError
from django.urls import reverse_lazy
@@ -16,6 +16,7 @@ from django.utils.translation import gettext as _
from django_filters.filters import (
BooleanFilter,
CharFilter,
IsoDateTimeFilter,
ModelMultipleChoiceFilter,
MultipleChoiceFilter,
UUIDFilter,
@@ -153,7 +154,8 @@ class UserSerializer(ModelSerializer):
if SERIALIZER_CONTEXT_BLUEPRINT in self.context:
self.fields["password"] = CharField(required=False, allow_null=True)
self.fields["permissions"] = ListField(
required=False, child=ChoiceField(choices=get_permission_choices())
required=False,
child=ChoiceField(choices=get_permission_choices()),
)
def create(self, validated_data: dict) -> User:
@@ -241,6 +243,7 @@ class UserSerializer(ModelSerializer):
"type",
"uuid",
"password_change_date",
"last_updated",
]
extra_kwargs = {
"name": {"allow_blank": True},
@@ -267,7 +270,10 @@ class UserSelfSerializer(ModelSerializer):
ListSerializer(
child=inline_serializer(
"UserSelfGroups",
{"name": CharField(read_only=True), "pk": CharField(read_only=True)},
{
"name": CharField(read_only=True),
"pk": CharField(read_only=True),
},
)
)
)
@@ -315,7 +321,8 @@ class UserSelfSerializer(ModelSerializer):
class SessionUserSerializer(PassiveSerializer):
"""Response for the /user/me endpoint, returns the currently active user (as `user` property)
and, if this user is being impersonated, the original user in the `original` property."""
and, if this user is being impersonated, the original user in the `original` property.
"""
user = UserSelfSerializer()
original = UserSelfSerializer(required=False)
@@ -331,6 +338,14 @@ class UsersFilter(FilterSet):
method="filter_attributes",
)
date_joined__lt = IsoDateTimeFilter(field_name="date_joined", lookup_expr="lt")
date_joined = IsoDateTimeFilter(field_name="date_joined")
date_joined__gt = IsoDateTimeFilter(field_name="date_joined", lookup_expr="gt")
last_updated__lt = IsoDateTimeFilter(field_name="last_updated", lookup_expr="lt")
last_updated = IsoDateTimeFilter(field_name="last_updated")
last_updated__gt = IsoDateTimeFilter(field_name="last_updated", lookup_expr="gt")
is_superuser = BooleanFilter(field_name="ak_groups", method="filter_is_superuser")
uuid = UUIDFilter(field_name="uuid")
@@ -376,6 +391,8 @@ class UsersFilter(FilterSet):
fields = [
"username",
"email",
"date_joined",
"last_updated",
"name",
"is_active",
"is_superuser",
@@ -390,15 +407,18 @@ class UserViewSet(UsedByMixin, ModelViewSet):
"""User Viewset"""
queryset = User.objects.none()
ordering = ["username"]
ordering = ["username", "date_joined", "last_updated"]
serializer_class = UserSerializer
filterset_class = UsersFilter
search_fields = ["username", "name", "is_active", "email", "uuid", "attributes"]
search_fields = ["email", "name", "uuid", "username"]
def get_ql_fields(self):
from djangoql.schema import BoolField, StrField
from authentik.enterprise.search.fields import ChoiceSearchField, JSONSearchField
from authentik.enterprise.search.fields import (
ChoiceSearchField,
JSONSearchField,
)
return [
StrField(User, "username"),
@@ -435,6 +455,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
user: User = self.get_object()
planner = FlowPlanner(flow)
planner.allow_empty_flows = True
self.request._request.user = AnonymousUser()
try:
plan = planner.plan(
self.request._request,
@@ -492,7 +513,12 @@ class UserViewSet(UsedByMixin, ModelViewSet):
)
},
)
@action(detail=False, methods=["POST"], pagination_class=None, filter_backends=[])
@action(
detail=False,
methods=["POST"],
pagination_class=None,
filter_backends=[],
)
def service_account(self, request: Request) -> Response:
"""Create a new user account that is marked as a service account"""
username = request.data.get("name")
@@ -536,7 +562,13 @@ class UserViewSet(UsedByMixin, ModelViewSet):
return Response(data={"non_field_errors": [str(exc)]}, status=400)
@extend_schema(responses={200: SessionUserSerializer(many=False)})
@action(url_path="me", url_name="me", detail=False, pagination_class=None, filter_backends=[])
@action(
url_path="me",
url_name="me",
detail=False,
pagination_class=None,
filter_backends=[],
)
def user_me(self, request: Request) -> Response:
"""Get information about current user"""
context = {"request": request}
@@ -662,14 +694,18 @@ class UserViewSet(UsedByMixin, ModelViewSet):
if not request.user.has_perm(
"authentik_core.impersonate", user_to_be
) and not request.user.has_perm("authentik_core.impersonate"):
LOGGER.debug("User attempted to impersonate without permissions", user=request.user)
LOGGER.debug(
"User attempted to impersonate without permissions",
user=request.user,
)
return Response(status=401)
if user_to_be.pk == self.request.user.pk:
LOGGER.debug("User attempted to impersonate themselves", user=request.user)
return Response(status=401)
if not reason and request.tenant.impersonation_require_reason:
LOGGER.debug(
"User attempted to impersonate without providing a reason", user=request.user
"User attempted to impersonate without providing a reason",
user=request.user,
)
return Response(status=401)
@@ -708,7 +744,8 @@ class UserViewSet(UsedByMixin, ModelViewSet):
@extend_schema(
responses={
200: inline_serializer(
"UserPathSerializer", {"paths": ListField(child=CharField(), read_only=True)}
"UserPathSerializer",
{"paths": ListField(child=CharField(), read_only=True)},
)
},
parameters=[

View File

@@ -0,0 +1,27 @@
# Generated by Django 5.1.11 on 2025-07-15 15:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("auth", "0012_alter_user_first_name_max_length"),
("authentik_core", "0049_alter_token_options"),
]
operations = [
migrations.AddField(
model_name="user",
name="last_updated",
field=models.DateTimeField(auto_now=True),
),
migrations.AddIndex(
model_name="user",
index=models.Index(fields=["last_updated"], name="authentik_c_last_up_ed7486_idx"),
),
migrations.AddIndex(
model_name="user",
index=models.Index(fields=["date_joined"], name="authentik_c_date_jo_58c256_idx"),
),
]

View File

@@ -274,6 +274,8 @@ class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser):
ak_groups = models.ManyToManyField("Group", related_name="users")
password_change_date = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
objects = UserManager()
class Meta:
@@ -293,6 +295,8 @@ class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser):
models.Index(fields=["uuid"]),
models.Index(fields=["path"]),
models.Index(fields=["type"]),
models.Index(fields=["date_joined"]),
models.Index(fields=["last_updated"]),
]
def __str__(self):

View File

@@ -15,7 +15,11 @@
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}">
{% block head_before %}
{% endblock %}
{% include "base/theme.html" %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
<style>{{ brand_css }}</style>
<script src="{% versioned_script 'dist/poly-%v.js' %}" type="module"></script>
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>

View File

@@ -0,0 +1,11 @@
{% if ui_theme == "dark" %}
<meta name="color-scheme" content="dark" />
<meta name="theme-color" content="#18191a">
{% elif ui_theme == "light" %}
<meta name="color-scheme" content="light" />
<meta name="theme-color" content="#ffffff">
{% else %}
<meta name="color-scheme" content="light dark" />
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
{% endif %}

View File

@@ -4,8 +4,6 @@
{% block head %}
<script src="{% versioned_script 'dist/admin/AdminInterface-%v.js' %}" type="module"></script>
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
{% include "base/header_js.html" %}
{% endblock %}

View File

@@ -4,8 +4,6 @@
{% block head %}
<script src="{% versioned_script 'dist/user/UserInterface-%v.js' %}" type="module"></script>
<meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: dark)">
{% include "base/header_js.html" %}
{% endblock %}

View File

@@ -21,7 +21,7 @@ from authentik.core.tests.utils import (
create_test_flow,
create_test_user,
)
from authentik.flows.models import FlowDesignation
from authentik.flows.models import FlowAuthenticationRequirement, FlowDesignation
from authentik.lib.generators import generate_id, generate_key
from authentik.stages.email.models import EmailStage
@@ -103,8 +103,11 @@ class TestUsersAPI(APITestCase):
self.assertTrue(self.admin.check_password(new_pw))
def test_recovery(self):
"""Test user recovery link (no recovery flow set)"""
flow = create_test_flow(FlowDesignation.RECOVERY)
"""Test user recovery link"""
flow = create_test_flow(
FlowDesignation.RECOVERY,
authentication=FlowAuthenticationRequirement.REQUIRE_UNAUTHENTICATED,
)
brand: Brand = create_test_brand()
brand.flow_recovery = flow
brand.save()
@@ -387,3 +390,72 @@ class TestUsersAPI(APITestCase):
self.assertFalse(
AuthenticatedSession.objects.filter(session__session_key=session_id).exists()
)
def test_sort_by_last_updated(self):
"""Test API sorting by last_updated"""
User.objects.all().delete()
admin = create_test_admin_user()
self.client.force_login(admin)
user = create_test_user()
admin.first_name = "Sample change"
admin.last_name = "To trigger an update"
admin.save()
# Ascending
response = self.client.get(
reverse("authentik_api:user-list"),
data={
"ordering": "last_updated",
},
)
self.assertEqual(response.status_code, 200)
body = loads(response.content)
self.assertEqual(len(body["results"]), 2)
self.assertEqual(body["results"][0]["pk"], user.pk)
# Descending
response = self.client.get(
reverse("authentik_api:user-list"),
data={
"ordering": "-last_updated",
},
)
self.assertEqual(response.status_code, 200)
body = loads(response.content)
self.assertEqual(len(body["results"]), 2)
self.assertEqual(body["results"][0]["pk"], admin.pk)
def test_sort_by_date_joined(self):
"""Test API sorting by date_joined"""
User.objects.all().delete()
admin = create_test_admin_user()
self.client.force_login(admin)
user = create_test_user()
response = self.client.get(
reverse("authentik_api:user-list"),
data={
"ordering": "date_joined",
},
)
self.assertEqual(response.status_code, 200)
body = loads(response.content)
self.assertEqual(len(body["results"]), 2)
self.assertEqual(body["results"][0]["pk"], admin.pk)
response = self.client.get(
reverse("authentik_api:user-list"),
data={
"ordering": "-date_joined",
},
)
self.assertEqual(response.status_code, 200)
body = loads(response.content)
self.assertEqual(len(body["results"]), 2)
self.assertEqual(body["results"][0]["pk"], user.pk)

View File

@@ -46,8 +46,10 @@ class InterfaceView(TemplateView):
"""Base interface view"""
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
brand = CurrentBrandSerializer(self.request.brand)
kwargs["config_json"] = dumps(ConfigView(request=Request(self.request)).get_config().data)
kwargs["brand_json"] = dumps(CurrentBrandSerializer(self.request.brand).data)
kwargs["ui_theme"] = brand.data["ui_theme"]
kwargs["brand_json"] = dumps(brand.data)
kwargs["version_family"] = f"{LOCAL_VERSION.major}.{LOCAL_VERSION.minor}"
kwargs["version_subdomain"] = f"version-{LOCAL_VERSION.major}-{LOCAL_VERSION.minor}"
kwargs["build"] = get_build_hash()

View File

@@ -55,6 +55,7 @@ class TestEnterpriseAudit(APITestCase):
self.assertIsNotNone(event)
self.assertIsNotNone(event.context["diff"])
diff = event.context["diff"]
diff.pop("last_updated")
self.assertEqual(
diff,
{
@@ -116,6 +117,7 @@ class TestEnterpriseAudit(APITestCase):
self.assertIsNotNone(event)
self.assertIsNotNone(event.context["diff"])
diff = event.context["diff"]
diff.pop("last_updated")
self.assertEqual(
diff,
{

View File

@@ -301,6 +301,7 @@ class SessionEndStage(ChallengeStageView):
"flow_slug": self.request.brand.flow_invalidation.slug,
},
)
return SessionEndChallenge(data=data)
# This can never be reached since this challenge is created on demand and only the

View File

@@ -42,7 +42,7 @@ class TestBindingsAPI(APITestCase):
)
def test_invalid_too_little(self):
"""Test invvalid binding (too little)"""
"""Test invalid binding (too little)"""
response = self.client.post(
reverse("authentik_api:policybinding-list"),
data={"target": self.pbm.pk, "order": 0},

View File

@@ -70,6 +70,7 @@ class OAuth2ProviderSerializer(ProviderSerializer):
"signing_key",
"encryption_key",
"redirect_uris",
"backchannel_logout_uri",
"sub_mode",
"property_mappings",
"issuer_mode",

View File

@@ -1,5 +1,8 @@
"""OAuth/OpenID Constants"""
from django.db import models
from django.utils.translation import gettext_lazy as _
GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code"
GRANT_TYPE_IMPLICIT = "implicit"
GRANT_TYPE_REFRESH_TOKEN = "refresh_token" # nosec
@@ -51,3 +54,23 @@ AMR_MFA = "mfa"
AMR_OTP = "otp"
AMR_WEBAUTHN = "user"
AMR_SMART_CARD = "sc"
class SubModes(models.TextChoices):
"""Mode after which 'sub' attribute is generated, for compatibility reasons"""
HASHED_USER_ID = "hashed_user_id", _("Based on the Hashed User ID")
USER_ID = "user_id", _("Based on user ID")
USER_UUID = "user_uuid", _("Based on user UUID")
USER_USERNAME = "user_username", _("Based on the username")
USER_EMAIL = (
"user_email",
_("Based on the User's Email. This is recommended over the UPN method."),
)
USER_UPN = (
"user_upn",
_(
"Based on the User's UPN, only works if user has a 'upn' attribute set. "
"Use this method only if you have different UPN and Mail domains."
),
)

View File

@@ -4,10 +4,8 @@ from dataclasses import asdict, dataclass, field
from hashlib import sha256
from typing import TYPE_CHECKING, Any
from django.db import models
from django.http import HttpRequest
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from authentik.core.models import default_token_duration
from authentik.events.signals import get_login_event
@@ -18,6 +16,7 @@ from authentik.providers.oauth2.constants import (
AMR_PASSWORD,
AMR_SMART_CARD,
AMR_WEBAUTHN,
SubModes,
)
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
@@ -30,26 +29,6 @@ def hash_session_key(session_key: str) -> str:
return sha256(session_key.encode("ascii")).hexdigest()
class SubModes(models.TextChoices):
"""Mode after which 'sub' attribute is generated, for compatibility reasons"""
HASHED_USER_ID = "hashed_user_id", _("Based on the Hashed User ID")
USER_ID = "user_id", _("Based on user ID")
USER_UUID = "user_uuid", _("Based on user UUID")
USER_USERNAME = "user_username", _("Based on the username")
USER_EMAIL = (
"user_email",
_("Based on the User's Email. This is recommended over the UPN method."),
)
USER_UPN = (
"user_upn",
_(
"Based on the User's UPN, only works if user has a 'upn' attribute set. "
"Use this method only if you have different UPN and Mail domains."
),
)
@dataclass(slots=True)
class IDToken:
"""The primary extension that OpenID Connect makes to OAuth 2.0 to enable End-Users to be

View File

@@ -0,0 +1,28 @@
# Generated by Django 5.1.11 on 2025-07-04 03:23
import authentik.lib.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_oauth2", "0028_migrate_session"),
]
operations = [
migrations.AddField(
model_name="oauth2provider",
name="backchannel_logout_uri",
field=models.TextField(
blank=True,
validators=[authentik.lib.models.DomainlessURLValidator(schemes=("http", "https"))],
verbose_name="Back-Channel Logout URI",
),
),
migrations.AlterField(
model_name="oauth2provider",
name="_redirect_uris",
field=models.JSONField(default=list, verbose_name="Redirect URIs"),
),
]

View File

@@ -6,7 +6,7 @@ import json
from dataclasses import asdict, dataclass
from functools import cached_property
from hashlib import sha256
from typing import Any
from typing import TYPE_CHECKING, Any
from urllib.parse import urlparse, urlunparse
from cryptography.hazmat.primitives.asymmetric.ec import (
@@ -42,11 +42,14 @@ from authentik.core.models import (
)
from authentik.crypto.models import CertificateKeyPair
from authentik.lib.generators import generate_code_fixed_length, generate_id, generate_key
from authentik.lib.models import SerializerModel
from authentik.lib.models import DomainlessURLValidator, SerializerModel
from authentik.lib.utils.time import timedelta_string_validator
from authentik.providers.oauth2.id_token import IDToken, SubModes
from authentik.providers.oauth2.constants import SubModes
from authentik.sources.oauth.models import OAuthSource
if TYPE_CHECKING:
from authentik.providers.oauth2.id_token import IDToken
LOGGER = get_logger()
@@ -193,9 +196,14 @@ class OAuth2Provider(WebfingerProvider, Provider):
default=generate_client_secret,
)
_redirect_uris = models.JSONField(
default=dict,
default=list,
verbose_name=_("Redirect URIs"),
)
backchannel_logout_uri = models.TextField(
validators=[DomainlessURLValidator(schemes=("http", "https"))],
verbose_name=_("Back-Channel Logout URI"),
blank=True,
)
include_claims_in_id_token = models.BooleanField(
default=True,
@@ -480,13 +488,15 @@ class AccessToken(SerializerModel, ExpiringModel, BaseGrantModel):
return f"Access Token for {self.provider_id} for user {self.user_id}"
@property
def id_token(self) -> IDToken:
def id_token(self) -> "IDToken":
"""Load ID Token from json"""
from authentik.providers.oauth2.id_token import IDToken
raw_token = json.loads(self._id_token)
return from_dict(IDToken, raw_token)
@id_token.setter
def id_token(self, value: IDToken):
def id_token(self, value: "IDToken"):
self.token = value.to_access_token(self.provider)
self._id_token = json.dumps(asdict(value))
@@ -531,13 +541,15 @@ class RefreshToken(SerializerModel, ExpiringModel, BaseGrantModel):
return f"Refresh Token for {self.provider_id} for user {self.user_id}"
@property
def id_token(self) -> IDToken:
def id_token(self) -> "IDToken":
"""Load ID Token from json"""
from authentik.providers.oauth2.id_token import IDToken
raw_token = json.loads(self._id_token)
return from_dict(IDToken, raw_token)
@id_token.setter
def id_token(self, value: IDToken):
def id_token(self, value: "IDToken"):
self._id_token = json.dumps(asdict(value))
@property

View File

@@ -1,17 +1,34 @@
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from structlog.stdlib import get_logger
from authentik.core.models import AuthenticatedSession, User
from authentik.providers.oauth2.models import AccessToken, DeviceToken, RefreshToken
from authentik.providers.oauth2.tasks import backchannel_logout_notification_dispatch
LOGGER = get_logger()
@receiver(pre_delete, sender=AuthenticatedSession)
def user_session_deleted_oauth_tokens_removal(sender, instance: AuthenticatedSession, **_):
def user_session_deleted_oauth_backchannel_logout_and_tokens_removal(
sender, instance: AuthenticatedSession, **_
):
"""Revoke tokens upon user logout"""
AccessToken.objects.filter(
LOGGER.debug("Sending back-channel logout notifications signal!", session=instance)
access_tokens = AccessToken.objects.filter(
user=instance.user,
session__session__session_key=instance.session.session_key,
).delete()
)
backchannel_logout_notification_dispatch.send(
revocations=[
(token.provider_id, token.id_token.iss, token.session.user.uid)
for token in access_tokens
],
)
access_tokens.delete()
@receiver(post_save, sender=User)

View File

@@ -0,0 +1,68 @@
"""OAuth2 Provider Tasks"""
from django.utils.translation import gettext_lazy as _
from django_dramatiq_postgres.middleware import CurrentTask
from dramatiq.actor import actor
from structlog.stdlib import get_logger
from authentik.lib.utils.http import get_http_session
from authentik.providers.oauth2.models import OAuth2Provider
from authentik.providers.oauth2.utils import create_logout_token
from authentik.tasks.models import Task
LOGGER = get_logger()
@actor(description=_("Send a back-channel logout request to the registered client"))
def send_backchannel_logout_request(provider_pk: int, iss: str, sub: str = None) -> bool:
"""Send a back-channel logout request to the registered client
Args:
provider_pk: The OAuth2 provider's primary key
iss: The issuer URL for the logout token
sub: The subject identifier to include in the logout token
Returns:
bool: True if the request was sent successfully, False otherwise
"""
self: Task = CurrentTask.get_task()
LOGGER.debug("Sending back-channel logout request", provider_pk=provider_pk, sub=sub)
provider = OAuth2Provider.objects.filter(pk=provider_pk).first()
if provider is None:
return
# Generate the logout token
logout_token = create_logout_token(iss, provider, None, sub)
# Get the back-channel logout URI from the provider's dedicated backchannel_logout_uri field
# Back-channel logout requires explicit configuration - no fallback to redirect URIs
backchannel_logout_uri = provider.backchannel_logout_uri
if not backchannel_logout_uri:
self.info("No back-channel logout URI found for provider")
return
# Send the back-channel logout request
response = get_http_session().post(
backchannel_logout_uri,
data={"logout_token": logout_token},
headers={"Content-Type": "application/x-www-form-urlencoded"},
allow_redirects=True,
)
response.raise_for_status()
self.info("Back-channel logout successful", sub=sub)
return True
@actor(description=_("Handle backchannel logout notifications dispatched via signal"))
def backchannel_logout_notification_dispatch(revocations: list, **kwargs):
"""Handle backchannel logout notifications dispatched via signal"""
for revocation in revocations:
provider_pk, iss, sub = revocation
provider = OAuth2Provider.objects.filter(pk=provider_pk).first()
send_backchannel_logout_request.send_with_options(
args=(provider_pk, iss, sub),
rel_obj=provider,
)

View File

@@ -81,4 +81,46 @@ class TestAPI(APITestCase):
},
)
self.assertJSONEqual(response.content, {"redirect_uris": ["Invalid Regex Pattern: **"]})
def test_backchannel_logout_uri_validation(self):
"""Test backchannel_logout_uri API validation"""
response = self.client.post(
reverse("authentik_api:oauth2provider-list"),
data={
"name": generate_id(),
"authorization_flow": create_test_flow().pk,
"invalidation_flow": create_test_flow().pk,
"redirect_uris": [
{"matching_mode": "strict", "url": "http://goauthentik.io"},
],
"backchannel_logout_uri": "invalid-url",
},
)
self.assertEqual(response.status_code, 400)
def test_backchannel_logout_uri_create_and_retrieve(self):
"""Test creating and retrieving backchannel logout URI"""
response = self.client.post(
reverse("authentik_api:oauth2provider-list"),
data={
"name": generate_id(),
"authorization_flow": create_test_flow().pk,
"invalidation_flow": create_test_flow().pk,
"redirect_uris": [
{"matching_mode": "strict", "url": "http://goauthentik.io"},
],
"backchannel_logout_uri": "http://goauthentik.io/logout",
},
)
self.assertEqual(response.status_code, 201)
provider_data = response.json()
self.assertEqual(provider_data["backchannel_logout_uri"], "http://goauthentik.io/logout")
# Test retrieving the provider
provider_pk = provider_data["pk"]
response = self.client.get(
reverse("authentik_api:oauth2provider-detail", kwargs={"pk": provider_pk})
)
self.assertEqual(response.status_code, 200)
retrieved_data = response.json()
self.assertEqual(retrieved_data["backchannel_logout_uri"], "http://goauthentik.io/logout")

View File

@@ -0,0 +1,223 @@
"""Test OAuth2 Back-Channel Logout implementation"""
from unittest.mock import Mock, patch
import jwt
from django.test import RequestFactory
from django.utils import timezone
from dramatiq.results.errors import ResultFailure
from requests import Response
from requests.exceptions import HTTPError, Timeout
from authentik.core.models import Application, AuthenticatedSession, Session
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.lib.generators import generate_id
from authentik.providers.oauth2.id_token import hash_session_key
from authentik.providers.oauth2.models import (
AccessToken,
OAuth2Provider,
RedirectURI,
RedirectURIMatchingMode,
RefreshToken,
)
from authentik.providers.oauth2.tasks import send_backchannel_logout_request
from authentik.providers.oauth2.tests.utils import OAuthTestCase
from authentik.providers.oauth2.utils import create_logout_token
class TestBackChannelLogout(OAuthTestCase):
"""Test Back-Channel Logout functionality"""
def setUp(self) -> None:
super().setUp()
self.factory = RequestFactory()
self.user = create_test_admin_user()
self.app = Application.objects.create(name=generate_id(), slug="test-app")
self.provider = OAuth2Provider.objects.create(
name=generate_id(),
authorization_flow=create_test_flow(),
redirect_uris=[
RedirectURI(RedirectURIMatchingMode.STRICT, "http://testserver/callback"),
],
signing_key=self.keypair,
)
self.app.provider = self.provider
self.app.save()
def _create_session(self, session_key=None):
"""Create a session with the given key or a generated one"""
session_key = session_key or f"session-{generate_id()}"
session = Session.objects.create(
session_key=session_key,
expires=timezone.now() + timezone.timedelta(hours=1),
last_ip="255.255.255.255",
)
auth_session = AuthenticatedSession.objects.create(
session=session,
user=self.user,
)
return auth_session
def _create_token(
self, provider, user, session=None, token_type="access", token_id=None
): # nosec
"""Create a token of the specified type"""
token_id = token_id or f"{token_type}-token-{generate_id()}"
kwargs = {
"provider": provider,
"user": user,
"session": session,
"token": token_id,
"_id_token": "{}",
"auth_time": timezone.now(),
}
if token_type == "access": # nosec
return AccessToken.objects.create(**kwargs)
else: # refresh
return RefreshToken.objects.create(**kwargs)
def _create_provider(self, name=None):
"""Create an OAuth2 provider"""
name = name or f"provider-{generate_id()}"
provider = OAuth2Provider.objects.create(
name=name,
authorization_flow=create_test_flow(),
redirect_uris=[
RedirectURI(RedirectURIMatchingMode.STRICT, f"http://{name}/callback"),
],
signing_key=self.keypair,
)
return provider
def _create_logout_token(
self,
provider: OAuth2Provider | None = None,
session_id: str | None = None,
sub: str | None = None,
):
"""Create a logout token with the given parameters"""
provider = provider or self.provider
# Create a token with the same issuer that the view will expect
# Use the same request object that will be used in the test
request = self.factory.post("/backchannel_logout")
return create_logout_token(
iss=provider.get_issuer(request),
provider=provider,
session_key=session_id,
sub=sub,
)
def _decode_token(self, token, provider=None):
"""Helper to decode and validate a JWT token"""
provider = provider or self.provider
key, alg = provider.jwt_key
if alg != "HS256":
key = provider.signing_key.public_key
return jwt.decode(
token, key, algorithms=[alg], options={"verify_exp": False, "verify_aud": False}
)
def test_create_logout_token_variants(self):
"""Test creating logout tokens with different combinations of parameters"""
# Test case 1: With session_id only
session_id = "test-session-123"
token1 = self._create_logout_token(session_id=session_id)
decoded1 = self._decode_token(token1)
self.assertIn("iss", decoded1)
self.assertEqual(decoded1["aud"], self.provider.client_id)
self.assertIn("iat", decoded1)
self.assertIn("jti", decoded1)
self.assertEqual(decoded1["sid"], hash_session_key(session_id))
self.assertIn("events", decoded1)
self.assertIn("http://schemas.openid.net/event/backchannel-logout", decoded1["events"])
self.assertNotIn("sub", decoded1)
# Test case 2: With sub only
sub = "user-123"
token2 = self._create_logout_token(sub=sub)
decoded2 = self._decode_token(token2)
self.assertEqual(decoded2["sub"], sub)
self.assertIn("events", decoded2)
self.assertIn("http://schemas.openid.net/event/backchannel-logout", decoded2["events"])
self.assertNotIn("sid", decoded2)
# Test case 3: With both session_id and sub
token3 = self._create_logout_token(session_id=session_id, sub=sub)
decoded3 = self._decode_token(token3)
self.assertEqual(decoded3["sid"], hash_session_key(session_id))
self.assertEqual(decoded3["sub"], sub)
self.assertIn("events", decoded3)
@patch("authentik.providers.oauth2.tasks.get_http_session")
def test_send_backchannel_logout_request_scenarios(self, mock_get_session):
"""Test various scenarios for backchannel logout request task"""
# Setup provider with backchannel logout URI
self.provider.backchannel_logout_uri = "http://testserver/backchannel_logout"
self.provider.save()
# Setup mock session and response
mock_session = Mock()
mock_get_session.return_value = mock_session
mock_response = Mock(spec=Response)
mock_response.status_code = 200
mock_response.raise_for_status.return_value = None # No exception for successful request
mock_session.post.return_value = mock_response
result = send_backchannel_logout_request.send(
self.provider.pk, "http://testserver", sub="test-user-uid"
)
self.assertTrue(result)
mock_session.post.assert_called_once()
call_args = mock_session.post.call_args
self.assertIn("logout_token", call_args[1]["data"])
self.assertEqual(
call_args[1]["headers"]["Content-Type"], "application/x-www-form-urlencoded"
)
# Scenario 2: Failed request (400 response) - should raise exception
mock_session.post.reset_mock()
error_response = Mock(spec=Response)
error_response.status_code = 400
error_response.raise_for_status.side_effect = HTTPError("HTTP 400")
mock_session.post.return_value = error_response
with self.assertRaises(ResultFailure):
send_backchannel_logout_request.send(
self.provider.pk, "http://testserver", sub="test-user-uid"
).get_result()
# Scenario 3: No URI configured
mock_session.post.reset_mock()
self.provider.backchannel_logout_uri = ""
self.provider.save()
result = send_backchannel_logout_request.send(
self.provider.pk, "http://testserver", sub="test-user-uid"
).get_result()
self.assertIsNone(result)
mock_session.post.assert_not_called()
# Scenario 4: No sub provided - should fail
result = send_backchannel_logout_request.send(
self.provider.pk, "http://testserver"
).get_result()
self.assertIsNone(result)
# Scenario 5: Non-existent provider
result = send_backchannel_logout_request.send(
99999, "http://testserver", sub="test-user-uid"
).get_result()
self.assertIsNone(result)
# Scenario 6: Request timeout
mock_session.post.side_effect = Timeout("Request timed out")
self.provider.backchannel_logout_uri = "http://testserver/backchannel_logout"
self.provider.save()
with self.assertRaises(ResultFailure):
send_backchannel_logout_request.send(
self.provider.pk, "http://testserver", sub="test-user-uid"
).get_result()

View File

@@ -11,9 +11,9 @@ from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
from authentik.lib.generators import generate_id
from authentik.providers.oauth2.constants import ACR_AUTHENTIK_DEFAULT
from authentik.providers.oauth2.id_token import IDToken
from authentik.providers.oauth2.models import (
AccessToken,
IDToken,
OAuth2Provider,
RedirectURI,
RedirectURIMatchingMode,

View File

@@ -10,11 +10,11 @@ from django.utils import timezone
from authentik.core.models import Application, AuthenticatedSession, Session
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
from authentik.lib.generators import generate_id
from authentik.providers.oauth2.id_token import IDToken
from authentik.providers.oauth2.models import (
AccessToken,
ClientTypes,
DeviceToken,
IDToken,
OAuth2Provider,
RedirectURI,
RedirectURIMatchingMode,

View File

@@ -11,9 +11,9 @@ from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
from authentik.events.models import Event, EventAction
from authentik.lib.generators import generate_id
from authentik.providers.oauth2.id_token import IDToken
from authentik.providers.oauth2.models import (
AccessToken,
IDToken,
OAuth2Provider,
RedirectURI,
RedirectURIMatchingMode,

View File

@@ -1,8 +1,10 @@
"""OAuth2/OpenID Utils"""
import re
import uuid
from base64 import b64decode
from binascii import Error
from time import time
from typing import Any
from urllib.parse import urlparse
@@ -14,6 +16,7 @@ from structlog.stdlib import get_logger
from authentik.core.middleware import CTX_AUTH_VIA, KEY_USER
from authentik.events.models import Event, EventAction
from authentik.providers.oauth2.errors import BearerTokenError
from authentik.providers.oauth2.id_token import hash_session_key
from authentik.providers.oauth2.models import AccessToken, OAuth2Provider
LOGGER = get_logger()
@@ -211,3 +214,36 @@ class HttpResponseRedirectScheme(HttpResponseRedirect):
) -> None:
self.allowed_schemes = allowed_schemes or ["http", "https", "ftp"]
super().__init__(redirect_to, *args, **kwargs)
def create_logout_token(
iss: str,
provider: OAuth2Provider,
session_key: str | None = None,
sub: str | None = None,
) -> str:
"""Create a logout token for Back-Channel Logout
As per https://openid.net/specs/openid-connect-backchannel-1_0.html
"""
LOGGER.debug("Creating logout token", provider=provider, session_key=session_key, sub=sub)
# Create the logout token payload
payload = {
"iss": str(iss),
"aud": provider.client_id,
"iat": int(time()),
"jti": str(uuid.uuid4()),
"events": {
"http://schemas.openid.net/event/backchannel-logout": {},
},
}
# Add either sub or sid (or both)
if sub:
payload["sub"] = sub
if session_key:
payload["sid"] = hash_session_key(session_key)
# Encode the token
return provider.encode(payload)

View File

@@ -9,7 +9,8 @@ from django.views.decorators.csrf import csrf_exempt
from structlog.stdlib import get_logger
from authentik.providers.oauth2.errors import TokenIntrospectionError
from authentik.providers.oauth2.models import AccessToken, IDToken, OAuth2Provider, RefreshToken
from authentik.providers.oauth2.id_token import IDToken
from authentik.providers.oauth2.models import AccessToken, OAuth2Provider, RefreshToken
from authentik.providers.oauth2.utils import TokenResponse, authenticate_provider
LOGGER = get_logger()

View File

@@ -72,6 +72,8 @@ class ProviderInfoView(View):
"device_authorization_endpoint": self.request.build_absolute_uri(
reverse("authentik_providers_oauth2:device")
),
"backchannel_logout_supported": True,
"backchannel_logout_session_supported": True,
"response_types_supported": [
ResponseTypes.CODE,
ResponseTypes.ID_TOKEN,

View File

@@ -4,8 +4,6 @@
{% block head %}
<script src="{% versioned_script 'dist/rac/index-%v.js' %}" type="module"></script>
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<link rel="icon" href="{{ tenant.branding_favicon_url }}">
<link rel="shortcut icon" href="{{ tenant.branding_favicon_url }}">
{% include "base/header_js.html" %}

View File

@@ -190,6 +190,7 @@ class SAMLProviderSerializer(ProviderSerializer):
"sign_response",
"sp_binding",
"default_relay_state",
"default_name_id_policy",
"url_download_metadata",
"url_sso_post",
"url_sso_redirect",

View File

@@ -0,0 +1,31 @@
# Generated by Django 5.1.11 on 2025-06-18 09:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_saml", "0018_alter_samlprovider_acs_url"),
]
operations = [
migrations.AddField(
model_name="samlprovider",
name="default_name_id_policy",
field=models.TextField(
choices=[
("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "Email"),
("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", "Persistent"),
("urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName", "X509"),
(
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
"Windows",
),
("urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "Transient"),
("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", "Unspecified"),
],
default="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
),
),
]

View File

@@ -12,6 +12,7 @@ from authentik.core.models import PropertyMapping, Provider
from authentik.crypto.models import CertificateKeyPair
from authentik.lib.models import DomainlessURLValidator
from authentik.lib.utils.time import timedelta_string_validator
from authentik.sources.saml.models import SAMLNameIDPolicy
from authentik.sources.saml.processors.constants import (
DSA_SHA1,
ECDSA_SHA1,
@@ -179,6 +180,9 @@ class SAMLProvider(Provider):
default_relay_state = models.TextField(
default="", blank=True, help_text=_("Default relay_state value for IDP-initiated logins")
)
default_name_id_policy = models.TextField(
choices=SAMLNameIDPolicy.choices, default=SAMLNameIDPolicy.UNSPECIFIED
)
sign_assertion = models.BooleanField(default=True)
sign_response = models.BooleanField(default=False)

View File

@@ -205,6 +205,13 @@ class AssertionProcessor:
def get_name_id(self) -> Element:
"""Get NameID Element"""
name_id = Element(f"{{{NS_SAML_ASSERTION}}}NameID")
# For requests that don't specify a NameIDPolicy, check if we
# can fall back to the provider default
if (
self.auth_n_request.name_id_policy == SAML_NAME_ID_FORMAT_UNSPECIFIED
and self.provider.default_name_id_policy != SAML_NAME_ID_FORMAT_UNSPECIFIED
):
self.auth_n_request.name_id_policy = self.provider.default_name_id_policy
name_id.attrib["Format"] = self.auth_n_request.name_id_policy
# persistent is used as a fallback, so always generate it
persistent = self.http_request.user.uid

View File

@@ -13,6 +13,7 @@ from authentik.lib.xml import lxml_from_string
from authentik.providers.saml.exceptions import CannotHandleAssertion
from authentik.providers.saml.models import SAMLProvider
from authentik.providers.saml.utils.encoding import decode_base64_and_inflate
from authentik.sources.saml.models import SAMLNameIDPolicy
from authentik.sources.saml.processors.constants import (
DSA_SHA1,
NS_MAP,
@@ -175,7 +176,9 @@ class AuthNRequestParser:
def idp_initiated(self) -> AuthNRequest:
"""Create IdP Initiated AuthNRequest"""
relay_state = None
request = AuthNRequest(relay_state=None)
if self.provider.default_relay_state != "":
relay_state = self.provider.default_relay_state
return AuthNRequest(relay_state=relay_state)
request.relay_state = self.provider.default_relay_state
if self.provider.default_name_id_policy != SAMLNameIDPolicy.UNSPECIFIED:
request.name_id_policy = self.provider.default_name_id_policy
return request

View File

@@ -13,6 +13,7 @@ from authentik.crypto.models import CertificateKeyPair
from authentik.flows.models import Flow
from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.utils.encoding import PEM_FOOTER, PEM_HEADER
from authentik.sources.saml.models import SAMLNameIDPolicy
from authentik.sources.saml.processors.constants import (
NS_MAP,
NS_SAML_METADATA,
@@ -46,6 +47,7 @@ class ServiceProviderMetadata:
auth_n_request_signed: bool
assertion_signed: bool
name_id_policy: SAMLNameIDPolicy
signing_keypair: CertificateKeyPair | None = None
@@ -60,6 +62,7 @@ class ServiceProviderMetadata:
provider.issuer = self.entity_id
provider.sp_binding = self.acs_binding
provider.acs_url = self.acs_location
provider.default_name_id_policy = self.name_id_policy
if self.signing_keypair and self.auth_n_request_signed:
self.signing_keypair.name = f"Provider {name} - SAML Signing Certificate"
self.signing_keypair.save()
@@ -148,6 +151,11 @@ class ServiceProviderMetadataParser:
if signing_keypair:
self.check_signature(root, signing_keypair)
name_id_format = descriptor.findall(f"{{{NS_SAML_METADATA}}}NameIDFormat")
name_id_policy = SAMLNameIDPolicy.UNSPECIFIED
if len(name_id_format) > 0:
name_id_policy = SAMLNameIDPolicy(name_id_format[0].text)
return ServiceProviderMetadata(
entity_id=entity_id,
acs_binding=acs_binding,
@@ -155,4 +163,5 @@ class ServiceProviderMetadataParser:
auth_n_request_signed=auth_n_request_signed,
assertion_signed=assertion_signed,
signing_keypair=signing_keypair,
name_id_policy=name_id_policy,
)

View File

@@ -4,7 +4,7 @@
cacheDuration="PT604800S"
entityID="http://localhost:8080/saml/metadata">
<md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://localhost:8080/saml/acs"
index="1" />

View File

@@ -14,6 +14,7 @@ from authentik.lib.xml import lxml_from_string
from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.metadata import MetadataProcessor
from authentik.providers.saml.processors.metadata_parser import ServiceProviderMetadataParser
from authentik.sources.saml.models import SAMLNameIDPolicy
from authentik.sources.saml.processors.constants import ECDSA_SHA256, NS_MAP, NS_SAML_METADATA
@@ -86,6 +87,7 @@ class TestServiceProviderMetadataParser(TestCase):
self.assertEqual(provider.acs_url, "http://localhost:8080/saml/acs")
self.assertEqual(provider.issuer, "http://localhost:8080/saml/metadata")
self.assertEqual(provider.sp_binding, SAMLBindings.POST)
self.assertEqual(provider.default_name_id_policy, SAMLNameIDPolicy.EMAIL)
self.assertEqual(
len(provider.property_mappings.all()),
len(SAMLPropertyMapping.objects.exclude(managed__isnull=True)),

View File

@@ -75,7 +75,9 @@ TENANT_APPS = [
"pgtrigger",
"authentik.admin",
"authentik.api",
"authentik.core",
"authentik.crypto",
"authentik.enterprise",
"authentik.events",
"authentik.flows",
"authentik.outposts",
@@ -171,6 +173,7 @@ SPECTACULAR_SETTINGS = {
"PromptTypeEnum": "authentik.stages.prompt.models.FieldTypes",
"ProxyMode": "authentik.providers.proxy.models.ProxyMode",
"TaskAggregatedStatusEnum": "authentik.tasks.models.TaskStatus",
"SAMLNameIDPolicyEnum": "authentik.sources.saml.models.SAMLNameIDPolicy",
"UserTypeEnum": "authentik.core.models.UserTypes",
"UserVerificationEnum": "authentik.stages.authenticator_webauthn.models.UserVerification",
},
@@ -245,10 +248,12 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = True
MESSAGE_STORAGE = "authentik.root.messages.storage.ChannelsStorage"
MIDDLEWARE_FIRST = [
"django_prometheus.middleware.PrometheusBeforeMiddleware",
]
MIDDLEWARE = [
"django_tenants.middleware.default.DefaultTenantMiddleware",
"authentik.root.middleware.LoggingMiddleware",
"django_prometheus.middleware.PrometheusBeforeMiddleware",
"authentik.root.middleware.ClientIPMiddleware",
"authentik.stages.user_login.middleware.BoundSessionMiddleware",
"authentik.core.middleware.AuthenticationMiddleware",
@@ -261,6 +266,8 @@ MIDDLEWARE = [
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"authentik.core.middleware.ImpersonateMiddleware",
]
MIDDLEWARE_LAST = [
"django_prometheus.middleware.PrometheusAfterMiddleware",
]
@@ -496,7 +503,9 @@ _DISALLOWED_ITEMS = [
"SHARED_APPS",
"TENANT_APPS",
"INSTALLED_APPS",
"MIDDLEWARE_FIRST",
"MIDDLEWARE",
"MIDDLEWARE_LAST",
"AUTHENTICATION_BACKENDS",
"SPECTACULAR_SETTINGS",
"REST_FRAMEWORK",
@@ -514,16 +523,35 @@ SILENCED_SYSTEM_CHECKS = [
]
def _update_settings(app_path: str):
def subtract_list(a: list, b: list) -> list:
return [item for item in a if item not in b]
def _filter_and_update(apps: list[str]) -> None:
for _app in set(apps):
if not _app.startswith("authentik"):
continue
_update_settings(f"{_app}.settings")
def _update_settings(app_path: str) -> None:
try:
settings_module = importlib.import_module(app_path)
CONFIG.log("debug", "Loaded app settings", path=app_path)
SHARED_APPS.extend(getattr(settings_module, "SHARED_APPS", []))
TENANT_APPS.extend(getattr(settings_module, "TENANT_APPS", []))
new_shared_apps = subtract_list(getattr(settings_module, "SHARED_APPS", []), SHARED_APPS)
new_tenant_apps = subtract_list(getattr(settings_module, "TENANT_APPS", []), TENANT_APPS)
SHARED_APPS.extend(new_shared_apps)
TENANT_APPS.extend(new_tenant_apps)
_filter_and_update(new_shared_apps + new_tenant_apps)
MIDDLEWARE_FIRST.extend(getattr(settings_module, "MIDDLEWARE_FIRST", []))
MIDDLEWARE.extend(getattr(settings_module, "MIDDLEWARE", []))
AUTHENTICATION_BACKENDS.extend(getattr(settings_module, "AUTHENTICATION_BACKENDS", []))
SPECTACULAR_SETTINGS.update(getattr(settings_module, "SPECTACULAR_SETTINGS", {}))
REST_FRAMEWORK.update(getattr(settings_module, "REST_FRAMEWORK", {}))
for _attr in dir(settings_module):
if not _attr.startswith("__") and _attr not in _DISALLOWED_ITEMS:
globals()[_attr] = getattr(settings_module, _attr)
@@ -538,26 +566,13 @@ if DEBUG:
SHARED_APPS.insert(SHARED_APPS.index("django.contrib.staticfiles"), "daphne")
enable_debug_trace(True)
TENANT_APPS.append("authentik.core")
CONFIG.log("info", "Booting authentik", version=__version__)
# Attempt to load enterprise app, if available
try:
importlib.import_module("authentik.enterprise.apps")
CONFIG.log("info", "Enabled authentik enterprise")
TENANT_APPS.append("authentik.enterprise")
_update_settings("authentik.enterprise.settings")
except ImportError:
pass
# Load subapps's settings
for _app in set(SHARED_APPS + TENANT_APPS):
if not _app.startswith("authentik"):
continue
_update_settings(f"{_app}.settings")
_filter_and_update(SHARED_APPS + TENANT_APPS)
_update_settings("data.user_settings")
MIDDLEWARE = list(OrderedDict.fromkeys(MIDDLEWARE_FIRST + MIDDLEWARE + MIDDLEWARE_LAST))
SHARED_APPS = list(OrderedDict.fromkeys(SHARED_APPS + TENANT_APPS))
INSTALLED_APPS = list(OrderedDict.fromkeys(SHARED_APPS + TENANT_APPS))

View File

@@ -0,0 +1,32 @@
# Generated by Django 5.1.11 on 2025-06-18 09:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_sources_saml", "0019_migrate_usersamlsourceconnection_identifier"),
]
operations = [
migrations.AlterField(
model_name="samlsource",
name="name_id_policy",
field=models.TextField(
choices=[
("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "Email"),
("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", "Persistent"),
("urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName", "X509"),
(
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
"Windows",
),
("urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "Transient"),
("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", "Unspecified"),
],
default="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
help_text="NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent.",
),
),
]

View File

@@ -39,6 +39,7 @@ from authentik.sources.saml.processors.constants import (
SAML_NAME_ID_FORMAT_EMAIL,
SAML_NAME_ID_FORMAT_PERSISTENT,
SAML_NAME_ID_FORMAT_TRANSIENT,
SAML_NAME_ID_FORMAT_UNSPECIFIED,
SAML_NAME_ID_FORMAT_WINDOWS,
SAML_NAME_ID_FORMAT_X509,
SHA1,
@@ -73,6 +74,7 @@ class SAMLNameIDPolicy(models.TextChoices):
X509 = SAML_NAME_ID_FORMAT_X509
WINDOWS = SAML_NAME_ID_FORMAT_WINDOWS
TRANSIENT = SAML_NAME_ID_FORMAT_TRANSIENT
UNSPECIFIED = SAML_NAME_ID_FORMAT_UNSPECIFIED
class SAMLSource(Source):

File diff suppressed because one or more lines are too long

View File

@@ -44,6 +44,8 @@ class EmailStageSerializer(StageSerializer):
"subject",
"template",
"activate_user_on_success",
"recovery_max_attempts",
"recovery_cache_timeout",
]
extra_kwargs = {"password": {"write_only": True}}

View File

@@ -0,0 +1,28 @@
# Generated by Django 5.1.11 on 2025-07-23 11:26
import authentik.lib.utils.time
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_email", "0005_alter_emailstage_token_expiry"),
]
operations = [
migrations.AddField(
model_name="emailstage",
name="recovery_cache_timeout",
field=models.TextField(
default="minutes=5",
help_text="The time window used to count recent account recovery attempts. If the number of attempts exceed recovery_max_attempts within this period, further attempts will be rate-limited. (Format: hours=1;minutes=2;seconds=3).",
validators=[authentik.lib.utils.time.timedelta_string_validator],
),
),
migrations.AddField(
model_name="emailstage",
name="recovery_max_attempts",
field=models.PositiveIntegerField(default=5),
),
]

View File

@@ -16,6 +16,8 @@ from authentik.flows.models import Stage
from authentik.lib.config import CONFIG
from authentik.lib.utils.time import timedelta_string_validator
EMAIL_RECOVERY_MAX_ATTEMPTS = 5
LOGGER = get_logger()
@@ -70,6 +72,17 @@ class EmailStage(Stage):
use_ssl = models.BooleanField(default=False)
timeout = models.IntegerField(default=10)
from_address = models.EmailField(default="system@authentik.local")
recovery_max_attempts = models.PositiveIntegerField(default=EMAIL_RECOVERY_MAX_ATTEMPTS)
recovery_cache_timeout = models.TextField(
default="minutes=5",
validators=[timedelta_string_validator],
help_text=_(
"The time window used to count recent account recovery attempts. "
"If the number of attempts exceed recovery_max_attempts within "
"this period, further attempts will be rate-limited. "
"(Format: hours=1;minutes=2;seconds=3)."
),
)
activate_user_on_success = models.BooleanField(
default=False, help_text=_("Activate users upon completion of stage.")

View File

@@ -1,9 +1,12 @@
"""authentik multi-stage authentication engine"""
from datetime import timedelta
import math
from datetime import UTC, datetime, timedelta
from hashlib import sha256
from uuid import uuid4
from django.contrib import messages
from django.core.cache import cache
from django.http import HttpRequest, HttpResponse
from django.http.request import QueryDict
from django.template.exceptions import TemplateSyntaxError
@@ -27,6 +30,8 @@ from authentik.stages.email.models import EmailStage
from authentik.stages.email.tasks import send_mails
from authentik.stages.email.utils import TemplateEmailMessage
EMAIL_RECOVERY_CACHE_KEY = "goauthentik.io/stages/email/stage/"
PLAN_CONTEXT_EMAIL_SENT = "email_sent"
PLAN_CONTEXT_EMAIL_OVERRIDE = "email"
@@ -170,10 +175,66 @@ class EmailStageView(ChallengeStageView):
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
return super().challenge_invalid(response)
def _get_cache_key(self) -> str:
"""Return the cache key used for rate limiting email recovery attempts."""
user = self.get_pending_user()
user_email_hashed = sha256(user.email.lower().encode("utf-8")).hexdigest()
return EMAIL_RECOVERY_CACHE_KEY + user_email_hashed
def _is_rate_limited(self) -> int | None:
"""Check whether the email recovery attempt should be rate limited.
If the request should be rate limited, update the cache and return the
remaining time in minutes before the user is allowed to try again.
Otherwise, return None."""
cache_key = self._get_cache_key()
attempts = cache.get(cache_key, [])
stage = self.executor.current_stage
stage.refresh_from_db()
max_attempts = stage.recovery_max_attempts
cache_timeout_delta = timedelta_from_string(stage.recovery_cache_timeout)
_now = now()
start_window = _now - cache_timeout_delta
# Convert unix timestamps to datetime objects for comparison
recent_attempts_in_window = [
datetime.fromtimestamp(attempt, UTC)
for attempt in attempts
if datetime.fromtimestamp(attempt, UTC) > start_window
]
if len(recent_attempts_in_window) >= max_attempts:
retry_after = (min(recent_attempts_in_window) + cache_timeout_delta) - _now
minutes_left = max(1, math.ceil(retry_after.total_seconds() / 60))
return minutes_left
recent_attempts_in_window.append(_now)
# Convert datetime objects back to unix timestamps to update cache
recent_attempts_in_window = [attempt.timestamp() for attempt in recent_attempts_in_window]
cache.set(
cache_key,
recent_attempts_in_window,
int(cache_timeout_delta.total_seconds()),
)
return None
def challenge_invalid(self, response: ChallengeResponse) -> HttpResponse:
if minutes_left := self._is_rate_limited():
error = _(
"Too many account verification attempts. Please try again after {minutes} minutes."
).format(minutes=minutes_left)
messages.error(self.request, error)
return super().challenge_invalid(response)
if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
messages.error(self.request, _("No pending user."))
return super().challenge_invalid(response)
self.send_email()
messages.success(self.request, _("Email Successfully sent."))
# We can't call stage_ok yet, as we're still waiting

View File

@@ -1,7 +1,9 @@
"""email tests"""
from hashlib import sha256
from unittest.mock import MagicMock, PropertyMock, patch
from django.contrib import messages
from django.core import mail
from django.core.mail.backends.locmem import EmailBackend
from django.core.mail.backends.smtp import EmailBackend as SMTPEmailBackend
@@ -9,6 +11,7 @@ from django.test import RequestFactory
from django.urls import reverse
from django.utils.http import urlencode
from authentik.brands.models import Brand
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.flows.markers import StageMarker
from authentik.flows.models import FlowDesignation, FlowStageBinding, FlowToken
@@ -17,6 +20,7 @@ from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import QS_KEY_TOKEN, SESSION_KEY_PLAN, FlowExecutorView
from authentik.lib.config import CONFIG
from authentik.lib.generators import generate_id
from authentik.lib.tests.utils import get_request
from authentik.stages.consent.stage import SESSION_KEY_CONSENT_TOKEN
from authentik.stages.email.models import EmailStage
from authentik.stages.email.stage import PLAN_CONTEXT_EMAIL_OVERRIDE, EmailStageView
@@ -291,3 +295,173 @@ class TestEmailStage(FlowTestCase):
stage_view.get_full_url(**{QS_KEY_TOKEN: token}),
f"http://testserver/if/flow/{self.flow.slug}/?foo=bar&flow_token={token}",
)
def test_get_cache_key(self):
"""Test to ensure that the correct cache key is returned."""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
request = self.factory.post(url)
request.user = self.user
request.session = session
executor = FlowExecutorView(request=request, flow=self.flow)
executor.plan = plan
stage_view = EmailStageView(executor, request=request)
cache_key = stage_view._get_cache_key()
expected_hash = sha256(self.user.email.lower().encode("utf-8")).hexdigest()
expected_cache_key = "goauthentik.io/stages/email/stage/" + expected_hash
self.assertEqual(cache_key, expected_cache_key)
def test_is_rate_limited_returns_none(self):
"""Test to ensure None is returned if the request shouldn't be rate limited."""
self.stage.recovery_max_attempts = 2
self.stage.recovery_cache_timeout = "minutes=10"
self.stage.save()
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
request = self.factory.post(url)
request.user = self.user
request.session = session
executor = FlowExecutorView(request=request, flow=self.flow)
executor.current_stage = self.stage
executor.plan = plan
stage_view = EmailStageView(executor, request=request)
result = stage_view._is_rate_limited()
self.assertIsNone(result)
def test_is_rate_limited_returns_remaining_time(self):
"""Test to ensure the remaining time is returned if the request
should be rate limited."""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
request = self.factory.post(url)
request.user = self.user
request.session = session
executor = FlowExecutorView(request=request, flow=self.flow)
executor.current_stage = self.stage
executor.plan = plan
stage_view = EmailStageView(executor, request=request)
test_cases = [
# 2 attempts within 2 minutes
(2, "seconds=120", 2),
# 4 attempts within 5 minutes
(4, "minutes=5", 5),
# 6 attempts within 5 minutes. Although 299 seconds is less than
# 5 minutes, the user is intentionally shown "5 minutes". This is
# because an initial rate limiting message like "Try again after 4 minutes"
# can be confusing.
(6, "seconds=299", 5),
]
for test_case in test_cases:
max_attempts, cache_timeout, minutes_remaining = test_case
with self.subTest(
f"Test recovery with {max_attempts} max attempts and "
f"{cache_timeout} cache timeout seconds"
):
self.stage.recovery_max_attempts = max_attempts
self.stage.recovery_cache_timeout = cache_timeout
self.stage.save()
# Simulate multiple requests
for _ in range(max_attempts):
stage_view._is_rate_limited()
# The following request should be rate-limited
result = stage_view._is_rate_limited()
self.assertEqual(result, minutes_remaining)
def _challenge_invalid_helper(self):
"""Helper to test the challenge_invalid() method."""
self.stage.recovery_max_attempts = 1
self.stage.recovery_cache_timeout = "seconds=300"
self.stage.save()
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
request = get_request(url, user=self.user)
request.session = session
request.brand = Brand.objects.create(domain="foo-domain.com", default=True)
executor = FlowExecutorView(request=request, flow=self.flow)
executor.current_stage = self.stage
executor.plan = plan
stage_view = EmailStageView(executor, request=request)
challenge_response = stage_view.get_response_instance(data={})
challenge_response.is_valid()
return challenge_response, stage_view, request
def test_challenge_invalid_not_rate_limited(self):
"""Tests that the request is not rate limited and email is sent."""
challenge_response, stage_view, request = self._challenge_invalid_helper()
with patch.object(stage_view, "send_email") as mock_send_email:
result = stage_view.challenge_invalid(challenge_response)
self.assertEqual(result.status_code, 200)
mock_send_email.assert_called_once()
message_list = list(messages.get_messages(request))
self.assertEqual(len(message_list), 1)
self.assertEqual(
"Email Successfully sent.",
message_list[-1].message,
)
def test_challenge_invalid_returns_error_if_rate_limited(self):
"""Tests that an error is returned if the request is rate limited. Ensure
that an email is not sent."""
challenge_response, stage_view, request = self._challenge_invalid_helper()
# Initial request that shouldn't be rate limited
stage_view.challenge_invalid(challenge_response)
with patch.object(stage_view, "send_email") as mock_send_email:
# This next request should be rate limited
result = stage_view.challenge_invalid(challenge_response)
self.assertEqual(result.status_code, 200)
mock_send_email.assert_not_called()
message_list = list(messages.get_messages(request))
self.assertEqual(len(message_list), 2)
self.assertEqual(
"Too many account verification attempts. Please try again after 5 minutes.",
message_list[-1].message,
)

View File

@@ -61,6 +61,8 @@ entries:
subject: authentik
template: email/password_reset.html
activate_user_on_success: true
recovery_max_attempts: 5
recovery_cache_timeout: minutes=5
- identifiers:
name: default-recovery-user-write
id: default-recovery-user-write

View File

@@ -4689,6 +4689,14 @@
"format": "uuid"
},
"title": "Roles"
},
"children": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"title": "Children"
}
},
"required": []
@@ -8465,6 +8473,10 @@
},
"title": "Redirect uris"
},
"backchannel_logout_uri": {
"type": "string",
"title": "Back-Channel Logout URI"
},
"sub_mode": {
"type": "string",
"enum": [
@@ -9287,6 +9299,18 @@
"type": "string",
"title": "Default relay state",
"description": "Default relay_state value for IDP-initiated logins"
},
"default_name_id_policy": {
"type": "string",
"enum": [
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
"urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName",
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
],
"title": "Default name id policy"
}
},
"required": []
@@ -11714,7 +11738,8 @@
"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
"urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName",
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
],
"title": "Name id policy",
"description": "NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent."
@@ -14311,6 +14336,18 @@
"type": "boolean",
"title": "Activate user on success",
"description": "Activate users upon completion of stage."
},
"recovery_max_attempts": {
"type": "integer",
"minimum": 0,
"maximum": 2147483647,
"title": "Recovery max attempts"
},
"recovery_cache_timeout": {
"type": "string",
"minLength": 1,
"title": "Recovery cache timeout",
"description": "The time window used to count recent account recovery attempts. If the number of attempts exceed recovery_max_attempts within this period, further attempts will be rate-limited. (Format: hours=1;minutes=2;seconds=3)."
}
},
"required": []

19
go.mod
View File

@@ -5,12 +5,12 @@ go 1.24.0
require (
beryju.io/ldap v0.1.0
github.com/avast/retry-go/v4 v4.6.1
github.com/coreos/go-oidc/v3 v3.14.1
github.com/getsentry/sentry-go v0.34.1
github.com/coreos/go-oidc/v3 v3.15.0
github.com/getsentry/sentry-go v0.35.0
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-ldap/ldap/v3 v3.4.11
github.com/go-openapi/runtime v0.28.0
github.com/golang-jwt/jwt/v5 v5.2.3
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.2
github.com/gorilla/mux v1.8.1
@@ -22,14 +22,14 @@ require (
github.com/mitchellh/mapstructure v1.5.0
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
github.com/pires/go-proxyproto v0.8.1
github.com/prometheus/client_golang v1.22.0
github.com/prometheus/client_golang v1.23.0
github.com/redis/go-redis/v9 v9.11.0
github.com/sethvargo/go-envconfig v1.3.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2025064.2
goauthentik.io/api/v3 v3.2025064.7
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.16.0
@@ -69,18 +69,17 @@ require (
github.com/oklog/ulid v1.3.1 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
go.mongodb.org/mongo-driver v1.14.0 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

36
go.sum
View File

@@ -16,8 +16,8 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/coreos/go-oidc/v3 v3.15.0 h1:R6Oz8Z4bqWR7VFQ+sPSvZPQv4x8M+sJkDO5ojgwlyAg=
github.com/coreos/go-oidc/v3 v3.15.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -26,8 +26,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/getsentry/sentry-go v0.34.1 h1:HSjc1C/OsnZttohEPrrqKH42Iud0HuLCXpv8cU1pWcw=
github.com/getsentry/sentry-go v0.34.1/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
github.com/getsentry/sentry-go v0.35.0 h1:+FJNlnjJsZMG3g0/rmmP7GiKjQoUF5EXfEtBwtPtkzY=
github.com/getsentry/sentry-go v0.35.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
@@ -67,8 +67,8 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
@@ -140,14 +140,14 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
@@ -185,8 +185,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
goauthentik.io/api/v3 v3.2025064.2 h1:WFXe12hfsRe29EkLCxWCvrdK6peAkCA6ftdEh04hKLg=
goauthentik.io/api/v3 v3.2025064.2/go.mod h1:82lqAz4jxzl6Cg0YDbhNtvvTG2rm6605ZhdJFnbbsl8=
goauthentik.io/api/v3 v3.2025064.7 h1:nh7Uh9K/XsHEz6hmPvZCK+aQC9uqwOto5hTCvtdvXPc=
goauthentik.io/api/v3 v3.2025064.7/go.mod h1:82lqAz4jxzl6Cg0YDbhNtvvTG2rm6605ZhdJFnbbsl8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
@@ -211,8 +211,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@@ -17,6 +17,7 @@ type LDAPGroup struct {
Uid string
GidNumber string
Member []string
MemberOf []string
IsSuperuser bool
IsVirtualGroup bool
Attributes map[string]interface{}
@@ -38,6 +39,7 @@ func (lg *LDAPGroup) Entry() *ldap.Entry {
"ak-superuser": {strconv.FormatBool(lg.IsSuperuser)},
"objectClass": objectClass,
"member": lg.Member,
"memberOf": lg.MemberOf,
"cn": {lg.CN},
"uid": {lg.Uid},
"sAMAccountName": {lg.CN},
@@ -52,7 +54,8 @@ func FromAPIGroup(g api.Group, si server.LDAPServerInstance) *LDAPGroup {
CN: g.Name,
Uid: string(g.Pk),
GidNumber: si.GetGroupGidNumber(g),
Member: si.UsersForGroup(g),
Member: si.MembersForGroup(g),
MemberOf: si.MemberOfForGroup(g),
IsVirtualGroup: false,
IsSuperuser: *g.IsSuperuser,
Attributes: g.Attributes,

View File

@@ -155,7 +155,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
if needGroups {
errs.Go(func() error {
gapisp := sentry.StartSpan(errCtx, "authentik.providers.ldap.search.api_group")
searchReq, skip := utils.ParseFilterForGroup(c.CoreApi.CoreGroupsList(gapisp.Context()).IncludeUsers(true), parsedFilter, false)
searchReq, skip := utils.ParseFilterForGroup(c.CoreApi.CoreGroupsList(gapisp.Context()).IncludeUsers(true).IncludeChildren(true), parsedFilter, false)
if skip {
req.Log().Trace("Skip backend request")
return nil

View File

@@ -165,7 +165,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
for _, u := range g.UsersObj {
if flag.UserPk == u.Pk {
// TODO: Is there a better way to clone this object?
fg := api.NewGroup(g.Pk, g.NumPk, g.Name, g.ParentName, []api.GroupMember{u}, []api.Role{})
fg := api.NewGroup(g.Pk, g.NumPk, g.Name, g.ParentName, []api.GroupMember{u}, []api.Role{}, []api.GroupChild{})
fg.SetUsers([]int32{flag.UserPk})
if g.Parent.IsSet() {
if p := g.Parent.Get(); p != nil {

View File

@@ -32,7 +32,8 @@ type LDAPServerInstance interface {
GetUserGidNumber(api.User) string
GetGroupGidNumber(api.Group) string
UsersForGroup(api.Group) []string
MembersForGroup(api.Group) []string
MemberOfForGroup(api.Group) []string
GetFlags(dn string) *flags.UserFlags
SetFlags(dn string, flags *flags.UserFlags)

View File

@@ -15,12 +15,27 @@ func (pi *ProviderInstance) GroupsForUser(user api.User) []string {
return groups
}
func (pi *ProviderInstance) UsersForGroup(group api.Group) []string {
func (pi *ProviderInstance) MembersForGroup(group api.Group) []string {
users := make([]string, len(group.UsersObj))
for i, user := range group.UsersObj {
users[i] = pi.GetUserDN(user.Username)
}
return users
children := make([]string, len(group.ChildrenObj))
for i, child := range group.ChildrenObj {
children[i] = pi.GetGroupDN(child.Name)
}
return append(users, children...)
}
func (pi *ProviderInstance) MemberOfForGroup(group api.Group) []string {
if group.ParentName.IsSet() {
parent := group.ParentName.Get()
if parent != nil {
return []string{pi.GetGroupDN(*group.ParentName.Get())}
}
}
return []string{}
}
func (pi *ProviderInstance) GetUserDN(user string) string {

View File

@@ -20,6 +20,7 @@ import (
"goauthentik.io/internal/config"
"goauthentik.io/internal/outpost/proxyv2/codecs"
"goauthentik.io/internal/outpost/proxyv2/constants"
"goauthentik.io/internal/outpost/proxyv2/filesystemstore"
"goauthentik.io/internal/outpost/proxyv2/redisstore"
"goauthentik.io/internal/utils"
)
@@ -90,7 +91,10 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL)
return rs, nil
}
dir := os.TempDir()
cs := sessions.NewFilesystemStore(dir)
cs, err := filesystemstore.GetPersistentStore(dir)
if err != nil {
return nil, err
}
cs.Codecs = codecs.CodecsFromPairs(maxAge, []byte(*p.CookieSecret))
// https://github.com/markbates/goth/commit/7276be0fdf719ddff753f3574ef0f967e4a5a5f7
// set the maxLength of the cookies stored on the disk to a larger number to prevent issues with:
@@ -123,7 +127,7 @@ func (a *Application) getAllCodecs() []securecookie.Codec {
}
func (a *Application) Logout(ctx context.Context, filter func(c Claims) bool) error {
if _, ok := a.sessions.(*sessions.FilesystemStore); ok {
if _, ok := a.sessions.(*filesystemstore.Store); ok {
files, err := os.ReadDir(os.TempDir())
if err != nil {
return err

View File

@@ -0,0 +1,226 @@
package filesystemstore
import (
"context"
"errors"
"os"
"path"
"strings"
"sync"
"syscall"
"time"
"github.com/gorilla/sessions"
log "github.com/sirupsen/logrus"
)
const (
SessionCleanupInterval = 5 * time.Minute
SessionCleanupLockFileName = "session-cleanup.lock"
SessionFilePrefix = "session_"
SessionTestFile = SessionFilePrefix + "write_test"
)
var (
ErrSessionCleanupAlreadyRunning = errors.New("session cleanup is already running by another instance")
ErrSessionStoreNoPermission = errors.New("path is not writable")
ErrSessionStorePathNotExist = errors.New("path does not exist")
)
type Store struct {
*sessions.FilesystemStore
storePath string
log *log.Entry
}
// NewStore checks if the specified store path exists, is writable and creates a new filesystem session store.
func NewStore(storePath string, keyPairs ...[]byte) (*Store, error) {
if storePath == "" {
storePath = os.TempDir()
}
// check if path exists
_, err := os.ReadDir(storePath)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, ErrSessionStorePathNotExist
}
return nil, err
}
// check if path is writable
testPath := path.Join(storePath, SessionTestFile)
testFile, err := os.OpenFile(testPath, os.O_CREATE, 0600)
if err != nil {
if errors.Is(err, os.ErrPermission) {
return nil, ErrSessionStoreNoPermission
}
return nil, err
}
if err = testFile.Close(); err != nil {
return nil, err
}
if err = os.Remove(testPath); err != nil {
return nil, err
}
return &Store{
FilesystemStore: sessions.NewFilesystemStore(storePath, keyPairs...),
storePath: storePath,
log: log.WithField("logger", "authentik.outpost.proxyv2.filesystemstore"),
}, nil
}
// SessionCleanup acquires a file lock to ensure only one instance runs at a time,
// then checks and deletes expired session files from the filesystem session store.
// It supports context-based cancellation to allow graceful shutdowns or timeouts.
func (s *Store) SessionCleanup(ctx context.Context) error {
s.log.Info("Starting session cleanup")
lockPath := path.Join(s.storePath, SessionCleanupLockFileName)
lockFile, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600)
if err != nil {
return err
}
defer func() {
if closeErr := lockFile.Close(); closeErr != nil {
s.log.WithError(closeErr).Warn("failed to close lock file")
}
}()
err = syscall.Flock(int(lockFile.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
if err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == syscall.EWOULDBLOCK {
return ErrSessionCleanupAlreadyRunning
}
return err
}
defer func() {
if flockErr := syscall.Flock(int(lockFile.Fd()), syscall.LOCK_UN); flockErr != nil {
s.log.WithError(flockErr).Warn("failed to unlock file")
}
if removeErr := os.Remove(lockPath); removeErr != nil {
s.log.WithError(removeErr).Warn("failed to remove lock file")
}
}()
return s.sessionCleanup(ctx)
}
// sessionCleanup checks the modification time of all session files and removes them
// when they reach the configured maximum age in the session store.
// Since the FilesystemStore from Gorilla does not have a session cleanup function,
// it is only necessary for the filesystem session store.
func (s *Store) sessionCleanup(ctx context.Context) error {
files, err := os.ReadDir(s.storePath)
if err != nil {
return err
}
var errs []error
for _, file := range files {
select {
case <-ctx.Done():
s.log.Warn("session cleanup interrupted during file processing")
return ctx.Err()
default:
}
if !strings.HasPrefix(file.Name(), SessionFilePrefix) {
continue
}
fullPath := path.Join(s.storePath, file.Name())
stat, err := os.Lstat(fullPath)
if err != nil {
s.log.WithError(err).WithField("path", fullPath).Warning("failed to read stats from file")
errs = append(errs, err)
continue
}
modTime := stat.ModTime()
if time.Since(modTime) <= time.Duration(s.Options.MaxAge)*time.Second {
s.log.WithField("max-age", s.Options.MaxAge).WithField("modified", modTime.String()).Debug("session still valid")
continue
}
s.log.WithField("path", fullPath).WithField("modified", modTime.String()).Info("cleanup expired session")
if err = os.Remove(fullPath); err != nil {
s.log.WithError(err).WithField("path", fullPath).Warn("failed to delete session")
errs = append(errs, err)
continue
}
}
return errors.Join(errs...)
}
var (
cancelCleanup context.CancelFunc
doneCleanup chan struct{}
globalStore *Store
mu sync.Mutex
)
// GetPersistentStore creates a new filesystem store if it is the first time the function has been called,
// or if the path string has changed. It then stores this in the globalStore variable.
// If the function is called multiple times, the store from the variable is returned to ensure that only one instance is running.
func GetPersistentStore(path string) (*Store, error) {
mu.Lock()
defer mu.Unlock()
if globalStore == nil || globalStore.storePath != path {
if cancelCleanup != nil {
cancelCleanup()
if doneCleanup != nil {
<-doneCleanup
}
}
store, err := NewStore(path)
if err != nil {
return nil, err
}
globalStore = store
ctx, cancel := context.WithCancel(context.Background())
cancelCleanup = cancel
doneCleanup = make(chan struct{})
go func() {
defer close(doneCleanup)
globalStore.log.Info("Scheduling session cleanup job")
ticker := time.NewTicker(SessionCleanupInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
err := globalStore.SessionCleanup(ctx)
if err == nil {
continue
}
if errors.Is(err, ErrSessionCleanupAlreadyRunning) {
globalStore.log.WithError(err).Warn("Session cleanup is locked by another job")
continue
}
globalStore.log.WithError(err).Warn("Session cleanup returned error")
}
}
}()
}
return globalStore, nil
}
// StopPersistentStore stops the cleanup background job and clears the globalStore variable.
func StopPersistentStore() {
mu.Lock()
defer mu.Unlock()
if cancelCleanup != nil {
cancelCleanup()
if doneCleanup != nil {
<-doneCleanup
}
}
cancelCleanup = nil
doneCleanup = nil
globalStore = nil
}

View File

@@ -0,0 +1,146 @@
package filesystemstore
import (
"context"
"os"
"path"
"path/filepath"
"syscall"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func createTempSessionFile(t *testing.T, dir string, modTime time.Time) string {
t.Helper()
path := filepath.Join(dir, "session_test")
err := os.WriteFile(path, []byte("session data"), 0600)
require.NoError(t, err)
err = os.Chtimes(path, modTime, modTime)
require.NoError(t, err)
return path
}
func TestNewStore_PathNotExist(t *testing.T) {
_, err := NewStore("/invalid_path")
assert.ErrorIs(t, err, ErrSessionStorePathNotExist)
}
func TestNewStore_PathNotWritable(t *testing.T) {
storePath := path.Join(os.TempDir(), "test")
err := os.Mkdir(storePath, 0400)
require.NoError(t, err)
_, err = NewStore(storePath)
assert.ErrorIs(t, err, ErrSessionStoreNoPermission)
_ = os.RemoveAll(storePath)
}
func TestNewStore(t *testing.T) {
tmpDir := t.TempDir()
store, err := NewStore(tmpDir)
assert.NoError(t, err)
assert.NotEmpty(t, store)
}
func TestSessionCleanup_RemovesExpired(t *testing.T) {
tmpDir := t.TempDir()
store, err := NewStore(tmpDir)
require.NoError(t, err)
store.Options.MaxAge = 1 // 1 second
// Create an expired session file
oldTime := time.Now().Add(-10 * time.Second)
createTempSessionFile(t, tmpDir, oldTime)
ctx := context.Background()
err = store.SessionCleanup(ctx)
assert.NoError(t, err)
// File should be deleted
files, _ := os.ReadDir(tmpDir)
assert.Empty(t, files)
}
func TestSessionCleanup_PreservesValid(t *testing.T) {
tmpDir := t.TempDir()
store, err := NewStore(tmpDir)
require.NoError(t, err)
store.Options.MaxAge = 3600 // 1 hour
// Create a valid (non-expired) session file
modTime := time.Now().Add(-10 * time.Second)
createTempSessionFile(t, tmpDir, modTime)
ctx := context.Background()
err = store.SessionCleanup(ctx)
assert.NoError(t, err)
// File should still exist
files, _ := os.ReadDir(tmpDir)
assert.Len(t, files, 1)
}
func TestSessionCleanup_ContextCancel(t *testing.T) {
tmpDir := t.TempDir()
store, err := NewStore(tmpDir)
require.NoError(t, err)
ctx, cancel := context.WithCancel(context.Background())
cancel() // cancel immediately
err = store.SessionCleanup(ctx)
assert.ErrorIs(t, err, context.Canceled)
}
func TestSessionCleanup_AlreadyRunning(t *testing.T) {
tmpDir := t.TempDir()
store, err := NewStore(tmpDir)
require.NoError(t, err)
// Manually acquire the lock before calling SessionCleanup
lockPath := path.Join(tmpDir, SessionCleanupLockFileName)
lockFile, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600)
require.NoError(t, err, "failed to create lock file")
err = syscall.Flock(int(lockFile.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
require.NoError(t, err, "failed to acquire lock for test")
// Run SessionCleanup while lock is held
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err = store.SessionCleanup(ctx)
assert.ErrorIs(t, err, ErrSessionCleanupAlreadyRunning)
// Unlock and clean up
_ = syscall.Flock(int(lockFile.Fd()), syscall.LOCK_UN)
_ = lockFile.Close()
_ = os.Remove(lockPath)
}
func TestPersistentStore_ReusesStore(t *testing.T) {
tmpDir := t.TempDir()
store1, err := GetPersistentStore(tmpDir)
require.NoError(t, err)
assert.NotNil(t, store1)
store2, err := GetPersistentStore(tmpDir)
require.NoError(t, err)
assert.Equal(t, store1, store2)
StopPersistentStore()
}
func TestStopPersistentStore(t *testing.T) {
tmpDir := t.TempDir()
_, err := GetPersistentStore(tmpDir)
require.NoError(t, err)
StopPersistentStore()
// call again should not panic
StopPersistentStore()
}

View File

@@ -37,11 +37,16 @@ ARG VERSION
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
LABEL org.opencontainers.image.url=https://goauthentik.io
LABEL org.opencontainers.image.description="goauthentik.io LDAP outpost, see https://goauthentik.io for more info."
LABEL org.opencontainers.image.source=https://github.com/goauthentik/authentik
LABEL org.opencontainers.image.version=${VERSION}
LABEL org.opencontainers.image.revision=${GIT_BUILD_HASH}
LABEL org.opencontainers.image.authors="Authentik Security Inc." \
org.opencontainers.image.description="goauthentik.io LDAP outpost, see https://goauthentik.io for more info." \
org.opencontainers.image.documentation="https://docs.goauthentik.io" \
org.opencontainers.image.licenses="https://github.com/goauthentik/authentik/blob/main/LICENSE" \
org.opencontainers.image.revision=${GIT_BUILD_HASH} \
org.opencontainers.image.source="https://github.com/goauthentik/authentik" \
org.opencontainers.image.title="authentik LDAP outpost image" \
org.opencontainers.image.url="https://goauthentik.io" \
org.opencontainers.image.vendor="Authentik Security Inc." \
org.opencontainers.image.version=${VERSION}
RUN apt-get update && \
apt-get upgrade -y && \

View File

@@ -9,7 +9,7 @@
"version": "0.0.0",
"license": "MIT",
"devDependencies": {
"aws-cdk": "^2.1022.0",
"aws-cdk": "^2.1024.0",
"cross-env": "^10.0.0"
},
"engines": {
@@ -24,9 +24,9 @@
"license": "MIT"
},
"node_modules/aws-cdk": {
"version": "2.1022.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1022.0.tgz",
"integrity": "sha512-GHCu+tDtYMqCiElCl7Fad2/Bt2GmtXEV3dynudoAsV9PlL5ETeLmEN7jflDQxhmr7KhKpQeZJo/PM0DoWCvoHw==",
"version": "2.1024.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1024.0.tgz",
"integrity": "sha512-hY0iVT2gPX/QOQXL7RSP2sqIRI/4BYU27vSmbhZxLEj//c3pkMkd9QpIHj7gOhyWC2gf6n5JuYPw27Dgw8FEdA==",
"dev": true,
"license": "Apache-2.0",
"bin": {

View File

@@ -10,7 +10,7 @@
"node": ">=20"
},
"devDependencies": {
"aws-cdk": "^2.1022.0",
"aws-cdk": "^2.1024.0",
"cross-env": "^10.0.0"
}
}

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-07-28 16:09+0000\n"
"POT-Creation-Date: 2025-08-07 00:12+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -1483,27 +1483,27 @@ msgstr ""
msgid "Invalid Regex Pattern: {url}"
msgstr ""
#: authentik/providers/oauth2/id_token.py
#: authentik/providers/oauth2/constants.py
msgid "Based on the Hashed User ID"
msgstr ""
#: authentik/providers/oauth2/id_token.py
#: authentik/providers/oauth2/constants.py
msgid "Based on user ID"
msgstr ""
#: authentik/providers/oauth2/id_token.py
#: authentik/providers/oauth2/constants.py
msgid "Based on user UUID"
msgstr ""
#: authentik/providers/oauth2/id_token.py
#: authentik/providers/oauth2/constants.py
msgid "Based on the username"
msgstr ""
#: authentik/providers/oauth2/id_token.py
#: authentik/providers/oauth2/constants.py
msgid "Based on the User's Email. This is recommended over the UPN method."
msgstr ""
#: authentik/providers/oauth2/id_token.py
#: authentik/providers/oauth2/constants.py
msgid ""
"Based on the User's UPN, only works if user has a 'upn' attribute set. Use "
"this method only if you have different UPN and Mail domains."
@@ -1617,6 +1617,10 @@ msgstr ""
msgid "Redirect URIs"
msgstr ""
#: authentik/providers/oauth2/models.py
msgid "Back-Channel Logout URI"
msgstr ""
#: authentik/providers/oauth2/models.py
msgid "Include claims in id_token"
msgstr ""
@@ -1732,6 +1736,14 @@ msgstr ""
msgid "Device Tokens"
msgstr ""
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr ""
#: authentik/providers/oauth2/tasks.py
msgid "Handle backchannel logout notifications dispatched via signal"
msgstr ""
#: authentik/providers/oauth2/views/authorize.py
#: authentik/providers/saml/views/flows.py
#, python-brace-format
@@ -3238,6 +3250,13 @@ msgstr ""
msgid "Account Confirmation"
msgstr ""
#: authentik/stages/email/models.py
msgid ""
"The time window used to count recent account recovery attempts. If the "
"number of attempts exceed recovery_max_attempts within this period, further "
"attempts will be rate-limited. (Format: hours=1;minutes=2;seconds=3)."
msgstr ""
#: authentik/stages/email/models.py
msgid "Activate users upon completion of stage."
msgstr ""
@@ -3262,6 +3281,13 @@ msgstr ""
msgid "Email sent."
msgstr ""
#: authentik/stages/email/stage.py
#, python-brace-format
msgid ""
"Too many account verification attempts. Please try again after {minutes} "
"minutes."
msgstr ""
#: authentik/stages/email/stage.py
msgid "Email Successfully sent."
msgstr ""

Binary file not shown.

View File

@@ -19,7 +19,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-06-04 00:12+0000\n"
"POT-Creation-Date: 2025-08-07 00:12+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Marc Schmitt, 2025\n"
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
@@ -33,6 +33,10 @@ msgstr ""
msgid "Version history"
msgstr "Historique des versions"
#: authentik/admin/tasks.py
msgid "Update latest version info."
msgstr "Mettre à jour les dernières informations de version."
#: authentik/admin/tasks.py
#, python-brace-format
msgid "New version {version} available!"
@@ -88,10 +92,25 @@ msgstr "Instances du plan"
msgid "authentik Export - {date}"
msgstr "Export authentik - {date}"
#: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py
#, python-brace-format
msgid "Successfully imported {count} files."
msgstr "{count} fichiers importés avec succès."
#: authentik/blueprints/v1/tasks.py
msgid "Find blueprints as `blueprints_find` does, but return a safe dict."
msgstr ""
"Cherche les plans comme le fait `blueprints_find`, mais renvoie un safe "
"dict."
#: authentik/blueprints/v1/tasks.py
msgid "Find blueprints and check if they need to be created in the database."
msgstr ""
"Cherche les plans et vérifie s'ils doivent être créés dans la base de "
"données."
#: authentik/blueprints/v1/tasks.py
msgid "Apply single blueprint."
msgstr "Applique un seul plan."
#: authentik/blueprints/v1/tasks.py
msgid "Remove blueprints which couldn't be fetched."
msgstr "Supprime les plans qui n'ont pas pu être récupérés."
#: authentik/brands/models.py
msgid ""
@@ -129,10 +148,6 @@ msgstr "Marques"
msgid "User does not have access to application."
msgstr "L'utilisateur n'a pas accès à l'application."
#: authentik/core/api/devices.py
msgid "Extra description not available"
msgstr "Description supplémentaire indisponible"
#: authentik/core/api/groups.py
msgid "Cannot set group as parent of itself."
msgstr "Impossible de définir le groupe en tant que parent de lui-même."
@@ -379,6 +394,10 @@ msgstr "Jetons"
msgid "View token's key"
msgstr "Voir la clé du jeton"
#: authentik/core/models.py
msgid "Set a token's key"
msgstr "Définir la clé d'un jeton"
#: authentik/core/models.py
msgid "Property Mapping"
msgstr "Mappage de propriété"
@@ -434,6 +453,14 @@ msgstr "{source} liée avec succès !"
msgid "Source is not configured for enrollment."
msgstr "La source n'est pas configurée pour l'inscription."
#: authentik/core/tasks.py
msgid "Remove expired objects."
msgstr "Supprime les objets expirés"
#: authentik/core/tasks.py
msgid "Remove temporary users created by SAML Sources."
msgstr "Supprime les utilisateurs temporaires créés par les sources SAML."
#: authentik/core/templates/if/error.html
msgid "Go home"
msgstr "Retourner à l'accueil"
@@ -486,6 +513,12 @@ msgstr "Paire de clé/certificat"
msgid "Certificate-Key Pairs"
msgstr "Paires de clé/certificat"
#: authentik/crypto/tasks.py
msgid "Discover, import and update certificates from the filesystem."
msgstr ""
"Découvre, importe et met à jour les certificats depuis le système de "
"fichiers."
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr "Entreprise est requis pour créer/mettre à jour cet objet."
@@ -538,6 +571,18 @@ msgstr "Politiques d'unicité des mots de passe"
msgid "User Password History"
msgstr "Historique des mots de passe utilisateur"
#: authentik/enterprise/policies/unique_password/tasks.py
msgid ""
"Check if any UniquePasswordPolicy exists, and if not, purge the password "
"history table."
msgstr ""
"Vérifie si une politique de mot de passe unique existe et, si ce n'est pas "
"le cas, purge la table de l'historique des mots de passe."
#: authentik/enterprise/policies/unique_password/tasks.py
msgid "Remove user password history that are too old."
msgstr "Supprime l'historique des mots de passe utilisateur trop anciens."
#: authentik/enterprise/policy.py
msgid "Enterprise required to access this feature."
msgstr "Entreprise est requis pour accéder à cette fonctionnalité."
@@ -586,6 +631,42 @@ msgstr "Mappage de propriété Google Workspace"
msgid "Google Workspace Provider Mappings"
msgstr "Mappages de propriété Google Workspace"
#: authentik/enterprise/providers/google_workspace/tasks.py
msgid "Sync Google Workspace provider objects."
msgstr "Synchronise les objets du fournisseur Google Workspace."
#: authentik/enterprise/providers/google_workspace/tasks.py
msgid "Full sync for Google Workspace provider."
msgstr "Synchronisation complète pour le fournisseur Google Workspace."
#: authentik/enterprise/providers/google_workspace/tasks.py
msgid "Sync a direct object (user, group) for Google Workspace provider."
msgstr ""
"Synchronise un objet direct (utilisateur, groupe) pour le fournisseur Google"
" Workspace."
#: authentik/enterprise/providers/google_workspace/tasks.py
msgid ""
"Dispatch syncs for a direct object (user, group) for Google Workspace "
"providers."
msgstr ""
"Déclenche des synchronisations pour un objet direct (utilisateur, groupe) "
"pour les fournisseurs Google Workspace."
#: authentik/enterprise/providers/google_workspace/tasks.py
msgid "Sync a related object (memberships) for Google Workspace provider."
msgstr ""
"Synchronise un objet lié (appartenances) pour le fournisseur Google "
"Workspace."
#: authentik/enterprise/providers/google_workspace/tasks.py
msgid ""
"Dispatch syncs for a related object (memberships) for Google Workspace "
"providers."
msgstr ""
"Déclenche des synchronisations pour un objet lié (appartenances) pour les "
"fournisseurs Google Workspace."
#: authentik/enterprise/providers/microsoft_entra/models.py
msgid "Microsoft Entra Provider User"
msgstr "Utilisateur du fournisseur Microsoft Entra"
@@ -614,6 +695,42 @@ msgstr "Mappage de propriété Microsoft Entra"
msgid "Microsoft Entra Provider Mappings"
msgstr "Mappages de propriété Microsoft Entra"
#: authentik/enterprise/providers/microsoft_entra/tasks.py
msgid "Sync Microsoft Entra provider objects."
msgstr "Synchronise les objets du fournisseur Microsoft Entra."
#: authentik/enterprise/providers/microsoft_entra/tasks.py
msgid "Full sync for Microsoft Entra provider."
msgstr "Synchronisation complète pour le fournisseur Microsoft Entra."
#: authentik/enterprise/providers/microsoft_entra/tasks.py
msgid "Sync a direct object (user, group) for Microsoft Entra provider."
msgstr ""
"Synchronise un objet direct (utilisateur, groupe) pour le fournisseur "
"Microsoft Entra."
#: authentik/enterprise/providers/microsoft_entra/tasks.py
msgid ""
"Dispatch syncs for a direct object (user, group) for Microsoft Entra "
"providers."
msgstr ""
"Déclenche les synchronisations pour un objet direct (utilisateur, groupe) "
"pour les fournisseurs Microsoft Entra."
#: authentik/enterprise/providers/microsoft_entra/tasks.py
msgid "Sync a related object (memberships) for Microsoft Entra provider."
msgstr ""
"Synchronise un objet lié (appartenances) pour le fournisseur Microsoft "
"Entra."
#: authentik/enterprise/providers/microsoft_entra/tasks.py
msgid ""
"Dispatch syncs for a related object (memberships) for Microsoft Entra "
"providers."
msgstr ""
"Déclenche des synchronisations pour un objet lié (appartenances) pour les "
"fournisseurs Microsoft Entra."
#: authentik/enterprise/providers/ssf/models.py
#: authentik/providers/oauth2/models.py
msgid "Signing Key"
@@ -652,8 +769,12 @@ msgid "SSF Stream Events"
msgstr "Évènements du flux SSF"
#: authentik/enterprise/providers/ssf/tasks.py
msgid "Failed to send request"
msgstr "Échec de l'envoi de la requête"
msgid "Dispatch SSF events."
msgstr "Distribue les événements SSF."
#: authentik/enterprise/providers/ssf/tasks.py
msgid "Send an SSF event."
msgstr "Envoye un événement SSF."
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
msgid "Endpoint Authenticator Google Device Trust Connector Stage"
@@ -725,10 +846,9 @@ msgstr "Étape Source"
msgid "Source Stages"
msgstr "Étapes Source"
#: authentik/events/api/tasks.py
#, python-brace-format
msgid "Successfully started task {name}."
msgstr "La tâche {name} a été démarrée avec succès."
#: authentik/enterprise/tasks.py
msgid "Update enterprise license status."
msgstr "Mettre à jour le statut de licence entreprise."
#: authentik/events/models.py
msgid "Event"
@@ -840,6 +960,15 @@ msgstr ""
"Définir à quel groupe d'utilisateur cette notification doit être envoyée et "
"affichée. Si laissé vide, les notifications ne seront pas envoyées."
#: authentik/events/models.py
msgid ""
"When enabled, notification will be sent to user the user that triggered the "
"event.When destination_group is configured, notification is sent to both."
msgstr ""
"Lorsque cette option est activée, une notification est envoyée à "
"l'utilisateur qui a déclenché l'événement. Si destination_group est "
"configuré, la notification est envoyée aux deux."
#: authentik/events/models.py
msgid "Notification Rule"
msgstr "Règle de Notification"
@@ -856,10 +985,6 @@ msgstr "Mappage de Webhook"
msgid "Webhook Mappings"
msgstr "Mappages de Webhook"
#: authentik/events/models.py
msgid "Run task"
msgstr "Lancer la tâche"
#: authentik/events/models.py
msgid "System Task"
msgstr "Tâches du système"
@@ -868,9 +993,31 @@ msgstr "Tâches du système"
msgid "System Tasks"
msgstr "Tâches du système"
#: authentik/events/system_tasks.py
msgid "Task has not been run yet."
msgstr "Tâche pas encore exécutée."
#: authentik/events/tasks.py
msgid "Dispatch new event notifications."
msgstr "Envoye les notifications d'un nouvel événement."
#: authentik/events/tasks.py
msgid ""
"Check if policies attached to NotificationRule match event and dispatch "
"notification tasks."
msgstr ""
"Vérifier si les politiques attachées à une règle de notifications "
"correspondent à l'événement et déclenche les tâches de notification."
#: authentik/events/tasks.py
msgid "Send notification."
msgstr "Envoye une notification."
#: authentik/events/tasks.py
msgid "Cleanup events for GDPR compliance."
msgstr "Nettoye les événements pour la conformité au RGPD."
#: authentik/events/tasks.py
msgid "Cleanup seen notifications and notifications whose event expired."
msgstr ""
"Nettoye les notifications vues et les notifications dont l'événement a "
"expiré."
#: authentik/flows/api/flows.py
#, python-brace-format
@@ -1051,32 +1198,6 @@ msgstr ""
"Si activé, le fournisseur ne changera ou ne créera pas d'objets auprès du "
"système distant."
#: authentik/lib/sync/outgoing/tasks.py
msgid "Starting full provider sync"
msgstr "Démarrage d'une synchronisation complète du fournisseur"
#: authentik/lib/sync/outgoing/tasks.py
msgid "Syncing users"
msgstr "Synchronisation des utilisateurs"
#: authentik/lib/sync/outgoing/tasks.py
msgid "Syncing groups"
msgstr "Synchronisation des groupes"
#: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format
msgid "Syncing page {page} of {object_type}"
msgstr "Synchronisation de la page {page} de {object_type}"
#: authentik/lib/sync/outgoing/tasks.py
msgid "Dropping mutating request due to dry run"
msgstr "Abandon de la requête de mutation en raison d'une simulation"
#: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format
msgid "Stopping sync due to error: {error}"
msgstr "Arrêt de la synchronisation due à l'erreur : {error}"
#: authentik/lib/utils/time.py
#, python-format
msgid "%(value)s is not in the correct format of 'hours=3;minutes=1'."
@@ -1183,6 +1304,32 @@ msgstr "Avant-poste"
msgid "Outposts"
msgstr "Avant-postes"
#: authentik/outposts/tasks.py
msgid "Update cached state of service connection."
msgstr "Met à jour l'état mis en cache de la connexion de service."
#: authentik/outposts/tasks.py
msgid "Create/update/monitor/delete the deployment of an Outpost."
msgstr "Crée/met à jour/surveille/supprime le déploiement d'un avant-poste."
#: authentik/outposts/tasks.py
msgid "Ensure that all Outposts have valid Service Accounts and Tokens."
msgstr ""
"S'assure que tous les avant-postes ont des comptes de service et des jetons "
"valides."
#: authentik/outposts/tasks.py
msgid "Send update to outpost"
msgstr "Envoye une mise à jour à un avant-poste"
#: authentik/outposts/tasks.py
msgid "Checks the local environment and create Service connections."
msgstr "Vérifie l'environnement local et crée les connexions de service."
#: authentik/outposts/tasks.py
msgid "Terminate session on all outposts."
msgstr "Met fin à la session sur tous les avant-postes."
#: authentik/policies/denied.py
msgid "Access denied"
msgstr "Accès refusé"
@@ -1517,29 +1664,29 @@ msgstr "Rechercher dans l'annuaire LDAP complet"
msgid "Invalid Regex Pattern: {url}"
msgstr "Pattern de regex invalide : {url}"
#: authentik/providers/oauth2/id_token.py
#: authentik/providers/oauth2/constants.py
msgid "Based on the Hashed User ID"
msgstr "Basé sur le hash de l'ID utilisateur"
#: authentik/providers/oauth2/id_token.py
#: authentik/providers/oauth2/constants.py
msgid "Based on user ID"
msgstr "Basé sur l'ID de l'utilisateur"
#: authentik/providers/oauth2/id_token.py
#: authentik/providers/oauth2/constants.py
msgid "Based on user UUID"
msgstr "Basé sur le UUID de l'utilisateur"
#: authentik/providers/oauth2/id_token.py
#: authentik/providers/oauth2/constants.py
msgid "Based on the username"
msgstr "Basé sur le nom d'utilisateur"
#: authentik/providers/oauth2/id_token.py
#: authentik/providers/oauth2/constants.py
msgid "Based on the User's Email. This is recommended over the UPN method."
msgstr ""
"Basé sur le courriel utilisateur. Ceci est recommandé par rapport à la "
"méthode UPN."
#: authentik/providers/oauth2/id_token.py
#: authentik/providers/oauth2/constants.py
msgid ""
"Based on the User's UPN, only works if user has a 'upn' attribute set. Use "
"this method only if you have different UPN and Mail domains."
@@ -1662,6 +1809,10 @@ msgstr "Secret du client"
msgid "Redirect URIs"
msgstr "URIs de redirection"
#: authentik/providers/oauth2/models.py
msgid "Back-Channel Logout URI"
msgstr "URI de déconnexion Back-Channel"
#: authentik/providers/oauth2/models.py
msgid "Include claims in id_token"
msgstr "Include les demandes utilisateurs dans id_token"
@@ -1790,6 +1941,15 @@ msgstr "Jeton d'équipement"
msgid "Device Tokens"
msgstr "Jetons d'équipement"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr "Envoyer une requête de déconnexion Back-Channel au client enregistré"
#: authentik/providers/oauth2/tasks.py
msgid "Handle backchannel logout notifications dispatched via signal"
msgstr ""
"Gérer les notifications de déconnexion Back-Channel envoyées via un signal"
#: authentik/providers/oauth2/views/authorize.py
#: authentik/providers/saml/views/flows.py
#, python-brace-format
@@ -1901,6 +2061,10 @@ msgstr "Fournisseur Proxy"
msgid "Proxy Providers"
msgstr "Fournisseur de Proxy"
#: authentik/providers/proxy/tasks.py
msgid "Terminate session on Proxy outpost."
msgstr "Met fin à la session sur l'avant-poste Proxy."
#: authentik/providers/rac/models.py authentik/stages/user_login/models.py
msgid ""
"Determines how long a session lasts. Default of 0 means that the sessions "
@@ -2245,6 +2409,35 @@ msgstr "Mappage fournisseur SCIM"
msgid "SCIM Provider Mappings"
msgstr "Mappages fournisseur SCIM"
#: authentik/providers/scim/tasks.py
msgid "Sync SCIM provider objects."
msgstr "Synchronise les objets du fournisseur SCIM."
#: authentik/providers/scim/tasks.py
msgid "Full sync for SCIM provider."
msgstr "Synchronisation complète pour le fournisseur SCIM."
#: authentik/providers/scim/tasks.py
msgid "Sync a direct object (user, group) for SCIM provider."
msgstr ""
"Synchronise un objet direct (utilisateur, groupe) pour le fournisseur SCIM."
#: authentik/providers/scim/tasks.py
msgid "Dispatch syncs for a direct object (user, group) for SCIM providers."
msgstr ""
"Déclenche les synchronisations pour un objet direct (utilisateur, groupe) "
"pour les fournisseurs SCIM."
#: authentik/providers/scim/tasks.py
msgid "Sync a related object (memberships) for SCIM provider."
msgstr "Synchronise un objet lié (appartenances) pour le fournisseur SCIM."
#: authentik/providers/scim/tasks.py
msgid "Dispatch syncs for a related object (memberships) for SCIM providers."
msgstr ""
"Déclenche des synchronisations pour un objet lié (appartenances) pour les "
"fournisseurs SCIM."
#: authentik/rbac/models.py
msgid "Role"
msgstr "Rôle"
@@ -2399,6 +2592,14 @@ msgstr "Connexion du groupe à la source Kerberos"
msgid "Group Kerberos Source Connections"
msgstr "Connexions du groupe à la source Kerberos"
#: authentik/sources/kerberos/tasks.py
msgid "Check connectivity for Kerberos sources."
msgstr "Vérifie la connectivité des sources Kerberos."
#: authentik/sources/kerberos/tasks.py
msgid "Sync Kerberos source."
msgstr "Synchronise la source Kerberos."
#: authentik/sources/kerberos/views.py
msgid "SPNEGO authentication required"
msgstr "Authentification SPNEGO requise"
@@ -2566,6 +2767,18 @@ msgstr "Connexions du groupe à la source LDAP"
msgid "Password does not match Active Directory Complexity."
msgstr "Le mot de passe ne correspond pas à la complexité d'Active Directory."
#: authentik/sources/ldap/tasks.py
msgid "Check connectivity for LDAP source."
msgstr "Vérifie la connectivité des sources LDAP."
#: authentik/sources/ldap/tasks.py
msgid "Sync LDAP source."
msgstr "Synchronise la source LDAP."
#: authentik/sources/ldap/tasks.py
msgid "Sync page for LDAP source."
msgstr "Synchronise une page pour la source LDAP."
#: authentik/sources/oauth/clients/oauth2.py
msgid "No token received."
msgstr "Pas de jeton reçu."
@@ -2715,6 +2928,14 @@ msgstr "Source d'OAuth Azure AD"
msgid "Azure AD OAuth Sources"
msgstr "Source d'OAuth Azure AD"
#: authentik/sources/oauth/models.py
msgid "Entra ID OAuth Source"
msgstr "Source d'OAuth Entra ID"
#: authentik/sources/oauth/models.py
msgid "Entra ID OAuth Sources"
msgstr "Sources d'OAuth Entra ID"
#: authentik/sources/oauth/models.py
msgid "OpenID OAuth Source"
msgstr "Source d'OAuth OpenID"
@@ -2771,6 +2992,14 @@ msgstr "Connexion du groupe à la source OAuth"
msgid "Group OAuth Source Connections"
msgstr "Connexions du groupe à la source OAuth"
#: authentik/sources/oauth/tasks.py
msgid ""
"Update OAuth sources' config from well_known, and JWKS info from the "
"configured URL."
msgstr ""
"Met à jour la configuration des sources OAuth à partir de well_known, et les"
" informations JWKS à partir de l'URL configurée."
#: authentik/sources/oauth/views/callback.py
#, python-brace-format
msgid "Authentication failed: {reason}"
@@ -2829,6 +3058,10 @@ msgstr "Connexion du groupe à la source Plex"
msgid "Group Plex Source Connections"
msgstr "Connexions du groupe à la source OAuth"
#: authentik/sources/plex/tasks.py
msgid "Check the validity of a Plex source."
msgstr "Vérifie la validité d'une source Plex."
#: authentik/sources/saml/models.py
msgid "Redirect Binding"
msgstr "Liaison de Redirection"
@@ -3273,6 +3506,13 @@ msgstr "Type d'appareil WebAuthn"
msgid "WebAuthn Device types"
msgstr "Types d'appareil WebAuthn"
#: authentik/stages/authenticator_webauthn/tasks.py
msgid ""
"Background task to import FIDO Alliance MDS blob and AAGUIDs into database."
msgstr ""
"Tâche de fond pour importer le blob MDS de la FIDO Alliance et les AAGUID "
"dans la base de données."
#: authentik/stages/captcha/models.py
msgid "Public key, acquired your captcha Provider."
msgstr "Clé publique, acquise auprès de votre fournisseur captcha."
@@ -3371,6 +3611,17 @@ msgstr "Réinitialiser le Mot de Passe"
msgid "Account Confirmation"
msgstr "Confirmation du Compte"
#: authentik/stages/email/models.py
msgid ""
"The time window used to count recent account recovery attempts. If the "
"number of attempts exceed recovery_max_attempts within this period, further "
"attempts will be rate-limited. (Format: hours=1;minutes=2;seconds=3)."
msgstr ""
"La fenêtre de temps utilisée pour compter les tentatives récentes de "
"récupération de compte. Si le nombre de tentatives dépasse "
"recovery_max_attempts au cours de cette période, les tentatives "
"supplémentaires seront limitées. (Format : hours=1;minutes=2;seconds=3)."
#: authentik/stages/email/models.py
msgid "Activate users upon completion of stage."
msgstr "Activer les utilisateurs à la complétion de l'étape."
@@ -3395,10 +3646,23 @@ msgstr "Pas d'utilisateurs en attente."
msgid "Email sent."
msgstr "Email envoyé."
#: authentik/stages/email/stage.py
#, python-brace-format
msgid ""
"Too many account verification attempts. Please try again after {minutes} "
"minutes."
msgstr ""
"Trop de tentatives de vérification de compte. Veuillez réessayer après "
"{minutes} minutes."
#: authentik/stages/email/stage.py
msgid "Email Successfully sent."
msgstr "Couriel envoyé avec succès."
#: authentik/stages/email/tasks.py
msgid "Send email."
msgstr "Envoye un courriel."
#: authentik/stages/email/templates/email/account_confirmation.html
#: authentik/stages/email/templates/email/account_confirmation.txt
msgid "Welcome!"
@@ -3867,6 +4131,16 @@ msgstr ""
"souvenir de moi ne sera pas proposée. (Format: "
"hours=-1;minutes=-2;seconds=-3)"
#: authentik/stages/user_login/models.py
msgid ""
"When set to a non-zero value, authentik will save a cookie with a longer "
"expiry,to remember the device the user is logging in from. (Format: "
"hours=-1;minutes=-2;seconds=-3)"
msgstr ""
"Si cette valeur est différente de zéro, authentik enregistrera un cookie "
"avec une expiration plus longue, afin de se souvenir de l'appareil à partir "
"duquel l'utilisateur se connecte. (Format : hours=-1;minutes=-2;seconds=-3)"
#: authentik/stages/user_login/models.py
msgid "User Login Stage"
msgstr "Étape de connexion utlisateur"
@@ -3918,6 +4192,38 @@ msgid "Failed to update user. Please try again later."
msgstr ""
"Échec de mise à jour de l'utilisateur. Merci de réessayer ultérieurement,"
#: authentik/tasks/models.py
msgid "Tenant this task belongs to"
msgstr "Tenant auquel cette tâche appartient"
#: authentik/tasks/models.py
msgid "Retry failed task"
msgstr "Relancer la tâche échouée"
#: authentik/tasks/models.py
msgid "Worker status"
msgstr "État du worker"
#: authentik/tasks/models.py
msgid "Worker statuses"
msgstr "États du worker"
#: authentik/tasks/schedules/models.py
msgid "Unique schedule identifier"
msgstr "Identifiant unique des planifications"
#: authentik/tasks/schedules/models.py
msgid "User schedule identifier"
msgstr "Identifiant utilisateur des planifications"
#: authentik/tasks/schedules/models.py
msgid "Manually trigger a schedule"
msgstr "Déclencher manuellement une planification"
#: authentik/tasks/tasks.py
msgid "Remove old worker statuses."
msgstr "Supprime les anciens statuts des workers."
#: authentik/tenants/models.py
msgid ""
"Schema name must start with t_, only contain lowercase letters and numbers "
@@ -4010,3 +4316,76 @@ msgstr "Domaine"
#: authentik/tenants/models.py
msgid "Domains"
msgstr "Domaines"
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
msgid "Queue name"
msgstr "Nom de la file"
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
msgid "Dramatiq actor name"
msgstr "Nom de l'acteur Dramatiq"
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
msgid "Message body"
msgstr "Corps du message"
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
msgid "Task status"
msgstr "État de la tâche"
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
msgid "Task last modified time"
msgstr "Heure de dernière modification de la tâche"
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
msgid "Task result"
msgstr "Résultat de la tâche"
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
msgid "Result expiry time"
msgstr "Délai d'expiration du résultat"
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
msgid "Task"
msgstr "Tâche"
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
msgid "Tasks"
msgstr "Tâches"
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
#, python-format
msgid "%(value)s is not a valid crontab"
msgstr "%(value)s n'est pas un crontab valide"
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
msgid "Dramatiq actor to call"
msgstr "Acteur Dramatiq à invoquer"
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
msgid "Args to send to the actor"
msgstr "Args à passer à l'acteur"
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
msgid "Kwargs to send to the actor"
msgstr "Kwargs à passer à l'acteur"
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
msgid "Options to send to the actor"
msgstr "Options à passer à l'acteur"
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
msgid "When to schedule tasks"
msgstr "Quand planifier les tâches"
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
msgid "Pause this schedule"
msgstr "Mettre cette planification en pause"
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
msgid "Schedule"
msgstr "Planification"
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
msgid "Schedules"
msgstr "Planifications"

View File

@@ -186,19 +186,19 @@ class MetricsMiddleware(Middleware):
"The total number of dead-lettered tasks.",
self.labels,
)
self.inprogress_messages = Gauge(
f"{self.prefix}_tasks_inprogress",
self.in_progress_messages = Gauge(
f"{self.prefix}_tasks_in_progress",
"The number of tasks in progress.",
self.labels,
multiprocess_mode="livesum",
)
self.inprogress_delayed_messages = Gauge(
f"{self.prefix}_tasks_delayed_inprogress",
self.in_progress_delayed_messages = Gauge(
f"{self.prefix}_tasks_delayed_in_progress",
"The number of delayed tasks in memory.",
self.labels,
)
self.messages_durations = Histogram(
f"{self.prefix}_tasks_duration_miliseconds",
f"{self.prefix}_tasks_duration_milliseconds",
"The time spent processing tasks.",
self.labels,
buckets=(
@@ -244,15 +244,15 @@ class MetricsMiddleware(Middleware):
def before_delay_message(self, broker: Broker, message: Message):
self.delayed_messages.add(message.message_id)
self.inprogress_delayed_messages.labels(*self._make_labels(message)).inc()
self.in_progress_delayed_messages.labels(*self._make_labels(message)).inc()
def before_process_message(self, broker: Broker, message: Message):
labels = self._make_labels(message)
if message.message_id in self.delayed_messages:
self.delayed_messages.remove(message.message_id)
self.inprogress_delayed_messages.labels(*labels).dec()
self.in_progress_delayed_messages.labels(*labels).dec()
self.inprogress_messages.labels(*labels).inc()
self.in_progress_messages.labels(*labels).inc()
self.message_start_times[message.message_id] = current_millis()
def after_process_message(
@@ -269,7 +269,7 @@ class MetricsMiddleware(Middleware):
message_duration = current_millis() - message_start_time
self.messages_durations.labels(*labels).observe(message_duration)
self.inprogress_messages.labels(*labels).dec()
self.in_progress_messages.labels(*labels).dec()
self.total_messages.labels(*labels).inc()
if exception is not None:
self.total_errored_messages.labels(*labels).inc()

View File

@@ -10,7 +10,8 @@
"license": "MIT",
"dependencies": {
"deepmerge-ts": "^7.1.5",
"prism-react-renderer": "^2.4.1"
"prism-react-renderer": "^2.4.1",
"react-dom": ">=18"
},
"devDependencies": {
"@docusaurus/theme-common": "^3.8.1",
@@ -34,8 +35,7 @@
"@docusaurus/theme-common": "^3.8.1",
"@docusaurus/theme-search-algolia": "^3.8.1",
"@docusaurus/types": "^3.8.0",
"react": ">=18",
"react-dom": ">=18"
"react": ">=18"
},
"peerDependenciesMeta": {
"@docusaurus/theme-search-algolia": {
@@ -43,9 +43,6 @@
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
@@ -4656,9 +4653,9 @@
}
},
"node_modules/@types/react-dom": {
"version": "19.1.6",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz",
"integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==",
"version": "19.1.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz",
"integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -15821,25 +15818,25 @@
}
},
"node_modules/react": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"version": "19.1.1",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"version": "19.1.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"scheduler": "^0.26.0"
},
"peerDependencies": {
"react": "^19.1.0"
"react": "^19.1.1"
}
},
"node_modules/react-fast-compare": {
@@ -17958,9 +17955,9 @@
}
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"dev": true,
"license": "Apache-2.0",
"bin": {

View File

@@ -661,16 +661,16 @@
}
},
"node_modules/@gerrit0/mini-shiki": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.7.0.tgz",
"integrity": "sha512-7iY9wg4FWXmeoFJpUL2u+tsmh0d0jcEJHAIzVxl3TG4KL493JNnisdLAILZ77zcD+z3J0keEXZ+lFzUgzQzPDg==",
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.9.2.tgz",
"integrity": "sha512-Tvsj+AOO4Z8xLRJK900WkyfxHsZQu+Zm1//oT1w443PO6RiYMoq/4NGOhaNuZoUMYsjKIAPVQ6eOFMddj6yphQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/engine-oniguruma": "^3.7.0",
"@shikijs/langs": "^3.7.0",
"@shikijs/themes": "^3.7.0",
"@shikijs/types": "^3.7.0",
"@shikijs/engine-oniguruma": "^3.9.2",
"@shikijs/langs": "^3.9.2",
"@shikijs/themes": "^3.9.2",
"@shikijs/types": "^3.9.2",
"@shikijs/vscode-textmate": "^10.0.2"
}
},
@@ -845,40 +845,40 @@
}
},
"node_modules/@shikijs/engine-oniguruma": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.7.0.tgz",
"integrity": "sha512-5BxcD6LjVWsGu4xyaBC5bu8LdNgPCVBnAkWTtOCs/CZxcB22L8rcoWfv7Hh/3WooVjBZmFtyxhgvkQFedPGnFw==",
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.9.2.tgz",
"integrity": "sha512-Vn/w5oyQ6TUgTVDIC/BrpXwIlfK6V6kGWDVVz2eRkF2v13YoENUvaNwxMsQU/t6oCuZKzqp9vqtEtEzKl9VegA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.7.0",
"@shikijs/types": "3.9.2",
"@shikijs/vscode-textmate": "^10.0.2"
}
},
"node_modules/@shikijs/langs": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.7.0.tgz",
"integrity": "sha512-1zYtdfXLr9xDKLTGy5kb7O0zDQsxXiIsw1iIBcNOO8Yi5/Y1qDbJ+0VsFoqTlzdmneO8Ij35g7QKF8kcLyznCQ==",
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.9.2.tgz",
"integrity": "sha512-X1Q6wRRQXY7HqAuX3I8WjMscjeGjqXCg/Sve7J2GWFORXkSrXud23UECqTBIdCSNKJioFtmUGJQNKtlMMZMn0w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.7.0"
"@shikijs/types": "3.9.2"
}
},
"node_modules/@shikijs/themes": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.7.0.tgz",
"integrity": "sha512-VJx8497iZPy5zLiiCTSIaOChIcKQwR0FebwE9S3rcN0+J/GTWwQ1v/bqhTbpbY3zybPKeO8wdammqkpXc4NVjQ==",
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.9.2.tgz",
"integrity": "sha512-6z5lBPBMRfLyyEsgf6uJDHPa6NAGVzFJqH4EAZ+03+7sedYir2yJBRu2uPZOKmj43GyhVHWHvyduLDAwJQfDjA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.7.0"
"@shikijs/types": "3.9.2"
}
},
"node_modules/@shikijs/types": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.7.0.tgz",
"integrity": "sha512-MGaLeaRlSWpnP0XSAum3kP3a8vtcTsITqoEPYdt3lQG3YCdQH4DnEhodkYcNMcU0uW0RffhoD1O3e0vG5eSBBg==",
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.9.2.tgz",
"integrity": "sha512-/M5L0Uc2ljyn2jKvj4Yiah7ow/W+DJSglVafvWAJ/b8AZDeeRAdMu3c2riDzB7N42VD+jSnWxeP9AKtd4TfYVw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -904,13 +904,13 @@
}
},
"node_modules/@types/node": {
"version": "24.1.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
"integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==",
"version": "24.2.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz",
"integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.8.0"
"undici-types": "~7.10.0"
}
},
"node_modules/@types/unist": {
@@ -2668,9 +2668,9 @@
}
},
"node_modules/tmp": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
"integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz",
"integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2704,13 +2704,13 @@
}
},
"node_modules/typedoc": {
"version": "0.28.8",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.8.tgz",
"integrity": "sha512-16GfLopc8icHfdvqZDqdGBoS2AieIRP2rpf9mU+MgN+gGLyEQvAO0QgOa6NJ5QNmQi0LFrDY9in4F2fUNKgJKA==",
"version": "0.28.9",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.9.tgz",
"integrity": "sha512-aw45vwtwOl3QkUAmWCnLV9QW1xY+FSX2zzlit4MAfE99wX+Jij4ycnpbAWgBXsRrxmfs9LaYktg/eX5Bpthd3g==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@gerrit0/mini-shiki": "^3.7.0",
"@gerrit0/mini-shiki": "^3.9.0",
"lunr": "^2.3.9",
"markdown-it": "^14.1.0",
"minimatch": "^9.0.5",
@@ -2724,13 +2724,13 @@
"pnpm": ">= 10"
},
"peerDependencies": {
"typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x"
"typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x"
}
},
"node_modules/typedoc-plugin-markdown": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.7.1.tgz",
"integrity": "sha512-HN/fHLm2S6MD4HX8txfB4eWvVBzX/mEYy5U5s1KTAdh3E5uX5/lilswqTzZlPTT6fNZInAboAdFGpbAuBKnE4A==",
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.8.0.tgz",
"integrity": "sha512-BQqXnT9PETe6WEFf8bcsvvGEGQHbwTo/BFyY+RUIsSB05Y0Wn56iF+fK1PY2OKJJIhV4kp4dp7osaP9Bm5a0Zw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2762,9 +2762,9 @@
"license": "MIT"
},
"node_modules/undici-types": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
"version": "7.10.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
"dev": true,
"license": "MIT"
},

View File

@@ -501,17 +501,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz",
"integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==",
"version": "8.39.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.0.tgz",
"integrity": "sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.38.0",
"@typescript-eslint/type-utils": "8.38.0",
"@typescript-eslint/utils": "8.38.0",
"@typescript-eslint/visitor-keys": "8.38.0",
"@typescript-eslint/scope-manager": "8.39.0",
"@typescript-eslint/type-utils": "8.39.0",
"@typescript-eslint/utils": "8.39.0",
"@typescript-eslint/visitor-keys": "8.39.0",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -525,9 +525,9 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.38.0",
"@typescript-eslint/parser": "^8.39.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
@@ -541,16 +541,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz",
"integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==",
"version": "8.39.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.0.tgz",
"integrity": "sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.38.0",
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/typescript-estree": "8.38.0",
"@typescript-eslint/visitor-keys": "8.38.0",
"@typescript-eslint/scope-manager": "8.39.0",
"@typescript-eslint/types": "8.39.0",
"@typescript-eslint/typescript-estree": "8.39.0",
"@typescript-eslint/visitor-keys": "8.39.0",
"debug": "^4.3.4"
},
"engines": {
@@ -562,18 +562,18 @@
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz",
"integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==",
"version": "8.39.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.0.tgz",
"integrity": "sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.38.0",
"@typescript-eslint/types": "^8.38.0",
"@typescript-eslint/tsconfig-utils": "^8.39.0",
"@typescript-eslint/types": "^8.39.0",
"debug": "^4.3.4"
},
"engines": {
@@ -584,18 +584,18 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <5.9.0"
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz",
"integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==",
"version": "8.39.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.0.tgz",
"integrity": "sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/visitor-keys": "8.38.0"
"@typescript-eslint/types": "8.39.0",
"@typescript-eslint/visitor-keys": "8.39.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -606,9 +606,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz",
"integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==",
"version": "8.39.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.0.tgz",
"integrity": "sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -619,19 +619,19 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <5.9.0"
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz",
"integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==",
"version": "8.39.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.0.tgz",
"integrity": "sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/typescript-estree": "8.38.0",
"@typescript-eslint/utils": "8.38.0",
"@typescript-eslint/types": "8.39.0",
"@typescript-eslint/typescript-estree": "8.39.0",
"@typescript-eslint/utils": "8.39.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -644,13 +644,13 @@
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz",
"integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==",
"version": "8.39.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.0.tgz",
"integrity": "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -662,16 +662,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz",
"integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==",
"version": "8.39.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.0.tgz",
"integrity": "sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.38.0",
"@typescript-eslint/tsconfig-utils": "8.38.0",
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/visitor-keys": "8.38.0",
"@typescript-eslint/project-service": "8.39.0",
"@typescript-eslint/tsconfig-utils": "8.39.0",
"@typescript-eslint/types": "8.39.0",
"@typescript-eslint/visitor-keys": "8.39.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -687,7 +687,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <5.9.0"
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
@@ -730,16 +730,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz",
"integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==",
"version": "8.39.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.0.tgz",
"integrity": "sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.38.0",
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/typescript-estree": "8.38.0"
"@typescript-eslint/scope-manager": "8.39.0",
"@typescript-eslint/types": "8.39.0",
"@typescript-eslint/typescript-estree": "8.39.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -750,17 +750,17 @@
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz",
"integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==",
"version": "8.39.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.0.tgz",
"integrity": "sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/types": "8.39.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -4694,9 +4694,9 @@
}
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -4708,16 +4708,16 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.38.0.tgz",
"integrity": "sha512-FsZlrYK6bPDGoLeZRuvx2v6qrM03I0U0SnfCLPs/XCCPCFD80xU9Pg09H/K+XFa68uJuZo7l/Xhs+eDRg2l3hg==",
"version": "8.39.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.0.tgz",
"integrity": "sha512-lH8FvtdtzcHJCkMOKnN73LIn6SLTpoojgJqDAxPm1jCR14eWSGPX8ul/gggBdPMk/d5+u9V854vTYQ8T5jF/1Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.38.0",
"@typescript-eslint/parser": "8.38.0",
"@typescript-eslint/typescript-estree": "8.38.0",
"@typescript-eslint/utils": "8.38.0"
"@typescript-eslint/eslint-plugin": "8.39.0",
"@typescript-eslint/parser": "8.39.0",
"@typescript-eslint/typescript-estree": "8.39.0",
"@typescript-eslint/utils": "8.39.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4728,7 +4728,7 @@
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/unbox-primitive": {

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