Compare commits

...

107 Commits

Author SHA1 Message Date
Jens Langhammer
fbce9611d2 fix dep, make post request
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-17 21:06:05 +02:00
Jens Langhammer
e6643a69cd add in app support bundle
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-17 18:18:39 +02:00
Jens Langhammer
0fdeaee559 add support command
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-17 18:17:49 +02:00
Fuad
f9fd1bbf09 website/integrations: fix typo in seafile doc (#15633)
Typo: fix OAUTH_PROVIDER_DOMAIN url

Signed-off-by: Fuad <26776550+fuad00@users.noreply.github.com>
2025-07-17 15:44:48 +00:00
Dominic R
3ba3b11a76 root: Ignore ./media for codespell (#15632)
Closes https://github.com/goauthentik/authentik/issues/15631

Signed-off-by: Dominic R <dominic@sdko.org>
2025-07-17 17:41:55 +02:00
Teffen Ellis
19e558e916 website: Prep for workspaces. Clean up shared deps. (#15602)
website: Prepare NPM Workspaces.
2025-07-17 10:06:05 -04:00
dependabot[bot]
e15fadfedd web: bump prettier-plugin-packagejson from 2.5.16 to 2.5.18 in /packages/prettier-config (#15615)
web: bump prettier-plugin-packagejson in /packages/prettier-config

Bumps [prettier-plugin-packagejson](https://github.com/matzkoh/prettier-plugin-packagejson) from 2.5.16 to 2.5.18.
- [Release notes](https://github.com/matzkoh/prettier-plugin-packagejson/releases)
- [Commits](https://github.com/matzkoh/prettier-plugin-packagejson/compare/v2.5.16...v2.5.18)

---
updated-dependencies:
- dependency-name: prettier-plugin-packagejson
  dependency-version: 2.5.18
  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-07-17 16:04:35 +02:00
dependabot[bot]
52854e61c7 web: bump @codemirror/theme-one-dark from 6.1.2 to 6.1.3 in /web (#15618)
Bumps [@codemirror/theme-one-dark](https://github.com/codemirror/theme-one-dark) from 6.1.2 to 6.1.3.
- [Changelog](https://github.com/codemirror/theme-one-dark/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/theme-one-dark/compare/6.1.2...6.1.3)

---
updated-dependencies:
- dependency-name: "@codemirror/theme-one-dark"
  dependency-version: 6.1.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-17 15:25:09 +02:00
dependabot[bot]
53aa0113ca web: bump prettier-plugin-packagejson from 2.5.17 to 2.5.18 in /packages/esbuild-plugin-live-reload (#15614)
web: bump prettier-plugin-packagejson

Bumps [prettier-plugin-packagejson](https://github.com/matzkoh/prettier-plugin-packagejson) from 2.5.17 to 2.5.18.
- [Release notes](https://github.com/matzkoh/prettier-plugin-packagejson/releases)
- [Commits](https://github.com/matzkoh/prettier-plugin-packagejson/compare/v2.5.17...v2.5.18)

---
updated-dependencies:
- dependency-name: prettier-plugin-packagejson
  dependency-version: 2.5.18
  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-07-17 15:07:47 +02:00
dependabot[bot]
9f71face62 web: bump @types/node from 24.0.10 to 24.0.14 in /packages/esbuild-plugin-live-reload (#15613)
web: bump @types/node in /packages/esbuild-plugin-live-reload

Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.0.10 to 24.0.14.
- [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.0.14
  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-07-17 15:07:41 +02:00
dependabot[bot]
2fadefb5b4 web: bump @types/node from 24.0.4 to 24.0.14 in /packages/prettier-config (#15616)
web: bump @types/node in /packages/prettier-config

Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.0.4 to 24.0.14.
- [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.0.14
  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-07-17 15:07:22 +02:00
dependabot[bot]
23e92bceae web: bump @types/dompurify from 3.0.5 to 3.2.0 in /web (#15617)
Bumps [@types/dompurify](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/dompurify) from 3.0.5 to 3.2.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/dompurify)

---
updated-dependencies:
- dependency-name: "@types/dompurify"
  dependency-version: 3.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-07-17 15:07:14 +02:00
dependabot[bot]
1ff2eea20a web: bump @codemirror/legacy-modes from 6.4.1 to 6.5.1 in /web (#15619)
Bumps [@codemirror/legacy-modes](https://github.com/codemirror/legacy-modes) from 6.4.1 to 6.5.1.
- [Changelog](https://github.com/codemirror/legacy-modes/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/legacy-modes/compare/6.4.1...6.5.1)

---
updated-dependencies:
- dependency-name: "@codemirror/legacy-modes"
  dependency-version: 6.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-17 15:07:01 +02:00
dependabot[bot]
abcd2179bf web: bump mermaid from 11.6.0 to 11.9.0 in /web (#15620)
Bumps [mermaid](https://github.com/mermaid-js/mermaid) from 11.6.0 to 11.9.0.
- [Release notes](https://github.com/mermaid-js/mermaid/releases)
- [Changelog](https://github.com/mermaid-js/mermaid/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/mermaid-js/mermaid/commits)

---
updated-dependencies:
- dependency-name: mermaid
  dependency-version: 11.9.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-07-17 15:06:52 +02:00
dependabot[bot]
6a4b5850a0 web: bump @types/node from 22.15.19 to 24.0.14 in /web (#15621)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.15.19 to 24.0.14.
- [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.0.14
  dependency-type: direct:development
  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-07-17 15:06:36 +02:00
dependabot[bot]
821c8c36cd lifecycle/aws: bump aws-cdk from 2.1020.2 to 2.1021.0 in /lifecycle/aws (#15622)
Bumps [aws-cdk](https://github.com/aws/aws-cdk-cli/tree/HEAD/packages/aws-cdk) from 2.1020.2 to 2.1021.0.
- [Release notes](https://github.com/aws/aws-cdk-cli/releases)
- [Commits](https://github.com/aws/aws-cdk-cli/commits/aws-cdk@v2.1021.0/packages/aws-cdk)

---
updated-dependencies:
- dependency-name: aws-cdk
  dependency-version: 2.1021.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-17 15:06:19 +02:00
dependabot[bot]
8838efe3c0 core: bump msgraph-sdk from 1.37.0 to 1.38.0 (#15624)
Bumps [msgraph-sdk](https://github.com/microsoftgraph/msgraph-sdk-python) from 1.37.0 to 1.38.0.
- [Release notes](https://github.com/microsoftgraph/msgraph-sdk-python/releases)
- [Changelog](https://github.com/microsoftgraph/msgraph-sdk-python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/microsoftgraph/msgraph-sdk-python/compare/v1.37.0...v1.38.0)

---
updated-dependencies:
- dependency-name: msgraph-sdk
  dependency-version: 1.38.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-07-17 15:06:07 +02:00
transifex-integration[bot]
433a4a3037 translate: Updates for file locale/en/LC_MESSAGES/django.po in de (#15627)
Translate locale/en/LC_MESSAGES/django.po in de

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

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-07-17 15:05:15 +02:00
transifex-integration[bot]
2d69a67e9d translate: Updates for file web/xliff/en.xlf in de (#15628)
Translate web/xliff/en.xlf in de

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

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-07-17 15:04:53 +02:00
authentik-automation[bot]
1294cc64e8 core, web: update translations (#15612) 2025-07-17 03:00:00 +02:00
Jens L.
910326a05a providers/oauth2: Add cause to debug issues and better tests (#15057)
* fix incorrect tests/add more

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

* add cause for oauth authorization errors

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

* include request_id in token response

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

* rework device endpoints

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

* fix some stuff

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

* fix tests

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-16 21:30:21 +02:00
Marcelo Elizeche Landó
9257b3e570 core: bump aiohttp from 3.12.13 to v3.12.14 (#15603) 2025-07-16 20:32:21 +02:00
Marcelo Elizeche Landó
cdd18a7e5a core: bump azure-identity from 1.23.0 to v1.23.1 (#15605) 2025-07-16 20:32:19 +02:00
Marcelo Elizeche Landó
88bea46648 core: bump boto3 from 1.39.3 to v1.39.7 (#15606) 2025-07-16 20:32:16 +02:00
Marcelo Elizeche Landó
295090a80b core: bump certifi from 2025.6.15 to v2025.7.14 (#15607) 2025-07-16 20:32:13 +02:00
Marcelo Elizeche Landó
bff607a5c3 core: bump microsoft-kiota-authentication-azure from 1.9.3 to v1.9.4 (#15608) 2025-07-16 20:32:10 +02:00
Marcelo Elizeche Landó
bfb2fb4fcf core: bump microsoft-kiota-http from 1.9.3 to v1.9.4 (#15609) 2025-07-16 20:32:06 +02:00
Marcelo Elizeche Landó
93015b0fce core: bump opentelemetry-api from 1.34.1 to v1.35.0 (#15610) 2025-07-16 20:32:03 +02:00
Marcelo Elizeche Landó
9b6c0d3f1a core: bump orjson from 3.10.18 to v3.11.0 (#15611) 2025-07-16 20:32:00 +02:00
Marcelo Elizeche Landó
66e95ddb20 core: bump asgiref from 3.9.0 to v3.9.1 (#15604) 2025-07-16 20:21:08 +02:00
dependabot[bot]
c5d8524a7d web: bump @types/react-dom from 19.1.5 to 19.1.6 in /packages/docusaurus-config (#15592)
web: bump @types/react-dom in /packages/docusaurus-config

---
updated-dependencies:
- dependency-name: "@types/react-dom"
  dependency-version: 19.1.6
  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-07-16 18:08:28 +02:00
dependabot[bot]
a4761064c2 web: bump @sentry/browser from 9.38.0 to 9.39.0 in /web in the sentry group across 1 directory (#15586)
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.38.0 to 9.39.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/9.39.0/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/9.38.0...9.39.0)

---
updated-dependencies:
- dependency-name: "@sentry/browser"
  dependency-version: 9.39.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-16 18:08:21 +02:00
dependabot[bot]
b0de8bf71f web: bump @types/react from 19.1.6 to 19.1.8 in /packages/docusaurus-config (#15593)
web: bump @types/react in /packages/docusaurus-config

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-version: 19.1.8
  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-07-16 18:08:13 +02:00
Jens L.
32100fd3b9 events: improve error formatting in events (#15187)
* events: improve error formatting in events

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

* fix

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-16 17:22:54 +02:00
dependabot[bot]
4815e97162 web: bump @types/react-dom from 19.1.5 to 19.1.6 in /web (#15596)
---
updated-dependencies:
- dependency-name: "@types/react-dom"
  dependency-version: 19.1.6
  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-07-16 17:20:32 +02:00
dependabot[bot]
dee99c38bb web: bump prettier from 3.5.3 to 3.6.2 in /packages/docusaurus-config (#15594)
---
updated-dependencies:
- dependency-name: prettier
  dependency-version: 3.6.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-07-16 17:20:23 +02:00
dependabot[bot]
a024056b62 web: bump prettier from 3.6.1 to 3.6.2 in /packages/prettier-config (#15595)
---
updated-dependencies:
- dependency-name: prettier
  dependency-version: 3.6.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-16 17:20:01 +02:00
authentik-automation[bot]
a8dc21b707 core, web: update translations (#15580)
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-16 16:49:51 +02:00
dependabot[bot]
7ccda743df web: bump the storybook group across 1 directory with 5 updates (#15587)
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.16 to 9.0.17
- [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.0.17/code/addons/docs)

Updates `@storybook/addon-links` from 9.0.16 to 9.0.17
- [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.0.17/code/addons/links)

Updates `@storybook/web-components` from 9.0.16 to 9.0.17
- [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.0.17/code/renderers/web-components)

Updates `@storybook/web-components-vite` from 9.0.16 to 9.0.17
- [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.0.17/code/frameworks/web-components-vite)

Updates `storybook` from 9.0.16 to 9.0.17
- [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.0.17/code/core)

---
updated-dependencies:
- dependency-name: "@storybook/addon-docs"
  dependency-version: 9.0.17
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/addon-links"
  dependency-version: 9.0.17
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components"
  dependency-version: 9.0.17
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components-vite"
  dependency-version: 9.0.17
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: storybook
  dependency-version: 9.0.17
  dependency-type: direct:development
  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-07-16 16:49:31 +02:00
dependabot[bot]
0c795dd077 core: bump github.com/golang-jwt/jwt/v5 from 5.2.2 to 5.2.3 (#15582)
Bumps [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) from 5.2.2 to 5.2.3.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v5.2.2...v5.2.3)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v5
  dependency-version: 5.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-16 16:19:53 +02:00
dependabot[bot]
5df9ed3582 website: bump the build group in /website with 3 updates (#15583)
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.6 to 1.4.8
- [Release notes](https://github.com/web-infra-dev/rspack/releases)
- [Commits](https://github.com/web-infra-dev/rspack/commits/v1.4.8/packages/rspack)

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

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

---
updated-dependencies:
- dependency-name: "@rspack/binding-darwin-arm64"
  dependency-version: 1.4.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@rspack/binding-linux-arm64-gnu"
  dependency-version: 1.4.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@rspack/binding-linux-x64-gnu"
  dependency-version: 1.4.8
  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-16 16:19:50 +02:00
dependabot[bot]
a47b4934a5 website: bump @types/node from 24.0.13 to 24.0.14 in /website (#15584)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.0.13 to 24.0.14.
- [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.0.14
  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-16 16:19:47 +02:00
dependabot[bot]
338a6e74f4 core: bump sentry-sdk from 2.32.0 to 2.33.0 (#15585)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.32.0 to 2.33.0.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.32.0...2.33.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-version: 2.33.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-07-16 16:19:44 +02:00
dependabot[bot]
8897af1048 web: bump the esbuild group across 2 directories with 5 updates (#15588)
Bumps the esbuild group with 1 update in the /packages/esbuild-plugin-live-reload directory: [esbuild](https://github.com/evanw/esbuild).
Bumps the esbuild group with 2 updates in the /web directory: [esbuild](https://github.com/evanw/esbuild) and [esbuild-plugins-node-modules-polyfill](https://github.com/imranbarbhuiya/esbuild-plugins-node-modules-polyfill).


Updates `esbuild` from 0.25.5 to 0.25.6
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.25.5...v0.25.6)

Updates `@esbuild/darwin-arm64` from 0.25.5 to 0.25.6
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.25.5...v0.25.6)

Updates `@esbuild/linux-arm64` from 0.25.5 to 0.25.6
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.25.5...v0.25.6)

Updates `@esbuild/linux-x64` from 0.25.5 to 0.25.6
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.25.5...v0.25.6)

Updates `esbuild` from 0.25.5 to 0.25.6
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.25.5...v0.25.6)

Updates `esbuild-plugins-node-modules-polyfill` from 1.7.0 to 1.7.1
- [Release notes](https://github.com/imranbarbhuiya/esbuild-plugins-node-modules-polyfill/releases)
- [Changelog](https://github.com/imranbarbhuiya/esbuild-plugins-node-modules-polyfill/blob/main/CHANGELOG.md)
- [Commits](https://github.com/imranbarbhuiya/esbuild-plugins-node-modules-polyfill/compare/v1.7.0...v1.7.1)

Updates `@esbuild/darwin-arm64` from 0.25.5 to 0.25.6
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.25.5...v0.25.6)

Updates `@esbuild/linux-arm64` from 0.25.5 to 0.25.6
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.25.5...v0.25.6)

Updates `@esbuild/linux-x64` from 0.25.5 to 0.25.6
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.25.5...v0.25.6)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.25.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: esbuild
- dependency-name: "@esbuild/darwin-arm64"
  dependency-version: 0.25.6
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: esbuild
- dependency-name: "@esbuild/linux-arm64"
  dependency-version: 0.25.6
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: esbuild
- dependency-name: "@esbuild/linux-x64"
  dependency-version: 0.25.6
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: esbuild
- dependency-name: esbuild
  dependency-version: 0.25.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: esbuild
- dependency-name: esbuild-plugins-node-modules-polyfill
  dependency-version: 1.7.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: esbuild
- dependency-name: "@esbuild/darwin-arm64"
  dependency-version: 0.25.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: esbuild
- dependency-name: "@esbuild/linux-arm64"
  dependency-version: 0.25.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: esbuild
- dependency-name: "@esbuild/linux-x64"
  dependency-version: 0.25.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: esbuild
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-16 16:19:39 +02:00
dependabot[bot]
56ec3f7def web: bump the rollup group across 1 directory with 5 updates (#15589)
---
updated-dependencies:
- dependency-name: "@rollup/rollup-darwin-arm64"
  dependency-version: 4.45.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rollup
- dependency-name: "@rollup/rollup-linux-arm64-gnu"
  dependency-version: 4.45.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rollup
- dependency-name: "@rollup/rollup-linux-x64-gnu"
  dependency-version: 4.45.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rollup
- dependency-name: "@rollup/plugin-commonjs"
  dependency-version: 28.0.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: rollup
- dependency-name: rollup
  dependency-version: 4.45.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  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-16 16:19:36 +02:00
dependabot[bot]
53fd893d91 web: bump the swc group across 1 directory with 12 updates (#15590)
---
updated-dependencies:
- dependency-name: "@swc/cli"
  dependency-version: 0.7.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core"
  dependency-version: 1.12.14
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: swc
- dependency-name: "@swc/core-darwin-arm64"
  dependency-version: 1.12.14
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: swc
- dependency-name: "@swc/core-darwin-x64"
  dependency-version: 1.12.14
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm-gnueabihf"
  dependency-version: 1.12.14
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-gnu"
  dependency-version: 1.12.14
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-musl"
  dependency-version: 1.12.14
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-gnu"
  dependency-version: 1.12.14
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-musl"
  dependency-version: 1.12.14
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: swc
- dependency-name: "@swc/core-win32-arm64-msvc"
  dependency-version: 1.12.14
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: swc
- dependency-name: "@swc/core-win32-ia32-msvc"
  dependency-version: 1.12.14
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: swc
- dependency-name: "@swc/core-win32-x64-msvc"
  dependency-version: 1.12.14
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: swc
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-16 16:19:33 +02:00
dependabot[bot]
f7d9a8cafe web: bump @types/react from 19.1.5 to 19.1.8 in /web (#15597)
---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-version: 19.1.8
  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-07-16 16:17:31 +02:00
Jens L.
f97c1071f3 website/integrations: re-add sitemap (#15600)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-16 15:24:36 +02:00
Teffen Ellis
4da1115a7c web: Storybook v9 (#15550)
* web: Update Storybook. Clean up theme styles.

* web: Ignore Storybook output.
2025-07-16 04:29:01 +00:00
Jens L.
63b1ccd4c3 enterprise/audit: fix diff with update_fields (#15574)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-16 00:07:51 +02:00
Jens L.
63aa7f4684 enterprise: fix frontend considering license valid when it isnt (#15578)
* web: fix mis-matched license state check between backend and frontend

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

* make license key unique

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-15 22:36:57 +02:00
Teffen Ellis
d997930b60 website: Prep for 3.8 path resolution. (#15575) 2025-07-15 19:09:40 +02:00
Jens L.
a088a62981 stages/email: only update is_active on user to not overwrite external changes (#15508)
* stages/email: only update is_active on user to not overwrite external changes

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

* respect update_fields for diff

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-15 16:43:41 +02:00
Teffen Ellis
118e05f256 website: Update commands. (#15561) 2025-07-15 16:36:20 +02:00
dependabot[bot]
b30500094f website: bump the eslint group in /website with 3 updates (#15565)
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.36.0 to 8.37.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.37.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.36.0 to 8.37.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.37.0/packages/parser)

Updates `typescript-eslint` from 8.36.0 to 8.37.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.37.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: typescript-eslint
  dependency-version: 8.37.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-07-15 16:32:41 +02:00
Marcelo Elizeche Landó
21af51ba59 website/docs: Fix typo in matrix documentation (#15558)
Fix typo in matrix documentation
2025-07-15 07:35:11 -05:00
authentik-automation[bot]
87da0497e0 core, web: update translations (#15560)
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-15 14:20:48 +02:00
dependabot[bot]
87317d6e7f core: bump goauthentik.io/api/v3 from 3.2025063.4 to 3.2025063.5 (#15564)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2025063.4 to 3.2025063.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.2025063.4...v3.2025063.5)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  dependency-version: 3.2025063.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-15 14:20:39 +02:00
authentik-automation[bot]
071305da18 stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#15563)
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-15 13:39:47 +02:00
dependabot[bot]
1dc8ed5e55 web: bump the eslint group across 2 directories with 3 updates (#15566)
---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.37.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.37.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.37.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: typescript-eslint
  dependency-version: 8.37.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.37.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.37.0
  dependency-type: direct:development
  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-07-15 13:37:16 +02:00
dependabot[bot]
dc8dee985f core: bump astral-sh/uv from 0.7.20 to 0.7.21 (#15567)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.7.20 to 0.7.21.
- [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.7.20...0.7.21)

---
updated-dependencies:
- dependency-name: astral-sh/uv
  dependency-version: 0.7.21
  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-15 13:37:06 +02:00
dependabot[bot]
2b20b06baa website: bump the build group in /website with 6 updates (#15568)
Bumps the build group in /website with 6 updates:

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


Updates `@swc/core-darwin-arm64` from 1.12.11 to 1.12.14
- [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.12.11...v1.12.14)

Updates `@swc/core-linux-arm64-gnu` from 1.12.11 to 1.12.14
- [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.12.11...v1.12.14)

Updates `@swc/core-linux-x64-gnu` from 1.12.11 to 1.12.14
- [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.12.11...v1.12.14)

Updates `@swc/html-darwin-arm64` from 1.12.11 to 1.12.14
- [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.12.11...v1.12.14)

Updates `@swc/html-linux-arm64-gnu` from 1.12.11 to 1.12.14
- [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.12.11...v1.12.14)

Updates `@swc/html-linux-x64-gnu` from 1.12.11 to 1.12.14
- [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.12.11...v1.12.14)

---
updated-dependencies:
- dependency-name: "@swc/core-darwin-arm64"
  dependency-version: 1.12.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@swc/core-linux-arm64-gnu"
  dependency-version: 1.12.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@swc/core-linux-x64-gnu"
  dependency-version: 1.12.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@swc/html-darwin-arm64"
  dependency-version: 1.12.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@swc/html-linux-arm64-gnu"
  dependency-version: 1.12.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@swc/html-linux-x64-gnu"
  dependency-version: 1.12.14
  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-15 13:36:57 +02:00
Teffen Ellis
6cab1f85e4 web: Fix intermediate wizard steps propagating refresh events to parent (#15548)
* web: Fix issue where wizard steps with refresh events trigger parent rerenders.

* Apply suggestions from code review. Tidy.

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

---------

Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2025-07-15 02:28:57 +02:00
Tana M Berry
f836c38b18 website/docs: added enterprise label to new Logging docs (#15556)
added enterprise label

Co-authored-by: Tana M Berry <tana@goauthentik.io>
2025-07-14 18:18:14 -03:00
Tana M Berry
07e373e505 website/docs: fix a typo in SSF docs (#15554)
website/docs/add-secure-apps/providers/ssf/index.md

Co-authored-by: Tana M Berry <tana@goauthentik.io>
2025-07-14 11:36:16 -05:00
Teffen Ellis
e361d38978 web: Disable autocomplete. (#15551) 2025-07-14 15:49:17 +00:00
Teffen Ellis
3ba1691db6 web: Fix missing TypeScript dependency when running make for first time. (#15502)
* web: Fix missing TypeScript dependency when running make for first time.

Co-authored-by: Connor Peshek <connor@connorpeshek.me>

* Update Makefile

Co-authored-by: Jens L. <jens@goauthentik.io>
Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>

---------

Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Co-authored-by: Connor Peshek <connor@connorpeshek.me>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-07-14 11:40:00 -04:00
Tana M Berry
7c2987ea32 website/docs: add use case, move diagram, link to ABM (#15491)
* add use case, move diagram, link to ABM

* change word to match

* fix UI

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

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

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Tana M Berry <tana@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-07-14 10:08:49 -05:00
authentik-automation[bot]
4ca88caf07 web: bump API Client version (#15547)
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-14 14:08:52 +00:00
Jens L.
6c939341b0 sources/oauth: add entra ID source and move logic over (#15538)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-14 15:44:07 +02:00
dependabot[bot]
4142584788 core: bump goauthentik.io/api/v3 from 3.2025063.3 to 3.2025063.4 (#15541)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2025063.3 to 3.2025063.4.
- [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.2025063.3...v3.2025063.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-14 12:43:49 +02:00
dependabot[bot]
f6fbafd280 core: bump github.com/grafana/pyroscope-go from 1.2.2 to 1.2.3 (#15540)
Bumps [github.com/grafana/pyroscope-go](https://github.com/grafana/pyroscope-go) from 1.2.2 to 1.2.3.
- [Release notes](https://github.com/grafana/pyroscope-go/releases)
- [Commits](https://github.com/grafana/pyroscope-go/compare/v1.2.2...v1.2.3)

---
updated-dependencies:
- dependency-name: github.com/grafana/pyroscope-go
  dependency-version: 1.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-14 12:43:00 +02:00
dependabot[bot]
7c9555bee8 website: bump the eslint group in /website with 2 updates (#15542)
Bumps the eslint group in /website with 2 updates: [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) and [eslint](https://github.com/eslint/eslint).


Updates `@eslint/js` from 9.30.1 to 9.31.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.31.0/packages/js)

Updates `eslint` from 9.30.1 to 9.31.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.30.1...v9.31.0)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.31.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: eslint
  dependency-version: 9.31.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-07-14 12:42:49 +02:00
dependabot[bot]
82cd64dfe7 website: bump @types/node from 24.0.12 to 24.0.13 in /website (#15544)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.0.12 to 24.0.13.
- [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.0.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-14 12:42:37 +02:00
dependabot[bot]
28f0b48e33 web: bump @sentry/browser from 9.37.0 to 9.38.0 in /web in the sentry group across 1 directory (#15545)
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.37.0 to 9.38.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.37.0...9.38.0)

---
updated-dependencies:
- dependency-name: "@sentry/browser"
  dependency-version: 9.38.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-14 12:42:25 +02:00
dependabot[bot]
38c02dc490 web: bump the eslint group across 3 directories with 2 updates (#15546)
Bumps the eslint group with 1 update in the /packages/eslint-config directory: [eslint](https://github.com/eslint/eslint).
Bumps the eslint group with 1 update in the /packages/prettier-config directory: [eslint](https://github.com/eslint/eslint).
Bumps the eslint group with 1 update in the /web directory: [eslint](https://github.com/eslint/eslint).


Updates `eslint` from 9.30.1 to 9.31.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.30.1...v9.31.0)

Updates `@eslint/js` from 9.30.1 to 9.31.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.31.0/packages/js)

Updates `eslint` from 9.30.1 to 9.31.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.30.1...v9.31.0)

Updates `@eslint/js` from 9.30.1 to 9.31.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.31.0/packages/js)

Updates `eslint` from 9.30.1 to 9.31.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.30.1...v9.31.0)

Updates `@eslint/js` from 9.30.1 to 9.31.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.31.0/packages/js)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.31.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: "@eslint/js"
  dependency-version: 9.31.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: eslint
  dependency-version: 9.31.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: "@eslint/js"
  dependency-version: 9.31.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: eslint
  dependency-version: 9.31.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: eslint
- dependency-name: "@eslint/js"
  dependency-version: 9.31.0
  dependency-type: direct:development
  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-07-14 12:42:13 +02:00
Pascal Weidenhammer
79505969db website/docs: Use Django case insensitive filter for unique emails (#15539)
* Use Django case insensitive filter for unique emails

https://docs.djangoproject.com/en/dev/ref/models/querysets/#std-fieldlookup-iexact

* use ak_user_by

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-07-14 10:35:26 +00:00
dependabot[bot]
9870888456 web: bump brace-expansion from 1.1.11 to 1.1.12 in /packages/eslint-config (#15536)
web: bump brace-expansion in /packages/eslint-config

Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-14 01:05:03 +02:00
authentik-automation[bot]
5c06e1920e web: bump API Client version (#15537)
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-13 23:02:28 +00:00
Jens L.
1506ad8aa4 ci: fix NPM publish again (#15535)
* ci: npm apparently needs a tag flag now for some reason...? like what the hell is a dist-tag even

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

* revert frontend styling

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

* fix autosubmit duplicate label

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-14 00:40:24 +02:00
Jens L.
21b6204c90 sources/SCIM: Full Patch support for User and Group (#15485)
* add patch support

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

* fix group members

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

* add tests for group adding

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

* format, more tests

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

* mark patch as supported

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

* fix

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

* support excludedAttributes

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

* allow updating externalId

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

* more patcher tests

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

* let the ai do things?

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

* format

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

* fix ai generated code

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

* fix tests

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

* remove the old code

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

* add fix to handle URN format

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

* tests pass

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

* improve 404 handling for non uuid IDs

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

* better None path handling

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

* split code to make it more readable

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

* handle patch operation with Path None and value containing urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:organization

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

* fix tests that were not correct

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

* fix external ID change - the bad way

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

* add separate field for externalId

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

* more schema fixes

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

* fix replace for manager

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

* save last_updated

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

* more unittests

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

* more tests

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-14 00:02:15 +02:00
transifex-integration[bot]
05621735cb translate: Updates for file locale/en/LC_MESSAGES/django.po in es (#15534)
Translate locale/en/LC_MESSAGES/django.po in es

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

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-07-13 19:57:54 +00:00
authentik-automation[bot]
f9ffd35ab8 core, web: update translations (#15532)
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-13 21:39:57 +02:00
transifex-integration[bot]
c3ded3a835 translate: Updates for file web/xliff/en.xlf in it (#15530)
Translate web/xliff/en.xlf in it

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

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-07-12 17:50:47 +02:00
transifex-integration[bot]
7629c22050 translate: Updates for file locale/en/LC_MESSAGES/django.po in it (#15529)
Translate locale/en/LC_MESSAGES/django.po in it

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

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-07-12 17:49:33 +02:00
Jens L.
29a66410fd stages/prompt: fix list policy for prompt validation failing with multiple policies (#15522)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-11 21:59:45 +02:00
dependabot[bot]
f147d40c5f website: bump the build group in /website with 3 updates (#15515)
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.5 to 1.4.6
- [Release notes](https://github.com/web-infra-dev/rspack/releases)
- [Commits](https://github.com/web-infra-dev/rspack/commits/v1.4.6/packages/rspack)

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

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

---
updated-dependencies:
- dependency-name: "@rspack/binding-darwin-arm64"
  dependency-version: 1.4.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@rspack/binding-linux-arm64-gnu"
  dependency-version: 1.4.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build
- dependency-name: "@rspack/binding-linux-x64-gnu"
  dependency-version: 1.4.6
  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-11 18:37:04 +02:00
dependabot[bot]
15b556c1be website: bump @reduxjs/toolkit from 1.9.7 to 2.8.2 in /website (#15516)
Bumps [@reduxjs/toolkit](https://github.com/reduxjs/redux-toolkit) from 1.9.7 to 2.8.2.
- [Release notes](https://github.com/reduxjs/redux-toolkit/releases)
- [Commits](https://github.com/reduxjs/redux-toolkit/compare/v1.9.7...v2.8.2)

---
updated-dependencies:
- dependency-name: "@reduxjs/toolkit"
  dependency-version: 2.8.2
  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-07-11 15:48:49 +02:00
dependabot[bot]
522e8a26a2 core: bump twilio from 9.6.4 to 9.6.5 (#15517)
Bumps [twilio](https://github.com/twilio/twilio-python) from 9.6.4 to 9.6.5.
- [Release notes](https://github.com/twilio/twilio-python/releases)
- [Changelog](https://github.com/twilio/twilio-python/blob/main/CHANGES.md)
- [Commits](https://github.com/twilio/twilio-python/compare/9.6.4...9.6.5)

---
updated-dependencies:
- dependency-name: twilio
  dependency-version: 9.6.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-11 15:48:32 +02:00
dependabot[bot]
403d762f65 core: bump xmlsec from 1.3.15 to 1.3.16 (#15518)
Bumps [xmlsec](https://github.com/mehcode/python-xmlsec) from 1.3.15 to 1.3.16.
- [Release notes](https://github.com/mehcode/python-xmlsec/releases)
- [Commits](https://github.com/mehcode/python-xmlsec/compare/1.3.15...1.3.16)

---
updated-dependencies:
- dependency-name: xmlsec
  dependency-version: 1.3.16
  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-11 15:48:07 +02:00
dependabot[bot]
cbc65ffd74 web: bump @sentry/browser from 9.36.0 to 9.37.0 in /web in the sentry group across 1 directory (#15519)
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.36.0 to 9.37.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.36.0...9.37.0)

---
updated-dependencies:
- dependency-name: "@sentry/browser"
  dependency-version: 9.37.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-11 15:47:25 +02:00
authentik-automation[bot]
9a9bafdfb4 core, web: update translations (#15514) 2025-07-11 02:52:59 +02:00
Teffen Ellis
198d2a1a8a web: Clean up file methods. (#15479) 2025-07-10 22:47:22 +00:00
Tana M Berry
239edace16 website/docs: add noun for SSO (#15509)
* add noun for SSO

* change to use term platform

---------

Co-authored-by: Tana M Berry <tana@goauthentik.io>
2025-07-10 17:32:02 -05:00
Teffen Ellis
370d5ff0c0 web: Fix form captcha submission (#15482)
* web: Validate Captcha during form submission.

web: Clean up loading state. Remove outdated.

Flesh out story. Adjust centering.

* web: Fix issue where setting password warns of missing username.

* web: Fix issue where private method triggers runtime error.
2025-07-10 22:23:34 +00:00
Teffen Ellis
635b09621b web: Form submission (#15477)
* web: Clean up usage of method.

* web: Clean up form submission behavior.

* web: Normalize use of form submission listener.
2025-07-10 21:35:59 +00:00
Teffen Ellis
4335498ac5 web: Import organization (#14696)
* web: Clean up locale.

* web: Clean ambiguous imports.

* web: Clean up entrypoint imports.

* web: Format imports.

* web: Normalize extensions.

* web: Tidy order.

* web: Remove TS aliases.
2025-07-10 20:36:56 +00:00
Teffen Ellis
72af009de8 website/docs: Improved Version Picker. (#14404)
* website: Flesh out version picker. Port 3.8 theme.

* website: Update Dockerfile to include compose.

* website: Flesh out branch override. Tidy list items.
2025-07-10 15:36:48 -04:00
Teffen Ellis
3a07d5d829 web: Consistent use of static styles (#15510)
* web: Initial style clean up.

* web: Clean up type 2 styles.

* web: Clean up type 3 styles.

* web: Add Prettier formatter.
2025-07-10 19:35:58 +00:00
Marc 'risson' Schmitt
7122891f0f providers/proxy: fix ingress-nginx proxy buffer size annotations (#15506)
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-07-10 15:06:35 +00:00
Dominic R
c32d6cc75e website/docs: troubleshooting: Fix variable for postgres database in k8s (#15503) 2025-07-10 16:41:27 +02:00
dependabot[bot]
eaf6be74f3 web: bump @sentry/browser from 9.35.0 to 9.36.0 in /web in the sentry group across 1 directory (#15492)
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.35.0 to 9.36.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.35.0...9.36.0)

---
updated-dependencies:
- dependency-name: "@sentry/browser"
  dependency-version: 9.36.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-10 15:08:50 +02:00
dependabot[bot]
c35650afbd core: bump golang.org/x/sync from 0.15.0 to 0.16.0 (#15493)
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.15.0 to 0.16.0.
- [Commits](https://github.com/golang/sync/compare/v0.15.0...v0.16.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-version: 0.16.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-07-10 15:06:15 +02:00
dependabot[bot]
a1f9ff8b7d core: bump maxmind/geoipupdate from v7.1.0 to v7.1.1 (#15495)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-10 13:46:49 +02:00
dependabot[bot]
962f7513ba core: bump astral-sh/uv from 0.7.19 to 0.7.20 (#15496)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-10 13:46:32 +02:00
Teffen Ellis
0ec5ea69ef Docusaurus 3.8 prep integrations (#15483)
* website: Port gitignore.

* website: Flesh out Integrations clean up.
2025-07-09 16:41:11 -04:00
Teffen Ellis
d8a3098329 web: Fix dangling div. (#15478) 2025-07-09 22:00:18 +02:00
1112 changed files with 24726 additions and 18114 deletions

View File

@@ -31,4 +31,4 @@ If changes to the frontend have been made
If applicable
- [ ] The documentation has been updated
- [ ] The documentation has been formatted (`make website`)
- [ ] The documentation has been formatted (`make docs`)

View File

@@ -28,7 +28,7 @@ jobs:
working-directory: gen-ts-api/
run: |
npm i
npm publish
npm publish --tag generated
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
- name: Upgrade /web

View File

@@ -62,7 +62,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
go build -o /go/authentik ./cmd/server
# Stage 3: MaxMind GeoIP
FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.1.0 AS geoip
FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.1.1 AS geoip
ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN"
ENV GEOIPUPDATE_VERBOSE="1"
@@ -75,7 +75,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.7.19 AS uv
FROM ghcr.io/astral-sh/uv:0.7.21 AS uv
# Stage 5: Base python image
FROM ghcr.io/goauthentik/fips-python:3.13.5-slim-bookworm-fips AS python-base

View File

@@ -1,4 +1,4 @@
.PHONY: gen dev-reset all clean test web website
.PHONY: gen dev-reset all clean test web docs
SHELL := /usr/bin/env bash
.SHELLFLAGS += ${SHELLFLAGS} -e -o pipefail
@@ -73,7 +73,7 @@ core-i18n-extract:
--ignore website \
-l en
install: web-install website-install core-install ## Install all requires dependencies for `web`, `website` and `core`
install: node-install docs-install core-install ## Install all requires dependencies for `node`, `docs` and `core`
dev-drop-db:
dropdb -U ${pg_user} -h ${pg_host} ${pg_name}
@@ -183,18 +183,23 @@ gen-dev-config: ## Generate a local development config file
gen: gen-build gen-client-ts
#########################
## Node.js
#########################
node-install: ## Install the necessary libraries to build Node.js packages
npm ci
npm ci --prefix web
#########################
## Web
#########################
web-build: web-install ## Build the Authentik UI
web-build: node-install ## Build the Authentik UI
cd web && npm run build
web: web-lint-fix web-lint web-check-compile ## Automatically fix formatting issues in the Authentik UI source code, lint the code, and compile it
web-install: ## Install the necessary libraries to build the Authentik UI
cd web && npm ci
web-test: ## Run tests for the Authentik UI
cd web && npm run test
@@ -221,22 +226,28 @@ web-i18n-extract:
cd web && npm run extract-locales
#########################
## Website
## Docs
#########################
website: website-lint-fix website-build ## Automatically fix formatting issues in the Authentik website/docs source code, lint the code, and compile it
docs: docs-lint-fix docs-build ## Automatically fix formatting issues in the Authentik docs source code, lint the code, and compile it
website-install:
cd website && npm ci
docs-install:
npm ci --prefix website
website-lint-fix: lint-codespell
cd website && npm run prettier
docs-lint-fix: lint-codespell
npm run prettier --prefix website
website-build:
cd website && npm run build
docs-build:
npm run build --prefix website
website-watch: ## Build and watch the documentation website, updating automatically
cd website && npm run watch
docs-watch: ## Build and watch the topics documentation
npm run start --prefix website
docs-integrations-build:
npm run build --prefix website -w integrations
docs-integrations-watch: ## Build and watch the Integrations documentation
npm run start --prefix website -w integrations
#########################
## Docker

View File

@@ -42,7 +42,11 @@ class Exporter:
if model in self.excluded_models:
continue
for obj in self.get_model_instances(model):
yield BlueprintEntry.from_model(obj)
yield BlueprintEntry.from_model(self.alter_model(obj))
def alter_model(self, model: Model):
"""Hook to modify the model before exporting"""
return model
def get_model_instances(self, model: type[Model]) -> QuerySet:
"""Return a queryset for `model`. Can be used to filter some

View File

@@ -11,7 +11,6 @@ from authentik.core.expression.exceptions import SkipObjectException
from authentik.core.models import User
from authentik.events.models import Event, EventAction
from authentik.lib.expression.evaluator import BaseEvaluator
from authentik.lib.utils.errors import exception_to_string
from authentik.policies.types import PolicyRequest
PROPERTY_MAPPING_TIME = Histogram(
@@ -69,12 +68,11 @@ class PropertyMappingEvaluator(BaseEvaluator):
# For dry-run requests we don't save exceptions
if self.dry_run:
return
error_string = exception_to_string(exc)
event = Event.new(
EventAction.PROPERTY_MAPPING_EXCEPTION,
expression=expression_source,
message=error_string,
)
message="Failed to execute property mapping",
).with_exception(exc)
if "request" in self._context:
req: PolicyRequest = self._context["request"]
if req.http_request:

View File

@@ -2,6 +2,7 @@
from datetime import timedelta
from django.http import HttpResponse
from django.utils.timezone import now
from django.utils.translation import gettext as _
from drf_spectacular.types import OpenApiTypes
@@ -10,16 +11,21 @@ from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, IntegerField
from rest_framework.permissions import IsAuthenticated
from rest_framework.renderers import BaseRenderer
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.validators import UniqueValidator
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
from authentik.core.models import User, UserTypes
from authentik.enterprise.bundle import generate_support_bundle
from authentik.enterprise.license import LicenseKey, LicenseSummarySerializer
from authentik.enterprise.models import License
from authentik.rbac.decorators import permission_required
from authentik.rbac.permissions import HasPermission
from authentik.tenants.utils import get_unique_identifier
@@ -53,6 +59,7 @@ class LicenseSerializer(ModelSerializer):
"external_users",
]
extra_kwargs = {
"key": {"validators": [UniqueValidator(queryset=License.objects.all())]},
"name": {"read_only": True},
"expiry": {"read_only": True},
"internal_users": {"read_only": True},
@@ -145,3 +152,24 @@ class LicenseViewSet(UsedByMixin, ModelViewSet):
)
response.is_valid(raise_exception=True)
return Response(response.data)
class BinaryRenderer(BaseRenderer):
media_type = "application/gzip"
format = "bin"
class SupportBundleView(APIView):
"""Generate a support bundle."""
permission_classes = [HasPermission("authentik_rbac.view_system_info")]
pagination_class = None
filter_backends = []
renderer_classes = [BinaryRenderer]
@extend_schema(responses=bytes, request=None)
def post(self, request: Request) -> Response:
"""Generate a support bundle."""
response = HttpResponse(generate_support_bundle(), content_type=BinaryRenderer.media_type)
response["Content-Disposition"] = 'attachment; filename="authentik_support.tgz"'
return response

View File

@@ -65,13 +65,17 @@ class EnterpriseAuditMiddleware(AuditMiddleware):
data[field.name] = deepcopy(field_value)
return cleanse_dict(data)
def diff(self, before: dict, after: dict) -> dict:
def diff(self, before: dict, after: dict, update_fields: list[str] | None = None) -> dict:
"""Generate diff between dicts"""
diff = {}
for key, value in before.items():
if update_fields and key not in update_fields:
continue
if after.get(key) != value:
diff[key] = {"previous_value": value, "new_value": after.get(key)}
for key, value in after.items():
if update_fields and key not in update_fields:
continue
if key not in before and key not in diff and before.get(key) != value:
diff[key] = {"previous_value": before.get(key), "new_value": value}
return sanitize_item(diff)
@@ -95,6 +99,7 @@ class EnterpriseAuditMiddleware(AuditMiddleware):
instance: Model,
created: bool,
thread_kwargs: dict | None = None,
update_fields: list[str] | None = None,
**_,
):
if not self.enabled:
@@ -108,7 +113,7 @@ class EnterpriseAuditMiddleware(AuditMiddleware):
prev_state = {}
# Get current state
new_state = self.serialize_simple(instance)
diff = self.diff(prev_state, new_state)
diff = self.diff(prev_state, new_state, update_fields)
thread_kwargs["diff"] = diff
return super().post_save_handler(request, sender, instance, created, thread_kwargs, **_)

View File

@@ -7,6 +7,7 @@ from rest_framework.test import APITestCase
from authentik.core.models import Group, User
from authentik.core.tests.utils import create_test_admin_user
from authentik.enterprise.audit.middleware import EnterpriseAuditMiddleware
from authentik.events.models import Event, EventAction
from authentik.events.utils import sanitize_item
from authentik.lib.generators import generate_id
@@ -208,3 +209,23 @@ class TestEnterpriseAudit(APITestCase):
diff,
{"users": {"remove": [user.pk]}},
)
@patch(
"authentik.enterprise.audit.middleware.EnterpriseAuditMiddleware.enabled",
PropertyMock(return_value=True),
)
def test_diff_update_fields(self):
"""Test update audit log"""
self.client.force_login(self.user)
diff = EnterpriseAuditMiddleware(None).diff(
{
"foo": "bar",
"is_active": False,
},
{
"foo": "baz",
"is_active": True,
},
update_fields=["is_active"],
)
self.assertEqual(diff, {"is_active": {"new_value": True, "previous_value": False}})

View File

@@ -0,0 +1,53 @@
import re
from io import BytesIO
from tarfile import TarInfo, open
from django.db.models import Model
from django.db.models.fields import CharField, SlugField, TextField
from django.db.models.fields.json import JSONField
from authentik.blueprints.v1.exporter import Exporter
from authentik.core.models import User
from lifecycle.support import encrypt, generate
SENSITIVE_VALUE_PLACEHOLDER = "<REDACTED>"
class SupportExporter(Exporter):
"""Blueprint exporter which censors sensitive model attributes"""
sensitive_fields = re.compile(
# Partially taken from Django's SafeExceptionReporterFilter
"API|AUTH|TOKEN|KEY|SECRET|PASS|SIGNATURE|CREDENTIALS",
re.I,
)
def __init__(self):
super().__init__()
self.excluded_models.append(User)
def alter_model(self, model: Model):
for field in model._meta.fields:
if not self.sensitive_fields.search(field.name):
continue
if isinstance(field, TextField | CharField | SlugField):
setattr(model, field.name, SENSITIVE_VALUE_PLACEHOLDER)
elif isinstance(field, JSONField):
setattr(model, field.name, {})
return model
def generate_support_bundle():
fh = BytesIO()
exporter = SupportExporter()
files = {
"authentik/support.jwe": encrypt(generate()),
"authentik/blueprint.yaml": exporter.export_to_string(),
}
with open(fileobj=fh, mode="w:gz") as tar:
for path, file in files.items():
info = TarInfo(path)
info.size = len(file)
tar.addfile(info, BytesIO(file.encode()))
final_data = fh.getvalue()
return final_data

View File

@@ -16,7 +16,7 @@ from authentik.stages.authenticator.models import Device
class AuthenticatorEndpointGDTCStage(ConfigurableStage, FriendlyNamedStage, Stage):
"""Setup Google Chrome Device-trust connection"""
"""Setup Google Chrome Device Trust connection"""
credentials = models.JSONField()

View File

@@ -1,7 +1,12 @@
"""API URLs"""
from authentik.enterprise.api import LicenseViewSet
from django.urls import path
from authentik.enterprise.api import LicenseViewSet, SupportBundleView
api_urlpatterns = [
("enterprise/license", LicenseViewSet),
path(
"enterprise/support_bundle/", SupportBundleView.as_view(), name="enterprise_support_bundle"
),
]

View File

@@ -20,7 +20,7 @@ from authentik.core.models import Group, User
from authentik.events.models import Event, EventAction, Notification
from authentik.events.utils import model_to_dict
from authentik.lib.sentry import should_ignore_exception
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.errors import exception_to_dict
from authentik.stages.authenticator_static.models import StaticToken
IGNORED_MODELS = tuple(
@@ -170,14 +170,16 @@ class AuditMiddleware:
thread = EventNewThread(
EventAction.SUSPICIOUS_REQUEST,
request,
message=exception_to_string(exception),
message=str(exception),
exception=exception_to_dict(exception),
)
thread.run()
elif not should_ignore_exception(exception):
thread = EventNewThread(
EventAction.SYSTEM_EXCEPTION,
request,
message=exception_to_string(exception),
message=str(exception),
exception=exception_to_dict(exception),
)
thread.run()

View File

@@ -38,6 +38,7 @@ from authentik.events.utils import (
)
from authentik.lib.models import DomainlessURLValidator, SerializerModel
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.errors import exception_to_dict
from authentik.lib.utils.http import get_http_session
from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.models import PolicyBindingModel
@@ -163,6 +164,12 @@ class Event(SerializerModel, ExpiringModel):
event = Event(action=action, app=app, context=cleaned_kwargs)
return event
def with_exception(self, exc: Exception) -> "Event":
"""Add data from 'exc' to the event in a database-saveable format"""
self.context.setdefault("message", str(exc))
self.context["exception"] = exception_to_dict(exc)
return self
def set_user(self, user: User) -> "Event":
"""Set `.user` based on user, ensuring the correct attributes are copied.
This should only be used when self.from_http is *not* used."""

View File

@@ -127,8 +127,8 @@ class SystemTask(TenantTask):
)
Event.new(
EventAction.SYSTEM_TASK_EXCEPTION,
message=f"Task {self.__name__} encountered an error: {exception_to_string(exc)}",
).save()
message=f"Task {self.__name__} encountered an error",
).with_exception(exc).save()
def run(self, *args, **kwargs):
raise NotImplementedError

View File

@@ -56,7 +56,6 @@ from authentik.flows.planner import (
)
from authentik.flows.stage import AccessDeniedStage, StageView
from authentik.lib.sentry import SentryIgnoredException, should_ignore_exception
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.reflection import all_subclasses, class_to_path
from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs
from authentik.policies.engine import PolicyEngine
@@ -239,8 +238,8 @@ class FlowExecutorView(APIView):
capture_exception(exc)
Event.new(
action=EventAction.SYSTEM_EXCEPTION,
message=exception_to_string(exc),
).from_http(self.request)
message="System exception during flow execution.",
).with_exception(exc).from_http(self.request)
challenge = FlowErrorChallenge(self.request, exc)
challenge.is_valid(raise_exception=True)
return to_stage_response(self.request, HttpChallengeResponse(challenge))

View File

@@ -14,7 +14,6 @@ from authentik.events.models import Event, EventAction
from authentik.lib.expression.exceptions import ControlFlowException
from authentik.lib.sync.mapper import PropertyMappingManager
from authentik.lib.sync.outgoing.exceptions import NotFoundSyncException, StopSync
from authentik.lib.utils.errors import exception_to_string
if TYPE_CHECKING:
from django.db.models import Model
@@ -106,9 +105,9 @@ class BaseOutgoingSyncClient[
# Value error can be raised when assigning invalid data to an attribute
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Failed to evaluate property-mapping {exception_to_string(exc)}",
message="Failed to evaluate property-mapping",
mapping=exc.mapping,
).save()
).with_exception(exc).save()
raise StopSync(exc, obj, exc.mapping) from exc
if not raw_final_object:
raise StopSync(ValueError("No mappings configured"), obj)

View File

@@ -2,6 +2,8 @@
from traceback import extract_tb
from structlog.tracebacks import ExceptionDictTransformer
from authentik.lib.utils.reflection import class_to_path
TRACEBACK_HEADER = "Traceback (most recent call last):"
@@ -17,3 +19,8 @@ def exception_to_string(exc: Exception) -> str:
f"{class_to_path(exc.__class__)}: {str(exc)}",
]
)
def exception_to_dict(exc: Exception) -> dict:
"""Format exception as a dictionary"""
return ExceptionDictTransformer()((type(exc), exc, exc.__traceback__))

View File

@@ -35,7 +35,6 @@ from authentik.events.models import Event, EventAction
from authentik.lib.config import CONFIG
from authentik.lib.models import InheritanceForeignKey, SerializerModel
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.errors import exception_to_string
from authentik.outposts.controllers.k8s.utils import get_namespace
OUR_VERSION = parse(__version__)
@@ -326,9 +325,8 @@ class Outpost(SerializerModel, ManagedModel):
"While setting the permissions for the service-account, a "
"permission was not found: Check "
"https://goauthentik.io/docs/troubleshooting/missing_permission"
)
+ exception_to_string(exc),
).set_user(user).save()
),
).with_exception(exc).set_user(user).save()
else:
app_label, perm = model_or_perm.split(".")
permission = Permission.objects.filter(

View File

@@ -10,7 +10,7 @@ from structlog.stdlib import get_logger
from authentik.events.models import Event, EventAction
from authentik.lib.config import CONFIG
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.errors import exception_to_dict
from authentik.lib.utils.reflection import class_to_path
from authentik.policies.apps import HIST_POLICIES_EXECUTION_TIME
from authentik.policies.exceptions import PolicyException
@@ -95,10 +95,13 @@ class PolicyProcess(PROCESS_CLASS):
except PolicyException as exc:
# Either use passed original exception or whatever we have
src_exc = exc.src_exc if exc.src_exc else exc
error_string = exception_to_string(src_exc)
# Create policy exception event, only when we're not debugging
if not self.request.debug:
self.create_event(EventAction.POLICY_EXCEPTION, message=error_string)
self.create_event(
EventAction.POLICY_EXCEPTION,
message="Policy failed to execute",
exception=exception_to_dict(src_exc),
)
LOGGER.debug("P_ENG(proc): error, using failure result", exc=src_exc)
policy_result = PolicyResult(self.binding.failure_result, str(src_exc))
policy_result.source_binding = self.binding
@@ -143,5 +146,5 @@ class PolicyProcess(PROCESS_CLASS):
try:
self.connection.send(self.profiling_wrapper())
except Exception as exc:
LOGGER.warning("Policy failed to run", exc=exception_to_string(exc))
LOGGER.warning("Policy failed to run", exc=exc)
self.connection.send(PolicyResult(False, str(exc)))

View File

@@ -241,4 +241,4 @@ class TestPolicyProcess(TestCase):
self.assertEqual(len(events), 1)
event = events.first()
self.assertEqual(event.user["username"], self.user.username)
self.assertIn("division by zero", event.context["message"])
self.assertIn("Policy failed to execute", event.context["message"])

View File

@@ -15,12 +15,14 @@ class OAuth2Error(SentryIgnoredException):
error: str
description: str
cause: str | None = None
def create_dict(self):
def create_dict(self, request: HttpRequest):
"""Return error as dict for JSON Rendering"""
return {
"error": self.error,
"error_description": self.description,
"request_id": request.request_id,
}
def __repr__(self) -> str:
@@ -31,9 +33,15 @@ class OAuth2Error(SentryIgnoredException):
return Event.new(
EventAction.CONFIGURATION_ERROR,
message=message or self.description,
cause=self.cause,
error=self.error,
**kwargs,
)
def with_cause(self, cause: str):
self.cause = cause
return self
class RedirectUriError(OAuth2Error):
"""The request fails due to a missing, invalid, or mismatching
@@ -243,13 +251,14 @@ class TokenRevocationError(OAuth2Error):
self.description = self.errors[error]
class DeviceCodeError(OAuth2Error):
class DeviceCodeError(TokenError):
"""
Device-code flow errors
See https://datatracker.ietf.org/doc/html/rfc8628#section-3.2
Can also use codes form TokenError
"""
errors = {
errors = TokenError.errors | {
"authorization_pending": (
"The authorization request is still pending as the end user hasn't "
"yet completed the user-interaction steps"
@@ -261,10 +270,15 @@ class DeviceCodeError(OAuth2Error):
"authorization request but SHOULD wait for user interaction before "
"restarting to avoid unnecessary polling."
),
"slow_down": (
'A variant of "authorization_pending", the authorization request is'
"still pending and polling should continue, but the interval MUST"
"be increased by 5 seconds for this and all subsequent requests."
),
}
def __init__(self, error: str):
super().__init__()
super().__init__(error)
self.error = error
self.description = self.errors[error]

View File

@@ -12,7 +12,7 @@ from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.events.models import Event, EventAction
from authentik.lib.generators import generate_id
from authentik.lib.utils.time import timedelta_from_string
from authentik.providers.oauth2.constants import TOKEN_TYPE
from authentik.providers.oauth2.constants import SCOPE_OFFLINE_ACCESS, SCOPE_OPENID, TOKEN_TYPE
from authentik.providers.oauth2.errors import AuthorizeError, ClientIdError, RedirectUriError
from authentik.providers.oauth2.models import (
AccessToken,
@@ -43,7 +43,7 @@ class TestAuthorize(OAuthTestCase):
authorization_flow=create_test_flow(),
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://local.invalid/Foo")],
)
with self.assertRaises(AuthorizeError):
with self.assertRaises(AuthorizeError) as cm:
request = self.factory.get(
"/",
data={
@@ -53,6 +53,7 @@ class TestAuthorize(OAuthTestCase):
},
)
OAuthAuthorizationParams.from_request(request)
self.assertEqual(cm.exception.error, "unsupported_response_type")
def test_invalid_client_id(self):
"""Test invalid client ID"""
@@ -68,7 +69,7 @@ class TestAuthorize(OAuthTestCase):
authorization_flow=create_test_flow(),
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://local.invalid/Foo")],
)
with self.assertRaises(AuthorizeError):
with self.assertRaises(AuthorizeError) as cm:
request = self.factory.get(
"/",
data={
@@ -79,19 +80,30 @@ class TestAuthorize(OAuthTestCase):
},
)
OAuthAuthorizationParams.from_request(request)
self.assertEqual(cm.exception.error, "request_not_supported")
def test_invalid_redirect_uri(self):
"""test missing/invalid redirect URI"""
def test_invalid_redirect_uri_missing(self):
"""test missing redirect URI"""
OAuth2Provider.objects.create(
name=generate_id(),
client_id="test",
authorization_flow=create_test_flow(),
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://local.invalid")],
)
with self.assertRaises(RedirectUriError):
with self.assertRaises(RedirectUriError) as cm:
request = self.factory.get("/", data={"response_type": "code", "client_id": "test"})
OAuthAuthorizationParams.from_request(request)
with self.assertRaises(RedirectUriError):
self.assertEqual(cm.exception.cause, "redirect_uri_missing")
def test_invalid_redirect_uri(self):
"""test invalid redirect URI"""
OAuth2Provider.objects.create(
name=generate_id(),
client_id="test",
authorization_flow=create_test_flow(),
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://local.invalid")],
)
with self.assertRaises(RedirectUriError) as cm:
request = self.factory.get(
"/",
data={
@@ -101,6 +113,7 @@ class TestAuthorize(OAuthTestCase):
},
)
OAuthAuthorizationParams.from_request(request)
self.assertEqual(cm.exception.cause, "redirect_uri_no_match")
def test_blocked_redirect_uri(self):
"""test missing/invalid redirect URI"""
@@ -108,9 +121,9 @@ class TestAuthorize(OAuthTestCase):
name=generate_id(),
client_id="test",
authorization_flow=create_test_flow(),
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "data:local.invalid")],
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "data:localhost")],
)
with self.assertRaises(RedirectUriError):
with self.assertRaises(RedirectUriError) as cm:
request = self.factory.get(
"/",
data={
@@ -120,6 +133,7 @@ class TestAuthorize(OAuthTestCase):
},
)
OAuthAuthorizationParams.from_request(request)
self.assertEqual(cm.exception.cause, "redirect_uri_forbidden_scheme")
def test_invalid_redirect_uri_empty(self):
"""test missing/invalid redirect URI"""
@@ -129,9 +143,6 @@ class TestAuthorize(OAuthTestCase):
authorization_flow=create_test_flow(),
redirect_uris=[],
)
with self.assertRaises(RedirectUriError):
request = self.factory.get("/", data={"response_type": "code", "client_id": "test"})
OAuthAuthorizationParams.from_request(request)
request = self.factory.get(
"/",
data={
@@ -150,12 +161,9 @@ class TestAuthorize(OAuthTestCase):
name=generate_id(),
client_id="test",
authorization_flow=create_test_flow(),
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://local.invalid?")],
redirect_uris=[RedirectURI(RedirectURIMatchingMode.REGEX, "http://local.invalid?")],
)
with self.assertRaises(RedirectUriError):
request = self.factory.get("/", data={"response_type": "code", "client_id": "test"})
OAuthAuthorizationParams.from_request(request)
with self.assertRaises(RedirectUriError):
with self.assertRaises(RedirectUriError) as cm:
request = self.factory.get(
"/",
data={
@@ -165,6 +173,7 @@ class TestAuthorize(OAuthTestCase):
},
)
OAuthAuthorizationParams.from_request(request)
self.assertEqual(cm.exception.cause, "redirect_uri_no_match")
def test_redirect_uri_invalid_regex(self):
"""test missing/invalid redirect URI (invalid regex)"""
@@ -172,12 +181,9 @@ class TestAuthorize(OAuthTestCase):
name=generate_id(),
client_id="test",
authorization_flow=create_test_flow(),
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "+")],
redirect_uris=[RedirectURI(RedirectURIMatchingMode.REGEX, "+")],
)
with self.assertRaises(RedirectUriError):
request = self.factory.get("/", data={"response_type": "code", "client_id": "test"})
OAuthAuthorizationParams.from_request(request)
with self.assertRaises(RedirectUriError):
with self.assertRaises(RedirectUriError) as cm:
request = self.factory.get(
"/",
data={
@@ -187,23 +193,22 @@ class TestAuthorize(OAuthTestCase):
},
)
OAuthAuthorizationParams.from_request(request)
self.assertEqual(cm.exception.cause, "redirect_uri_no_match")
def test_empty_redirect_uri(self):
"""test empty redirect URI (configure in provider)"""
def test_redirect_uri_regex(self):
"""test valid redirect URI (regex)"""
OAuth2Provider.objects.create(
name=generate_id(),
client_id="test",
authorization_flow=create_test_flow(),
redirect_uris=[RedirectURI(RedirectURIMatchingMode.REGEX, ".+")],
)
with self.assertRaises(RedirectUriError):
request = self.factory.get("/", data={"response_type": "code", "client_id": "test"})
OAuthAuthorizationParams.from_request(request)
request = self.factory.get(
"/",
data={
"response_type": "code",
"client_id": "test",
"redirect_uri": "http://localhost",
"redirect_uri": "http://foo.bar.baz",
},
)
OAuthAuthorizationParams.from_request(request)
@@ -258,7 +263,7 @@ class TestAuthorize(OAuthTestCase):
GrantTypes.IMPLICIT,
)
# Implicit without openid scope
with self.assertRaises(AuthorizeError):
with self.assertRaises(AuthorizeError) as cm:
request = self.factory.get(
"/",
data={
@@ -285,7 +290,7 @@ class TestAuthorize(OAuthTestCase):
self.assertEqual(
OAuthAuthorizationParams.from_request(request).grant_type, GrantTypes.HYBRID
)
with self.assertRaises(AuthorizeError):
with self.assertRaises(AuthorizeError) as cm:
request = self.factory.get(
"/",
data={
@@ -295,6 +300,7 @@ class TestAuthorize(OAuthTestCase):
},
)
OAuthAuthorizationParams.from_request(request)
self.assertEqual(cm.exception.error, "unsupported_response_type")
def test_full_code(self):
"""Test full authorization"""
@@ -613,3 +619,54 @@ class TestAuthorize(OAuthTestCase):
},
},
)
def test_openid_missing_invalid(self):
"""test request requiring an OpenID scope to be set"""
OAuth2Provider.objects.create(
name=generate_id(),
client_id="test",
authorization_flow=create_test_flow(),
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://localhost")],
)
request = self.factory.get(
"/",
data={
"response_type": "id_token",
"client_id": "test",
"redirect_uri": "http://localhost",
"scope": "",
},
)
with self.assertRaises(AuthorizeError) as cm:
OAuthAuthorizationParams.from_request(request)
self.assertEqual(cm.exception.cause, "scope_openid_missing")
@apply_blueprint("system/providers-oauth2.yaml")
def test_offline_access_invalid(self):
"""test request for offline_access with invalid response type"""
provider = OAuth2Provider.objects.create(
name=generate_id(),
client_id="test",
authorization_flow=create_test_flow(),
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://localhost")],
)
provider.property_mappings.set(
ScopeMapping.objects.filter(
managed__in=[
"goauthentik.io/providers/oauth2/scope-openid",
"goauthentik.io/providers/oauth2/scope-offline_access",
]
)
)
request = self.factory.get(
"/",
data={
"response_type": "id_token",
"client_id": "test",
"redirect_uri": "http://localhost",
"scope": f"{SCOPE_OPENID} {SCOPE_OFFLINE_ACCESS}",
"nonce": generate_id(),
},
)
parsed = OAuthAuthorizationParams.from_request(request)
self.assertNotIn(SCOPE_OFFLINE_ACCESS, parsed.scope)

View File

@@ -68,7 +68,11 @@ class TestTokenClientCredentialsStandard(OAuthTestCase):
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content.decode(),
{"error": "invalid_grant", "error_description": TokenError.errors["invalid_grant"]},
{
"error": "invalid_grant",
"error_description": TokenError.errors["invalid_grant"],
"request_id": response.headers["X-authentik-id"],
},
)
def test_no_provider(self):
@@ -87,7 +91,11 @@ class TestTokenClientCredentialsStandard(OAuthTestCase):
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content.decode(),
{"error": "invalid_grant", "error_description": TokenError.errors["invalid_grant"]},
{
"error": "invalid_grant",
"error_description": TokenError.errors["invalid_grant"],
"request_id": response.headers["X-authentik-id"],
},
)
def test_permission_denied(self):
@@ -110,7 +118,11 @@ class TestTokenClientCredentialsStandard(OAuthTestCase):
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content.decode(),
{"error": "invalid_grant", "error_description": TokenError.errors["invalid_grant"]},
{
"error": "invalid_grant",
"error_description": TokenError.errors["invalid_grant"],
"request_id": response.headers["X-authentik-id"],
},
)
def test_incorrect_scopes(self):

View File

@@ -68,7 +68,11 @@ class TestTokenClientCredentialsStandardCompat(OAuthTestCase):
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content.decode(),
{"error": "invalid_grant", "error_description": TokenError.errors["invalid_grant"]},
{
"error": "invalid_grant",
"error_description": TokenError.errors["invalid_grant"],
"request_id": response.headers["X-authentik-id"],
},
)
def test_wrong_token(self):
@@ -85,7 +89,11 @@ class TestTokenClientCredentialsStandardCompat(OAuthTestCase):
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content.decode(),
{"error": "invalid_grant", "error_description": TokenError.errors["invalid_grant"]},
{
"error": "invalid_grant",
"error_description": TokenError.errors["invalid_grant"],
"request_id": response.headers["X-authentik-id"],
},
)
def test_no_provider(self):
@@ -104,7 +112,11 @@ class TestTokenClientCredentialsStandardCompat(OAuthTestCase):
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content.decode(),
{"error": "invalid_grant", "error_description": TokenError.errors["invalid_grant"]},
{
"error": "invalid_grant",
"error_description": TokenError.errors["invalid_grant"],
"request_id": response.headers["X-authentik-id"],
},
)
def test_permission_denied(self):
@@ -127,7 +139,11 @@ class TestTokenClientCredentialsStandardCompat(OAuthTestCase):
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content.decode(),
{"error": "invalid_grant", "error_description": TokenError.errors["invalid_grant"]},
{
"error": "invalid_grant",
"error_description": TokenError.errors["invalid_grant"],
"request_id": response.headers["X-authentik-id"],
},
)
def test_successful(self):

View File

@@ -68,7 +68,11 @@ class TestTokenClientCredentialsUserNamePassword(OAuthTestCase):
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content.decode(),
{"error": "invalid_grant", "error_description": TokenError.errors["invalid_grant"]},
{
"error": "invalid_grant",
"error_description": TokenError.errors["invalid_grant"],
"request_id": response.headers["X-authentik-id"],
},
)
def test_wrong_token(self):
@@ -86,7 +90,11 @@ class TestTokenClientCredentialsUserNamePassword(OAuthTestCase):
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content.decode(),
{"error": "invalid_grant", "error_description": TokenError.errors["invalid_grant"]},
{
"error": "invalid_grant",
"error_description": TokenError.errors["invalid_grant"],
"request_id": response.headers["X-authentik-id"],
},
)
def test_no_provider(self):
@@ -106,7 +114,11 @@ class TestTokenClientCredentialsUserNamePassword(OAuthTestCase):
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content.decode(),
{"error": "invalid_grant", "error_description": TokenError.errors["invalid_grant"]},
{
"error": "invalid_grant",
"error_description": TokenError.errors["invalid_grant"],
"request_id": response.headers["X-authentik-id"],
},
)
def test_permission_denied(self):
@@ -130,7 +142,11 @@ class TestTokenClientCredentialsUserNamePassword(OAuthTestCase):
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content.decode(),
{"error": "invalid_grant", "error_description": TokenError.errors["invalid_grant"]},
{
"error": "invalid_grant",
"error_description": TokenError.errors["invalid_grant"],
"request_id": response.headers["X-authentik-id"],
},
)
def test_successful(self):

View File

@@ -80,6 +80,7 @@ class TestTokenPKCE(OAuthTestCase):
"revoked, does not match the redirection URI used in the authorization "
"request, or was issued to another client"
),
"request_id": response.headers["X-authentik-id"],
},
)
self.assertEqual(response.status_code, 400)
@@ -136,6 +137,7 @@ class TestTokenPKCE(OAuthTestCase):
"revoked, does not match the redirection URI used in the authorization "
"request, or was issued to another client"
),
"request_id": response.headers["X-authentik-id"],
},
)
self.assertEqual(response.status_code, 400)

View File

@@ -190,7 +190,7 @@ class OAuthAuthorizationParams:
allowed_redirect_urls = self.provider.redirect_uris
if not self.redirect_uri:
LOGGER.warning("Missing redirect uri.")
raise RedirectUriError("", allowed_redirect_urls)
raise RedirectUriError("", allowed_redirect_urls).with_cause("redirect_uri_missing")
if len(allowed_redirect_urls) < 1:
LOGGER.info("Setting redirect for blank redirect_uris", redirect=self.redirect_uri)
@@ -219,10 +219,14 @@ class OAuthAuthorizationParams:
provider=self.provider,
)
if not match_found:
raise RedirectUriError(self.redirect_uri, allowed_redirect_urls)
raise RedirectUriError(self.redirect_uri, allowed_redirect_urls).with_cause(
"redirect_uri_no_match"
)
# Check against forbidden schemes
if urlparse(self.redirect_uri).scheme in FORBIDDEN_URI_SCHEMES:
raise RedirectUriError(self.redirect_uri, allowed_redirect_urls)
raise RedirectUriError(self.redirect_uri, allowed_redirect_urls).with_cause(
"redirect_uri_forbidden_scheme"
)
def check_scope(self, github_compat=False):
"""Ensure openid scope is set in Hybrid flows, or when requesting an id_token"""
@@ -251,7 +255,9 @@ class OAuthAuthorizationParams:
or self.response_type in [ResponseTypes.ID_TOKEN, ResponseTypes.ID_TOKEN_TOKEN]
):
LOGGER.warning("Missing 'openid' scope.")
raise AuthorizeError(self.redirect_uri, "invalid_scope", self.grant_type, self.state)
raise AuthorizeError(
self.redirect_uri, "invalid_scope", self.grant_type, self.state
).with_cause("scope_openid_missing")
if SCOPE_OFFLINE_ACCESS in self.scope:
# https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
# Don't explicitly request consent with offline_access, as the spec allows for
@@ -286,7 +292,9 @@ class OAuthAuthorizationParams:
return
if not self.nonce:
LOGGER.warning("Missing nonce for OpenID Request")
raise AuthorizeError(self.redirect_uri, "invalid_request", self.grant_type, self.state)
raise AuthorizeError(
self.redirect_uri, "invalid_request", self.grant_type, self.state
).with_cause("nonce_missing")
def check_code_challenge(self):
"""PKCE validation of the transformation method."""
@@ -345,10 +353,10 @@ class AuthorizationFlowInitView(PolicyAccessView):
self.request, github_compat=self.github_compat
)
except AuthorizeError as error:
LOGGER.warning(error.description, redirect_uri=error.redirect_uri)
LOGGER.warning(error.description, redirect_uri=error.redirect_uri, cause=error.cause)
raise RequestValidationError(error.get_response(self.request)) from None
except OAuth2Error as error:
LOGGER.warning(error.description)
LOGGER.warning(error.description, cause=error.cause)
raise RequestValidationError(
bad_request_message(self.request, error.description, title=error.error)
) from None

View File

@@ -2,7 +2,7 @@
from urllib.parse import urlencode
from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest, JsonResponse
from django.http import HttpRequest, HttpResponse
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.timezone import now
@@ -14,7 +14,9 @@ from structlog.stdlib import get_logger
from authentik.core.models import Application
from authentik.lib.config import CONFIG
from authentik.lib.utils.time import timedelta_from_string
from authentik.providers.oauth2.errors import DeviceCodeError
from authentik.providers.oauth2.models import DeviceToken, OAuth2Provider
from authentik.providers.oauth2.utils import TokenResponse
from authentik.providers.oauth2.views.device_init import QS_KEY_CODE
LOGGER = get_logger()
@@ -28,38 +30,36 @@ class DeviceView(View):
provider: OAuth2Provider
scopes: list[str] = []
def parse_request(self) -> HttpResponse | None:
def parse_request(self):
"""Parse incoming request"""
client_id = self.request.POST.get("client_id", None)
if not client_id:
return HttpResponseBadRequest()
provider = OAuth2Provider.objects.filter(
client_id=client_id,
).first()
raise DeviceCodeError("invalid_client")
provider = OAuth2Provider.objects.filter(client_id=client_id).first()
if not provider:
return HttpResponseBadRequest()
raise DeviceCodeError("invalid_client")
try:
_ = provider.application
except Application.DoesNotExist:
return HttpResponseBadRequest()
raise DeviceCodeError("invalid_client") from None
self.provider = provider
self.client_id = client_id
self.scopes = self.request.POST.get("scope", "").split(" ")
return None
def dispatch(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
throttle = AnonRateThrottle()
throttle.rate = CONFIG.get("throttle.providers.oauth2.device", "20/hour")
throttle.num_requests, throttle.duration = throttle.parse_rate(throttle.rate)
if not throttle.allow_request(request, self):
return HttpResponse(status=429)
return TokenResponse(DeviceCodeError("slow_down").create_dict(request), status=429)
return super().dispatch(request, *args, **kwargs)
def post(self, request: HttpRequest) -> HttpResponse:
"""Generate device token"""
resp = self.parse_request()
if resp:
return resp
try:
self.parse_request()
except DeviceCodeError as exc:
return TokenResponse(exc.create_dict(request), status=400)
until = timedelta_from_string(self.provider.access_code_validity)
token: DeviceToken = DeviceToken.objects.create(
expires=now() + until, provider=self.provider, _scope=" ".join(self.scopes)
@@ -67,7 +67,7 @@ class DeviceView(View):
device_url = self.request.build_absolute_uri(
reverse("authentik_providers_oauth2_root:device-login")
)
return JsonResponse(
return TokenResponse(
{
"device_code": token.device_code,
"verification_uri": device_url,

View File

@@ -598,9 +598,9 @@ class TokenView(View):
return TokenResponse(self.create_device_code_response())
raise TokenError("unsupported_grant_type")
except (TokenError, DeviceCodeError) as error:
return TokenResponse(error.create_dict(), status=400)
return TokenResponse(error.create_dict(request), status=400)
except UserAuthError as error:
return TokenResponse(error.create_dict(), status=403)
return TokenResponse(error.create_dict(request), status=403)
def create_code_response(self) -> dict[str, Any]:
"""See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1"""

View File

@@ -65,7 +65,7 @@ class TokenRevokeView(View):
return TokenResponse(data={}, status=200)
except TokenRevocationError as exc:
return TokenResponse(exc.create_dict(), status=401)
return TokenResponse(exc.create_dict(request), status=401)
except Http404:
# Token not found should return a HTTP 200
# https://datatracker.ietf.org/doc/html/rfc7009#section-2.2

View File

@@ -102,6 +102,7 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]):
# Buffer sizes for large headers with JWTs
"nginx.ingress.kubernetes.io/proxy-buffers-number": "4",
"nginx.ingress.kubernetes.io/proxy-buffer-size": "16k",
"nginx.ingress.kubernetes.io/proxy-busy-buffers-size": "32k",
# Enable TLS in traefik
"traefik.ingress.kubernetes.io/router.tls": "true",
}

View File

@@ -23,7 +23,6 @@ from authentik.core.models import Application
from authentik.events.models import Event, EventAction
from authentik.lib.expression.exceptions import ControlFlowException
from authentik.lib.sync.mapper import PropertyMappingManager
from authentik.lib.utils.errors import exception_to_string
from authentik.policies.api.exec import PolicyTestResultSerializer
from authentik.policies.engine import PolicyEngine
from authentik.policies.types import PolicyResult
@@ -142,9 +141,9 @@ class RadiusOutpostConfigViewSet(ListModelMixin, GenericViewSet):
# Value error can be raised when assigning invalid data to an attribute
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Failed to evaluate property-mapping {exception_to_string(exc)}",
message="Failed to evaluate property-mapping",
mapping=exc.mapping,
).save()
).with_exception(exc).save()
return None
return b64encode(packet.RequestPacket()).decode()

View File

@@ -2,7 +2,7 @@
from enum import Enum
from pydantic import Field
from pydantic import AnyUrl, BaseModel, ConfigDict, Field
from pydanticscim.group import Group as BaseGroup
from pydanticscim.responses import PatchOperation as BasePatchOperation
from pydanticscim.responses import PatchRequest as BasePatchRequest
@@ -12,19 +12,95 @@ from pydanticscim.service_provider import ChangePassword, Filter, Patch, Sort
from pydanticscim.service_provider import (
ServiceProviderConfiguration as BaseServiceProviderConfiguration,
)
from pydanticscim.user import AddressKind
from pydanticscim.user import User as BaseUser
SCIM_USER_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:User"
SCIM_GROUP_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:Group"
class Address(BaseModel):
formatted: str | None = Field(
None,
description="The full mailing address, formatted for display "
"or use with a mailing label. This attribute MAY contain newlines.",
)
streetAddress: str | None = Field(
None,
description="The full street address component, which may "
"include house number, street name, P.O. box, and multi-line "
"extended street address information. This attribute MAY contain newlines.",
)
locality: str | None = Field(None, description="The city or locality component.")
region: str | None = Field(None, description="The state or region component.")
postalCode: str | None = Field(None, description="The zip code or postal code component.")
country: str | None = Field(None, description="The country name component.")
type: AddressKind | None = Field(
None,
description="A label indicating the attribute's function, e.g., 'work' or 'home'.",
)
primary: bool | None = None
class Manager(BaseModel):
value: str | None = Field(
None,
description="The id of the SCIM resource representingthe User's manager. REQUIRED.",
)
ref: AnyUrl | None = Field(
None,
alias="$ref",
description="The URI of the SCIM resource representing the User's manager. REQUIRED.",
)
displayName: str | None = Field(
None,
description="The displayName of the User's manager. OPTIONAL and READ-ONLY.",
)
class EnterpriseUser(BaseModel):
employeeNumber: str | None = Field(
None,
description="Numeric or alphanumeric identifier assigned to a person, "
"typically based on order of hire or association with anorganization.",
)
costCenter: str | None = Field(None, description="Identifies the name of a cost center.")
organization: str | None = Field(None, description="Identifies the name of an organization.")
division: str | None = Field(None, description="Identifies the name of a division.")
department: str | None = Field(
None,
description="Numeric or alphanumeric identifier assigned to a person,"
" typically based on order of hire or association with anorganization.",
)
manager: Manager | None = Field(
None,
description="The User's manager. A complex type that optionally allows "
"service providers to represent organizational hierarchy by referencing"
" the 'id' attribute of another User.",
)
class User(BaseUser):
"""Modified User schema with added externalId field"""
model_config = ConfigDict(serialize_by_alias=True)
id: str | int | None = None
schemas: list[str] = [SCIM_USER_SCHEMA]
externalId: str | None = None
meta: dict | None = None
addresses: list[Address] | None = Field(
None,
description=(
"A physical mailing address for this User. Canonical type "
"values of 'work', 'home', and 'other'."
),
)
enterprise_user: EnterpriseUser | None = Field(
default=None,
alias="urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
serialization_alias="urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
)
class Group(BaseGroup):
@@ -92,7 +168,7 @@ class PatchOperation(BasePatchOperation):
"""PatchOperation with optional path"""
op: PatchOp
path: str | None
path: str | None = None
class SCIMError(BaseSCIMError):

View File

@@ -28,7 +28,6 @@ from tenant_schemas_celery.app import CeleryApp as TenantAwareCeleryApp
from authentik import get_full_version
from authentik.lib.sentry import should_ignore_exception
from authentik.lib.utils.errors import exception_to_string
# set the default Django settings module for the 'celery' program.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings")
@@ -83,8 +82,8 @@ def task_error_hook(task_id: str, exception: Exception, traceback, *args, **kwar
CTX_TASK_ID.set(...)
if not should_ignore_exception(exception):
Event.new(
EventAction.SYSTEM_EXCEPTION, message=exception_to_string(exception), task_id=task_id
).save()
EventAction.SYSTEM_EXCEPTION, message="Failed to execute task", task_id=task_id
).with_exception(exception).save()
def _get_startup_tasks_default_tenant() -> list[Callable]:

View File

@@ -8,7 +8,6 @@ from authentik.events.models import TaskStatus
from authentik.events.system_tasks import SystemTask
from authentik.lib.config import CONFIG
from authentik.lib.sync.outgoing.exceptions import StopSync
from authentik.lib.utils.errors import exception_to_string
from authentik.root.celery import CELERY_APP
from authentik.sources.kerberos.models import KerberosSource
from authentik.sources.kerberos.sync import KerberosSync
@@ -64,5 +63,5 @@ def kerberos_sync_single(self, source_pk: str):
syncer.sync()
self.set_status(TaskStatus.SUCCESSFUL, *syncer.messages)
except StopSync as exc:
LOGGER.warning(exception_to_string(exc))
LOGGER.warning("Error syncing kerberos", exc=exc, source=source)
self.set_error(exc)

View File

@@ -12,7 +12,6 @@ from authentik.events.models import TaskStatus
from authentik.events.system_tasks import SystemTask
from authentik.lib.config import CONFIG
from authentik.lib.sync.outgoing.exceptions import StopSync
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.reflection import class_to_path, path_to_class
from authentik.root.celery import CELERY_APP
from authentik.sources.ldap.models import LDAPSource
@@ -149,5 +148,5 @@ def ldap_sync(self: SystemTask, source_pk: str, sync_class: str, page_cache_key:
cache.delete(page_cache_key)
except (LDAPException, StopSync) as exc:
# No explicit event is created here as .set_status with an error will do that
LOGGER.warning(exception_to_string(exc))
LOGGER.warning("Failed to sync LDAP", exc=exc, source=source)
self.set_error(exc)

View File

@@ -10,6 +10,7 @@ AUTHENTIK_SOURCES_OAUTH_TYPES = [
"authentik.sources.oauth.types.apple",
"authentik.sources.oauth.types.azure_ad",
"authentik.sources.oauth.types.discord",
"authentik.sources.oauth.types.entra_id",
"authentik.sources.oauth.types.facebook",
"authentik.sources.oauth.types.github",
"authentik.sources.oauth.types.gitlab",

View File

@@ -232,7 +232,7 @@ class GoogleOAuthSource(CreatableType, OAuthSource):
class AzureADOAuthSource(CreatableType, OAuthSource):
"""Social Login using Azure AD."""
"""(Deprecated) Social Login using Azure AD."""
class Meta:
abstract = True
@@ -240,6 +240,17 @@ class AzureADOAuthSource(CreatableType, OAuthSource):
verbose_name_plural = _("Azure AD OAuth Sources")
# TODO: When removing this, add a migration for OAuthSource that sets
# provider_type to `entraid` if it is currently `azuread`
class EntraIDOAuthSource(CreatableType, OAuthSource):
"""Social Login using Entra ID."""
class Meta:
abstract = True
verbose_name = _("Entra ID OAuth Source")
verbose_name_plural = _("Entra ID OAuth Sources")
class OpenIDConnectOAuthSource(CreatableType, OAuthSource):
"""Login using a Generic OpenID-Connect compliant provider."""

View File

@@ -1,12 +1,12 @@
"""azure ad Type tests"""
"""Entra ID Type tests"""
from django.test import TestCase
from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.types.azure_ad import AzureADOAuthCallback, AzureADType
from authentik.sources.oauth.types.entra_id import EntraIDOAuthCallback, EntraIDType
# https://docs.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0&tabs=http#response-2
AAD_USER = {
EID_USER = {
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
"@odata.id": (
"https://graph.microsoft.com/v2/7ce9b89e-646a-41d2-9fa6-8371c6a8423d/"
@@ -41,11 +41,11 @@ class TestTypeAzureAD(TestCase):
def test_enroll_context(self):
"""Test azure_ad Enrollment context"""
ak_context = AzureADType().get_base_user_properties(source=self.source, info=AAD_USER)
self.assertEqual(ak_context["username"], AAD_USER["userPrincipalName"])
self.assertEqual(ak_context["email"], AAD_USER["mail"])
self.assertEqual(ak_context["name"], AAD_USER["displayName"])
ak_context = EntraIDType().get_base_user_properties(source=self.source, info=EID_USER)
self.assertEqual(ak_context["username"], EID_USER["userPrincipalName"])
self.assertEqual(ak_context["email"], EID_USER["mail"])
self.assertEqual(ak_context["name"], EID_USER["displayName"])
def test_user_id(self):
"""Test azure AD user ID"""
self.assertEqual(AzureADOAuthCallback().get_user_id(AAD_USER), AAD_USER["id"])
"""Test Entra ID user ID"""
self.assertEqual(EntraIDOAuthCallback().get_user_id(EID_USER), EID_USER["id"])

View File

@@ -1,105 +1,17 @@
"""AzureAD OAuth2 Views"""
from typing import Any
from authentik.sources.oauth.types.entra_id import EntraIDType
from authentik.sources.oauth.types.registry import registry
from requests import RequestException
from structlog.stdlib import get_logger
from authentik.sources.oauth.clients.oauth2 import UserprofileHeaderAuthClient
from authentik.sources.oauth.models import AuthorizationCodeAuthMethod
from authentik.sources.oauth.types.oidc import OpenIDConnectOAuth2Callback
from authentik.sources.oauth.types.registry import SourceType, registry
from authentik.sources.oauth.views.redirect import OAuthRedirect
LOGGER = get_logger()
class AzureADOAuthRedirect(OAuthRedirect):
"""Azure AD OAuth2 Redirect"""
def get_additional_parameters(self, source): # pragma: no cover
return {
"scope": ["openid", "https://graph.microsoft.com/User.Read"],
}
class AzureADClient(UserprofileHeaderAuthClient):
"""Fetch AzureAD group information"""
def get_profile_info(self, token):
profile_data = super().get_profile_info(token)
if "https://graph.microsoft.com/GroupMember.Read.All" not in self.source.additional_scopes:
return profile_data
group_response = self.session.request(
"get",
"https://graph.microsoft.com/v1.0/me/memberOf",
headers={"Authorization": f"{token['token_type']} {token['access_token']}"},
)
try:
group_response.raise_for_status()
except RequestException as exc:
LOGGER.warning(
"Unable to fetch user profile",
exc=exc,
response=exc.response.text if exc.response else str(exc),
)
return None
profile_data["raw_groups"] = group_response.json()
return profile_data
class AzureADOAuthCallback(OpenIDConnectOAuth2Callback):
"""AzureAD OAuth2 Callback"""
client_class = AzureADClient
def get_user_id(self, info: dict[str, str]) -> str:
# Default try to get `id` for the Graph API endpoint
# fallback to OpenID logic in case the profile URL was changed
return info.get("id", super().get_user_id(info))
# TODO: When removing this, add a migration for OAuthSource that sets
# provider_type to `entraid` if it is currently `azuread`
@registry.register()
class AzureADType(SourceType):
class AzureADType(EntraIDType):
"""Azure AD Type definition"""
callback_view = AzureADOAuthCallback
redirect_view = AzureADOAuthRedirect
verbose_name = "Azure AD"
name = "azuread"
urls_customizable = True
authorization_url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
access_token_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token" # nosec
profile_url = "https://graph.microsoft.com/v1.0/me"
oidc_well_known_url = (
"https://login.microsoftonline.com/common/.well-known/openid-configuration"
)
oidc_jwks_url = "https://login.microsoftonline.com/common/discovery/keys"
authorization_code_auth_method = AuthorizationCodeAuthMethod.POST_BODY
def get_base_user_properties(self, info: dict[str, Any], **kwargs) -> dict[str, Any]:
mail = info.get("mail", None) or info.get("otherMails", [None])[0]
# Format group info
groups = []
group_id_dict = {}
for group in info.get("raw_groups", {}).get("value", []):
if group["@odata.type"] != "#microsoft.graph.group":
continue
groups.append(group["id"])
group_id_dict[group["id"]] = group
info["raw_groups"] = group_id_dict
return {
"username": info.get("userPrincipalName"),
"email": mail,
"name": info.get("displayName"),
"groups": groups,
}
def get_base_group_properties(self, source, group_id, **kwargs):
raw_group = kwargs["info"]["raw_groups"][group_id]
return {
"name": raw_group["displayName"],
}

View File

@@ -0,0 +1,102 @@
"""EntraID OAuth2 Views"""
from typing import Any
from requests import RequestException
from structlog.stdlib import get_logger
from authentik.sources.oauth.clients.oauth2 import UserprofileHeaderAuthClient
from authentik.sources.oauth.models import AuthorizationCodeAuthMethod
from authentik.sources.oauth.types.oidc import OpenIDConnectOAuth2Callback
from authentik.sources.oauth.types.registry import SourceType, registry
from authentik.sources.oauth.views.redirect import OAuthRedirect
LOGGER = get_logger()
class EntraIDOAuthRedirect(OAuthRedirect):
"""Entra ID OAuth2 Redirect"""
def get_additional_parameters(self, source): # pragma: no cover
return {
"scope": ["openid", "https://graph.microsoft.com/User.Read"],
}
class EntraIDClient(UserprofileHeaderAuthClient):
"""Fetch EntraID group information"""
def get_profile_info(self, token):
profile_data = super().get_profile_info(token)
if "https://graph.microsoft.com/GroupMember.Read.All" not in self.source.additional_scopes:
return profile_data
group_response = self.session.request(
"get",
"https://graph.microsoft.com/v1.0/me/memberOf",
headers={"Authorization": f"{token['token_type']} {token['access_token']}"},
)
try:
group_response.raise_for_status()
except RequestException as exc:
LOGGER.warning(
"Unable to fetch user profile",
exc=exc,
response=exc.response.text if exc.response else str(exc),
)
return None
profile_data["raw_groups"] = group_response.json()
return profile_data
class EntraIDOAuthCallback(OpenIDConnectOAuth2Callback):
"""EntraID OAuth2 Callback"""
client_class = EntraIDClient
def get_user_id(self, info: dict[str, str]) -> str:
# Default try to get `id` for the Graph API endpoint
# fallback to OpenID logic in case the profile URL was changed
return info.get("id", super().get_user_id(info))
@registry.register()
class EntraIDType(SourceType):
"""Entra ID Type definition"""
callback_view = EntraIDOAuthCallback
redirect_view = EntraIDOAuthRedirect
verbose_name = "Entra ID"
name = "entraid"
urls_customizable = True
authorization_url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
access_token_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token" # nosec
profile_url = "https://graph.microsoft.com/v1.0/me"
oidc_jwks_url = "https://login.microsoftonline.com/common/discovery/keys"
authorization_code_auth_method = AuthorizationCodeAuthMethod.POST_BODY
def get_base_user_properties(self, info: dict[str, Any], **kwargs) -> dict[str, Any]:
mail = info.get("mail", None) or info.get("otherMails", [None])[0]
# Format group info
groups = []
group_id_dict = {}
for group in info.get("raw_groups", {}).get("value", []):
if group["@odata.type"] != "#microsoft.graph.group":
continue
groups.append(group["id"])
group_id_dict[group["id"]] = group
info["raw_groups"] = group_id_dict
return {
"username": info.get("userPrincipalName"),
"email": mail,
"name": info.get("displayName"),
"groups": groups,
}
def get_base_group_properties(self, source, group_id, **kwargs):
raw_group = kwargs["info"]["raw_groups"][group_id]
return {
"name": raw_group["displayName"],
}

View File

@@ -18,6 +18,7 @@ class SCIMSourceGroupSerializer(SourceSerializer):
model = SCIMSourceGroup
fields = [
"id",
"external_id",
"group",
"group_obj",
"source",
@@ -31,5 +32,5 @@ class SCIMSourceGroupViewSet(UsedByMixin, ModelViewSet):
queryset = SCIMSourceGroup.objects.all().select_related("group")
serializer_class = SCIMSourceGroupSerializer
filterset_fields = ["source__slug", "group__name", "group__group_uuid"]
search_fields = ["source__slug", "group__name", "attributes"]
search_fields = ["source__slug", "group__name", "attributes", "external_id"]
ordering = ["group__name"]

View File

@@ -18,6 +18,7 @@ class SCIMSourceUserSerializer(SourceSerializer):
model = SCIMSourceUser
fields = [
"id",
"external_id",
"user",
"user_obj",
"source",
@@ -31,5 +32,5 @@ class SCIMSourceUserViewSet(UsedByMixin, ModelViewSet):
queryset = SCIMSourceUser.objects.all().select_related("user")
serializer_class = SCIMSourceUserSerializer
filterset_fields = ["source__slug", "user__username", "user__id"]
search_fields = ["source__slug", "user__username", "attributes"]
search_fields = ["source__slug", "user__username", "attributes", "user__uuid", "external_id"]
ordering = ["user__username"]

View File

@@ -0,0 +1,4 @@
SCIM_URN_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:Schema"
SCIM_URN_GROUP = "urn:ietf:params:scim:schemas:core:2.0:Group"
SCIM_URN_USER = "urn:ietf:params:scim:schemas:core:2.0:User"
SCIM_URN_USER_ENTERPRISE = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"

View File

@@ -1,8 +0,0 @@
"""SCIM Errors"""
from authentik.lib.sentry import SentryIgnoredException
class PatchError(SentryIgnoredException):
"""Error raised within an atomic block when an error happened
so nothing is saved"""

View File

@@ -0,0 +1,98 @@
# Generated by Django 5.1.11 on 2025-07-13 01:07
import uuid
from django.db import migrations, models
from django.apps.registry import Apps
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def migrate_ext_id(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
SCIMSourceUser = apps.get_model("authentik_sources_scim", "SCIMSourceUser")
SCIMSourceGroup = apps.get_model("authentik_sources_scim", "SCIMSourceGroup")
db_alias = schema_editor.connection.alias
for user in SCIMSourceUser.objects.using(db_alias).all():
user.external_id = user.id
user.save(update_fields=["external_id"])
for group in SCIMSourceGroup.objects.using(db_alias).all():
group.external_id = group.id
group.save(update_fields=["external_id"])
class Migration(migrations.Migration):
dependencies = [
("authentik_sources_scim", "0002_scimsourcepropertymapping"),
]
operations = [
migrations.AlterUniqueTogether(
name="scimsourcegroup",
unique_together=set(),
),
migrations.AlterUniqueTogether(
name="scimsourceuser",
unique_together=set(),
),
migrations.AddField(
model_name="scimsourcegroup",
name="external_id",
field=models.TextField(default=None, null=True),
preserve_default=False,
),
migrations.AddField(
model_name="scimsourceuser",
name="external_id",
field=models.TextField(default=None, null=True),
preserve_default=False,
),
migrations.AlterUniqueTogether(
name="scimsourcegroup",
unique_together={("external_id", "source")},
),
migrations.AlterUniqueTogether(
name="scimsourceuser",
unique_together={("external_id", "source")},
),
migrations.RunPython(migrate_ext_id, migrations.RunPython.noop),
migrations.AlterField(
model_name="scimsourcegroup",
name="external_id",
field=models.TextField(),
preserve_default=False,
),
migrations.AlterField(
model_name="scimsourceuser",
name="external_id",
field=models.TextField(),
preserve_default=False,
),
migrations.AddIndex(
model_name="scimsourcegroup",
index=models.Index(fields=["external_id"], name="authentik_s_externa_05e346_idx"),
),
migrations.AddIndex(
model_name="scimsourceuser",
index=models.Index(fields=["external_id"], name="authentik_s_externa_4bd760_idx"),
),
migrations.AlterField(
model_name="scimsourcegroup",
name="id",
field=models.TextField(default=uuid.uuid4, primary_key=True, serialize=False),
),
migrations.AlterField(
model_name="scimsourceuser",
name="id",
field=models.TextField(default=uuid.uuid4, primary_key=True, serialize=False),
),
migrations.AddField(
model_name="scimsourcegroup",
name="last_update",
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name="scimsourceuser",
name="last_update",
field=models.DateTimeField(auto_now=True),
),
]

View File

@@ -1,6 +1,7 @@
"""SCIM Source"""
from typing import Any
from uuid import uuid4
from django.db import models
from django.templatetags.static import static
@@ -103,10 +104,12 @@ class SCIMSourcePropertyMapping(PropertyMapping):
class SCIMSourceUser(SerializerModel):
"""Mapping of a user and source to a SCIM user ID"""
id = models.TextField(primary_key=True)
id = models.TextField(primary_key=True, default=uuid4)
external_id = models.TextField()
user = models.ForeignKey(User, on_delete=models.CASCADE)
source = models.ForeignKey(SCIMSource, on_delete=models.CASCADE)
attributes = models.JSONField(default=dict)
last_update = models.DateTimeField(auto_now=True)
@property
def serializer(self) -> BaseSerializer:
@@ -115,7 +118,10 @@ class SCIMSourceUser(SerializerModel):
return SCIMSourceUserSerializer
class Meta:
unique_together = (("id", "user", "source"),)
unique_together = (("external_id", "source"),)
indexes = [
models.Index(fields=["external_id"]),
]
def __str__(self) -> str:
return f"SCIM User {self.user_id} to {self.source_id}"
@@ -124,10 +130,12 @@ class SCIMSourceUser(SerializerModel):
class SCIMSourceGroup(SerializerModel):
"""Mapping of a group and source to a SCIM user ID"""
id = models.TextField(primary_key=True)
id = models.TextField(primary_key=True, default=uuid4)
external_id = models.TextField()
group = models.ForeignKey(Group, on_delete=models.CASCADE)
source = models.ForeignKey(SCIMSource, on_delete=models.CASCADE)
attributes = models.JSONField(default=dict)
last_update = models.DateTimeField(auto_now=True)
@property
def serializer(self) -> BaseSerializer:
@@ -136,7 +144,10 @@ class SCIMSourceGroup(SerializerModel):
return SCIMSourceGroupSerializer
class Meta:
unique_together = (("id", "group", "source"),)
unique_together = (("external_id", "source"),)
indexes = [
models.Index(fields=["external_id"]),
]
def __str__(self) -> str:
return f"SCIM Group {self.group_id} to {self.source_id}"

View File

View File

@@ -0,0 +1,180 @@
from dataclasses import dataclass
from enum import Enum
from authentik.sources.scim.constants import (
SCIM_URN_GROUP,
SCIM_URN_SCHEMA,
SCIM_URN_USER,
SCIM_URN_USER_ENTERPRISE,
)
# Token types for SCIM path parsing
class TokenType(Enum):
ATTRIBUTE = "ATTRIBUTE"
DOT = "DOT"
LBRACKET = "LBRACKET"
RBRACKET = "RBRACKET"
LPAREN = "LPAREN"
RPAREN = "RPAREN"
STRING = "STRING"
NUMBER = "NUMBER"
BOOLEAN = "BOOLEAN"
NULL = "NULL"
OPERATOR = "OPERATOR"
AND = "AND"
OR = "OR"
NOT = "NOT"
EOF = "EOF"
@dataclass
class Token:
type: TokenType
value: str
position: int = 0
class SCIMPathLexer:
"""Lexer for SCIM paths and filter expressions"""
OPERATORS = ["eq", "ne", "co", "sw", "ew", "gt", "lt", "ge", "le", "pr"]
def __init__(self, text: str):
self.schema_urns = [
SCIM_URN_SCHEMA,
SCIM_URN_GROUP,
SCIM_URN_USER,
SCIM_URN_USER_ENTERPRISE,
]
self.text = text
self.pos = 0
self.current_char = self.text[self.pos] if self.pos < len(self.text) else None
def advance(self):
"""Move to next character"""
self.pos += 1
self.current_char = self.text[self.pos] if self.pos < len(self.text) else None
def skip_whitespace(self):
"""Skip whitespace characters"""
while self.current_char and self.current_char.isspace():
self.advance()
def read_string(self, quote_char):
"""Read a quoted string"""
value = ""
self.advance() # Skip opening quote
while self.current_char and self.current_char != quote_char:
if self.current_char == "\\":
self.advance()
if self.current_char:
value += self.current_char
self.advance()
else:
value += self.current_char
self.advance()
if self.current_char == quote_char:
self.advance() # Skip closing quote
return value
def read_number(self):
"""Read a number (integer or float)"""
value = ""
while self.current_char and (self.current_char.isdigit() or self.current_char == "."):
value += self.current_char
self.advance()
return value
def read_identifier(self):
"""Read an identifier (attribute name or operator) - supports URN format"""
value = ""
while self.current_char and (self.current_char.isalnum() or self.current_char in "_-:"):
value += self.current_char
self.advance()
# If the identifier value so far is a schema URN, take that as the identifier and
# treat the next part as a sub_attribute
if value in self.schema_urns:
self.current_char = "."
return value
# Handle dots within URN identifiers (like "2.0")
# A dot is part of the identifier if it's followed by a digit
if (
self.current_char == "."
and self.pos + 1 < len(self.text)
and self.text[self.pos + 1].isdigit()
):
value += self.current_char
self.advance()
# Continue reading digits after the dot
while self.current_char and self.current_char.isdigit():
value += self.current_char
self.advance()
return value
def get_next_token(self) -> Token: # noqa PLR0911
"""Get the next token from the input"""
while self.current_char:
if self.current_char.isspace():
self.skip_whitespace()
continue
if self.current_char == ".":
self.advance()
return Token(TokenType.DOT, ".")
if self.current_char == "[":
self.advance()
return Token(TokenType.LBRACKET, "[")
if self.current_char == "]":
self.advance()
return Token(TokenType.RBRACKET, "]")
if self.current_char == "(":
self.advance()
return Token(TokenType.LPAREN, "(")
if self.current_char == ")":
self.advance()
return Token(TokenType.RPAREN, ")")
if self.current_char in "\"'":
quote_char = self.current_char
value = self.read_string(quote_char)
return Token(TokenType.STRING, value)
if self.current_char.isdigit():
value = self.read_number()
return Token(TokenType.NUMBER, value)
if self.current_char.isalpha() or self.current_char == "_":
value = self.read_identifier()
# Check for special keywords
if value.lower() == "true":
return Token(TokenType.BOOLEAN, True)
elif value.lower() == "false":
return Token(TokenType.BOOLEAN, False)
elif value.lower() == "null":
return Token(TokenType.NULL, None)
elif value.lower() == "and":
return Token(TokenType.AND, "and")
elif value.lower() == "or":
return Token(TokenType.OR, "or")
elif value.lower() == "not":
return Token(TokenType.NOT, "not")
elif value.lower() in self.OPERATORS:
return Token(TokenType.OPERATOR, value.lower())
else:
return Token(TokenType.ATTRIBUTE, value)
# Skip unknown characters
self.advance()
return Token(TokenType.EOF, "")

View File

@@ -0,0 +1,131 @@
from typing import Any
from authentik.sources.scim.patch.lexer import SCIMPathLexer, TokenType
class SCIMPathParser:
"""Parser for SCIM paths including filter expressions"""
def __init__(self):
self.lexer = None
self.current_token = None
def parse_path(self, path: str | None) -> list[dict[str, Any]]:
"""Parse a SCIM path into components"""
self.lexer = SCIMPathLexer(path)
self.current_token = self.lexer.get_next_token()
components = []
while self.current_token.type != TokenType.EOF:
component = self._parse_path_component()
if component:
components.append(component)
return components
def _parse_path_component(self) -> dict[str, Any] | None:
"""Parse a single path component"""
if self.current_token.type != TokenType.ATTRIBUTE:
return None
attribute = self.current_token.value
self._consume(TokenType.ATTRIBUTE)
filter_expr = None
sub_attribute = None
# Check for filter expression
if self.current_token.type == TokenType.LBRACKET:
self._consume(TokenType.LBRACKET)
filter_expr = self._parse_filter_expression()
self._consume(TokenType.RBRACKET)
# Check for sub-attribute
if self.current_token.type == TokenType.DOT:
self._consume(TokenType.DOT)
if self.current_token.type == TokenType.ATTRIBUTE:
sub_attribute = self.current_token.value
self._consume(TokenType.ATTRIBUTE)
return {"attribute": attribute, "filter": filter_expr, "sub_attribute": sub_attribute}
def _parse_filter_expression(self) -> dict[str, Any] | None:
"""Parse a filter expression like 'primary eq true' or
'type eq "work" and primary eq true'"""
return self._parse_or_expression()
def _parse_or_expression(self) -> dict[str, Any] | None:
"""Parse OR expressions"""
left = self._parse_and_expression()
while self.current_token.type == TokenType.OR:
self._consume(TokenType.OR)
right = self._parse_and_expression()
left = {"type": "logical", "operator": "or", "left": left, "right": right}
return left
def _parse_and_expression(self) -> dict[str, Any] | None:
"""Parse AND expressions"""
left = self._parse_primary_expression()
while self.current_token.type == TokenType.AND:
self._consume(TokenType.AND)
right = self._parse_primary_expression()
left = {"type": "logical", "operator": "and", "left": left, "right": right}
return left
def _parse_primary_expression(self) -> dict[str, Any] | None:
"""Parse primary expressions (attribute operator value)"""
if self.current_token.type == TokenType.LPAREN:
self._consume(TokenType.LPAREN)
expr = self._parse_or_expression()
self._consume(TokenType.RPAREN)
return expr
if self.current_token.type == TokenType.NOT:
self._consume(TokenType.NOT)
expr = self._parse_primary_expression()
return {"type": "logical", "operator": "not", "operand": expr}
if self.current_token.type != TokenType.ATTRIBUTE:
return None
attribute = self.current_token.value
self._consume(TokenType.ATTRIBUTE)
if self.current_token.type != TokenType.OPERATOR:
return None
operator = self.current_token.value
self._consume(TokenType.OPERATOR)
# Parse value
value = None
if self.current_token.type == TokenType.STRING:
value = self.current_token.value
self._consume(TokenType.STRING)
elif self.current_token.type == TokenType.NUMBER:
value = (
float(self.current_token.value)
if "." in self.current_token.value
else int(self.current_token.value)
)
self._consume(TokenType.NUMBER)
elif self.current_token.type == TokenType.BOOLEAN:
value = self.current_token.value
self._consume(TokenType.BOOLEAN)
elif self.current_token.type == TokenType.NULL:
value = None
self._consume(TokenType.NULL)
return {"type": "comparison", "attribute": attribute, "operator": operator, "value": value}
def _consume(self, expected_type: TokenType):
"""Consume a token of the expected type"""
if self.current_token.type == expected_type:
self.current_token = self.lexer.get_next_token()
else:
raise ValueError(f"Expected {expected_type}, got {self.current_token.type}")

View File

@@ -0,0 +1,246 @@
from typing import Any
from authentik.providers.scim.clients.schema import PatchOp, PatchOperation
from authentik.sources.scim.constants import SCIM_URN_USER_ENTERPRISE
from authentik.sources.scim.patch.parser import SCIMPathParser
class SCIMPatchProcessor:
"""Processes SCIM patch operations on Python dictionaries"""
def __init__(self):
self.parser = SCIMPathParser()
def apply_patches(self, data: dict[str, Any], patches: list[PatchOperation]) -> dict[str, Any]:
"""Apply a list of patch operations to the data"""
result = data.copy()
for _patch in patches:
patch = PatchOperation.model_validate(_patch)
if patch.path is None:
# Handle operations with no path - value contains attribute paths as keys
self._apply_bulk_operation(result, patch.op, patch.value)
elif patch.op == PatchOp.add:
self._apply_add(result, patch.path, patch.value)
elif patch.op == PatchOp.remove:
self._apply_remove(result, patch.path)
elif patch.op == PatchOp.replace:
self._apply_replace(result, patch.path, patch.value)
return result
def _apply_bulk_operation(
self, data: dict[str, Any], operation: PatchOp, value: dict[str, Any]
):
"""Apply bulk operations when path is None"""
if not isinstance(value, dict):
return
for path, val in value.items():
if operation == PatchOp.add:
self._apply_add(data, path, val)
elif operation == PatchOp.remove:
self._apply_remove(data, path)
elif operation == PatchOp.replace:
self._apply_replace(data, path, val)
def _apply_add(self, data: dict[str, Any], path: str, value: Any):
"""Apply ADD operation"""
components = self.parser.parse_path(path)
if len(components) == 1 and not components[0]["filter"]:
# Simple path
attr = components[0]["attribute"]
if components[0]["sub_attribute"]:
if attr not in data:
data[attr] = {}
# Somewhat hacky workaround for the manager attribute of the enterprise schema
# ideally we'd do this based on the schema
if attr == SCIM_URN_USER_ENTERPRISE and components[0]["sub_attribute"] == "manager":
data[attr][components[0]["sub_attribute"]] = {"value": value}
else:
data[attr][components[0]["sub_attribute"]] = value
elif attr in data:
data[attr].append(value)
else:
data[attr] = value
else:
# Complex path with filters
self._navigate_and_modify(data, components, value, "add")
def _apply_remove(self, data: dict[str, Any], path: str):
"""Apply REMOVE operation"""
components = self.parser.parse_path(path)
if len(components) == 1 and not components[0]["filter"]:
# Simple path
attr = components[0]["attribute"]
if components[0]["sub_attribute"]:
if attr in data and isinstance(data[attr], dict):
data[attr].pop(components[0]["sub_attribute"], None)
else:
data.pop(attr, None)
else:
# Complex path with filters
self._navigate_and_modify(data, components, None, "remove")
def _apply_replace(self, data: dict[str, Any], path: str, value: Any):
"""Apply REPLACE operation"""
components = self.parser.parse_path(path)
if len(components) == 1 and not components[0]["filter"]:
# Simple path
attr = components[0]["attribute"]
if components[0]["sub_attribute"]:
if attr not in data:
data[attr] = {}
# Somewhat hacky workaround for the manager attribute of the enterprise schema
# ideally we'd do this based on the schema
if attr == SCIM_URN_USER_ENTERPRISE and components[0]["sub_attribute"] == "manager":
data[attr][components[0]["sub_attribute"]] = {"value": value}
else:
data[attr][components[0]["sub_attribute"]] = value
else:
data[attr] = value
else:
# Complex path with filters
self._navigate_and_modify(data, components, value, "replace")
def _navigate_and_modify( # noqa PLR0912
self, data: dict[str, Any], components: list[dict[str, Any]], value: Any, operation: str
):
"""Navigate through complex paths and apply modifications"""
current = data
for i, component in enumerate(components):
attr = component["attribute"]
filter_expr = component["filter"]
sub_attr = component["sub_attribute"]
if filter_expr:
# Handle array with filter
if attr not in current:
if operation == "add":
current[attr] = []
else:
return
if not isinstance(current[attr], list):
return
# Find matching items
matching_items = []
for item in current[attr]:
if self._matches_filter(item, filter_expr):
matching_items.append(item)
if not matching_items and operation == "add":
# Create new item if none match (only for simple comparison filters)
if filter_expr.get("type", "comparison") == "comparison":
new_item = {filter_expr["attribute"]: filter_expr["value"]}
current[attr].append(new_item)
matching_items = [new_item]
# Apply operation to matching items
for item in matching_items:
if sub_attr:
if operation in {"add", "replace"}:
item[sub_attr] = value
elif operation == "remove":
item.pop(sub_attr, None)
elif operation in {"add", "replace"}:
if isinstance(value, dict):
item.update(value)
else:
# If value is not a dict, we can't merge it
pass
elif operation == "remove":
# Remove the entire item
if item in current[attr]:
current[attr].remove(item)
# Handle simple attribute
elif i == len(components) - 1:
# Last component
if sub_attr:
if attr not in current:
current[attr] = {}
if operation in {"add", "replace"}:
current[attr][sub_attr] = value
elif operation == "remove":
current[attr].pop(sub_attr, None)
elif operation in {"add", "replace"}:
current[attr] = value
elif operation == "remove":
current.pop(attr, None)
else:
# Navigate deeper
if attr not in current:
current[attr] = {}
current = current[attr]
def _matches_filter(self, item: dict[str, Any], filter_expr: dict[str, Any]) -> bool:
"""Check if an item matches the filter expression"""
if not filter_expr:
return True
filter_type = filter_expr.get("type", "comparison")
if filter_type == "comparison":
return self._matches_comparison(item, filter_expr)
elif filter_type == "logical":
return self._matches_logical(item, filter_expr)
return False
def _matches_comparison( # noqa PLR0912
self, item: dict[str, Any], filter_expr: dict[str, Any]
) -> bool:
"""Check if an item matches a comparison filter"""
attr = filter_expr["attribute"]
operator = filter_expr["operator"]
expected_value = filter_expr["value"]
if attr not in item:
return False
actual_value = item[attr]
if operator == "eq":
return actual_value == expected_value
elif operator == "ne":
return actual_value != expected_value
elif operator == "co":
return str(expected_value) in str(actual_value)
elif operator == "sw":
return str(actual_value).startswith(str(expected_value))
elif operator == "ew":
return str(actual_value).endswith(str(expected_value))
elif operator == "gt":
return actual_value > expected_value
elif operator == "lt":
return actual_value < expected_value
elif operator == "ge":
return actual_value >= expected_value
elif operator == "le":
return actual_value <= expected_value
elif operator == "pr":
return actual_value is not None
return False
def _matches_logical(self, item: dict[str, Any], filter_expr: dict[str, Any]) -> bool:
"""Check if an item matches a logical filter expression"""
operator = filter_expr["operator"]
if operator == "and":
left_result = self._matches_filter(item, filter_expr["left"])
right_result = self._matches_filter(item, filter_expr["right"])
return left_result and right_result
elif operator == "or":
left_result = self._matches_filter(item, filter_expr["left"])
right_result = self._matches_filter(item, filter_expr["right"])
return left_result or right_result
elif operator == "not":
operand_result = self._matches_filter(item, filter_expr["operand"])
return not operand_result
return False

View File

@@ -1101,17 +1101,6 @@
"returned": "default",
"uniqueness": "none"
},
{
"name": "password",
"type": "string",
"multiValued": false,
"description": "The User's cleartext password. This attribute is intended to be used as a means to specify an initial\npassword when creating a new User or to reset an existing User's password.",
"required": false,
"caseExact": false,
"mutability": "writeOnly",
"returned": "never",
"uniqueness": "none"
},
{
"name": "emails",
"type": "complex",

View File

@@ -75,7 +75,9 @@ class TestSCIMGroups(APITestCase):
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
)
self.assertEqual(response.status_code, 201)
self.assertTrue(SCIMSourceGroup.objects.filter(source=self.source, id=ext_id).exists())
self.assertTrue(
SCIMSourceGroup.objects.filter(source=self.source, external_id=ext_id).exists()
)
self.assertTrue(
Event.objects.filter(
action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
@@ -86,6 +88,7 @@ class TestSCIMGroups(APITestCase):
"""Test group create"""
user = create_test_user()
ext_id = generate_id()
name = generate_id()
response = self.client.post(
reverse(
"authentik_sources_scim:v2-groups",
@@ -95,7 +98,7 @@ class TestSCIMGroups(APITestCase):
),
data=dumps(
{
"displayName": generate_id(),
"displayName": name,
"externalId": ext_id,
"members": [{"value": str(user.uuid)}],
}
@@ -104,12 +107,22 @@ class TestSCIMGroups(APITestCase):
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
)
self.assertEqual(response.status_code, 201)
self.assertTrue(SCIMSourceGroup.objects.filter(source=self.source, id=ext_id).exists())
connection = SCIMSourceGroup.objects.filter(source=self.source, external_id=ext_id).first()
self.assertIsNotNone(connection)
self.assertTrue(
Event.objects.filter(
action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
).exists()
)
connection.refresh_from_db()
self.assertEqual(
connection.attributes,
{
"displayName": name,
"externalId": ext_id,
"members": [{"value": str(user.uuid)}],
},
)
def test_group_create_members_empty(self):
"""Test group create"""
@@ -126,7 +139,9 @@ class TestSCIMGroups(APITestCase):
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
)
self.assertEqual(response.status_code, 201)
self.assertTrue(SCIMSourceGroup.objects.filter(source=self.source, id=ext_id).exists())
self.assertTrue(
SCIMSourceGroup.objects.filter(source=self.source, external_id=ext_id).exists()
)
self.assertTrue(
Event.objects.filter(
action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
@@ -136,7 +151,9 @@ class TestSCIMGroups(APITestCase):
def test_group_create_duplicate(self):
"""Test group create (duplicate)"""
group = Group.objects.create(name=generate_id())
existing = SCIMSourceGroup.objects.create(source=self.source, group=group, id=uuid4())
existing = SCIMSourceGroup.objects.create(
source=self.source, group=group, external_id=uuid4()
)
ext_id = generate_id()
response = self.client.post(
reverse(
@@ -165,7 +182,9 @@ class TestSCIMGroups(APITestCase):
def test_group_update(self):
"""Test group update"""
group = Group.objects.create(name=generate_id())
existing = SCIMSourceGroup.objects.create(source=self.source, group=group, id=uuid4())
existing = SCIMSourceGroup.objects.create(
source=self.source, group=group, external_id=uuid4()
)
ext_id = generate_id()
response = self.client.put(
reverse(
@@ -205,12 +224,49 @@ class TestSCIMGroups(APITestCase):
},
)
def test_group_patch_add(self):
def test_group_patch_modify(self):
"""Test group patch"""
group = Group.objects.create(name=generate_id())
connection = SCIMSourceGroup.objects.create(
source=self.source,
group=group,
external_id=uuid4(),
attributes={"displayName": group.name, "members": []},
)
response = self.client.patch(
reverse(
"authentik_sources_scim:v2-groups",
kwargs={"source_slug": self.source.slug, "group_id": group.pk},
),
data=dumps(
{
"Operations": [
{
"op": "Add",
"value": {"externalId": "d85051cb-0557-4aa1-98ca-51eabcee4d40"},
}
]
}
),
content_type=SCIM_CONTENT_TYPE,
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
)
self.assertEqual(response.status_code, 200, response.content)
connection = SCIMSourceGroup.objects.filter(id="d85051cb-0557-4aa1-98ca-51eabcee4d40")
self.assertIsNotNone(connection)
def test_group_patch_member_add(self):
"""Test group patch"""
user = create_test_user()
other_user = create_test_user()
group = Group.objects.create(name=generate_id())
SCIMSourceGroup.objects.create(source=self.source, group=group, id=uuid4())
group.users.add(other_user)
connection = SCIMSourceGroup.objects.create(
source=self.source,
group=group,
external_id=uuid4(),
attributes={"displayName": group.name, "members": [{"value": str(other_user.uuid)}]},
)
response = self.client.patch(
reverse(
"authentik_sources_scim:v2-groups",
@@ -222,7 +278,7 @@ class TestSCIMGroups(APITestCase):
{
"op": "Add",
"path": "members",
"value": {"value": str(user.uuid)},
"value": [{"value": str(user.uuid)}],
}
]
}
@@ -230,16 +286,33 @@ class TestSCIMGroups(APITestCase):
content_type=SCIM_CONTENT_TYPE,
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
)
self.assertEqual(response.status_code, second=200)
self.assertEqual(response.status_code, 200, response.content)
self.assertTrue(group.users.filter(pk=user.pk).exists())
self.assertTrue(group.users.filter(pk=other_user.pk).exists())
connection.refresh_from_db()
self.assertEqual(
connection.attributes,
{
"displayName": group.name,
"members": sorted(
[{"value": str(other_user.uuid)}, {"value": str(user.uuid)}],
key=lambda u: u["value"],
),
},
)
def test_group_patch_remove(self):
def test_group_patch_member_remove(self):
"""Test group patch"""
user = create_test_user()
group = Group.objects.create(name=generate_id())
group.users.add(user)
SCIMSourceGroup.objects.create(source=self.source, group=group, id=uuid4())
connection = SCIMSourceGroup.objects.create(
source=self.source,
group=group,
external_id=uuid4(),
attributes={"displayName": group.name, "members": []},
)
response = self.client.patch(
reverse(
"authentik_sources_scim:v2-groups",
@@ -251,7 +324,7 @@ class TestSCIMGroups(APITestCase):
{
"op": "remove",
"path": "members",
"value": {"value": str(user.uuid)},
"value": [{"value": str(user.uuid)}],
}
]
}
@@ -259,13 +332,21 @@ class TestSCIMGroups(APITestCase):
content_type=SCIM_CONTENT_TYPE,
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
)
self.assertEqual(response.status_code, second=200)
self.assertEqual(response.status_code, 200, response.content)
self.assertFalse(group.users.filter(pk=user.pk).exists())
connection.refresh_from_db()
self.assertEqual(
connection.attributes,
{
"displayName": group.name,
"members": [],
},
)
def test_group_delete(self):
"""Test group delete"""
group = Group.objects.create(name=generate_id())
SCIMSourceGroup.objects.create(source=self.source, group=group, id=uuid4())
SCIMSourceGroup.objects.create(source=self.source, group=group, external_id=uuid4())
response = self.client.delete(
reverse(
"authentik_sources_scim:v2-groups",

View File

@@ -0,0 +1,510 @@
from unittest import TestCase
from authentik.sources.scim.constants import (
SCIM_URN_GROUP,
SCIM_URN_SCHEMA,
SCIM_URN_USER,
SCIM_URN_USER_ENTERPRISE,
)
from authentik.sources.scim.patch.lexer import SCIMPathLexer, Token, TokenType
class TestTokenType(TestCase):
"""Test TokenType enum"""
def test_token_type_values(self):
"""Test that all token types have correct values"""
self.assertEqual(TokenType.ATTRIBUTE.value, "ATTRIBUTE")
self.assertEqual(TokenType.DOT.value, "DOT")
self.assertEqual(TokenType.LBRACKET.value, "LBRACKET")
self.assertEqual(TokenType.RBRACKET.value, "RBRACKET")
self.assertEqual(TokenType.LPAREN.value, "LPAREN")
self.assertEqual(TokenType.RPAREN.value, "RPAREN")
self.assertEqual(TokenType.STRING.value, "STRING")
self.assertEqual(TokenType.NUMBER.value, "NUMBER")
self.assertEqual(TokenType.BOOLEAN.value, "BOOLEAN")
self.assertEqual(TokenType.NULL.value, "NULL")
self.assertEqual(TokenType.OPERATOR.value, "OPERATOR")
self.assertEqual(TokenType.AND.value, "AND")
self.assertEqual(TokenType.OR.value, "OR")
self.assertEqual(TokenType.NOT.value, "NOT")
self.assertEqual(TokenType.EOF.value, "EOF")
class TestToken(TestCase):
"""Test Token dataclass"""
def test_token_creation(self):
"""Test token creation with all parameters"""
token = Token(TokenType.ATTRIBUTE, "userName", 5)
self.assertEqual(token.type, TokenType.ATTRIBUTE)
self.assertEqual(token.value, "userName")
self.assertEqual(token.position, 5)
def test_token_creation_default_position(self):
"""Test token creation with default position"""
token = Token(TokenType.DOT, ".")
self.assertEqual(token.type, TokenType.DOT)
self.assertEqual(token.value, ".")
self.assertEqual(token.position, 0)
class TestSCIMPathLexer(TestCase):
"""Test SCIMPathLexer class"""
def setUp(self):
"""Set up test fixtures"""
self.simple_lexer = SCIMPathLexer("userName")
def test_init(self):
"""Test lexer initialization"""
lexer = SCIMPathLexer("test")
self.assertEqual(lexer.text, "test")
self.assertEqual(lexer.pos, 0)
self.assertEqual(lexer.current_char, "t")
self.assertIn(SCIM_URN_SCHEMA, lexer.schema_urns)
self.assertIn(SCIM_URN_GROUP, lexer.schema_urns)
self.assertIn(SCIM_URN_USER, lexer.schema_urns)
self.assertIn(SCIM_URN_USER_ENTERPRISE, lexer.schema_urns)
self.assertEqual(
lexer.OPERATORS, ["eq", "ne", "co", "sw", "ew", "gt", "lt", "ge", "le", "pr"]
)
def test_init_empty_string(self):
"""Test lexer initialization with empty string"""
lexer = SCIMPathLexer("")
self.assertEqual(lexer.text, "")
self.assertEqual(lexer.pos, 0)
self.assertIsNone(lexer.current_char)
def test_advance(self):
"""Test advance method"""
lexer = SCIMPathLexer("abc")
self.assertEqual(lexer.current_char, "a")
lexer.advance()
self.assertEqual(lexer.pos, 1)
self.assertEqual(lexer.current_char, "b")
lexer.advance()
self.assertEqual(lexer.pos, 2)
self.assertEqual(lexer.current_char, "c")
lexer.advance()
self.assertEqual(lexer.pos, 3)
self.assertIsNone(lexer.current_char)
def test_skip_whitespace(self):
"""Test skip_whitespace method"""
lexer = SCIMPathLexer(" \t\n abc")
lexer.skip_whitespace()
self.assertEqual(lexer.current_char, "a")
def test_skip_whitespace_only_whitespace(self):
"""Test skip_whitespace with only whitespace"""
lexer = SCIMPathLexer(" \t\n ")
lexer.skip_whitespace()
self.assertIsNone(lexer.current_char)
def test_skip_whitespace_no_whitespace(self):
"""Test skip_whitespace with no leading whitespace"""
lexer = SCIMPathLexer("abc")
original_pos = lexer.pos
lexer.skip_whitespace()
self.assertEqual(lexer.pos, original_pos)
self.assertEqual(lexer.current_char, "a")
def test_read_string_double_quotes(self):
"""Test reading double-quoted string"""
lexer = SCIMPathLexer('"hello world"')
result = lexer.read_string('"')
self.assertEqual(result, "hello world")
self.assertIsNone(lexer.current_char) # Should be at end
def test_read_string_single_quotes(self):
"""Test reading single-quoted string"""
lexer = SCIMPathLexer("'hello world'")
result = lexer.read_string("'")
self.assertEqual(result, "hello world")
self.assertIsNone(lexer.current_char)
def test_read_string_with_escapes(self):
"""Test reading string with escape characters"""
lexer = SCIMPathLexer('"hello \\"world\\""')
result = lexer.read_string('"')
self.assertEqual(result, 'hello "world"')
def test_read_string_with_backslash_at_end(self):
"""Test reading string with backslash at end"""
lexer = SCIMPathLexer('"hello\\"')
result = lexer.read_string('"')
self.assertEqual(result, 'hello"')
def test_read_string_unclosed(self):
"""Test reading unclosed string"""
lexer = SCIMPathLexer('"hello world')
result = lexer.read_string('"')
self.assertEqual(result, "hello world")
self.assertIsNone(lexer.current_char)
def test_read_string_empty(self):
"""Test reading empty string"""
lexer = SCIMPathLexer('""')
result = lexer.read_string('"')
self.assertEqual(result, "")
def test_read_number_integer(self):
"""Test reading integer number"""
lexer = SCIMPathLexer("123")
result = lexer.read_number()
self.assertEqual(result, "123")
self.assertIsNone(lexer.current_char)
def test_read_number_float(self):
"""Test reading float number"""
lexer = SCIMPathLexer("123.456")
result = lexer.read_number()
self.assertEqual(result, "123.456")
self.assertIsNone(lexer.current_char)
def test_read_number_with_multiple_dots(self):
"""Test reading number with multiple dots (invalid but handled)"""
lexer = SCIMPathLexer("123.456.789")
result = lexer.read_number()
self.assertEqual(result, "123.456.789")
self.assertIsNone(lexer.current_char)
def test_read_number_starting_with_dot(self):
"""Test reading number starting with dot"""
lexer = SCIMPathLexer(".123")
result = lexer.read_number()
self.assertEqual(result, ".123")
def test_read_identifier_simple(self):
"""Test reading simple identifier"""
lexer = SCIMPathLexer("userName")
result = lexer.read_identifier()
self.assertEqual(result, "userName")
self.assertIsNone(lexer.current_char)
def test_read_identifier_with_underscore(self):
"""Test reading identifier with underscore"""
lexer = SCIMPathLexer("user_name")
result = lexer.read_identifier()
self.assertEqual(result, "user_name")
def test_read_identifier_with_hyphen(self):
"""Test reading identifier with hyphen"""
lexer = SCIMPathLexer("user-name")
result = lexer.read_identifier()
self.assertEqual(result, "user-name")
def test_read_identifier_with_colon(self):
"""Test reading identifier with colon (URN format)"""
lexer = SCIMPathLexer("urn:ietf:params:scim:schemas:core:2.0:User")
result = lexer.read_identifier()
self.assertEqual(result, "urn:ietf:params:scim:schemas:core:2.0:User")
def test_read_identifier_schema_urn(self):
"""Test reading schema URN identifier"""
lexer = SCIMPathLexer(f"{SCIM_URN_USER}.userName")
result = lexer.read_identifier()
self.assertEqual(result, SCIM_URN_USER)
self.assertEqual(lexer.current_char, ".") # Should stop at dot and set current_char to dot
def test_read_identifier_with_version_number(self):
"""Test reading identifier with version number (dots followed by digits)"""
lexer = SCIMPathLexer("urn:ietf:params:scim:schemas:core:2.0:User")
result = lexer.read_identifier()
self.assertEqual(result, "urn:ietf:params:scim:schemas:core:2.0:User")
def test_read_identifier_partial_urn_match(self):
"""Test reading identifier that partially matches URN"""
lexer = SCIMPathLexer("urn:ietf:params:scim:schemas:core:2.0:CustomUser")
result = lexer.read_identifier()
self.assertEqual(result, "urn:ietf:params:scim:schemas:core:2.0:CustomUser")
# Test get_next_token method
def test_get_next_token_dot(self):
"""Test tokenizing dot"""
lexer = SCIMPathLexer(".")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.DOT)
self.assertEqual(token.value, ".")
def test_get_next_token_lbracket(self):
"""Test tokenizing left bracket"""
lexer = SCIMPathLexer("[")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.LBRACKET)
self.assertEqual(token.value, "[")
def test_get_next_token_rbracket(self):
"""Test tokenizing right bracket"""
lexer = SCIMPathLexer("]")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.RBRACKET)
self.assertEqual(token.value, "]")
def test_get_next_token_lparen(self):
"""Test tokenizing left parenthesis"""
lexer = SCIMPathLexer("(")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.LPAREN)
self.assertEqual(token.value, "(")
def test_get_next_token_rparen(self):
"""Test tokenizing right parenthesis"""
lexer = SCIMPathLexer(")")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.RPAREN)
self.assertEqual(token.value, ")")
def test_get_next_token_string_double_quotes(self):
"""Test tokenizing double-quoted string"""
lexer = SCIMPathLexer('"test string"')
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.STRING)
self.assertEqual(token.value, "test string")
def test_get_next_token_string_single_quotes(self):
"""Test tokenizing single-quoted string"""
lexer = SCIMPathLexer("'test string'")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.STRING)
self.assertEqual(token.value, "test string")
def test_get_next_token_number_integer(self):
"""Test tokenizing integer"""
lexer = SCIMPathLexer("123")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.NUMBER)
self.assertEqual(token.value, "123")
def test_get_next_token_number_float(self):
"""Test tokenizing float"""
lexer = SCIMPathLexer("123.45")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.NUMBER)
self.assertEqual(token.value, "123.45")
def test_get_next_token_boolean_true(self):
"""Test tokenizing boolean true"""
lexer = SCIMPathLexer("true")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.BOOLEAN)
self.assertTrue(token.value)
def test_get_next_token_boolean_false(self):
"""Test tokenizing boolean false"""
lexer = SCIMPathLexer("false")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.BOOLEAN)
self.assertFalse(token.value)
def test_get_next_token_boolean_case_insensitive(self):
"""Test tokenizing boolean with different cases"""
for value in ["TRUE", "True", "FALSE", "False"]:
with self.subTest(value=value):
lexer = SCIMPathLexer(value)
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.BOOLEAN)
def test_get_next_token_null(self):
"""Test tokenizing null"""
lexer = SCIMPathLexer("null")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.NULL)
self.assertIsNone(token.value)
def test_get_next_token_null_case_insensitive(self):
"""Test tokenizing null with different cases"""
for value in ["NULL", "Null"]:
with self.subTest(value=value):
lexer = SCIMPathLexer(value)
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.NULL)
def test_get_next_token_and(self):
"""Test tokenizing AND operator"""
lexer = SCIMPathLexer("and")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.AND)
self.assertEqual(token.value, "and")
def test_get_next_token_or(self):
"""Test tokenizing OR operator"""
lexer = SCIMPathLexer("or")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.OR)
self.assertEqual(token.value, "or")
def test_get_next_token_not(self):
"""Test tokenizing NOT operator"""
lexer = SCIMPathLexer("not")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.NOT)
self.assertEqual(token.value, "not")
def test_get_next_token_operators(self):
"""Test tokenizing all comparison operators"""
operators = ["eq", "ne", "co", "sw", "ew", "gt", "lt", "ge", "le", "pr"]
for op in operators:
with self.subTest(operator=op):
lexer = SCIMPathLexer(op)
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.OPERATOR)
self.assertEqual(token.value, op)
def test_get_next_token_operators_case_insensitive(self):
"""Test tokenizing operators with different cases"""
for op in ["EQ", "Eq", "NE", "Ne"]:
with self.subTest(operator=op):
lexer = SCIMPathLexer(op)
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.OPERATOR)
self.assertEqual(token.value, op.lower())
def test_get_next_token_attribute(self):
"""Test tokenizing attribute name"""
lexer = SCIMPathLexer("userName")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.ATTRIBUTE)
self.assertEqual(token.value, "userName")
def test_get_next_token_attribute_with_underscore(self):
"""Test tokenizing attribute name with underscore"""
lexer = SCIMPathLexer("_userName")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.ATTRIBUTE)
self.assertEqual(token.value, "_userName")
def test_get_next_token_eof(self):
"""Test tokenizing end of file"""
lexer = SCIMPathLexer("")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.EOF)
self.assertEqual(token.value, "")
def test_get_next_token_with_whitespace(self):
"""Test tokenizing with leading whitespace"""
lexer = SCIMPathLexer(" userName")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.ATTRIBUTE)
self.assertEqual(token.value, "userName")
def test_get_next_token_skip_unknown_characters(self):
"""Test that unknown characters are skipped"""
lexer = SCIMPathLexer("@#$userName")
token = lexer.get_next_token()
self.assertEqual(token.type, TokenType.ATTRIBUTE)
self.assertEqual(token.value, "userName")
def test_get_next_token_multiple_tokens(self):
"""Test tokenizing multiple tokens in sequence"""
lexer = SCIMPathLexer("userName.givenName")
token1 = lexer.get_next_token()
self.assertEqual(token1.type, TokenType.ATTRIBUTE)
self.assertEqual(token1.value, "userName")
token2 = lexer.get_next_token()
self.assertEqual(token2.type, TokenType.DOT)
self.assertEqual(token2.value, ".")
token3 = lexer.get_next_token()
self.assertEqual(token3.type, TokenType.ATTRIBUTE)
self.assertEqual(token3.value, "givenName")
token4 = lexer.get_next_token()
self.assertEqual(token4.type, TokenType.EOF)
def test_get_next_token_complex_filter(self):
"""Test tokenizing complex filter expression"""
lexer = SCIMPathLexer('emails[type eq "work" and primary eq true]')
tokens = []
while True:
token = lexer.get_next_token()
tokens.append(token)
if token.type == TokenType.EOF:
break
expected_types = [
TokenType.ATTRIBUTE, # emails
TokenType.LBRACKET, # [
TokenType.ATTRIBUTE, # type
TokenType.OPERATOR, # eq
TokenType.STRING, # "work"
TokenType.AND, # and
TokenType.ATTRIBUTE, # primary
TokenType.OPERATOR, # eq
TokenType.BOOLEAN, # true
TokenType.RBRACKET, # ]
TokenType.EOF,
]
self.assertEqual(len(tokens), len(expected_types))
for token, expected_type in zip(tokens, expected_types, strict=False):
self.assertEqual(token.type, expected_type)
def test_get_next_token_urn_attribute(self):
"""Test tokenizing URN-based attribute"""
lexer = SCIMPathLexer(f"{SCIM_URN_USER}.userName")
token1 = lexer.get_next_token()
self.assertEqual(token1.type, TokenType.ATTRIBUTE)
self.assertEqual(token1.value, SCIM_URN_USER)
token2 = lexer.get_next_token()
self.assertEqual(token2.type, TokenType.DOT)
token3 = lexer.get_next_token()
self.assertEqual(token3.type, TokenType.ATTRIBUTE)
self.assertEqual(token3.value, "userName")
def test_get_next_token_enterprise_urn(self):
"""Test tokenizing enterprise URN"""
lexer = SCIMPathLexer(f"{SCIM_URN_USER_ENTERPRISE}.manager")
token1 = lexer.get_next_token()
self.assertEqual(token1.type, TokenType.ATTRIBUTE)
self.assertEqual(token1.value, SCIM_URN_USER_ENTERPRISE)
token2 = lexer.get_next_token()
self.assertEqual(token2.type, TokenType.DOT)
def test_lexer_state_after_eof(self):
"""Test lexer state after reaching EOF"""
lexer = SCIMPathLexer("a")
# Get first token
token1 = lexer.get_next_token()
self.assertEqual(token1.type, TokenType.ATTRIBUTE)
# Get EOF token
token2 = lexer.get_next_token()
self.assertEqual(token2.type, TokenType.EOF)
# Should continue returning EOF
token3 = lexer.get_next_token()
self.assertEqual(token3.type, TokenType.EOF)
def test_read_identifier_edge_cases(self):
"""Test read_identifier with edge cases"""
# Test identifier ending with colon
lexer = SCIMPathLexer("test:")
result = lexer.read_identifier()
self.assertEqual(result, "test:")
# Test identifier with numbers
lexer = SCIMPathLexer("test123")
result = lexer.read_identifier()
self.assertEqual(result, "test123")
def test_complex_urn_parsing(self):
"""Test parsing complex URN with version numbers"""
urn = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
lexer = SCIMPathLexer(urn)
result = lexer.read_identifier()
self.assertEqual(result, urn)

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@ from authentik.core.tests.utils import create_test_user
from authentik.events.models import Event, EventAction
from authentik.lib.generators import generate_id
from authentik.providers.scim.clients.schema import User as SCIMUserSchema
from authentik.sources.scim.constants import SCIM_URN_USER_ENTERPRISE
from authentik.sources.scim.models import SCIMSource, SCIMSourcePropertyMapping, SCIMSourceUser
from authentik.sources.scim.views.v2.base import SCIM_CONTENT_TYPE
@@ -81,7 +82,9 @@ class TestSCIMUsers(APITestCase):
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
)
self.assertEqual(response.status_code, 201)
self.assertTrue(SCIMSourceUser.objects.filter(source=self.source, id=ext_id).exists())
self.assertTrue(
SCIMSourceUser.objects.filter(source=self.source, external_id=ext_id).exists()
)
self.assertTrue(
Event.objects.filter(
action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
@@ -174,14 +177,16 @@ class TestSCIMUsers(APITestCase):
)
self.assertEqual(response.status_code, 201)
self.assertEqual(
SCIMSourceUser.objects.get(source=self.source, id=ext_id).user.attributes["phone"],
SCIMSourceUser.objects.get(source=self.source, external_id=ext_id).user.attributes[
"phone"
],
"0123456789",
)
def test_user_update(self):
"""Test user update"""
user = create_test_user()
existing = SCIMSourceUser.objects.create(source=self.source, user=user, id=uuid4())
existing = SCIMSourceUser.objects.create(source=self.source, user=user, external_id=uuid4())
ext_id = generate_id()
response = self.client.put(
reverse(
@@ -209,10 +214,51 @@ class TestSCIMUsers(APITestCase):
)
self.assertEqual(response.status_code, 200)
def test_user_update_patch(self):
"""Test user update (patch)"""
user = create_test_user()
existing = SCIMSourceUser.objects.create(
source=self.source,
user=user,
external_id=uuid4(),
attributes={
"userName": generate_id(),
},
)
response = self.client.patch(
reverse(
"authentik_sources_scim:v2-users",
kwargs={
"source_slug": self.source.slug,
"user_id": str(user.uuid),
},
),
data=dumps(
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "Add",
"path": f"{SCIM_URN_USER_ENTERPRISE}:manager",
"value": "86b2ed3e-30cd-4881-bb58-c4e910821339",
}
],
}
),
content_type=SCIM_CONTENT_TYPE,
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
)
self.assertEqual(response.status_code, 200)
existing.refresh_from_db()
self.assertEqual(
existing.attributes[SCIM_URN_USER_ENTERPRISE],
{"manager": {"value": "86b2ed3e-30cd-4881-bb58-c4e910821339"}},
)
def test_user_delete(self):
"""Test user delete"""
user = create_test_user()
SCIMSourceUser.objects.create(source=self.source, user=user, id=uuid4())
SCIMSourceUser.objects.create(source=self.source, user=user, external_id=uuid4())
response = self.client.delete(
reverse(
"authentik_sources_scim:v2-users",

View File

@@ -0,0 +1,488 @@
from rest_framework.test import APITestCase
from authentik.core.tests.utils import create_test_user
from authentik.lib.generators import generate_id
from authentik.sources.scim.constants import SCIM_URN_USER_ENTERPRISE
from authentik.sources.scim.models import SCIMSource, SCIMSourceUser
from authentik.sources.scim.patch.processor import SCIMPatchProcessor
class TestSCIMUsersPatch(APITestCase):
"""Test SCIM User Patch"""
def test_add(self):
req = {
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{"op": "Add", "path": "name.givenName", "value": "aqwer"},
{"op": "Add", "path": "name.familyName", "value": "qwerqqqq"},
{"op": "Add", "path": "name.formatted", "value": "aqwer qwerqqqq"},
],
}
user = create_test_user()
source = SCIMSource.objects.create(slug=generate_id())
connection = SCIMSourceUser.objects.create(
user=user,
id=generate_id(),
source=source,
attributes={
"meta": {"resourceType": "User"},
"active": True,
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
SCIM_URN_USER_ENTERPRISE,
],
"userName": "test@t.goauthentik.io",
"externalId": "test",
"displayName": "Test MS",
},
)
updated = SCIMPatchProcessor().apply_patches(connection.attributes, req["Operations"])
self.assertEqual(
updated,
{
"meta": {"resourceType": "User"},
"active": True,
"name": {
"givenName": "aqwer",
"familyName": "qwerqqqq",
"formatted": "aqwer qwerqqqq",
},
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
SCIM_URN_USER_ENTERPRISE,
],
"userName": "test@t.goauthentik.io",
"externalId": "test",
"displayName": "Test MS",
},
)
def test_add_no_path(self):
"""Test add patch with no path set"""
req = {
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{"op": "Add", "value": {"externalId": "aqwer"}},
],
}
user = create_test_user()
source = SCIMSource.objects.create(slug=generate_id())
connection = SCIMSourceUser.objects.create(
user=user,
id=generate_id(),
source=source,
attributes={
"meta": {"resourceType": "User"},
"active": True,
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
SCIM_URN_USER_ENTERPRISE,
],
"userName": "test@t.goauthentik.io",
"displayName": "Test MS",
},
)
updated = SCIMPatchProcessor().apply_patches(connection.attributes, req["Operations"])
self.assertEqual(
updated,
{
"meta": {"resourceType": "User"},
"active": True,
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
SCIM_URN_USER_ENTERPRISE,
],
"userName": "test@t.goauthentik.io",
"externalId": "aqwer",
"displayName": "Test MS",
},
)
def test_replace(self):
req = {
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{"op": "Replace", "path": "name", "value": {"givenName": "aqwer"}},
],
}
user = create_test_user()
source = SCIMSource.objects.create(slug=generate_id())
connection = SCIMSourceUser.objects.create(
user=user,
id=generate_id(),
source=source,
attributes={
"meta": {"resourceType": "User"},
"active": True,
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
SCIM_URN_USER_ENTERPRISE,
],
"userName": "test@t.goauthentik.io",
"externalId": "test",
"displayName": "Test MS",
},
)
updated = SCIMPatchProcessor().apply_patches(connection.attributes, req["Operations"])
self.assertEqual(
updated,
{
"meta": {"resourceType": "User"},
"active": True,
"name": {
"givenName": "aqwer",
},
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
SCIM_URN_USER_ENTERPRISE,
],
"userName": "test@t.goauthentik.io",
"externalId": "test",
"displayName": "Test MS",
},
)
def test_replace_no_path(self):
"""Test value replace with no path"""
req = {
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{"op": "Replace", "value": {"externalId": "aqwer"}},
],
}
user = create_test_user()
source = SCIMSource.objects.create(slug=generate_id())
connection = SCIMSourceUser.objects.create(
user=user,
id=generate_id(),
source=source,
attributes={
"meta": {"resourceType": "User"},
"active": True,
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
SCIM_URN_USER_ENTERPRISE,
],
"userName": "test@t.goauthentik.io",
"externalId": "test",
"displayName": "Test MS",
},
)
updated = SCIMPatchProcessor().apply_patches(connection.attributes, req["Operations"])
self.assertEqual(
updated,
{
"meta": {"resourceType": "User"},
"active": True,
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
SCIM_URN_USER_ENTERPRISE,
],
"userName": "test@t.goauthentik.io",
"externalId": "aqwer",
"displayName": "Test MS",
},
)
def test_remove(self):
req = {
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{"op": "Remove", "path": "name", "value": {"givenName": "aqwer"}},
],
}
user = create_test_user()
source = SCIMSource.objects.create(slug=generate_id())
connection = SCIMSourceUser.objects.create(
user=user,
id=generate_id(),
source=source,
attributes={
"meta": {"resourceType": "User"},
"active": True,
"name": {
"givenName": "aqwer",
},
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
SCIM_URN_USER_ENTERPRISE,
],
"userName": "test@t.goauthentik.io",
"externalId": "test",
"displayName": "Test MS",
},
)
updated = SCIMPatchProcessor().apply_patches(connection.attributes, req["Operations"])
self.assertEqual(
updated,
{
"meta": {"resourceType": "User"},
"active": True,
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
SCIM_URN_USER_ENTERPRISE,
],
"userName": "test@t.goauthentik.io",
"externalId": "test",
"displayName": "Test MS",
},
)
def test_large(self):
"""Large amount of patch operations"""
req = {
"Operations": [
{
"op": "replace",
"path": "emails[primary eq true].value",
"value": "dandre_kling@wintheiser.info",
},
{
"op": "replace",
"path": "phoneNumbers[primary eq true].value",
"value": "72-634-1548",
},
{
"op": "replace",
"path": "phoneNumbers[primary eq true].display",
"value": "72-634-1548",
},
{"op": "replace", "path": "ims[primary eq true].value", "value": "GXSGJKWGHVVS"},
{"op": "replace", "path": "ims[primary eq true].display", "value": "IMCHDKUQIPYB"},
{
"op": "replace",
"path": "photos[primary eq true].display",
"value": "TWAWLHHSUNIV",
},
{
"op": "replace",
"path": "addresses[primary eq true].formatted",
"value": "TMINZQAJQDCL",
},
{
"op": "replace",
"path": "addresses[primary eq true].streetAddress",
"value": "081 Wisoky Key",
},
{
"op": "replace",
"path": "addresses[primary eq true].locality",
"value": "DPFASBZRPMDP",
},
{
"op": "replace",
"path": "addresses[primary eq true].region",
"value": "WHSTJSPIPTCF",
},
{
"op": "replace",
"path": "addresses[primary eq true].postalCode",
"value": "ko28 1qa",
},
{"op": "replace", "path": "addresses[primary eq true].country", "value": "Taiwan"},
{
"op": "replace",
"path": "entitlements[primary eq true].value",
"value": "NGBJMUYZVVBX",
},
{"op": "replace", "path": "roles[primary eq true].value", "value": "XEELVFMMWCVM"},
{
"op": "replace",
"path": "x509Certificates[primary eq true].value",
"value": "UYISMEDOXUZY",
},
{
"op": "replace",
"value": {
"externalId": "7faaefb0-0774-4d8e-8f6d-863c361bc72c",
"name.formatted": "Dell",
"name.familyName": "Gay",
"name.givenName": "Kyler",
"name.middleName": "Hannah",
"name.honorificPrefix": "Cassie",
"name.honorificSuffix": "Yolanda",
"displayName": "DPRLIJSFQMTL",
"nickName": "BKSPMIRMFBTI",
"title": "NBZCOAXVYJUY",
"userType": "ZGJMYZRUORZE",
"preferredLanguage": "as-IN",
"locale": "JLOJHLPWZODG",
"timezone": "America/Argentina/Rio_Gallegos",
"active": True,
f"{SCIM_URN_USER_ENTERPRISE}:employeeNumber": "PDFWRRZBQOHB",
f"{SCIM_URN_USER_ENTERPRISE}:costCenter": "HACMZWSEDOTQ",
f"{SCIM_URN_USER_ENTERPRISE}:organization": "LXVHJUOLNCLS",
f"{SCIM_URN_USER_ENTERPRISE}:division": "JASVTPKPBPMG",
f"{SCIM_URN_USER_ENTERPRISE}:department": "GMSBFLMNPABY",
},
},
],
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
}
user = create_test_user()
source = SCIMSource.objects.create(slug=generate_id())
connection = SCIMSourceUser.objects.create(
user=user,
id=generate_id(),
source=source,
attributes={
"active": True,
"addresses": [
{
"primary": "true",
"formatted": "BLJMCNXHYLZK",
"streetAddress": "7801 Jacobs Fork",
"locality": "HZJBJWFAKXDD",
"region": "GJXCXPMIIKWK",
"postalCode": "pv82 8ua",
"country": "India",
}
],
"displayName": "KEFXCHKHAFOT",
"emails": [{"primary": "true", "value": "scot@zemlak.uk"}],
"entitlements": [{"primary": "true", "value": "FTTUXWYDAAQC"}],
"externalId": "448d2786-7bf6-4e03-a4ef-64cbaf162fa7",
"ims": [{"primary": "true", "value": "IGWZUUMCMKXS", "display": "PJVGMMKYYHRU"}],
"locale": "PJNYJHWJILTI",
"name": {
"formatted": "Ladarius",
"familyName": "Manley",
"givenName": "Mazie",
"middleName": "Vernon",
"honorificPrefix": "Melyssa",
"honorificSuffix": "Demarcus",
},
"nickName": "HTPKOXMWZKHL",
"phoneNumbers": [
{"primary": "true", "value": "50-608-7660", "display": "50-608-7660"}
],
"photos": [{"primary": "true", "display": "KCONLNLSYTBP"}],
"preferredLanguage": "wae",
"profileUrl": "HPSEOIPXMGOH",
"roles": [{"primary": "true", "value": "TLGYITOIZGKP"}],
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
SCIM_URN_USER_ENTERPRISE,
],
"timezone": "America/Indiana/Petersburg",
"title": "EJWFXLHNHMCD",
SCIM_URN_USER_ENTERPRISE: {
"employeeNumber": "XHDMEJUURJNR",
"costCenter": "RXUYBXOTRCZH",
"organization": "CEXWXMBRYAHN",
"division": "XMPFMDCLRKCW",
"department": "BKMNJVMCJUYS",
"manager": "PNGSGXLYVWMV",
},
"userName": "imelda.auer@kshlerin.co.uk",
"userType": "PZFXORVSUAPU",
"x509Certificates": [{"primary": "true", "value": "KOVKWGIVVEHH"}],
},
)
updated = SCIMPatchProcessor().apply_patches(connection.attributes, req["Operations"])
self.assertEqual(
updated,
{
"active": True,
"addresses": [
{
"primary": "true",
"formatted": "BLJMCNXHYLZK",
"streetAddress": "7801 Jacobs Fork",
"locality": "HZJBJWFAKXDD",
"region": "GJXCXPMIIKWK",
"postalCode": "pv82 8ua",
"country": "India",
}
],
"displayName": "DPRLIJSFQMTL",
"emails": [{"primary": "true", "value": "scot@zemlak.uk"}],
"entitlements": [{"primary": "true", "value": "FTTUXWYDAAQC"}],
"externalId": "7faaefb0-0774-4d8e-8f6d-863c361bc72c",
"ims": [{"primary": "true", "value": "IGWZUUMCMKXS", "display": "PJVGMMKYYHRU"}],
"locale": "JLOJHLPWZODG",
"name": {
"formatted": "Dell",
"familyName": "Gay",
"givenName": "Kyler",
"middleName": "Hannah",
"honorificPrefix": "Cassie",
"honorificSuffix": "Yolanda",
},
"nickName": "BKSPMIRMFBTI",
"phoneNumbers": [
{"primary": "true", "value": "50-608-7660", "display": "50-608-7660"}
],
"photos": [{"primary": "true", "display": "KCONLNLSYTBP"}],
"preferredLanguage": "as-IN",
"profileUrl": "HPSEOIPXMGOH",
"roles": [{"primary": "true", "value": "TLGYITOIZGKP"}],
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
SCIM_URN_USER_ENTERPRISE,
],
"timezone": "America/Argentina/Rio_Gallegos",
"title": "NBZCOAXVYJUY",
SCIM_URN_USER_ENTERPRISE: {
"employeeNumber": "PDFWRRZBQOHB",
"costCenter": "HACMZWSEDOTQ",
"organization": "LXVHJUOLNCLS",
"division": "JASVTPKPBPMG",
"department": "GMSBFLMNPABY",
"manager": "PNGSGXLYVWMV",
},
"userName": "imelda.auer@kshlerin.co.uk",
"userType": "ZGJMYZRUORZE",
"x509Certificates": [{"primary": "true", "value": "KOVKWGIVVEHH"}],
},
)
def test_schema_urn_manager(self):
req = {
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "Add",
"value": {
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:manager": "foo"
},
},
],
}
user = create_test_user()
source = SCIMSource.objects.create(slug=generate_id())
connection = SCIMSourceUser.objects.create(
user=user,
id=generate_id(),
source=source,
attributes={
"meta": {"resourceType": "User"},
"active": True,
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
SCIM_URN_USER_ENTERPRISE,
],
"userName": "test@t.goauthentik.io",
"externalId": "test",
"displayName": "Test MS",
},
)
updated = SCIMPatchProcessor().apply_patches(connection.attributes, req["Operations"])
self.assertEqual(
updated,
{
"meta": {"resourceType": "User"},
"active": True,
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
SCIM_URN_USER_ENTERPRISE,
],
"userName": "test@t.goauthentik.io",
"externalId": "test",
"displayName": "Test MS",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
"manager": {"value": "foo"}
},
},
)

View File

@@ -1,6 +1,7 @@
"""SCIM Utils"""
from typing import Any
from uuid import UUID
from django.conf import settings
from django.core.paginator import Page, Paginator
@@ -21,6 +22,7 @@ from authentik.core.sources.mapper import SourceMapper
from authentik.lib.sync.mapper import PropertyMappingManager
from authentik.sources.scim.models import SCIMSource
from authentik.sources.scim.views.v2.auth import SCIMTokenAuth
from authentik.sources.scim.views.v2.exceptions import SCIMNotFoundError
SCIM_CONTENT_TYPE = "application/scim+json"
@@ -54,6 +56,13 @@ class SCIMView(APIView):
def get_authenticators(self):
return [SCIMTokenAuth(self)]
def remove_excluded_attributes(self, data: dict):
"""Remove attributes specified in excludedAttributes"""
excluded: str = self.request.query_params.get("excludedAttributes", "")
for key in excluded.split(","):
data.pop(key.strip(), None)
return data
def filter_parse(self, request: Request):
"""Parse the path of a Patch Operation"""
path = request.query_params.get("filter")
@@ -103,6 +112,12 @@ class SCIMObjectView(SCIMView):
# a source attribute before
self.mapper = SourceMapper(self.source)
self.manager = self.mapper.get_manager(self.model, ["data"])
for key, value in kwargs.items():
if key.endswith("_id"):
try:
UUID(value)
except ValueError:
raise SCIMNotFoundError("Invalid ID") from None
def build_object_properties(self, data: dict[str, Any]) -> dict[str, Any | dict[str, Any]]:
return self.mapper.build_object_properties(

View File

@@ -17,6 +17,7 @@ from authentik.core.models import Group, User
from authentik.providers.scim.clients.schema import SCIM_GROUP_SCHEMA, PatchOp, PatchOperation
from authentik.providers.scim.clients.schema import Group as SCIMGroupModel
from authentik.sources.scim.models import SCIMSourceGroup
from authentik.sources.scim.patch.processor import SCIMPatchProcessor
from authentik.sources.scim.views.v2.base import SCIMObjectView
from authentik.sources.scim.views.v2.exceptions import (
SCIMConflictError,
@@ -35,11 +36,12 @@ class GroupsView(SCIMObjectView):
payload = SCIMGroupModel(
schemas=[SCIM_GROUP_SCHEMA],
id=str(scim_group.group.pk),
externalId=scim_group.id,
externalId=scim_group.external_id,
displayName=scim_group.group.name,
members=[],
meta={
"resourceType": "Group",
"lastModified": scim_group.last_update,
"location": self.request.build_absolute_uri(
reverse(
"authentik_sources_scim:v2-groups",
@@ -54,7 +56,11 @@ class GroupsView(SCIMObjectView):
for member in scim_group.group.users.order_by("pk"):
member: User
payload.members.append(GroupMember(value=str(member.uuid)))
return payload.model_dump(mode="json", exclude_unset=True)
final_payload = payload.model_dump(mode="json", exclude_unset=True)
final_payload.update(scim_group.attributes)
return self.remove_excluded_attributes(
SCIMGroupModel.model_validate(final_payload).model_dump(mode="json", exclude_unset=True)
)
def get(self, request: Request, group_id: str | None = None, **kwargs) -> Response:
"""List Group handler"""
@@ -81,7 +87,7 @@ class GroupsView(SCIMObjectView):
)
@atomic
def update_group(self, connection: SCIMSourceGroup | None, data: QueryDict):
def update_group(self, connection: SCIMSourceGroup | None, data: QueryDict, apply_members=True):
"""Partial update a group"""
properties = self.build_object_properties(data)
@@ -94,7 +100,7 @@ class GroupsView(SCIMObjectView):
group.update_attributes(properties)
if "members" in data:
if "members" in data and apply_members:
query = Q()
for _member in data.get("members", []):
try:
@@ -105,14 +111,18 @@ class GroupsView(SCIMObjectView):
query |= Q(uuid=member.value)
if query:
group.users.set(User.objects.filter(query))
data["members"] = self._convert_members(group)
if not connection:
connection, _ = SCIMSourceGroup.objects.get_or_create(
connection, _ = SCIMSourceGroup.objects.update_or_create(
external_id=data.get("externalId") or str(uuid4()),
source=self.source,
group=group,
attributes=data,
id=data.get("externalId") or str(uuid4()),
defaults={
"attributes": data,
},
)
else:
connection.external_id = data.get("externalId", connection.external_id)
connection.attributes = data
connection.save()
return connection
@@ -139,6 +149,12 @@ class GroupsView(SCIMObjectView):
connection = self.update_group(connection, request.data)
return Response(self.group_to_scim(connection), status=200)
def _convert_members(self, group: Group):
users = []
for user in group.users.all().order_by("uuid"):
users.append({"value": str(user.uuid)})
return sorted(users, key=lambda u: u["value"])
@atomic
def patch(self, request: Request, group_id: str, **kwargs) -> Response:
"""Patch group handler"""
@@ -171,6 +187,13 @@ class GroupsView(SCIMObjectView):
query |= Q(uuid=member["value"])
if query:
connection.group.users.remove(*User.objects.filter(query))
patcher = SCIMPatchProcessor()
patched_data = patcher.apply_patches(
connection.attributes, request.data.get("Operations", [])
)
patched_data["members"] = self._convert_members(connection.group)
if patched_data != connection.attributes:
self.update_group(connection, patched_data, apply_members=False)
return Response(self.group_to_scim(connection), status=200)
@atomic

View File

@@ -33,9 +33,7 @@ class ServiceProviderConfigView(SCIMView):
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
"authenticationSchemes": auth_schemas,
# We only support patch for groups currently, so don't broadly advertise it.
# Implementations that require Group patch will use it regardless of this flag.
"patch": {"supported": False},
"patch": {"supported": True},
"bulk": {"supported": False, "maxOperations": 0, "maxPayloadSize": 0},
"filter": {
"supported": True,

View File

@@ -15,6 +15,7 @@ from authentik.core.models import User
from authentik.providers.scim.clients.schema import SCIM_USER_SCHEMA
from authentik.providers.scim.clients.schema import User as SCIMUserModel
from authentik.sources.scim.models import SCIMSourceUser
from authentik.sources.scim.patch.processor import SCIMPatchProcessor
from authentik.sources.scim.views.v2.base import SCIMObjectView
from authentik.sources.scim.views.v2.exceptions import SCIMConflictError, SCIMNotFoundError
@@ -29,7 +30,7 @@ class UsersView(SCIMObjectView):
payload = SCIMUserModel(
schemas=[SCIM_USER_SCHEMA],
id=str(scim_user.user.uuid),
externalId=scim_user.id,
externalId=scim_user.external_id,
userName=scim_user.user.username,
name=Name(
formatted=scim_user.user.name,
@@ -44,8 +45,7 @@ class UsersView(SCIMObjectView):
meta={
"resourceType": "User",
"created": scim_user.user.date_joined,
# TODO: use events to find last edit?
"lastModified": scim_user.user.date_joined,
"lastModified": scim_user.last_update,
"location": self.request.build_absolute_uri(
reverse(
"authentik_sources_scim:v2-users",
@@ -59,7 +59,9 @@ class UsersView(SCIMObjectView):
)
final_payload = payload.model_dump(mode="json", exclude_unset=True)
final_payload.update(scim_user.attributes)
return final_payload
return self.remove_excluded_attributes(
SCIMUserModel.model_validate(final_payload).model_dump(mode="json", exclude_unset=True)
)
def get(self, request: Request, user_id: str | None = None, **kwargs) -> Response:
"""List User handler"""
@@ -101,13 +103,16 @@ class UsersView(SCIMObjectView):
user.update_attributes(properties)
if not connection:
connection, _ = SCIMSourceUser.objects.get_or_create(
connection, _ = SCIMSourceUser.objects.update_or_create(
external_id=data.get("externalId") or str(uuid4()),
source=self.source,
user=user,
attributes=data,
id=data.get("externalId") or str(uuid4()),
defaults={
"attributes": data,
},
)
else:
connection.external_id = data.get("externalId", connection.external_id)
connection.attributes = data
connection.save()
return connection
@@ -127,6 +132,18 @@ class UsersView(SCIMObjectView):
connection = self.update_user(None, request.data)
return Response(self.user_to_scim(connection), status=201)
def patch(self, request: Request, user_id: str, **kwargs):
connection = SCIMSourceUser.objects.filter(source=self.source, user__uuid=user_id).first()
if not connection:
raise SCIMNotFoundError("User not found.")
patcher = SCIMPatchProcessor()
patched_data = patcher.apply_patches(
connection.attributes, request.data.get("Operations", [])
)
if patched_data != connection.attributes:
self.update_user(connection, patched_data)
return Response(self.user_to_scim(connection), status=200)
def put(self, request: Request, user_id: str, **kwargs) -> Response:
"""Update user handler"""
connection = SCIMSourceUser.objects.filter(source=self.source, user__uuid=user_id).first()

View File

@@ -13,7 +13,6 @@ from authentik.flows.exceptions import StageInvalidException
from authentik.flows.models import ConfigurableStage, FriendlyNamedStage, Stage
from authentik.lib.config import CONFIG
from authentik.lib.models import SerializerModel
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.time import timedelta_string_validator
from authentik.stages.authenticator.models import SideChannelDevice
from authentik.stages.email.utils import TemplateEmailMessage
@@ -160,9 +159,8 @@ class EmailDevice(SerializerModel, SideChannelDevice):
Event.new(
EventAction.CONFIGURATION_ERROR,
message=_("Exception occurred while rendering E-mail template"),
error=exception_to_string(exc),
template=stage.template,
).from_http(self.request)
).with_exception(exc).from_http(self.request)
raise StageInvalidException from exc
def __str__(self):

View File

@@ -17,7 +17,6 @@ from authentik.flows.challenge import (
from authentik.flows.exceptions import StageInvalidException
from authentik.flows.stage import ChallengeStageView
from authentik.lib.utils.email import mask_email
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.time import timedelta_from_string
from authentik.stages.authenticator_email.models import (
AuthenticatorEmailStage,
@@ -100,9 +99,8 @@ class AuthenticatorEmailStageView(ChallengeStageView):
Event.new(
EventAction.CONFIGURATION_ERROR,
message=_("Exception occurred while rendering E-mail template"),
error=exception_to_string(exc),
template=stage.template,
).from_http(self.request)
).with_exception(exc).from_http(self.request)
raise StageInvalidException from exc
def _has_email(self) -> str | None:

View File

@@ -19,7 +19,6 @@ from authentik.events.models import Event, EventAction, NotificationWebhookMappi
from authentik.events.utils import sanitize_item
from authentik.flows.models import ConfigurableStage, FriendlyNamedStage, Stage
from authentik.lib.models import SerializerModel
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.http import get_http_session
from authentik.stages.authenticator.models import SideChannelDevice
@@ -142,10 +141,9 @@ class AuthenticatorSMSStage(ConfigurableStage, FriendlyNamedStage, Stage):
Event.new(
EventAction.CONFIGURATION_ERROR,
message="Error sending SMS",
exc=exception_to_string(exc),
status_code=response.status_code,
body=response.text,
).set_user(device.user).save()
).with_exception(exc).set_user(device.user).save()
if response.status_code >= HttpResponseBadRequest.status_code:
raise ValidationError(response.text) from None
raise

File diff suppressed because one or more lines are too long

View File

@@ -21,7 +21,6 @@ from authentik.flows.models import FlowDesignation, FlowToken
from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView
from authentik.flows.views.executor import QS_KEY_TOKEN, QS_QUERY
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.time import timedelta_from_string
from authentik.stages.email.flow import pickle_flow_token_for_email
from authentik.stages.email.models import EmailStage
@@ -129,9 +128,8 @@ class EmailStageView(ChallengeStageView):
Event.new(
EventAction.CONFIGURATION_ERROR,
message=_("Exception occurred while rendering E-mail template"),
error=exception_to_string(exc),
template=current_stage.template,
).from_http(self.request)
).with_exception(exc).from_http(self.request)
raise StageInvalidException from exc
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
@@ -145,7 +143,7 @@ class EmailStageView(ChallengeStageView):
messages.success(request, _("Successfully verified Email."))
if self.executor.current_stage.activate_user_on_success:
user.is_active = True
user.save()
user.save(update_fields=["is_active"])
return self.executor.stage_ok()
if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
self.logger.debug("No pending user")

View File

@@ -191,9 +191,10 @@ class ListPolicyEngine(PolicyEngine):
self.use_cache = False
def bindings(self):
for policy in self.__list:
for idx, policy in enumerate(self.__list):
yield PolicyBinding(
policy=policy,
order=idx,
)

View File

@@ -214,7 +214,7 @@ class TestPromptStage(FlowTestCase):
"""Test challenge_response validation"""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
expr = "False"
expr_policy = ExpressionPolicy.objects.create(name="validate-form", expression=expr)
expr_policy = ExpressionPolicy.objects.create(name=generate_id(), expression=expr)
self.stage.validation_policies.set([expr_policy])
self.stage.save()
challenge_response = PromptChallengeResponse(
@@ -222,6 +222,18 @@ class TestPromptStage(FlowTestCase):
)
self.assertEqual(challenge_response.is_valid(), False)
def test_invalid_challenge_multiple(self):
"""Test challenge_response validation (multiple policies)"""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
expr_policy1 = ExpressionPolicy.objects.create(name=generate_id(), expression="False")
expr_policy2 = ExpressionPolicy.objects.create(name=generate_id(), expression="False")
self.stage.validation_policies.set([expr_policy1, expr_policy2])
self.stage.save()
challenge_response = PromptChallengeResponse(
None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
)
self.assertEqual(challenge_response.is_valid(), False)
def test_valid_challenge_request(self):
"""Test a request with valid challenge_response data"""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
@@ -234,7 +246,7 @@ class TestPromptStage(FlowTestCase):
"return request.context['prompt_data']['password_prompt'] "
"== request.context['prompt_data']['password2_prompt']"
)
expr_policy = ExpressionPolicy.objects.create(name="validate-form", expression=expr)
expr_policy = ExpressionPolicy.objects.create(name=generate_id(), expression=expr)
self.stage.validation_policies.set([expr_policy])
self.stage.save()
challenge_response = PromptChallengeResponse(

View File

@@ -10961,6 +10961,7 @@
"enum": [
"apple",
"openidconnect",
"entraid",
"azuread",
"discord",
"facebook",

8
go.mod
View File

@@ -10,14 +10,14 @@ require (
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.2
github.com/golang-jwt/jwt/v5 v5.2.3
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.2
github.com/gorilla/mux v1.8.1
github.com/gorilla/securecookie v1.1.2
github.com/gorilla/sessions v1.4.0
github.com/gorilla/websocket v1.5.3
github.com/grafana/pyroscope-go v1.2.2
github.com/grafana/pyroscope-go v1.2.3
github.com/jellydator/ttlcache/v3 v3.4.0
github.com/mitchellh/mapstructure v1.5.0
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
@@ -29,10 +29,10 @@ require (
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.2025063.3
goauthentik.io/api/v3 v3.2025063.5
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.15.0
golang.org/x/sync v0.16.0
gopkg.in/yaml.v2 v2.4.0
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
)

16
go.sum
View File

@@ -115,8 +115,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.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -180,8 +180,8 @@ github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2e
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/pyroscope-go v1.2.2 h1:uvKCyZMD724RkaCEMrSTC38Yn7AnFe8S2wiAIYdDPCE=
github.com/grafana/pyroscope-go v1.2.2/go.mod h1:zzT9QXQAp2Iz2ZdS216UiV8y9uXJYQiGE1q8v1FyhqU=
github.com/grafana/pyroscope-go v1.2.3 h1:Rp8mjqqGqmRDvV6XYmuedUAv7wVnQJK/M1pBt6uNwxU=
github.com/grafana/pyroscope-go v1.2.3/go.mod h1:zzT9QXQAp2Iz2ZdS216UiV8y9uXJYQiGE1q8v1FyhqU=
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg=
github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
@@ -298,8 +298,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.2025063.3 h1:Ci+iKWgyioG6QYN3yTZn0SLEnGLC8uLu4FUqMdF5AP8=
goauthentik.io/api/v3 v3.2025063.3/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
goauthentik.io/api/v3 v3.2025063.5 h1:j5el9/qI/72Q5x5QAiMzgQTswMj2TK3h74OaBcFEtkI=
goauthentik.io/api/v3 v3.2025063.5/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -384,8 +384,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@@ -100,6 +100,9 @@ elif [[ "$1" == "healthcheck" ]]; then
elif [[ "$1" == "dump_config" ]]; then
shift
exec python -m authentik.lib.config $@
elif [[ "$1" == "support" ]]; then
wait_for_db
exec python -m lifecycle.support
elif [[ "$1" == "debug" ]]; then
exec sleep infinity
else

View File

@@ -9,7 +9,7 @@
"version": "0.0.0",
"license": "MIT",
"devDependencies": {
"aws-cdk": "^2.1020.2",
"aws-cdk": "^2.1021.0",
"cross-env": "^7.0.3"
},
"engines": {
@@ -17,10 +17,11 @@
}
},
"node_modules/aws-cdk": {
"version": "2.1020.2",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1020.2.tgz",
"integrity": "sha512-yWdt3dJh4aPm1VNyEgfG3lozGrvddw0i7avt+Cl9KOYixmisQtAg39/aZqzVVqjzVZVEanXmz+tlhzzh75Z69A==",
"version": "2.1021.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1021.0.tgz",
"integrity": "sha512-kE557b4N9UFWax+7km3R6D56o4tGhpzOks/lRDugaoC8su3mocLCXJhb954b/IRl0ipnbZnY/Sftq+RQ/sxivg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"cdk": "bin/cdk"
},

View File

@@ -10,7 +10,7 @@
"node": ">=20"
},
"devDependencies": {
"aws-cdk": "^2.1020.2",
"aws-cdk": "^2.1021.0",
"cross-env": "^7.0.3"
}
}

View File

@@ -7,10 +7,11 @@ from os import environ, system
from pathlib import Path
from typing import Any
from psycopg import Connection, Cursor, connect
from psycopg import Connection, Cursor
from structlog.stdlib import get_logger
from authentik.lib.config import CONFIG, django_db_config
from lifecycle.wait_for_db import get_postgres
LOGGER = get_logger()
ADV_LOCK_UID = 1000
@@ -71,17 +72,7 @@ def release_lock(cursor: Cursor):
def run_migrations():
conn = connect(
dbname=CONFIG.get("postgresql.name"),
user=CONFIG.get("postgresql.user"),
password=CONFIG.get("postgresql.password"),
host=CONFIG.get("postgresql.host"),
port=CONFIG.get_int("postgresql.port"),
sslmode=CONFIG.get("postgresql.sslmode"),
sslrootcert=CONFIG.get("postgresql.sslrootcert"),
sslcert=CONFIG.get("postgresql.sslcert"),
sslkey=CONFIG.get("postgresql.sslkey"),
)
conn = get_postgres()
curr = conn.cursor()
try:
wait_for_lock(curr)

111
lifecycle/support.py Normal file
View File

@@ -0,0 +1,111 @@
import platform
from hashlib import sha512
from pprint import pprint
from ssl import OPENSSL_VERSION
from sys import version as python_version
from cryptography.exceptions import InternalError
from cryptography.hazmat.backends.openssl.backend import backend
from jwcrypto.common import json_encode
from jwcrypto.jwe import JWE
from jwcrypto.jwk import JWK
from jwt import encode
from psutil import cpu_count, virtual_memory
from redis import Redis
from authentik import get_full_version
from authentik.lib.config import CONFIG
from authentik.lib.utils.reflection import get_env
from authentik.root.install_id import get_install_id_raw
from lifecycle.wait_for_db import get_postgres, get_redis
try:
backend._enable_fips()
except InternalError:
pass
def get_version_history():
with get_postgres() as postgres:
cur = postgres.cursor()
cur.execute("""SELECT "timestamp", "version", "build" FROM authentik_version_history;""")
for x, y, z in cur.fetchall():
yield (x.timestamp(), y, z)
def get_postgres_version():
with get_postgres() as postgres:
cur = postgres.cursor()
cur.execute("""SELECT version();""")
return cur.fetchone()[0]
def get_redis_version():
redis: Redis = get_redis()
version = redis.info()
redis.close()
return f"{version["redis_version"]} {version["redis_mode"]} {version["os"]}"
def get_limited_config():
return {
"postgresql": {
"host": CONFIG.get("postgresql.host"),
},
"redis": {
"host": CONFIG.get("redis.host"),
},
"debug": CONFIG.get_bool("debug"),
"log_level": CONFIG.get("log_level"),
"error_reporting": {
"enabled": CONFIG.get_bool("error_reporting.enabled"),
},
}
def generate():
payload = {
"version": {
"history": list(get_version_history()),
"current": get_full_version(),
"postgres": get_postgres_version(),
"redis": get_redis_version(),
"ssl": OPENSSL_VERSION,
"python": python_version,
},
"env": get_env(),
"install_id_hash": sha512(get_install_id_raw().encode("ascii")).hexdigest()[:16],
"system": {
"cpu": {"count": cpu_count()},
"fips": backend._fips_enabled,
"memory_bytes": virtual_memory().total,
"architecture": platform.machine(),
"platform": platform.platform(),
"uname": " ".join(platform.uname()),
},
"config": get_limited_config(),
}
return payload
def encrypt(raw):
with open("authentik/enterprise/public.pem", "rb") as _key:
key = JWK.from_pem(_key.read())
jwe = JWE(
encode(raw, "foo"),
json_encode(
{
"alg": "ECDH-ES+A256KW",
"enc": "A256CBC-HS512",
"typ": "JWE",
}
),
)
jwe.add_recipient(key)
return jwe.serialize(compact=True)
if __name__ == "__main__":
data = generate()
snippet = encrypt(data)
pprint(data)

View File

@@ -13,23 +13,32 @@ from authentik.lib.config import CONFIG, redis_url
CHECK_THRESHOLD = 30
def get_postgres():
return connect(
dbname=CONFIG.get("postgresql.name"),
user=CONFIG.get("postgresql.user"),
password=CONFIG.get("postgresql.password"),
host=CONFIG.get("postgresql.host"),
port=CONFIG.get_int("postgresql.port"),
sslmode=CONFIG.get("postgresql.sslmode"),
sslrootcert=CONFIG.get("postgresql.sslrootcert"),
sslcert=CONFIG.get("postgresql.sslcert"),
sslkey=CONFIG.get("postgresql.sslkey"),
)
def get_redis():
url = CONFIG.get("cache.url") or redis_url(CONFIG.get("redis.db"))
return Redis.from_url(url)
def check_postgres():
attempt = 0
while True:
if attempt >= CHECK_THRESHOLD:
sysexit(1)
try:
conn = connect(
dbname=CONFIG.refresh("postgresql.name"),
user=CONFIG.refresh("postgresql.user"),
password=CONFIG.refresh("postgresql.password"),
host=CONFIG.refresh("postgresql.host"),
port=CONFIG.get_int("postgresql.port"),
sslmode=CONFIG.get("postgresql.sslmode"),
sslrootcert=CONFIG.get("postgresql.sslrootcert"),
sslcert=CONFIG.get("postgresql.sslcert"),
sslkey=CONFIG.get("postgresql.sslkey"),
)
conn = get_postgres()
conn.cursor()
break
except OperationalError as exc:
@@ -41,13 +50,12 @@ def check_postgres():
def check_redis():
url = CONFIG.get("cache.url") or redis_url(CONFIG.get("redis.db"))
attempt = 0
while True:
if attempt >= CHECK_THRESHOLD:
sysexit(1)
try:
redis = Redis.from_url(url)
redis = get_redis()
redis.ping()
break
except RedisError as exc:

View File

@@ -40,7 +40,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-07-05 00:10+0000\n"
"POT-Creation-Date: 2025-07-15 00:11+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Fabian, 2025\n"
"Language-Team: German (https://app.transifex.com/authentik/teams/119923/de/)\n"
@@ -407,7 +407,7 @@ msgstr "Eigenschaft"
#: authentik/core/models.py
msgid "Property Mappings"
msgstr "Eigenschaften"
msgstr "Property Mappings"
#: authentik/core/models.py
msgid "session data"
@@ -593,7 +593,7 @@ msgstr "Google Workspace Provider Gruppen"
#: authentik/providers/scim/models.py
msgid "Property mappings used for group creation/updating."
msgstr ""
"Eigenschaft, die für die Erstellung/Aktualisierung von Gruppen verwendet "
"Eigenschaften, die für die Erstellung/Aktualisierung von Gruppen verwendet "
"werden."
#: authentik/enterprise/providers/google_workspace/models.py
@@ -2754,6 +2754,14 @@ msgstr "Azure AD OAuth Quelle"
msgid "Azure AD OAuth Sources"
msgstr "Azure AD OAuth Quellen"
#: authentik/sources/oauth/models.py
msgid "Entra ID OAuth Source"
msgstr "Entra ID OAuth-Quelle"
#: authentik/sources/oauth/models.py
msgid "Entra ID OAuth Sources"
msgstr "Entra ID OAuth-Quellen"
#: authentik/sources/oauth/models.py
msgid "OpenID OAuth Source"
msgstr "OpenID OAuth Quelle"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-07-05 00:10+0000\n"
"POT-Creation-Date: 2025-07-15 00:11+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"
@@ -2454,6 +2454,14 @@ msgstr ""
msgid "Azure AD OAuth Sources"
msgstr ""
#: authentik/sources/oauth/models.py
msgid "Entra ID OAuth Source"
msgstr ""
#: authentik/sources/oauth/models.py
msgid "Entra ID OAuth Sources"
msgstr ""
#: authentik/sources/oauth/models.py
msgid "OpenID OAuth Source"
msgstr ""

Binary file not shown.

View File

@@ -15,7 +15,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-07-05 00:10+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Iamanaws, 2025\n"
"Language-Team: Spanish (https://app.transifex.com/authentik/teams/119923/es/)\n"
@@ -125,10 +125,6 @@ msgstr "Marcas"
msgid "User does not have access to application."
msgstr "El usuario no tiene acceso a la aplicación"
#: authentik/core/api/devices.py
msgid "Extra description not available"
msgstr "Descripción adicional no disponible."
#: authentik/core/api/groups.py
msgid "Cannot set group as parent of itself."
msgstr "No se puede establecer un grupo como su propio padre."
@@ -375,6 +371,10 @@ msgstr "Tokens"
msgid "View token's key"
msgstr "Ver llave del token"
#: authentik/core/models.py
msgid "Set a token's key"
msgstr "Establecer la clave de un token"
#: authentik/core/models.py
msgid "Property Mapping"
msgstr "Asignación de Propiedades"
@@ -838,6 +838,15 @@ msgstr ""
"Define a qué grupo de usuarios se les debe enviar y mostrar esta "
"notificación. Si se deja vacío, no se enviará Notificación."
#: 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 ""
"Cuando está habilitada, se enviará una notificación al usuario que "
"desencadenó el evento. Cuando se configura destination_group, la "
"notificación se enviará a ambos."
#: authentik/events/models.py
msgid "Notification Rule"
msgstr "Regla de notificación"
@@ -3869,6 +3878,17 @@ msgstr ""
"Recordarme. El valor predeterminado de 0 significa que no se mostrará la "
"opción Recordarme. (Formato: 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 ""
"Cuando se establece en un valor distinto de cero, authentik guardará una "
"cookie con una expiración más larga para recordar el dispositivo desde el "
"cual el usuario está iniciando sesión. (Formato: "
"hours=-1;minutes=-2;seconds=-3)"
#: authentik/stages/user_login/models.py
msgid "User Login Stage"
msgstr "Etapa de inicio de sesión"

Binary file not shown.

View File

@@ -20,7 +20,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-06-25 00:10+0000\n"
"POT-Creation-Date: 2025-07-05 00:10+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: albanobattistella <albanobattistella@gmail.com>, 2025\n"
"Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n"
@@ -373,6 +373,10 @@ msgstr "Tokens"
msgid "View token's key"
msgstr "Visualizza la chiave token"
#: authentik/core/models.py
msgid "Set a token's key"
msgstr "Imposta la chiave di un token"
#: authentik/core/models.py
msgid "Property Mapping"
msgstr "Mappatura della proprietà"
@@ -3868,6 +3872,16 @@ msgstr ""
"ricordami. Il valore predefinito di 0 significa che l'opzione ricordami non "
"verrà mostrata. (Formato: 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 ""
"Se impostato su un valore diverso da zero, authentik salverà un cookie con "
"una scadenza più lunga, per ricordare il dispositivo da cui l'utente sta "
"effettuando l'accesso. (Formato: ore=-1; minuti=-2; secondi=-3)"
#: authentik/stages/user_login/models.py
msgid "User Login Stage"
msgstr "Fase di accesso utente"

Binary file not shown.

Binary file not shown.

View File

@@ -4452,9 +4452,9 @@
"license": "MIT"
},
"node_modules/@types/react": {
"version": "19.1.6",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz",
"integrity": "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==",
"version": "19.1.8",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz",
"integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4462,9 +4462,9 @@
}
},
"node_modules/@types/react-dom": {
"version": "19.1.5",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz",
"integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==",
"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==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -14661,9 +14661,9 @@
}
},
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"bin": {

View File

@@ -132,9 +132,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
"integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz",
"integrity": "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==",
"cpu": [
"ppc64"
],
@@ -149,9 +149,9 @@
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
"integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.6.tgz",
"integrity": "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==",
"cpu": [
"arm"
],
@@ -166,9 +166,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
"integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.6.tgz",
"integrity": "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==",
"cpu": [
"arm64"
],
@@ -183,9 +183,9 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
"integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.6.tgz",
"integrity": "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==",
"cpu": [
"x64"
],
@@ -200,9 +200,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
"integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.6.tgz",
"integrity": "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==",
"cpu": [
"arm64"
],
@@ -217,9 +217,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
"integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.6.tgz",
"integrity": "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==",
"cpu": [
"x64"
],
@@ -234,9 +234,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
"integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.6.tgz",
"integrity": "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==",
"cpu": [
"arm64"
],
@@ -251,9 +251,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
"integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.6.tgz",
"integrity": "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==",
"cpu": [
"x64"
],
@@ -268,9 +268,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
"integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.6.tgz",
"integrity": "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==",
"cpu": [
"arm"
],
@@ -285,9 +285,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
"integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.6.tgz",
"integrity": "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==",
"cpu": [
"arm64"
],
@@ -302,9 +302,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
"integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.6.tgz",
"integrity": "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==",
"cpu": [
"ia32"
],
@@ -319,9 +319,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
"integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.6.tgz",
"integrity": "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==",
"cpu": [
"loong64"
],
@@ -336,9 +336,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
"integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.6.tgz",
"integrity": "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==",
"cpu": [
"mips64el"
],
@@ -353,9 +353,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
"integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.6.tgz",
"integrity": "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==",
"cpu": [
"ppc64"
],
@@ -370,9 +370,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
"integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.6.tgz",
"integrity": "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==",
"cpu": [
"riscv64"
],
@@ -387,9 +387,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
"integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.6.tgz",
"integrity": "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==",
"cpu": [
"s390x"
],
@@ -404,9 +404,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
"integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.6.tgz",
"integrity": "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==",
"cpu": [
"x64"
],
@@ -421,9 +421,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
"integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.6.tgz",
"integrity": "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==",
"cpu": [
"arm64"
],
@@ -438,9 +438,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
"integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.6.tgz",
"integrity": "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==",
"cpu": [
"x64"
],
@@ -455,9 +455,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
"integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.6.tgz",
"integrity": "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==",
"cpu": [
"arm64"
],
@@ -472,9 +472,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
"integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.6.tgz",
"integrity": "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==",
"cpu": [
"x64"
],
@@ -488,10 +488,27 @@
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.6.tgz",
"integrity": "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
"integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.6.tgz",
"integrity": "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==",
"cpu": [
"x64"
],
@@ -506,9 +523,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
"integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.6.tgz",
"integrity": "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==",
"cpu": [
"arm64"
],
@@ -523,9 +540,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
"integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.6.tgz",
"integrity": "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==",
"cpu": [
"ia32"
],
@@ -540,9 +557,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
"integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz",
"integrity": "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==",
"cpu": [
"x64"
],
@@ -887,9 +904,9 @@
}
},
"node_modules/@types/node": {
"version": "24.0.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz",
"integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==",
"version": "24.0.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz",
"integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1252,9 +1269,9 @@
}
},
"node_modules/esbuild": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
"integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz",
"integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -1265,31 +1282,32 @@
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.5",
"@esbuild/android-arm": "0.25.5",
"@esbuild/android-arm64": "0.25.5",
"@esbuild/android-x64": "0.25.5",
"@esbuild/darwin-arm64": "0.25.5",
"@esbuild/darwin-x64": "0.25.5",
"@esbuild/freebsd-arm64": "0.25.5",
"@esbuild/freebsd-x64": "0.25.5",
"@esbuild/linux-arm": "0.25.5",
"@esbuild/linux-arm64": "0.25.5",
"@esbuild/linux-ia32": "0.25.5",
"@esbuild/linux-loong64": "0.25.5",
"@esbuild/linux-mips64el": "0.25.5",
"@esbuild/linux-ppc64": "0.25.5",
"@esbuild/linux-riscv64": "0.25.5",
"@esbuild/linux-s390x": "0.25.5",
"@esbuild/linux-x64": "0.25.5",
"@esbuild/netbsd-arm64": "0.25.5",
"@esbuild/netbsd-x64": "0.25.5",
"@esbuild/openbsd-arm64": "0.25.5",
"@esbuild/openbsd-x64": "0.25.5",
"@esbuild/sunos-x64": "0.25.5",
"@esbuild/win32-arm64": "0.25.5",
"@esbuild/win32-ia32": "0.25.5",
"@esbuild/win32-x64": "0.25.5"
"@esbuild/aix-ppc64": "0.25.6",
"@esbuild/android-arm": "0.25.6",
"@esbuild/android-arm64": "0.25.6",
"@esbuild/android-x64": "0.25.6",
"@esbuild/darwin-arm64": "0.25.6",
"@esbuild/darwin-x64": "0.25.6",
"@esbuild/freebsd-arm64": "0.25.6",
"@esbuild/freebsd-x64": "0.25.6",
"@esbuild/linux-arm": "0.25.6",
"@esbuild/linux-arm64": "0.25.6",
"@esbuild/linux-ia32": "0.25.6",
"@esbuild/linux-loong64": "0.25.6",
"@esbuild/linux-mips64el": "0.25.6",
"@esbuild/linux-ppc64": "0.25.6",
"@esbuild/linux-riscv64": "0.25.6",
"@esbuild/linux-s390x": "0.25.6",
"@esbuild/linux-x64": "0.25.6",
"@esbuild/netbsd-arm64": "0.25.6",
"@esbuild/netbsd-x64": "0.25.6",
"@esbuild/openbsd-arm64": "0.25.6",
"@esbuild/openbsd-x64": "0.25.6",
"@esbuild/openharmony-arm64": "0.25.6",
"@esbuild/sunos-x64": "0.25.6",
"@esbuild/win32-arm64": "0.25.6",
"@esbuild/win32-ia32": "0.25.6",
"@esbuild/win32-x64": "0.25.6"
}
},
"node_modules/escape-string-regexp": {
@@ -2242,9 +2260,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2310,13 +2328,13 @@
}
},
"node_modules/prettier-plugin-packagejson": {
"version": "2.5.17",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.17.tgz",
"integrity": "sha512-1WYvhTix+4EMYZQYSjAxb6+KTCULINuHUTBcxYa2ipoUS9Y2zJVjE3kuZ5I7ZWIFqyK8xpwYIunXqN5eiT7Hew==",
"version": "2.5.18",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.18.tgz",
"integrity": "sha512-NKznPGcGrcj4NPGxnh+w78JXPyfB6I4RQSCM0v+CAXwpDG7OEpJQ5zMyfC5NBgKH1k7Skwcj5ak5by2mrHvC5g==",
"dev": true,
"license": "MIT",
"dependencies": {
"sort-package-json": "3.3.1",
"sort-package-json": "3.4.0",
"synckit": "0.11.8"
},
"peerDependencies": {
@@ -2489,9 +2507,9 @@
"license": "MIT"
},
"node_modules/sort-package-json": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-3.3.1.tgz",
"integrity": "sha512-awjhQR2Iy5UN3NuguAK5+RezcEuUg9Ra4O8y2Aj+DlJa7MywyHaipAPf9bu4qqFj0hsYHHoT9sS3aV7Ucu728g==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-3.4.0.tgz",
"integrity": "sha512-97oFRRMM2/Js4oEA9LJhjyMlde+2ewpZQf53pgue27UkbEXfHJnDzHlUxQ/DWUkzqmp7DFwJp8D+wi/TYeQhpA==",
"dev": true,
"license": "MIT",
"dependencies": {

View File

@@ -272,9 +272,10 @@
}
},
"node_modules/@eslint/js": {
"version": "9.30.1",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.1.tgz",
"integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==",
"version": "9.31.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz",
"integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==",
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
@@ -453,6 +454,7 @@
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
"run-parallel": "^1.1.9"
@@ -466,6 +468,7 @@
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
}
@@ -475,6 +478,7 @@
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
"fastq": "^1.6.0"
@@ -569,16 +573,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.36.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.36.0.tgz",
"integrity": "sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg==",
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz",
"integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.36.0",
"@typescript-eslint/type-utils": "8.36.0",
"@typescript-eslint/utils": "8.36.0",
"@typescript-eslint/visitor-keys": "8.36.0",
"@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/type-utils": "8.37.0",
"@typescript-eslint/utils": "8.37.0",
"@typescript-eslint/visitor-keys": "8.37.0",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -592,7 +597,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.36.0",
"@typescript-eslint/parser": "^8.37.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
@@ -602,20 +607,22 @@
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.36.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.36.0.tgz",
"integrity": "sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q==",
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz",
"integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.36.0",
"@typescript-eslint/types": "8.36.0",
"@typescript-eslint/typescript-estree": "8.36.0",
"@typescript-eslint/visitor-keys": "8.36.0",
"@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/visitor-keys": "8.37.0",
"debug": "^4.3.4"
},
"engines": {
@@ -631,13 +638,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.36.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.36.0.tgz",
"integrity": "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g==",
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz",
"integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.36.0",
"@typescript-eslint/types": "^8.36.0",
"@typescript-eslint/tsconfig-utils": "^8.37.0",
"@typescript-eslint/types": "^8.37.0",
"debug": "^4.3.4"
},
"engines": {
@@ -652,13 +660,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.36.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.36.0.tgz",
"integrity": "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA==",
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz",
"integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.36.0",
"@typescript-eslint/visitor-keys": "8.36.0"
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/visitor-keys": "8.37.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -669,10 +678,11 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.36.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.36.0.tgz",
"integrity": "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA==",
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz",
"integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
@@ -685,13 +695,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.36.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.36.0.tgz",
"integrity": "sha512-5aaGYG8cVDd6cxfk/ynpYzxBRZJk7w/ymto6uiyUFtdCozQIsQWh7M28/6r57Fwkbweng8qAzoMCPwSJfWlmsg==",
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz",
"integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/typescript-estree": "8.36.0",
"@typescript-eslint/utils": "8.36.0",
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/utils": "8.37.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -708,10 +720,11 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.36.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.36.0.tgz",
"integrity": "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ==",
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz",
"integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
@@ -721,15 +734,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.36.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.36.0.tgz",
"integrity": "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg==",
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz",
"integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.36.0",
"@typescript-eslint/tsconfig-utils": "8.36.0",
"@typescript-eslint/types": "8.36.0",
"@typescript-eslint/visitor-keys": "8.36.0",
"@typescript-eslint/project-service": "8.37.0",
"@typescript-eslint/tsconfig-utils": "8.37.0",
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/visitor-keys": "8.37.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -753,6 +767,7 @@
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
@@ -762,6 +777,7 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
@@ -777,6 +793,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
@@ -785,15 +802,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.36.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.36.0.tgz",
"integrity": "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g==",
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz",
"integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.36.0",
"@typescript-eslint/types": "8.36.0",
"@typescript-eslint/typescript-estree": "8.36.0"
"@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/types": "8.37.0",
"@typescript-eslint/typescript-estree": "8.37.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -808,12 +826,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.36.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.36.0.tgz",
"integrity": "sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA==",
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz",
"integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.36.0",
"@typescript-eslint/types": "8.37.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -1065,9 +1084,9 @@
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
@@ -1079,6 +1098,7 @@
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
},
@@ -1537,17 +1557,18 @@
}
},
"node_modules/eslint": {
"version": "9.30.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.1.tgz",
"integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==",
"version": "9.31.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz",
"integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==",
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.14.0",
"@eslint/core": "^0.15.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.30.1",
"@eslint/js": "9.31.0",
"@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
@@ -1801,6 +1822,18 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint/node_modules/@eslint/core": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
"integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/espree": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
@@ -1871,6 +1904,7 @@
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
@@ -1887,6 +1921,7 @@
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
@@ -1911,6 +1946,7 @@
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"reusify": "^1.0.4"
}
@@ -1932,6 +1968,7 @@
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -2148,7 +2185,8 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/has-bigints": {
"version": "1.1.0",
@@ -2495,6 +2533,7 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
@@ -2877,6 +2916,7 @@
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
}
@@ -2886,6 +2926,7 @@
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
@@ -3170,6 +3211,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
@@ -3288,7 +3330,8 @@
"type": "consulting",
"url": "https://feross.org/support"
}
]
],
"license": "MIT"
},
"node_modules/react": {
"version": "19.1.0",
@@ -3397,6 +3440,7 @@
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
"dev": true,
"license": "MIT",
"engines": {
"iojs": ">=1.0.0",
"node": ">=0.10.0"
@@ -3421,6 +3465,7 @@
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"queue-microtask": "^1.2.2"
}
@@ -3896,6 +3941,7 @@
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
@@ -3908,6 +3954,7 @@
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
"integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.12"
},
@@ -4028,14 +4075,16 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.36.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.36.0.tgz",
"integrity": "sha512-fTCqxthY+h9QbEgSIBfL9iV6CvKDFuoxg6bHPNpJ9HIUzS+jy2lCEyCmGyZRWEBSaykqcDPf1SJ+BfCI8DRopA==",
"version": "8.37.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.37.0.tgz",
"integrity": "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.36.0",
"@typescript-eslint/parser": "8.36.0",
"@typescript-eslint/utils": "8.36.0"
"@typescript-eslint/eslint-plugin": "8.37.0",
"@typescript-eslint/parser": "8.37.0",
"@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/utils": "8.37.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"

View File

@@ -124,12 +124,13 @@ const useLegacyCleanup = process.env.AK_FIX_LEGACY_IMPORTS === "true";
* @param {ParserOptions} options
*/
const preprocess = (input, { filepath, printWidth }) => {
if (input?.includes("ts-import-sorter: disable")) {
return input;
}
let output = input;
console.log("Looking for JSDoc...");
if (output.startsWith("/**\n")) {
console.log("JSDoc detected. Adding double newline if not present");
output = output.replace(/(^\s\*\/\n)(import)/m, "$1\n$2");
}
@@ -144,6 +145,7 @@ const preprocess = (input, { filepath, printWidth }) => {
wrappingStyle: "prettier",
groupRules: [
"^node:",
"^[./]",
...webSubmodules.map((submodule) => `^(@goauthentik/|#)${submodule}.+`),
"^#.+",
@@ -154,7 +156,6 @@ const preprocess = (input, { filepath, printWidth }) => {
"^(@?)lit(.*)$",
"\\.css$",
"^@goauthentik/api$",
"^[./]",
],
});

View File

@@ -1,12 +1,12 @@
{
"name": "@goauthentik/prettier-config",
"version": "3.0.1",
"version": "3.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@goauthentik/prettier-config",
"version": "3.0.1",
"version": "3.1.0",
"license": "MIT",
"dependencies": {
"format-imports": "^4.0.7"
@@ -181,9 +181,9 @@
}
},
"node_modules/@eslint/core": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz",
"integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==",
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
"integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
@@ -228,9 +228,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.30.1",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.1.tgz",
"integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==",
"version": "9.31.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz",
"integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==",
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -261,18 +261,6 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
"integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@goauthentik/tsconfig": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@goauthentik/tsconfig/-/tsconfig-1.0.4.tgz",
@@ -397,9 +385,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.0.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.4.tgz",
"integrity": "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==",
"version": "24.0.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz",
"integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -701,18 +689,18 @@
}
},
"node_modules/eslint": {
"version": "9.30.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.1.tgz",
"integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==",
"version": "9.31.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz",
"integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==",
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.14.0",
"@eslint/core": "^0.15.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.30.1",
"@eslint/js": "9.31.0",
"@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
@@ -1413,9 +1401,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1463,9 +1451,9 @@
}
},
"node_modules/prettier": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.1.tgz",
"integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==",
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
@@ -1478,13 +1466,13 @@
}
},
"node_modules/prettier-plugin-packagejson": {
"version": "2.5.16",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.16.tgz",
"integrity": "sha512-1EORN4SahAWU55ll+xp0PXhiUmD93PJlBE88GbWv7X5xtZ7ycj3GNbRGX+r75zWn70KAoYVO08rF2C/TqGCHPA==",
"version": "2.5.18",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.18.tgz",
"integrity": "sha512-NKznPGcGrcj4NPGxnh+w78JXPyfB6I4RQSCM0v+CAXwpDG7OEpJQ5zMyfC5NBgKH1k7Skwcj5ak5by2mrHvC5g==",
"dev": true,
"license": "MIT",
"dependencies": {
"sort-package-json": "3.2.2",
"sort-package-json": "3.4.0",
"synckit": "0.11.8"
},
"peerDependencies": {
@@ -1568,9 +1556,9 @@
"license": "MIT"
},
"node_modules/sort-package-json": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-3.2.2.tgz",
"integrity": "sha512-twAMvmzOcEPsN3N9zKPDpl6zproGU0JcBOQFU4T6e5wrStH8iuPiAjFz9g+cMRC52eQBUbZlFCeGt+F4vginkw==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-3.4.0.tgz",
"integrity": "sha512-97oFRRMM2/Js4oEA9LJhjyMlde+2ewpZQf53pgue27UkbEXfHJnDzHlUxQ/DWUkzqmp7DFwJp8D+wi/TYeQhpA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1584,6 +1572,9 @@
},
"bin": {
"sort-package-json": "cli.js"
},
"engines": {
"node": ">=20"
}
},
"node_modules/source-map-js": {

View File

@@ -1,6 +1,6 @@
{
"name": "@goauthentik/prettier-config",
"version": "3.0.1",
"version": "3.1.0",
"description": "authentik's Prettier config",
"license": "MIT",
"scripts": {

View File

@@ -44,10 +44,11 @@ dependencies = [
"kubernetes==33.1.0",
"ldap3==2.9.1",
"lxml==6.0.0",
"msgraph-sdk==1.37.0",
"msgraph-sdk==1.38.0",
"opencontainers==0.0.15",
"packaging==25.0",
"paramiko==3.5.1",
"psutil==7.0.0",
"psycopg[c,pool]==3.2.9",
"pydantic==2.11.7",
"pydantic-scim==0.0.8",
@@ -57,13 +58,13 @@ dependencies = [
"pyyaml==6.0.2",
"requests-oauthlib==2.0.0",
"scim2-filter-parser==0.7.0",
"sentry-sdk==2.32.0",
"sentry-sdk==2.33.0",
"service-identity==24.2.0",
"setproctitle==1.3.6",
"structlog==25.4.0",
"swagger-spec-validator==3.0.4",
"tenant-schemas-celery==3.0.0",
"twilio==9.6.4",
"twilio==9.6.5",
"ua-parser==1.0.1",
"unidecode==1.4.0",
"urllib3<3",
@@ -71,7 +72,7 @@ dependencies = [
"watchdog==6.0.0",
"webauthn==2.6.0",
"wsproto==1.2.0",
"xmlsec==1.3.15",
"xmlsec==1.3.16",
"zxcvbn==4.5.0",
]
@@ -150,6 +151,7 @@ skip = [
"./gen-go-api",
"*.api.mdx",
"./htmlcov",
"./media",
]
dictionary = ".github/codespell-dictionary.txt,-"
ignore-words = ".github/codespell-words.txt"

View File

@@ -7006,6 +7006,34 @@ paths:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/enterprise/support_bundle/:
post:
operationId: enterprise_support_bundle_create
description: Generate a support bundle.
tags:
- enterprise
security:
- authentik: []
responses:
'200':
content:
application/gzip:
schema:
type: string
format: binary
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/events/events/:
get:
operationId: events_events_list
@@ -55232,6 +55260,9 @@ components:
id:
type: string
minLength: 1
external_id:
type: string
minLength: 1
group:
type: string
format: uuid
@@ -55296,6 +55327,9 @@ components:
id:
type: string
minLength: 1
external_id:
type: string
minLength: 1
user:
type: integer
source:
@@ -56713,6 +56747,7 @@ components:
enum:
- apple
- openidconnect
- entraid
- azuread
- discord
- facebook
@@ -58946,6 +58981,8 @@ components:
properties:
id:
type: string
external_id:
type: string
group:
type: string
format: uuid
@@ -58960,9 +58997,9 @@ components:
type: object
additionalProperties: {}
required:
- external_id
- group
- group_obj
- id
- source
SCIMSourceGroupRequest:
type: object
@@ -58971,6 +59008,9 @@ components:
id:
type: string
minLength: 1
external_id:
type: string
minLength: 1
group:
type: string
format: uuid
@@ -58981,8 +59021,8 @@ components:
type: object
additionalProperties: {}
required:
- external_id
- group
- id
- source
SCIMSourcePropertyMapping:
type: object
@@ -59089,6 +59129,8 @@ components:
properties:
id:
type: string
external_id:
type: string
user:
type: integer
user_obj:
@@ -59102,7 +59144,7 @@ components:
type: object
additionalProperties: {}
required:
- id
- external_id
- source
- user
- user_obj
@@ -59113,6 +59155,9 @@ components:
id:
type: string
minLength: 1
external_id:
type: string
minLength: 1
user:
type: integer
source:
@@ -59122,7 +59167,7 @@ components:
type: object
additionalProperties: {}
required:
- id
- external_id
- source
- user
SMSDevice:

191
uv.lock generated
View File

@@ -13,7 +13,7 @@ wheels = [
[[package]]
name = "aiohttp"
version = "3.12.13"
version = "3.12.14"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohappyeyeballs" },
@@ -24,25 +24,25 @@ dependencies = [
{ name = "propcache" },
{ name = "yarl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/42/6e/ab88e7cb2a4058bed2f7870276454f85a7c56cd6da79349eb314fc7bbcaa/aiohttp-3.12.13.tar.gz", hash = "sha256:47e2da578528264a12e4e3dd8dd72a7289e5f812758fe086473fab037a10fcce", size = 7819160, upload-time = "2025-06-14T15:15:41.354Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e6/0b/e39ad954107ebf213a2325038a3e7a506be3d98e1435e1f82086eec4cde2/aiohttp-3.12.14.tar.gz", hash = "sha256:6e06e120e34d93100de448fd941522e11dafa78ef1a893c179901b7d66aa29f2", size = 7822921, upload-time = "2025-07-10T13:05:33.968Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/11/0f/db19abdf2d86aa1deec3c1e0e5ea46a587b97c07a16516b6438428b3a3f8/aiohttp-3.12.13-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d4a18e61f271127465bdb0e8ff36e8f02ac4a32a80d8927aa52371e93cd87938", size = 694910, upload-time = "2025-06-14T15:14:30.604Z" },
{ url = "https://files.pythonhosted.org/packages/d5/81/0ab551e1b5d7f1339e2d6eb482456ccbe9025605b28eed2b1c0203aaaade/aiohttp-3.12.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:532542cb48691179455fab429cdb0d558b5e5290b033b87478f2aa6af5d20ace", size = 472566, upload-time = "2025-06-14T15:14:32.275Z" },
{ url = "https://files.pythonhosted.org/packages/34/3f/6b7d336663337672d29b1f82d1f252ec1a040fe2d548f709d3f90fa2218a/aiohttp-3.12.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d7eea18b52f23c050ae9db5d01f3d264ab08f09e7356d6f68e3f3ac2de9dfabb", size = 464856, upload-time = "2025-06-14T15:14:34.132Z" },
{ url = "https://files.pythonhosted.org/packages/26/7f/32ca0f170496aa2ab9b812630fac0c2372c531b797e1deb3deb4cea904bd/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad7c8e5c25f2a26842a7c239de3f7b6bfb92304593ef997c04ac49fb703ff4d7", size = 1703683, upload-time = "2025-06-14T15:14:36.034Z" },
{ url = "https://files.pythonhosted.org/packages/ec/53/d5513624b33a811c0abea8461e30a732294112318276ce3dbf047dbd9d8b/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6af355b483e3fe9d7336d84539fef460120c2f6e50e06c658fe2907c69262d6b", size = 1684946, upload-time = "2025-06-14T15:14:38Z" },
{ url = "https://files.pythonhosted.org/packages/37/72/4c237dd127827b0247dc138d3ebd49c2ded6114c6991bbe969058575f25f/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a95cf9f097498f35c88e3609f55bb47b28a5ef67f6888f4390b3d73e2bac6177", size = 1737017, upload-time = "2025-06-14T15:14:39.951Z" },
{ url = "https://files.pythonhosted.org/packages/0d/67/8a7eb3afa01e9d0acc26e1ef847c1a9111f8b42b82955fcd9faeb84edeb4/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8ed8c38a1c584fe99a475a8f60eefc0b682ea413a84c6ce769bb19a7ff1c5ef", size = 1786390, upload-time = "2025-06-14T15:14:42.151Z" },
{ url = "https://files.pythonhosted.org/packages/48/19/0377df97dd0176ad23cd8cad4fd4232cfeadcec6c1b7f036315305c98e3f/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0b9170d5d800126b5bc89d3053a2363406d6e327afb6afaeda2d19ee8bb103", size = 1708719, upload-time = "2025-06-14T15:14:44.039Z" },
{ url = "https://files.pythonhosted.org/packages/61/97/ade1982a5c642b45f3622255173e40c3eed289c169f89d00eeac29a89906/aiohttp-3.12.13-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:372feeace612ef8eb41f05ae014a92121a512bd5067db8f25101dd88a8db11da", size = 1622424, upload-time = "2025-06-14T15:14:45.945Z" },
{ url = "https://files.pythonhosted.org/packages/99/ab/00ad3eea004e1d07ccc406e44cfe2b8da5acb72f8c66aeeb11a096798868/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a946d3702f7965d81f7af7ea8fb03bb33fe53d311df48a46eeca17e9e0beed2d", size = 1675447, upload-time = "2025-06-14T15:14:47.911Z" },
{ url = "https://files.pythonhosted.org/packages/3f/fe/74e5ce8b2ccaba445fe0087abc201bfd7259431d92ae608f684fcac5d143/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a0c4725fae86555bbb1d4082129e21de7264f4ab14baf735278c974785cd2041", size = 1707110, upload-time = "2025-06-14T15:14:50.334Z" },
{ url = "https://files.pythonhosted.org/packages/ef/c4/39af17807f694f7a267bd8ab1fbacf16ad66740862192a6c8abac2bff813/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b28ea2f708234f0a5c44eb6c7d9eb63a148ce3252ba0140d050b091b6e842d1", size = 1649706, upload-time = "2025-06-14T15:14:52.378Z" },
{ url = "https://files.pythonhosted.org/packages/38/e8/f5a0a5f44f19f171d8477059aa5f28a158d7d57fe1a46c553e231f698435/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d4f5becd2a5791829f79608c6f3dc745388162376f310eb9c142c985f9441cc1", size = 1725839, upload-time = "2025-06-14T15:14:54.617Z" },
{ url = "https://files.pythonhosted.org/packages/fd/ac/81acc594c7f529ef4419d3866913f628cd4fa9cab17f7bf410a5c3c04c53/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:60f2ce6b944e97649051d5f5cc0f439360690b73909230e107fd45a359d3e911", size = 1759311, upload-time = "2025-06-14T15:14:56.597Z" },
{ url = "https://files.pythonhosted.org/packages/38/0d/aabe636bd25c6ab7b18825e5a97d40024da75152bec39aa6ac8b7a677630/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69fc1909857401b67bf599c793f2183fbc4804717388b0b888f27f9929aa41f3", size = 1708202, upload-time = "2025-06-14T15:14:58.598Z" },
{ url = "https://files.pythonhosted.org/packages/1f/ab/561ef2d8a223261683fb95a6283ad0d36cb66c87503f3a7dde7afe208bb2/aiohttp-3.12.13-cp313-cp313-win32.whl", hash = "sha256:7d7e68787a2046b0e44ba5587aa723ce05d711e3a3665b6b7545328ac8e3c0dd", size = 420794, upload-time = "2025-06-14T15:15:00.939Z" },
{ url = "https://files.pythonhosted.org/packages/9d/47/b11d0089875a23bff0abd3edb5516bcd454db3fefab8604f5e4b07bd6210/aiohttp-3.12.13-cp313-cp313-win_amd64.whl", hash = "sha256:5a178390ca90419bfd41419a809688c368e63c86bd725e1186dd97f6b89c2706", size = 446735, upload-time = "2025-06-14T15:15:02.858Z" },
{ url = "https://files.pythonhosted.org/packages/06/48/e0d2fa8ac778008071e7b79b93ab31ef14ab88804d7ba71b5c964a7c844e/aiohttp-3.12.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3143a7893d94dc82bc409f7308bc10d60285a3cd831a68faf1aa0836c5c3c767", size = 695471, upload-time = "2025-07-10T13:04:20.124Z" },
{ url = "https://files.pythonhosted.org/packages/8d/e7/f73206afa33100804f790b71092888f47df65fd9a4cd0e6800d7c6826441/aiohttp-3.12.14-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3d62ac3d506cef54b355bd34c2a7c230eb693880001dfcda0bf88b38f5d7af7e", size = 473128, upload-time = "2025-07-10T13:04:21.928Z" },
{ url = "https://files.pythonhosted.org/packages/df/e2/4dd00180be551a6e7ee979c20fc7c32727f4889ee3fd5b0586e0d47f30e1/aiohttp-3.12.14-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48e43e075c6a438937c4de48ec30fa8ad8e6dfef122a038847456bfe7b947b63", size = 465426, upload-time = "2025-07-10T13:04:24.071Z" },
{ url = "https://files.pythonhosted.org/packages/de/dd/525ed198a0bb674a323e93e4d928443a680860802c44fa7922d39436b48b/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:077b4488411a9724cecc436cbc8c133e0d61e694995b8de51aaf351c7578949d", size = 1704252, upload-time = "2025-07-10T13:04:26.049Z" },
{ url = "https://files.pythonhosted.org/packages/d8/b1/01e542aed560a968f692ab4fc4323286e8bc4daae83348cd63588e4f33e3/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d8c35632575653f297dcbc9546305b2c1133391089ab925a6a3706dfa775ccab", size = 1685514, upload-time = "2025-07-10T13:04:28.186Z" },
{ url = "https://files.pythonhosted.org/packages/b3/06/93669694dc5fdabdc01338791e70452d60ce21ea0946a878715688d5a191/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b8ce87963f0035c6834b28f061df90cf525ff7c9b6283a8ac23acee6502afd4", size = 1737586, upload-time = "2025-07-10T13:04:30.195Z" },
{ url = "https://files.pythonhosted.org/packages/a5/3a/18991048ffc1407ca51efb49ba8bcc1645961f97f563a6c480cdf0286310/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0a2cf66e32a2563bb0766eb24eae7e9a269ac0dc48db0aae90b575dc9583026", size = 1786958, upload-time = "2025-07-10T13:04:32.482Z" },
{ url = "https://files.pythonhosted.org/packages/30/a8/81e237f89a32029f9b4a805af6dffc378f8459c7b9942712c809ff9e76e5/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdea089caf6d5cde975084a884c72d901e36ef9c2fd972c9f51efbbc64e96fbd", size = 1709287, upload-time = "2025-07-10T13:04:34.493Z" },
{ url = "https://files.pythonhosted.org/packages/8c/e3/bd67a11b0fe7fc12c6030473afd9e44223d456f500f7cf526dbaa259ae46/aiohttp-3.12.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a7865f27db67d49e81d463da64a59365ebd6b826e0e4847aa111056dcb9dc88", size = 1622990, upload-time = "2025-07-10T13:04:36.433Z" },
{ url = "https://files.pythonhosted.org/packages/83/ba/e0cc8e0f0d9ce0904e3cf2d6fa41904e379e718a013c721b781d53dcbcca/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0ab5b38a6a39781d77713ad930cb5e7feea6f253de656a5f9f281a8f5931b086", size = 1676015, upload-time = "2025-07-10T13:04:38.958Z" },
{ url = "https://files.pythonhosted.org/packages/d8/b3/1e6c960520bda094c48b56de29a3d978254637ace7168dd97ddc273d0d6c/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b3b15acee5c17e8848d90a4ebc27853f37077ba6aec4d8cb4dbbea56d156933", size = 1707678, upload-time = "2025-07-10T13:04:41.275Z" },
{ url = "https://files.pythonhosted.org/packages/0a/19/929a3eb8c35b7f9f076a462eaa9830b32c7f27d3395397665caa5e975614/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e4c972b0bdaac167c1e53e16a16101b17c6d0ed7eac178e653a07b9f7fad7151", size = 1650274, upload-time = "2025-07-10T13:04:43.483Z" },
{ url = "https://files.pythonhosted.org/packages/22/e5/81682a6f20dd1b18ce3d747de8eba11cbef9b270f567426ff7880b096b48/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7442488b0039257a3bdbc55f7209587911f143fca11df9869578db6c26feeeb8", size = 1726408, upload-time = "2025-07-10T13:04:45.577Z" },
{ url = "https://files.pythonhosted.org/packages/8c/17/884938dffaa4048302985483f77dfce5ac18339aad9b04ad4aaa5e32b028/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f68d3067eecb64c5e9bab4a26aa11bd676f4c70eea9ef6536b0a4e490639add3", size = 1759879, upload-time = "2025-07-10T13:04:47.663Z" },
{ url = "https://files.pythonhosted.org/packages/95/78/53b081980f50b5cf874359bde707a6eacd6c4be3f5f5c93937e48c9d0025/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f88d3704c8b3d598a08ad17d06006cb1ca52a1182291f04979e305c8be6c9758", size = 1708770, upload-time = "2025-07-10T13:04:49.944Z" },
{ url = "https://files.pythonhosted.org/packages/ed/91/228eeddb008ecbe3ffa6c77b440597fdf640307162f0c6488e72c5a2d112/aiohttp-3.12.14-cp313-cp313-win32.whl", hash = "sha256:a3c99ab19c7bf375c4ae3debd91ca5d394b98b6089a03231d4c580ef3c2ae4c5", size = 421688, upload-time = "2025-07-10T13:04:51.993Z" },
{ url = "https://files.pythonhosted.org/packages/66/5f/8427618903343402fdafe2850738f735fd1d9409d2a8f9bcaae5e630d3ba/aiohttp-3.12.14-cp313-cp313-win_amd64.whl", hash = "sha256:3f8aad695e12edc9d571f878c62bedc91adf30c760c8632f09663e5f564f4baa", size = 448098, upload-time = "2025-07-10T13:04:53.999Z" },
]
[[package]]
@@ -138,11 +138,11 @@ wheels = [
[[package]]
name = "asgiref"
version = "3.9.0"
version = "3.9.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6a/68/fb4fb78c9eac59d5e819108a57664737f855c5a8e9b76aec1738bb137f9e/asgiref-3.9.0.tar.gz", hash = "sha256:3dd2556d0f08c4fab8a010d9ab05ef8c34565f6bf32381d17505f7ca5b273767", size = 36772, upload-time = "2025-07-03T13:25:01.491Z" }
sdist = { url = "https://files.pythonhosted.org/packages/90/61/0aa957eec22ff70b830b22ff91f825e70e1ef732c06666a805730f28b36b/asgiref-3.9.1.tar.gz", hash = "sha256:a5ab6582236218e5ef1648f242fd9f10626cfd4de8dc377db215d5d5098e3142", size = 36870, upload-time = "2025-07-08T09:07:43.344Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3d/f9/76c9f4d4985b5a642926162e2d41fe6019b1fa929cfa58abb7d2dc9041e5/asgiref-3.9.0-py3-none-any.whl", hash = "sha256:06a41250a0114d2b6f6a2cb3ab962147d355b53d1de15eebc34a9d04a7b79981", size = 23788, upload-time = "2025-07-03T13:24:59.115Z" },
{ url = "https://files.pythonhosted.org/packages/7c/3c/0464dcada90d5da0e71018c04a140ad6349558afb30b3051b4264cc5b965/asgiref-3.9.1-py3-none-any.whl", hash = "sha256:f3bba7092a48005b5f5bacd747d36ee4a5a61f4a269a6df590b43144355ebd2c", size = 23790, upload-time = "2025-07-08T09:07:41.548Z" },
]
[[package]]
@@ -211,6 +211,7 @@ dependencies = [
{ name = "opencontainers" },
{ name = "packaging" },
{ name = "paramiko" },
{ name = "psutil" },
{ name = "psycopg", extra = ["c", "pool"] },
{ name = "pydantic" },
{ name = "pydantic-scim" },
@@ -306,10 +307,11 @@ requires-dist = [
{ name = "kubernetes", specifier = "==33.1.0" },
{ name = "ldap3", specifier = "==2.9.1" },
{ name = "lxml", specifier = "==6.0.0" },
{ name = "msgraph-sdk", specifier = "==1.37.0" },
{ name = "msgraph-sdk", specifier = "==1.38.0" },
{ name = "opencontainers", git = "https://github.com/vsoch/oci-python?rev=ceb4fcc090851717a3069d78e85ceb1e86c2740c" },
{ name = "packaging", specifier = "==25.0" },
{ name = "paramiko", specifier = "==3.5.1" },
{ name = "psutil", specifier = "==7.0.0" },
{ name = "psycopg", extras = ["c", "pool"], specifier = "==3.2.9" },
{ name = "pydantic", specifier = "==2.11.7" },
{ name = "pydantic-scim", specifier = "==0.0.8" },
@@ -319,13 +321,13 @@ requires-dist = [
{ name = "pyyaml", specifier = "==6.0.2" },
{ name = "requests-oauthlib", specifier = "==2.0.0" },
{ name = "scim2-filter-parser", specifier = "==0.7.0" },
{ name = "sentry-sdk", specifier = "==2.32.0" },
{ name = "sentry-sdk", specifier = "==2.33.0" },
{ name = "service-identity", specifier = "==24.2.0" },
{ name = "setproctitle", specifier = "==1.3.6" },
{ name = "structlog", specifier = "==25.4.0" },
{ name = "swagger-spec-validator", specifier = "==3.0.4" },
{ name = "tenant-schemas-celery", specifier = "==3.0.0" },
{ name = "twilio", specifier = "==9.6.4" },
{ name = "twilio", specifier = "==9.6.5" },
{ name = "ua-parser", specifier = "==1.0.1" },
{ name = "unidecode", specifier = "==1.4.0" },
{ name = "urllib3", specifier = "<3" },
@@ -333,7 +335,7 @@ requires-dist = [
{ name = "watchdog", specifier = "==6.0.0" },
{ name = "webauthn", specifier = "==2.6.0" },
{ name = "wsproto", specifier = "==1.2.0" },
{ name = "xmlsec", specifier = "==1.3.15" },
{ name = "xmlsec", specifier = "==1.3.16" },
{ name = "zxcvbn", specifier = "==4.5.0" },
]
@@ -464,7 +466,7 @@ wheels = [
[[package]]
name = "azure-identity"
version = "1.23.0"
version = "1.23.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "azure-core" },
@@ -473,9 +475,9 @@ dependencies = [
{ name = "msal-extensions" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/41/52/458c1be17a5d3796570ae2ed3c6b7b55b134b22d5ef8132b4f97046a9051/azure_identity-1.23.0.tar.gz", hash = "sha256:d9cdcad39adb49d4bb2953a217f62aec1f65bbb3c63c9076da2be2a47e53dde4", size = 265280, upload-time = "2025-05-14T00:18:30.408Z" }
sdist = { url = "https://files.pythonhosted.org/packages/b5/29/1201ffbb6a57a16524dd91f3e741b4c828a70aaba436578bdcb3fbcb438c/azure_identity-1.23.1.tar.gz", hash = "sha256:226c1ef982a9f8d5dcf6e0f9ed35eaef2a4d971e7dd86317e9b9d52e70a035e4", size = 266185, upload-time = "2025-07-15T19:16:38.077Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/07/16/a51d47780f41e4b87bb2d454df6aea90a44a346e918ac189d3700f3d728d/azure_identity-1.23.0-py3-none-any.whl", hash = "sha256:dbbeb64b8e5eaa81c44c565f264b519ff2de7ff0e02271c49f3cb492762a50b0", size = 186097, upload-time = "2025-05-14T00:18:32.734Z" },
{ url = "https://files.pythonhosted.org/packages/99/b3/e2d7ab810eb68575a5c7569b03c0228b8f4ce927ffa6211471b526f270c9/azure_identity-1.23.1-py3-none-any.whl", hash = "sha256:7eed28baa0097a47e3fb53bd35a63b769e6b085bb3cb616dfce2b67f28a004a1", size = 186810, upload-time = "2025-07-15T19:16:40.184Z" },
]
[[package]]
@@ -574,30 +576,30 @@ wheels = [
[[package]]
name = "boto3"
version = "1.39.3"
version = "1.39.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "botocore" },
{ name = "jmespath" },
{ name = "s3transfer" },
]
sdist = { url = "https://files.pythonhosted.org/packages/02/42/712a74bb86d06538c55067a35b8a82c57aa303eba95b2b1ee91c829288f4/boto3-1.39.3.tar.gz", hash = "sha256:0a367106497649ae3d8a7b571b8c3be01b7b935a0fe303d4cc2574ed03aecbb4", size = 111838, upload-time = "2025-07-03T19:26:00.988Z" }
sdist = { url = "https://files.pythonhosted.org/packages/9c/43/81ae62386917fa163f1d3bc59e77da56924f9824b5100fd2807e74a675fc/boto3-1.39.7.tar.gz", hash = "sha256:28daeb005e3381808e0e12995056ee8951056ddd43506c07482a15b40ae785b0", size = 111820, upload-time = "2025-07-16T16:29:52.858Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/15/70/723d2ab259aeaed6c96e5c1857ebe7d474ed9aa8f487dea352c60f33798f/boto3-1.39.3-py3-none-any.whl", hash = "sha256:056cfa2440fe1a157a7c2be897c749c83e1a322144aa4dad889f2fca66571019", size = 139906, upload-time = "2025-07-03T19:25:58.803Z" },
{ url = "https://files.pythonhosted.org/packages/cc/60/1d2d708f5bc125fe6fe262ea767c82020ea6deeda113e989cff5ab89a9f9/boto3-1.39.7-py3-none-any.whl", hash = "sha256:c8c0e11fff7bb85f903b860b6bfd4f509a4d749decf38bb6a409ffe5d6eb0c91", size = 139882, upload-time = "2025-07-16T16:29:51.176Z" },
]
[[package]]
name = "botocore"
version = "1.39.3"
version = "1.39.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jmespath" },
{ name = "python-dateutil" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/60/66/96e89cc261d75f0b8125436272c335c74d2a39df84504a0c3956adcd1301/botocore-1.39.3.tar.gz", hash = "sha256:da8f477e119f9f8a3aaa8b3c99d9c6856ed0a243680aa3a3fbbfc15a8d4093fb", size = 14132316, upload-time = "2025-07-03T19:25:49.502Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e7/9d/73f300c841a3c47d2baf4bf6ecb9d6de476edf91de8c3c26ceed5044e666/botocore-1.39.7.tar.gz", hash = "sha256:431e342ef97ecb387cea9df1ae8c4e0edc1b0c9c50d2e121cca77699f24f8dc1", size = 14201331, upload-time = "2025-07-16T16:29:43.011Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/53/e4/3698dbb037a44d82a501577c6e3824c19f4289f4afbcadb06793866250d8/botocore-1.39.3-py3-none-any.whl", hash = "sha256:66a81cfac18ad5e9f47696c73fdf44cdbd8f8ca51ab3fca1effca0aabf61f02f", size = 13791724, upload-time = "2025-07-03T19:25:44.026Z" },
{ url = "https://files.pythonhosted.org/packages/30/20/ab70de7441cbc4b8ffc5d6d9a8e02e4c703cc07accb7441ce54020e20950/botocore-1.39.7-py3-none-any.whl", hash = "sha256:1d11ba9f3cb46856bb541ed010db160093201a224d21ef854249513ae3af7e77", size = 13864168, upload-time = "2025-07-16T16:29:37.114Z" },
]
[[package]]
@@ -667,11 +669,11 @@ wheels = [
[[package]]
name = "certifi"
version = "2025.6.15"
version = "2025.7.14"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753, upload-time = "2025-06-15T02:45:51.329Z" }
sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650, upload-time = "2025-06-15T02:45:49.977Z" },
{ url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" },
]
[[package]]
@@ -1933,7 +1935,7 @@ wheels = [
[[package]]
name = "microsoft-kiota-authentication-azure"
version = "1.9.3"
version = "1.9.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@@ -1942,14 +1944,14 @@ dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-sdk" },
]
sdist = { url = "https://files.pythonhosted.org/packages/69/cf/d51a8274de4a2113f6acf920b3ec9260368f04da52bdc26b1e26f1e997cd/microsoft_kiota_authentication_azure-1.9.3.tar.gz", hash = "sha256:fcab4d81e58f636aca147f589637ceb17da8bc85d5d22f890b0244cc74a6009d", size = 4985, upload-time = "2025-03-24T16:34:08.271Z" }
sdist = { url = "https://files.pythonhosted.org/packages/53/70/02105d0f41d5742084f31226fa77e17072f5551161921b9f4a21a20c6270/microsoft_kiota_authentication_azure-1.9.4.tar.gz", hash = "sha256:a37174072265e17ea05020bf411f4e8694e5335a3aab9bb48926eb1f6ffd263d", size = 4986, upload-time = "2025-06-27T16:04:40.027Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/38/89420a0f5417a1bf5603de2e74a8981da79fcf1da456c0be9f7cff9c2864/microsoft_kiota_authentication_azure-1.9.3-py3-none-any.whl", hash = "sha256:bccfd312b70fd2b5222a06125022ebf6178d23333b6a594397c8d852a4efd7eb", size = 6905, upload-time = "2025-03-24T16:34:07.094Z" },
{ url = "https://files.pythonhosted.org/packages/5b/26/c2fc24eb5ab5e71dd999ec0c653d23d4d2f2cb2af94ad45d488c044b18d9/microsoft_kiota_authentication_azure-1.9.4-py3-none-any.whl", hash = "sha256:ed2f1bb3bbe165141d146f893e790bba8ded09e1001166e74e8b86d7e798ed46", size = 6908, upload-time = "2025-06-27T16:04:38.991Z" },
]
[[package]]
name = "microsoft-kiota-http"
version = "1.9.3"
version = "1.9.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx", extra = ["http2"] },
@@ -1957,9 +1959,9 @@ dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-sdk" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d0/73/bc8a49643e2cea65fd12cbe61e52be8f142b69e2be143c67219f52c35a08/microsoft_kiota_http-1.9.3.tar.gz", hash = "sha256:e4cda140a362cc304c6ca7495f88b48ae9a4b7d98fa01f5b278e52f3b502ea76", size = 21199, upload-time = "2025-03-24T16:34:16.237Z" }
sdist = { url = "https://files.pythonhosted.org/packages/74/a6/0746c11311ec6e768965bf100b89d768bce46d4b04e62a3d6cc9d57d58ea/microsoft_kiota_http-1.9.4.tar.gz", hash = "sha256:31e1d1d3686de66da2e4f89ce5c5bf58bc0041da1375798a07c95f785608a869", size = 21236, upload-time = "2025-06-27T16:04:47.758Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cf/f4/ef67b7e6dea7d77930df41f4a5926bbba2e362c28a46615908f2d00cf146/microsoft_kiota_http-1.9.3-py3-none-any.whl", hash = "sha256:9a5e2b7a96524b2978e01087f415d810d6bcad7cd866d6d60595ebab2e47c56c", size = 31508, upload-time = "2025-03-24T16:34:15.273Z" },
{ url = "https://files.pythonhosted.org/packages/78/93/ef990609c7b3ba3dbecbe294dbdf1eb4e26005835c4c75e5cb2f9c442581/microsoft_kiota_http-1.9.4-py3-none-any.whl", hash = "sha256:66636be391551eb2fe947f842e4ac76832eda9822b29bf33a8aed527a9511834", size = 31548, upload-time = "2025-06-27T16:04:46.962Z" },
]
[[package]]
@@ -2071,7 +2073,7 @@ wheels = [
[[package]]
name = "msgraph-sdk"
version = "1.37.0"
version = "1.38.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "azure-identity" },
@@ -2081,9 +2083,9 @@ dependencies = [
{ name = "microsoft-kiota-serialization-text" },
{ name = "msgraph-core" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a5/97/c3da48fa964afa07f5dd644d0fca57dbf6bb6a254c6741e10e1fb1552195/msgraph_sdk-1.37.0.tar.gz", hash = "sha256:92ccafe9f3d10f92e3d24794ff77924e4cc4f03202278595d3221f8cf1220d2f", size = 6001375, upload-time = "2025-07-08T12:33:35.195Z" }
sdist = { url = "https://files.pythonhosted.org/packages/10/4a/1231a710be05849b8f2c2ce484dcc5a84066bbbc01e8e2c0e7b812a8909c/msgraph_sdk-1.38.0.tar.gz", hash = "sha256:4bb5b30515e64de1e507641f923a348ce83bacb52e2703bb941c144a319c4ca7", size = 6085852, upload-time = "2025-07-17T01:14:36.377Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/77/22/8985a3fee5ed411f12775541898b2a9d470afd230419b709b5318da84556/msgraph_sdk-1.37.0-py3-none-any.whl", hash = "sha256:e51580cb12b78984280cf3094892e254f34405c9bd6450931b0baa91d7dad8f6", size = 24606234, upload-time = "2025-07-08T12:33:31.986Z" },
{ url = "https://files.pythonhosted.org/packages/c0/79/a97b43015afd7112b6f6b0ec1cc65fd06d4f0a9f6737427ba6faf1d5aed4/msgraph_sdk-1.38.0-py3-none-any.whl", hash = "sha256:a6c4725085323152581ed9c9b3433b3f4b6a2e3365a2674354c78a72da8bce9a", size = 24989016, upload-time = "2025-07-17T01:14:32.033Z" },
]
[[package]]
@@ -2165,65 +2167,65 @@ source = { git = "https://github.com/vsoch/oci-python?rev=ceb4fcc090851717a3069d
[[package]]
name = "opentelemetry-api"
version = "1.34.1"
version = "1.35.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "importlib-metadata" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/4d/5e/94a8cb759e4e409022229418294e098ca7feca00eb3c467bb20cbd329bda/opentelemetry_api-1.34.1.tar.gz", hash = "sha256:64f0bd06d42824843731d05beea88d4d4b6ae59f9fe347ff7dfa2cc14233bbb3", size = 64987, upload-time = "2025-06-10T08:55:19.818Z" }
sdist = { url = "https://files.pythonhosted.org/packages/99/c9/4509bfca6bb43220ce7f863c9f791e0d5001c2ec2b5867d48586008b3d96/opentelemetry_api-1.35.0.tar.gz", hash = "sha256:a111b959bcfa5b4d7dffc2fbd6a241aa72dd78dd8e79b5b1662bda896c5d2ffe", size = 64778, upload-time = "2025-07-11T12:23:28.804Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a5/3a/2ba85557e8dc024c0842ad22c570418dc02c36cbd1ab4b832a93edf071b8/opentelemetry_api-1.34.1-py3-none-any.whl", hash = "sha256:b7df4cb0830d5a6c29ad0c0691dbae874d8daefa934b8b1d642de48323d32a8c", size = 65767, upload-time = "2025-06-10T08:54:56.717Z" },
{ url = "https://files.pythonhosted.org/packages/1d/5a/3f8d078dbf55d18442f6a2ecedf6786d81d7245844b2b20ce2b8ad6f0307/opentelemetry_api-1.35.0-py3-none-any.whl", hash = "sha256:c4ea7e258a244858daf18474625e9cc0149b8ee354f37843415771a40c25ee06", size = 65566, upload-time = "2025-07-11T12:23:07.944Z" },
]
[[package]]
name = "opentelemetry-sdk"
version = "1.34.1"
version = "1.35.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-semantic-conventions" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6f/41/fe20f9036433da8e0fcef568984da4c1d1c771fa072ecd1a4d98779dccdd/opentelemetry_sdk-1.34.1.tar.gz", hash = "sha256:8091db0d763fcd6098d4781bbc80ff0971f94e260739aa6afe6fd379cdf3aa4d", size = 159441, upload-time = "2025-06-10T08:55:33.028Z" }
sdist = { url = "https://files.pythonhosted.org/packages/9a/cf/1eb2ed2ce55e0a9aa95b3007f26f55c7943aeef0a783bb006bdd92b3299e/opentelemetry_sdk-1.35.0.tar.gz", hash = "sha256:2a400b415ab68aaa6f04e8a6a9f6552908fb3090ae2ff78d6ae0c597ac581954", size = 160871, upload-time = "2025-07-11T12:23:39.566Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/07/1b/def4fe6aa73f483cabf4c748f4c25070d5f7604dcc8b52e962983491b29e/opentelemetry_sdk-1.34.1-py3-none-any.whl", hash = "sha256:308effad4059562f1d92163c61c8141df649da24ce361827812c40abb2a1e96e", size = 118477, upload-time = "2025-06-10T08:55:16.02Z" },
{ url = "https://files.pythonhosted.org/packages/01/4f/8e32b757ef3b660511b638ab52d1ed9259b666bdeeceba51a082ce3aea95/opentelemetry_sdk-1.35.0-py3-none-any.whl", hash = "sha256:223d9e5f5678518f4842311bb73966e0b6db5d1e0b74e35074c052cd2487f800", size = 119379, upload-time = "2025-07-11T12:23:24.521Z" },
]
[[package]]
name = "opentelemetry-semantic-conventions"
version = "0.55b1"
version = "0.56b0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5d/f0/f33458486da911f47c4aa6db9bda308bb80f3236c111bf848bd870c16b16/opentelemetry_semantic_conventions-0.55b1.tar.gz", hash = "sha256:ef95b1f009159c28d7a7849f5cbc71c4c34c845bb514d66adfdf1b3fff3598b3", size = 119829, upload-time = "2025-06-10T08:55:33.881Z" }
sdist = { url = "https://files.pythonhosted.org/packages/32/8e/214fa817f63b9f068519463d8ab46afd5d03b98930c39394a37ae3e741d0/opentelemetry_semantic_conventions-0.56b0.tar.gz", hash = "sha256:c114c2eacc8ff6d3908cb328c811eaf64e6d68623840be9224dc829c4fd6c2ea", size = 124221, upload-time = "2025-07-11T12:23:40.71Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1a/89/267b0af1b1d0ba828f0e60642b6a5116ac1fd917cde7fc02821627029bd1/opentelemetry_semantic_conventions-0.55b1-py3-none-any.whl", hash = "sha256:5da81dfdf7d52e3d37f8fe88d5e771e191de924cfff5f550ab0b8f7b2409baed", size = 196223, upload-time = "2025-06-10T08:55:17.638Z" },
{ url = "https://files.pythonhosted.org/packages/c7/3f/e80c1b017066a9d999efffe88d1cce66116dcf5cb7f80c41040a83b6e03b/opentelemetry_semantic_conventions-0.56b0-py3-none-any.whl", hash = "sha256:df44492868fd6b482511cc43a942e7194be64e94945f572db24df2e279a001a2", size = 201625, upload-time = "2025-07-11T12:23:25.63Z" },
]
[[package]]
name = "orjson"
version = "3.10.18"
version = "3.11.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/81/0b/fea456a3ffe74e70ba30e01ec183a9b26bec4d497f61dcfce1b601059c60/orjson-3.10.18.tar.gz", hash = "sha256:e8da3947d92123eda795b68228cafe2724815621fe35e8e320a9e9593a4bcd53", size = 5422810, upload-time = "2025-04-29T23:30:08.423Z" }
sdist = { url = "https://files.pythonhosted.org/packages/29/87/03ababa86d984952304ac8ce9fbd3a317afb4a225b9a81f9b606ac60c873/orjson-3.11.0.tar.gz", hash = "sha256:2e4c129da624f291bcc607016a99e7f04a353f6874f3bd8d9b47b88597d5f700", size = 5318246, upload-time = "2025-07-15T16:08:29.194Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/f0/8aedb6574b68096f3be8f74c0b56d36fd94bcf47e6c7ed47a7bd1474aaa8/orjson-3.10.18-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:69c34b9441b863175cc6a01f2935de994025e773f814412030f269da4f7be147", size = 249087, upload-time = "2025-04-29T23:29:19.083Z" },
{ url = "https://files.pythonhosted.org/packages/bc/f7/7118f965541aeac6844fcb18d6988e111ac0d349c9b80cda53583e758908/orjson-3.10.18-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:1ebeda919725f9dbdb269f59bc94f861afbe2a27dce5608cdba2d92772364d1c", size = 133273, upload-time = "2025-04-29T23:29:20.602Z" },
{ url = "https://files.pythonhosted.org/packages/fb/d9/839637cc06eaf528dd8127b36004247bf56e064501f68df9ee6fd56a88ee/orjson-3.10.18-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5adf5f4eed520a4959d29ea80192fa626ab9a20b2ea13f8f6dc58644f6927103", size = 136779, upload-time = "2025-04-29T23:29:22.062Z" },
{ url = "https://files.pythonhosted.org/packages/2b/6d/f226ecfef31a1f0e7d6bf9a31a0bbaf384c7cbe3fce49cc9c2acc51f902a/orjson-3.10.18-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7592bb48a214e18cd670974f289520f12b7aed1fa0b2e2616b8ed9e069e08595", size = 132811, upload-time = "2025-04-29T23:29:23.602Z" },
{ url = "https://files.pythonhosted.org/packages/73/2d/371513d04143c85b681cf8f3bce743656eb5b640cb1f461dad750ac4b4d4/orjson-3.10.18-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f872bef9f042734110642b7a11937440797ace8c87527de25e0c53558b579ccc", size = 137018, upload-time = "2025-04-29T23:29:25.094Z" },
{ url = "https://files.pythonhosted.org/packages/69/cb/a4d37a30507b7a59bdc484e4a3253c8141bf756d4e13fcc1da760a0b00cb/orjson-3.10.18-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0315317601149c244cb3ecef246ef5861a64824ccbcb8018d32c66a60a84ffbc", size = 138368, upload-time = "2025-04-29T23:29:26.609Z" },
{ url = "https://files.pythonhosted.org/packages/1e/ae/cd10883c48d912d216d541eb3db8b2433415fde67f620afe6f311f5cd2ca/orjson-3.10.18-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0da26957e77e9e55a6c2ce2e7182a36a6f6b180ab7189315cb0995ec362e049", size = 142840, upload-time = "2025-04-29T23:29:28.153Z" },
{ url = "https://files.pythonhosted.org/packages/6d/4c/2bda09855c6b5f2c055034c9eda1529967b042ff8d81a05005115c4e6772/orjson-3.10.18-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb70d489bc79b7519e5803e2cc4c72343c9dc1154258adf2f8925d0b60da7c58", size = 133135, upload-time = "2025-04-29T23:29:29.726Z" },
{ url = "https://files.pythonhosted.org/packages/13/4a/35971fd809a8896731930a80dfff0b8ff48eeb5d8b57bb4d0d525160017f/orjson-3.10.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e9e86a6af31b92299b00736c89caf63816f70a4001e750bda179e15564d7a034", size = 134810, upload-time = "2025-04-29T23:29:31.269Z" },
{ url = "https://files.pythonhosted.org/packages/99/70/0fa9e6310cda98365629182486ff37a1c6578e34c33992df271a476ea1cd/orjson-3.10.18-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:c382a5c0b5931a5fc5405053d36c1ce3fd561694738626c77ae0b1dfc0242ca1", size = 413491, upload-time = "2025-04-29T23:29:33.315Z" },
{ url = "https://files.pythonhosted.org/packages/32/cb/990a0e88498babddb74fb97855ae4fbd22a82960e9b06eab5775cac435da/orjson-3.10.18-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8e4b2ae732431127171b875cb2668f883e1234711d3c147ffd69fe5be51a8012", size = 153277, upload-time = "2025-04-29T23:29:34.946Z" },
{ url = "https://files.pythonhosted.org/packages/92/44/473248c3305bf782a384ed50dd8bc2d3cde1543d107138fd99b707480ca1/orjson-3.10.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d808e34ddb24fc29a4d4041dcfafbae13e129c93509b847b14432717d94b44f", size = 137367, upload-time = "2025-04-29T23:29:36.52Z" },
{ url = "https://files.pythonhosted.org/packages/ad/fd/7f1d3edd4ffcd944a6a40e9f88af2197b619c931ac4d3cfba4798d4d3815/orjson-3.10.18-cp313-cp313-win32.whl", hash = "sha256:ad8eacbb5d904d5591f27dee4031e2c1db43d559edb8f91778efd642d70e6bea", size = 142687, upload-time = "2025-04-29T23:29:38.292Z" },
{ url = "https://files.pythonhosted.org/packages/4b/03/c75c6ad46be41c16f4cfe0352a2d1450546f3c09ad2c9d341110cd87b025/orjson-3.10.18-cp313-cp313-win_amd64.whl", hash = "sha256:aed411bcb68bf62e85588f2a7e03a6082cc42e5a2796e06e72a962d7c6310b52", size = 134794, upload-time = "2025-04-29T23:29:40.349Z" },
{ url = "https://files.pythonhosted.org/packages/c2/28/f53038a5a72cc4fd0b56c1eafb4ef64aec9685460d5ac34de98ca78b6e29/orjson-3.10.18-cp313-cp313-win_arm64.whl", hash = "sha256:f54c1385a0e6aba2f15a40d703b858bedad36ded0491e55d35d905b2c34a4cc3", size = 131186, upload-time = "2025-04-29T23:29:41.922Z" },
{ url = "https://files.pythonhosted.org/packages/31/63/82d9b6b48624009d230bc6038e54778af8f84dfd54402f9504f477c5cfd5/orjson-3.11.0-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4a8ba9698655e16746fdf5266939427da0f9553305152aeb1a1cc14974a19cfb", size = 240125, upload-time = "2025-07-15T16:07:35.976Z" },
{ url = "https://files.pythonhosted.org/packages/16/3a/d557ed87c63237d4c97a7bac7ac054c347ab8c4b6da09748d162ca287175/orjson-3.11.0-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:67133847f9a35a5ef5acfa3325d4a2f7fe05c11f1505c4117bb086fc06f2a58f", size = 129189, upload-time = "2025-07-15T16:07:37.486Z" },
{ url = "https://files.pythonhosted.org/packages/69/5e/b2c9e22e2cd10aa7d76a629cee65d661e06a61fbaf4dc226386f5636dd44/orjson-3.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f797d57814975b78f5f5423acb003db6f9be5186b72d48bd97a1000e89d331d", size = 131953, upload-time = "2025-07-15T16:07:39.254Z" },
{ url = "https://files.pythonhosted.org/packages/e2/60/760fcd9b50eb44d1206f2b30c8d310b79714553b9d94a02f9ea3252ebe63/orjson-3.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:28acd19822987c5163b9e03a6e60853a52acfee384af2b394d11cb413b889246", size = 126922, upload-time = "2025-07-15T16:07:41.282Z" },
{ url = "https://files.pythonhosted.org/packages/6a/7a/8c46daa867ccc92da6de9567608be62052774b924a77c78382e30d50b579/orjson-3.11.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8d38d9e1e2cf9729658e35956cf01e13e89148beb4cb9e794c9c10c5cb252f8", size = 128787, upload-time = "2025-07-15T16:07:42.681Z" },
{ url = "https://files.pythonhosted.org/packages/f2/14/a2f1b123d85f11a19e8749f7d3f9ed6c9b331c61f7b47cfd3e9a1fedb9bc/orjson-3.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05f094edd2b782650b0761fd78858d9254de1c1286f5af43145b3d08cdacfd51", size = 131895, upload-time = "2025-07-15T16:07:44.519Z" },
{ url = "https://files.pythonhosted.org/packages/c8/10/362e8192df7528e8086ea712c5cb01355c8d4e52c59a804417ba01e2eb2d/orjson-3.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d09176a4a9e04a5394a4a0edd758f645d53d903b306d02f2691b97d5c736a9e", size = 133868, upload-time = "2025-07-15T16:07:46.227Z" },
{ url = "https://files.pythonhosted.org/packages/f8/4e/ef43582ef3e3dfd2a39bc3106fa543364fde1ba58489841120219da6e22f/orjson-3.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a585042104e90a61eda2564d11317b6a304eb4e71cd33e839f5af6be56c34d3", size = 128234, upload-time = "2025-07-15T16:07:48.123Z" },
{ url = "https://files.pythonhosted.org/packages/d7/fa/02dabb2f1d605bee8c4bb1160cfc7467976b1ed359a62cc92e0681b53c45/orjson-3.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d2218629dbfdeeb5c9e0573d59f809d42f9d49ae6464d2f479e667aee14c3ef4", size = 130232, upload-time = "2025-07-15T16:07:50.197Z" },
{ url = "https://files.pythonhosted.org/packages/16/76/951b5619605c8d2ede80cc989f32a66abc954530d86e84030db2250c63a1/orjson-3.11.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:613e54a2b10b51b656305c11235a9c4a5c5491ef5c283f86483d4e9e123ed5e4", size = 403648, upload-time = "2025-07-15T16:07:52.136Z" },
{ url = "https://files.pythonhosted.org/packages/96/e2/5fa53bb411455a63b3713db90b588e6ca5ed2db59ad49b3fb8a0e94e0dda/orjson-3.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9dac7fbf3b8b05965986c5cfae051eb9a30fced7f15f1d13a5adc608436eb486", size = 144572, upload-time = "2025-07-15T16:07:54.004Z" },
{ url = "https://files.pythonhosted.org/packages/ad/d0/7d6f91e1e0f034258c3a3358f20b0c9490070e8a7ab8880085547274c7f9/orjson-3.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93b64b254414e2be55ac5257124b5602c5f0b4d06b80bd27d1165efe8f36e836", size = 132766, upload-time = "2025-07-15T16:07:55.936Z" },
{ url = "https://files.pythonhosted.org/packages/ff/f8/4d46481f1b3fb40dc826d62179f96c808eb470cdcc74b6593fb114d74af3/orjson-3.11.0-cp313-cp313-win32.whl", hash = "sha256:359cbe11bc940c64cb3848cf22000d2aef36aff7bfd09ca2c0b9cb309c387132", size = 134638, upload-time = "2025-07-15T16:07:57.343Z" },
{ url = "https://files.pythonhosted.org/packages/85/3f/544938dcfb7337d85ee1e43d7685cf8f3bfd452e0b15a32fe70cb4ca5094/orjson-3.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:0759b36428067dc777b202dd286fbdd33d7f261c6455c4238ea4e8474358b1e6", size = 129411, upload-time = "2025-07-15T16:07:58.852Z" },
{ url = "https://files.pythonhosted.org/packages/43/0c/f75015669d7817d222df1bb207f402277b77d22c4833950c8c8c7cf2d325/orjson-3.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:51cdca2f36e923126d0734efaf72ddbb5d6da01dbd20eab898bdc50de80d7b5a", size = 126349, upload-time = "2025-07-15T16:08:00.322Z" },
]
[[package]]
@@ -2411,6 +2413,21 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/af/ab3c51ab7507a7325e98ffe691d9495ee3d3aa5f589afad65ec920d39821/protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e", size = 168724, upload-time = "2025-05-28T19:25:53.926Z" },
]
[[package]]
name = "psutil"
version = "7.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" },
{ url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" },
{ url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" },
{ url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" },
{ url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" },
{ url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" },
{ url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" },
]
[[package]]
name = "psycopg"
version = "3.2.9"
@@ -2964,15 +2981,15 @@ wheels = [
[[package]]
name = "sentry-sdk"
version = "2.32.0"
version = "2.33.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/10/59/eb90c45cb836cf8bec973bba10230ddad1c55e2b2e9ffa9d7d7368948358/sentry_sdk-2.32.0.tar.gz", hash = "sha256:9016c75d9316b0f6921ac14c8cd4fb938f26002430ac5be9945ab280f78bec6b", size = 334932, upload-time = "2025-06-27T08:10:02.89Z" }
sdist = { url = "https://files.pythonhosted.org/packages/09/0b/6139f589436c278b33359845ed77019cd093c41371f898283bbc14d26c02/sentry_sdk-2.33.0.tar.gz", hash = "sha256:cdceed05e186846fdf80ceea261fe0a11ebc93aab2f228ed73d076a07804152e", size = 335233, upload-time = "2025-07-15T12:07:42.413Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/01/a1/fc4856bd02d2097324fb7ce05b3021fb850f864b83ca765f6e37e92ff8ca/sentry_sdk-2.32.0-py2.py3-none-any.whl", hash = "sha256:6cf51521b099562d7ce3606da928c473643abe99b00ce4cb5626ea735f4ec345", size = 356122, upload-time = "2025-06-27T08:10:01.424Z" },
{ url = "https://files.pythonhosted.org/packages/93/e5/f24e9f81c9822a24a2627cfcb44c10a3971382e67e5015c6e068421f5787/sentry_sdk-2.33.0-py2.py3-none-any.whl", hash = "sha256:a762d3f19a1c240e16c98796f2a5023f6e58872997d5ae2147ac3ed378b23ec2", size = 356397, upload-time = "2025-07-15T12:07:40.729Z" },
]
[[package]]
@@ -3185,7 +3202,7 @@ wheels = [
[[package]]
name = "twilio"
version = "9.6.4"
version = "9.6.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@@ -3193,9 +3210,9 @@ dependencies = [
{ name = "pyjwt" },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c0/c6/46d00a9a4082f9ecb70da6643369e8b0bee61ffa35026f2df97f311971ce/twilio-9.6.4.tar.gz", hash = "sha256:879a6d2d93a52539660e59c2e49908ab5d51cf1284de7bb097cd129d7d9ad73c", size = 1038231, upload-time = "2025-07-03T09:50:32.018Z" }
sdist = { url = "https://files.pythonhosted.org/packages/7c/5e/0a591781cd360819f61ccb8337efbc5d9931cf286e5b13e5d3ca72735e06/twilio-9.6.5.tar.gz", hash = "sha256:4f246b3d3474090b55dc13034b7cec2f9250877f5298f8419a3fe8a447f36f71", size = 1037726, upload-time = "2025-07-10T11:59:39.64Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bb/02/71683ebf7cb68afc1eb8a191b8d6f85887d884c2a52f833eddd7dd22faaa/twilio-9.6.4-py2.py3-none-any.whl", hash = "sha256:3eca01998bdd2f105d3b46fcbd316d839660e898c704899f26e4182567ffd3d1", size = 1895325, upload-time = "2025-07-03T09:50:30.32Z" },
{ url = "https://files.pythonhosted.org/packages/60/89/b32b7c3afef8635f94d2b9a89ae95adf2f13c942c3af047ccaa6d0c442f3/twilio-9.6.5-py2.py3-none-any.whl", hash = "sha256:e9f547267b59fb474bfc715c042662d9d2b1a1bbcde34017954407aa95482277", size = 1895395, upload-time = "2025-07-10T11:59:37.619Z" },
]
[[package]]
@@ -3492,16 +3509,22 @@ wheels = [
[[package]]
name = "xmlsec"
version = "1.3.15"
version = "1.3.16"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "lxml" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6b/0b/d851367799b865500efd0b255c39fc5d30892ea28c1569ca185a76d19576/xmlsec-1.3.15.tar.gz", hash = "sha256:baa856b83d0012e278e6f6cbec96ac8128de667ca9fa9a2eeb02c752e816f6d8", size = 114117, upload-time = "2025-03-11T22:37:00.567Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ed/52/b025fe78a178d9eaffa1c77a4a73b20072e27d4679200cee76475034b399/xmlsec-1.3.16.tar.gz", hash = "sha256:2b6c70544c6d1d4ca006aaa314958e0ef3514dc81fffde1b23f2ec41a5791f9d", size = 114202, upload-time = "2025-07-10T12:45:37.847Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/13/17/0a272e6087ddb24bec96528acf061341845f458671e2a5cb35ff867a7c89/xmlsec-1.3.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6ac2154311d32a6571e22f224ed16356029e59bd5ca76edeb3922a809adfe89c", size = 3746315, upload-time = "2025-03-11T22:36:43.675Z" },
{ url = "https://files.pythonhosted.org/packages/b7/91/7ce9317e3a2a03e3811e62be52e091c1e661da2d59b5c7f60ec1840a1e6b/xmlsec-1.3.15-cp313-cp313-win32.whl", hash = "sha256:5ed218129f89b0592926ad2be42c017bece469db9b7380dc41bc09b01ca26d5d", size = 2146158, upload-time = "2025-03-11T22:36:44.887Z" },
{ url = "https://files.pythonhosted.org/packages/3d/e0/93311b9eedc11055ba667e666dc6ca1e2cc59c2356e91b73c3d5a6738fbf/xmlsec-1.3.15-cp313-cp313-win_amd64.whl", hash = "sha256:5fc29e69b064323317b3862751a3a8107670e0a17510ca4517bbdc1939a90b1a", size = 2442027, upload-time = "2025-03-11T22:36:46.431Z" },
{ url = "https://files.pythonhosted.org/packages/b6/72/dae0618ff7a86dada22bf0ae16730a48b98e513fa8098681156e74dd687c/xmlsec-1.3.16-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68b60867e6be61e06964be64d7501b88156f0ad0a4808b5c2eb8b16d8de9b6bc", size = 3424682, upload-time = "2025-07-10T12:18:39.34Z" },
{ url = "https://files.pythonhosted.org/packages/73/0a/6e1d9108f0b0801f874a153256ec044d1b1c53713517f8f24ff5c21544e5/xmlsec-1.3.16-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:174917827e26ff71dab512e5e87544dcd3309eb4a5dc18e6f1e8022789e1f1bf", size = 3830253, upload-time = "2025-07-10T12:18:41.092Z" },
{ url = "https://files.pythonhosted.org/packages/eb/35/0d11fceb1978564a14c95b687a08a27b96873aeb54d9c1a5c90efd824766/xmlsec-1.3.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb683689a7770a3a71657af96c1953c9d00da7e3c8d9a9851777fa2c9ecf741a", size = 4367122, upload-time = "2025-07-10T12:18:42.768Z" },
{ url = "https://files.pythonhosted.org/packages/65/9b/ad52185d08fd35ae323124c0a727697d2535e1794c864f4858e1b61eb7bf/xmlsec-1.3.16-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fda1627987e26989ead9ae8ec8b089f2affa0e922f585ab1c37d1c2111c78c7", size = 3791602, upload-time = "2025-07-10T12:18:44.295Z" },
{ url = "https://files.pythonhosted.org/packages/1b/fe/867975dd2a20640882282788f66c535d746cce01265a2732bde596a98cf7/xmlsec-1.3.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4fe9e8ab3497632ea242fc718a57e7524e0300537fd106a7455696b339ac0a6", size = 4098004, upload-time = "2025-07-10T12:18:45.945Z" },
{ url = "https://files.pythonhosted.org/packages/d4/35/66f717c4ff288c61c2e447fdc453fa21446d15e39ae756a129a3aa1bd4ae/xmlsec-1.3.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1014c6cb723ad99781a2f37c03969eb246b310da029ddf3250df998801bb62d7", size = 4400559, upload-time = "2025-07-10T12:18:47.389Z" },
{ url = "https://files.pythonhosted.org/packages/08/9c/2e6ee7f9f313b4848b2f9dac51fbf6615932f1aa6b9399d17ab29c2bd731/xmlsec-1.3.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:795eedbdb4dbc72c786dbf4193561de0f3ebd8f8ee3a32e81b085b24ccbee120", size = 4153379, upload-time = "2025-07-10T12:18:48.778Z" },
{ url = "https://files.pythonhosted.org/packages/ba/f4/08da3d448456e4c41ddc99a0e713313b774beed5a27689b066d47d114194/xmlsec-1.3.16-cp313-cp313-win32.whl", hash = "sha256:1f83fbd4c44bbeebd19db9de5969b68ed7d5b723d7812c5a62c795452f7c8945", size = 2155805, upload-time = "2025-07-10T12:18:51.697Z" },
{ url = "https://files.pythonhosted.org/packages/60/80/8ffe311f8e2273bb156b4cb0f2454e6a089723575921c4c07c24dd1aa3ae/xmlsec-1.3.16-cp313-cp313-win_amd64.whl", hash = "sha256:4b8b36cdccd13fa84a923958ebf5f73febe8a97da4ecff711fdeb97fec6896db", size = 2446038, upload-time = "2025-07-10T12:18:50.369Z" },
]
[[package]]

View File

@@ -0,0 +1,18 @@
import { extendStorybookTheme } from "./theme.js";
import { createUIThemeEffect, resolveUITheme } from "@goauthentik/web/common/theme.ts";
import { DocsContainer, DocsContainerProps } from "@storybook/addon-docs/blocks";
import { useEffect, useMemo, useState } from "react";
export const ThemedDocsContainer: React.FC<DocsContainerProps> = ({
theme: initialTheme = resolveUITheme(),
...props
}) => {
const [theme, setTheme] = useState(initialTheme);
const resolvedTheme = useMemo(() => extendStorybookTheme(theme), [theme]);
useEffect(() => createUIThemeEffect(setTheme), []);
return <DocsContainer {...props} theme={resolvedTheme} />;
};

View File

@@ -3,8 +3,8 @@
* @import { StorybookConfig } from "@storybook/web-components-vite";
* @import { InlineConfig, Plugin } from "vite";
*/
import postcssLit from "rollup-plugin-postcss-lit";
import tsconfigPaths from "vite-tsconfig-paths";
const CSSImportPattern = /import [\w$]+ from .+\.(css)/g;
const JavaScriptFilePattern = /\.m?(js|ts|tsx)$/;
@@ -27,11 +27,6 @@ const inlineCSSPlugin = {
},
};
/**
* @satisfies {InlineConfig}
*/
// const viteFinal = ;
/**
* @satisfies {StorybookConfig}
*/
@@ -42,18 +37,11 @@ const config = {
{ from: "../authentik", to: "/static/authentik" },
],
addons: [
"@storybook/addon-controls",
// ---
"@storybook/addon-links",
"@storybook/addon-essentials",
"storybook-addon-mock",
"@storybook/addon-docs",
],
framework: {
name: "@storybook/web-components-vite",
options: {},
},
docs: {
autodocs: "tag",
},
framework: "@storybook/web-components-vite",
async viteFinal(config) {
const [{ mergeConfig }, { createBundleDefinitions }] = await Promise.all([
import("vite"),
@@ -65,10 +53,11 @@ const config = {
*/
const overrides = {
define: createBundleDefinitions(),
plugins: [inlineCSSPlugin, postcssLit(), tsconfigPaths()],
plugins: [inlineCSSPlugin, postcssLit()],
};
return mergeConfig(config, overrides);
},
};
export default config;

View File

@@ -1,38 +1,16 @@
/**
* @file Storybook manager configuration.
*
* @import { ThemeVarsPartial } from "storybook/internal/theming";
*/
import { createUIThemeEffect, resolveUITheme } from "@goauthentik/web/common/theme.ts";
import { addons } from "@storybook/manager-api";
import { create } from "@storybook/theming/create";
/**
* @satisfies {Partial<ThemeVarsPartial>}
*/
const baseTheme = {
brandTitle: "authentik Storybook",
brandUrl: "https://goauthentik.io",
brandImage: "https://goauthentik.io/img/icon_left_brand_colour.svg",
brandTarget: "_self",
};
import { extendStorybookTheme } from "./theme.js";
const uiTheme = resolveUITheme();
import { createUIThemeEffect } from "@goauthentik/web/common/theme.ts";
addons.setConfig({
theme: create({
...baseTheme,
base: uiTheme,
}),
enableShortcuts: false,
});
import { addons } from "storybook/manager-api";
createUIThemeEffect((nextUITheme) => {
addons.setConfig({
theme: create({
...baseTheme,
base: nextUITheme,
}),
theme: extendStorybookTheme(nextUITheme),
enableShortcuts: false,
});
});

View File

@@ -1,58 +0,0 @@
<style>
body {
overflow-y: scroll;
margin: 0;
}
.sb-main-padded {
padding: 0 !important;
}
.story-shadow-container {
background-color: #fff;
box-shadow: 0 0 0.25em #ddd;
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 1em;
margin: 0 auto;
max-width: 72em;
padding: 1em;
width: 100%;
}
.docs-story .story-shadow-container {
box-shadow: none;
}
.story-shadow-container[display-mode="flex-wrap"] {
flex-wrap: wrap;
flex-direction: row;
}
.title {
border-bottom: 1px solid #ccc;
color: #333;
font-size: 13px;
font-weight: bold;
margin: 2rem -1rem 1rem;
padding-bottom: 0.25rem;
padding-left: 1rem;
}
.title code {
background-color: #f5f5f5;
border-radius: 0.25rem;
font-weight: bold;
padding: 0.1rem 0.25rem;
}
.sbdocs-preview .hljs {
color: #fff !important;
white-space: pre-line;
}
.sbdocs-pre > div {
margin: 1em 0;
}
</style>

View File

@@ -1,18 +1,36 @@
/// <reference types="../types/css.js" />
/**
* @file Storybook manager configuration.
*
* @import { Preview } from "@storybook/web-components";
*/
import { applyDocumentTheme } from "@goauthentik/web/common/theme.ts";
applyDocumentTheme();
import "#common/styles/storybook.css";
import { ThemedDocsContainer } from "./DocsContainer.tsx";
import { extendStorybookTheme } from "./theme.js";
import {
applyDocumentTheme,
createUIThemeEffect,
resolveUITheme,
} from "@goauthentik/web/common/theme.ts";
const base = resolveUITheme();
const theme = extendStorybookTheme(base);
createUIThemeEffect(applyDocumentTheme);
/**
* @satisfies {Preview}
*/
const preview = {
tags: ["autodocs"],
parameters: {
docs: {
theme,
container: ThemedDocsContainer,
},
options: {
storySort: {
method: "alphabetical",

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