Compare commits

..

53 Commits

Author SHA1 Message Date
Tana M Berry
b1cb339f3a tweak 2025-08-04 15:13:41 -05:00
Tana M Berry
e124e21119 Merge branch 'main' into docs-remove-phrase 2025-08-04 15:06:03 -05:00
Simonyi Gergő
213cf44928 root: enhance custom middleware experience (#15919)
* enable custom middleware positioning

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

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

* stop treating authentik.enterprise exceptionally in settings

This is the singular case where more apps are added.

* stop treating authentik.core exceptionally in settings

Uhh, fingers crossed? This has history, it goes back to 80d90b91e8
2025-08-04 21:05:05 +02:00
Tana M Berry
dc0c7a858a tweak to bump build 2025-08-04 13:53:50 -05:00
Tana M Berry
3fddbb918e removed phrase 2025-08-04 13:32:08 -05:00
Timo Christeleit
3c97b081b0 website/integrations: add hass-openid instructions (#14672)
* add instructions

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

* Changed proxy section formatting and some language

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

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

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

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

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

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

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

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

---------

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

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

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


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

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

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


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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-01 12:14:49 +02:00
dependabot[bot]
41ebfa24da web: bump the storybook group across 1 directory with 5 updates (#15912)
Bumps the storybook group with 4 updates in the /web directory: [@storybook/addon-docs](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/docs), [@storybook/addon-links](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/links), [@storybook/web-components](https://github.com/storybookjs/storybook/tree/HEAD/code/renderers/web-components) and [@storybook/web-components-vite](https://github.com/storybookjs/storybook/tree/HEAD/code/frameworks/web-components-vite).


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

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

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

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

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

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

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

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 5.9.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-01 12:14:08 +02:00
dependabot[bot]
e780b7d519 web: bump typescript from 5.8.3 to 5.9.2 in /packages/esbuild-plugin-live-reload (#15914)
web: bump typescript in /packages/esbuild-plugin-live-reload

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

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 5.9.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

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

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 5.9.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

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

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 5.9.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

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

---
updated-dependencies:
- dependency-name: "@floating-ui/dom"
  dependency-version: 1.7.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-31 15:54:06 +02:00
dependabot[bot]
5bdbf06351 web: bump chromedriver from 138.0.4 to 138.0.5 in /web (#15900)
Bumps [chromedriver](https://github.com/giggio/node-chromedriver) from 138.0.4 to 138.0.5.
- [Commits](https://github.com/giggio/node-chromedriver/compare/138.0.4...138.0.5)

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

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


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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-31 01:37:50 +02:00
dependabot[bot]
46c9bfb0aa website: bump the build group in /website with 2 updates (#15882)
Bumps the build group in /website with 2 updates: [@swc/core-darwin-arm64](https://github.com/swc-project/swc) and [@swc/core-linux-arm64-gnu](https://github.com/swc-project/swc).


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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-31 01:37:39 +02:00
dependabot[bot]
6d325d566c web: bump @sentry/browser from 9.42.1 to 9.43.0 in /web in the sentry group across 1 directory (#15883)
web: bump @sentry/browser in /web in the sentry group across 1 directory

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


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

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

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

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

---
updated-dependencies:
- dependency-name: typedoc-plugin-markdown
  dependency-version: 4.8.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-31 01:37:07 +02:00
Teffen Ellis
e04ca70cb2 web/a11y: Tables & Modals (#15877)
web: Prep for a11y, tables, modals.
2025-07-30 19:27:11 -04:00
Teffen Ellis
6a5342f621 web/a11y: Form Inputs (#15878)
web: Prep form inputs for a11y.
2025-07-30 19:27:03 -04:00
Teffen Ellis
e250c8f514 web/a11y: Navigation Banner (#15880)
web: Prepare navbar for a11y.
2025-07-30 19:26:34 -04:00
Teffen Ellis
a4e7aa0adc web/a11y: License notice ARIA attributes. (#15872)
web: Add ARIA attributes.
2025-07-30 19:26:23 -04:00
authentik-automation[bot]
ac79acd2bc core, web: update translations (#15873)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-07-30 10:34:47 +02:00
120 changed files with 3972 additions and 4760 deletions

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ from json import loads
from typing import Any
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.models import Permission
from django.contrib.auth.models import AnonymousUser, Permission
from django.db.transaction import atomic
from django.db.utils import IntegrityError
from django.urls import reverse_lazy
@@ -16,6 +16,7 @@ from django.utils.translation import gettext as _
from django_filters.filters import (
BooleanFilter,
CharFilter,
IsoDateTimeFilter,
ModelMultipleChoiceFilter,
MultipleChoiceFilter,
UUIDFilter,
@@ -241,6 +242,7 @@ class UserSerializer(ModelSerializer):
"type",
"uuid",
"password_change_date",
"last_updated",
]
extra_kwargs = {
"name": {"allow_blank": True},
@@ -331,6 +333,14 @@ class UsersFilter(FilterSet):
method="filter_attributes",
)
date_joined__lt = IsoDateTimeFilter(field_name="date_joined", lookup_expr="lt")
date_joined = IsoDateTimeFilter(field_name="date_joined")
date_joined__gt = IsoDateTimeFilter(field_name="date_joined", lookup_expr="gt")
last_updated__lt = IsoDateTimeFilter(field_name="last_updated", lookup_expr="lt")
last_updated = IsoDateTimeFilter(field_name="last_updated")
last_updated__gt = IsoDateTimeFilter(field_name="last_updated", lookup_expr="gt")
is_superuser = BooleanFilter(field_name="ak_groups", method="filter_is_superuser")
uuid = UUIDFilter(field_name="uuid")
@@ -376,6 +386,8 @@ class UsersFilter(FilterSet):
fields = [
"username",
"email",
"date_joined",
"last_updated",
"name",
"is_active",
"is_superuser",
@@ -390,10 +402,19 @@ class UserViewSet(UsedByMixin, ModelViewSet):
"""User Viewset"""
queryset = User.objects.none()
ordering = ["username"]
ordering = ["username", "date_joined", "last_updated"]
serializer_class = UserSerializer
filterset_class = UsersFilter
search_fields = ["username", "name", "is_active", "email", "uuid", "attributes"]
search_fields = [
"username",
"name",
"is_active",
"email",
"uuid",
"attributes",
"date_joined",
"last_updated",
]
def get_ql_fields(self):
from djangoql.schema import BoolField, StrField
@@ -435,6 +456,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
user: User = self.get_object()
planner = FlowPlanner(flow)
planner.allow_empty_flows = True
self.request._request.user = AnonymousUser()
try:
plan = planner.plan(
self.request._request,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -4689,6 +4689,14 @@
"format": "uuid"
},
"title": "Roles"
},
"children": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"title": "Children"
}
},
"required": []
@@ -9287,6 +9295,18 @@
"type": "string",
"title": "Default relay state",
"description": "Default relay_state value for IDP-initiated logins"
},
"default_name_id_policy": {
"type": "string",
"enum": [
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
"urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName",
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
],
"title": "Default name id policy"
}
},
"required": []
@@ -11714,7 +11734,8 @@
"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
"urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName",
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
],
"title": "Name id policy",
"description": "NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent."

19
go.mod
View File

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

36
go.sum
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -17958,9 +17958,9 @@
}
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"dev": true,
"license": "Apache-2.0",
"bin": {

View File

@@ -2728,9 +2728,9 @@
}
},
"node_modules/typedoc-plugin-markdown": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.7.1.tgz",
"integrity": "sha512-HN/fHLm2S6MD4HX8txfB4eWvVBzX/mEYy5U5s1KTAdh3E5uX5/lilswqTzZlPTT6fNZInAboAdFGpbAuBKnE4A==",
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.8.0.tgz",
"integrity": "sha512-BQqXnT9PETe6WEFf8bcsvvGEGQHbwTo/BFyY+RUIsSB05Y0Wn56iF+fK1PY2OKJJIhV4kp4dp7osaP9Bm5a0Zw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2741,9 +2741,9 @@
}
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"dev": true,
"license": "Apache-2.0",
"bin": {

View File

@@ -500,93 +500,6 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz",
"integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.38.0",
"@typescript-eslint/type-utils": "8.38.0",
"@typescript-eslint/utils": "8.38.0",
"@typescript-eslint/visitor-keys": "8.38.0",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.1.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.38.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
"version": "7.0.5",
"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.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz",
"integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.38.0",
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/typescript-estree": "8.38.0",
"@typescript-eslint/visitor-keys": "8.38.0",
"debug": "^4.3.4"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz",
"integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.38.0",
"@typescript-eslint/types": "^8.38.0",
"debug": "^4.3.4"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz",
@@ -605,48 +518,6 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz",
"integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz",
"integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/typescript-estree": "8.38.0",
"@typescript-eslint/utils": "8.38.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz",
@@ -661,98 +532,6 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz",
"integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.38.0",
"@typescript-eslint/tsconfig-utils": "8.38.0",
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/visitor-keys": "8.38.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
"ts-api-utils": "^2.1.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "2.0.2",
"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"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.5",
"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"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
"version": "7.7.2",
"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"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz",
"integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.38.0",
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/typescript-estree": "8.38.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz",
@@ -4694,9 +4473,9 @@
}
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -4731,6 +4510,227 @@
"typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz",
"integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.38.0",
"@typescript-eslint/type-utils": "8.38.0",
"@typescript-eslint/utils": "8.38.0",
"@typescript-eslint/visitor-keys": "8.38.0",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.1.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.38.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz",
"integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/typescript-estree": "8.38.0",
"@typescript-eslint/utils": "8.38.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz",
"integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.38.0",
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/typescript-estree": "8.38.0",
"@typescript-eslint/visitor-keys": "8.38.0",
"debug": "^4.3.4"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz",
"integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.38.0",
"@typescript-eslint/tsconfig-utils": "8.38.0",
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/visitor-keys": "8.38.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
"ts-api-utils": "^2.1.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz",
"integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.38.0",
"@typescript-eslint/types": "^8.38.0",
"debug": "^4.3.4"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz",
"integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": {
"version": "8.38.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz",
"integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.38.0",
"@typescript-eslint/types": "8.38.0",
"@typescript-eslint/typescript-estree": "8.38.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/typescript-eslint/node_modules/brace-expansion": {
"version": "2.0.2",
"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"
}
},
"node_modules/typescript-eslint/node_modules/ignore": {
"version": "7.0.5",
"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/node_modules/minimatch": {
"version": "9.0.5",
"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"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/typescript-eslint/node_modules/semver": {
"version": "7.7.2",
"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"
},
"engines": {
"node": ">=10"
}
},
"node_modules/unbox-primitive": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",

View File

@@ -1711,9 +1711,9 @@
}
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",

View File

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

View File

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

View File

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

View File

@@ -4718,6 +4718,11 @@ paths:
schema:
type: string
description: Attributes
- in: query
name: include_children
schema:
type: boolean
default: false
- in: query
name: include_users
schema:
@@ -4840,6 +4845,11 @@ paths:
format: uuid
description: A UUID string identifying this Group.
required: true
- in: query
name: include_children
schema:
type: boolean
default: false
- in: query
name: include_users
schema:
@@ -5654,6 +5664,21 @@ paths:
schema:
type: string
description: Attributes
- in: query
name: date_joined
schema:
type: string
format: date-time
- in: query
name: date_joined__gt
schema:
type: string
format: date-time
- in: query
name: date_joined__lt
schema:
type: string
format: date-time
- in: query
name: email
schema:
@@ -5688,6 +5713,21 @@ paths:
name: is_superuser
schema:
type: boolean
- in: query
name: last_updated
schema:
type: string
format: date-time
- in: query
name: last_updated__gt
schema:
type: string
format: date-time
- in: query
name: last_updated__lt
schema:
type: string
format: date-time
- in: query
name: name
schema:
@@ -22282,6 +22322,17 @@ paths:
schema:
type: string
format: uuid
- in: query
name: default_name_id_policy
schema:
type: string
enum:
- urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
- urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
- urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName
- urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
- urn:oasis:names:tc:SAML:2.0:nameid-format:transient
- in: query
name: default_relay_state
schema:
@@ -29498,6 +29549,7 @@ paths:
enum:
- urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
- urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
- urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName
- urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
- urn:oasis:names:tc:SAML:2.0:nameid-format:transient
@@ -46466,13 +46518,50 @@ components:
items:
$ref: '#/components/schemas/Role'
readOnly: true
children:
type: array
items:
type: string
format: uuid
children_obj:
type: array
items:
$ref: '#/components/schemas/GroupChild'
readOnly: true
nullable: true
required:
- children_obj
- name
- num_pk
- parent_name
- pk
- roles_obj
- users_obj
GroupChild:
type: object
description: Stripped down group serializer to show relevant children for groups
properties:
pk:
type: string
format: uuid
readOnly: true
title: Group uuid
name:
type: string
is_superuser:
type: boolean
description: Users added to this group will be superusers.
attributes:
type: object
additionalProperties: {}
group_uuid:
type: string
format: uuid
readOnly: true
required:
- group_uuid
- name
- pk
GroupKerberosSourceConnection:
type: object
description: Group Source Connection
@@ -46794,6 +46883,11 @@ components:
items:
type: string
format: uuid
children:
type: array
items:
type: string
format: uuid
required:
- name
GroupSAMLSourceConnection:
@@ -49063,14 +49157,6 @@ components:
- mode
- name
- user_attribute
NameIdPolicyEnum:
enum:
- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
- urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
- urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
- urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName
- urn:oasis:names:tc:SAML:2.0:nameid-format:transient
type: string
NetworkBindingEnum:
enum:
- no_binding
@@ -53685,6 +53771,11 @@ components:
items:
type: string
format: uuid
children:
type: array
items:
type: string
format: uuid
PatchedGroupSAMLSourceConnectionRequest:
type: object
description: Group Source Connection
@@ -55289,6 +55380,8 @@ components:
default_relay_state:
type: string
description: Default relay_state value for IDP-initiated logins
default_name_id_policy:
$ref: '#/components/schemas/SAMLNameIDPolicyEnum'
PatchedSAMLSourcePropertyMappingRequest:
type: object
description: SAMLSourcePropertyMapping Serializer
@@ -55382,7 +55475,7 @@ components:
be a security risk, as no validation of the request ID is done.
name_id_policy:
allOf:
- $ref: '#/components/schemas/NameIdPolicyEnum'
- $ref: '#/components/schemas/SAMLNameIDPolicyEnum'
description: NameID Policy sent to the IdP. Can be unset, in which case
no Policy is sent.
binding_type:
@@ -58130,6 +58223,15 @@ components:
required:
- download_url
- metadata
SAMLNameIDPolicyEnum:
enum:
- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
- urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
- urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
- urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName
- urn:oasis:names:tc:SAML:2.0:nameid-format:transient
- urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
type: string
SAMLPropertyMapping:
type: object
description: SAMLPropertyMapping Serializer
@@ -58347,6 +58449,8 @@ components:
default_relay_state:
type: string
description: Default relay_state value for IDP-initiated logins
default_name_id_policy:
$ref: '#/components/schemas/SAMLNameIDPolicyEnum'
url_download_metadata:
type: string
description: Get metadata download URL
@@ -58519,6 +58623,8 @@ components:
default_relay_state:
type: string
description: Default relay_state value for IDP-initiated logins
default_name_id_policy:
$ref: '#/components/schemas/SAMLNameIDPolicyEnum'
required:
- acs_url
- authorization_flow
@@ -58627,7 +58733,7 @@ components:
be a security risk, as no validation of the request ID is done.
name_id_policy:
allOf:
- $ref: '#/components/schemas/NameIdPolicyEnum'
- $ref: '#/components/schemas/SAMLNameIDPolicyEnum'
description: NameID Policy sent to the IdP. Can be unset, in which case
no Policy is sent.
binding_type:
@@ -58817,7 +58923,7 @@ components:
be a security risk, as no validation of the request ID is done.
name_id_policy:
allOf:
- $ref: '#/components/schemas/NameIdPolicyEnum'
- $ref: '#/components/schemas/SAMLNameIDPolicyEnum'
description: NameID Policy sent to the IdP. Can be unset, in which case
no Policy is sent.
binding_type:
@@ -61075,11 +61181,16 @@ components:
type: string
format: date-time
readOnly: true
last_updated:
type: string
format: date-time
readOnly: true
required:
- avatar
- date_joined
- groups_obj
- is_superuser
- last_updated
- name
- password_change_date
- pk

98
uv.lock generated
View File

@@ -19,7 +19,7 @@ wheels = [
[[package]]
name = "aiohttp"
version = "3.12.14"
version = "3.12.15"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohappyeyeballs" },
@@ -30,25 +30,25 @@ dependencies = [
{ name = "propcache" },
{ name = "yarl" },
]
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" }
sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" }
wheels = [
{ 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" },
{ url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741, upload-time = "2025-07-29T05:51:19.021Z" },
{ url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407, upload-time = "2025-07-29T05:51:21.165Z" },
{ url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703, upload-time = "2025-07-29T05:51:22.948Z" },
{ url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532, upload-time = "2025-07-29T05:51:25.211Z" },
{ url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794, upload-time = "2025-07-29T05:51:27.145Z" },
{ url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865, upload-time = "2025-07-29T05:51:29.366Z" },
{ url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238, upload-time = "2025-07-29T05:51:31.285Z" },
{ url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566, upload-time = "2025-07-29T05:51:33.219Z" },
{ url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270, upload-time = "2025-07-29T05:51:35.195Z" },
{ url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294, upload-time = "2025-07-29T05:51:37.215Z" },
{ url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958, upload-time = "2025-07-29T05:51:39.328Z" },
{ url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553, upload-time = "2025-07-29T05:51:41.356Z" },
{ url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688, upload-time = "2025-07-29T05:51:43.452Z" },
{ url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157, upload-time = "2025-07-29T05:51:45.643Z" },
{ url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050, upload-time = "2025-07-29T05:51:48.203Z" },
{ url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647, upload-time = "2025-07-29T05:51:50.718Z" },
{ url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067, upload-time = "2025-07-29T05:51:52.549Z" },
]
[[package]]
@@ -111,23 +111,23 @@ wheels = [
[[package]]
name = "argon2-cffi-bindings"
version = "21.2.0"
version = "25.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", size = 1779911, upload-time = "2021-12-01T08:52:55.68Z" }
sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d4/13/838ce2620025e9666aa8f686431f67a29052241692a3dd1ae9d3692a89d3/argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", size = 29658, upload-time = "2021-12-01T09:09:17.016Z" },
{ url = "https://files.pythonhosted.org/packages/b3/02/f7f7bb6b6af6031edb11037639c697b912e1dea2db94d436e681aea2f495/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", size = 80583, upload-time = "2021-12-01T09:09:19.546Z" },
{ url = "https://files.pythonhosted.org/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", size = 86168, upload-time = "2021-12-01T09:09:21.445Z" },
{ url = "https://files.pythonhosted.org/packages/74/f6/4a34a37a98311ed73bb80efe422fed95f2ac25a4cacc5ae1d7ae6a144505/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", size = 82709, upload-time = "2021-12-01T09:09:18.182Z" },
{ url = "https://files.pythonhosted.org/packages/74/2b/73d767bfdaab25484f7e7901379d5f8793cccbb86c6e0cbc4c1b96f63896/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", size = 83613, upload-time = "2021-12-01T09:09:22.741Z" },
{ url = "https://files.pythonhosted.org/packages/4f/fd/37f86deef67ff57c76f137a67181949c2d408077e2e3dd70c6c42912c9bf/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", size = 84583, upload-time = "2021-12-01T09:09:24.177Z" },
{ url = "https://files.pythonhosted.org/packages/6f/52/5a60085a3dae8fded8327a4f564223029f5f54b0cb0455a31131b5363a01/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", size = 88475, upload-time = "2021-12-01T09:09:26.673Z" },
{ url = "https://files.pythonhosted.org/packages/8b/95/143cd64feb24a15fa4b189a3e1e7efbaeeb00f39a51e99b26fc62fbacabd/argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", size = 27698, upload-time = "2021-12-01T09:09:27.87Z" },
{ url = "https://files.pythonhosted.org/packages/37/2c/e34e47c7dee97ba6f01a6203e0383e15b60fb85d78ac9a15cd066f6fe28b/argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", size = 30817, upload-time = "2021-12-01T09:09:30.267Z" },
{ url = "https://files.pythonhosted.org/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", size = 53104, upload-time = "2021-12-01T09:09:31.335Z" },
{ url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" },
{ url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" },
{ url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" },
{ url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" },
{ url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" },
{ url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" },
{ url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" },
{ url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" },
{ url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" },
{ url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" },
]
[[package]]
@@ -557,30 +557,30 @@ wheels = [
[[package]]
name = "boto3"
version = "1.39.15"
version = "1.40.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "botocore" },
{ name = "jmespath" },
{ name = "s3transfer" },
]
sdist = { url = "https://files.pythonhosted.org/packages/63/65/ddd4f52d138e52c1345c2d2421281a98449a6e4365290477befe06fa649a/boto3-1.39.15.tar.gz", hash = "sha256:b4483625f0d8c35045254dee46cd3c851bbc0450814f20b9b25bee1b5c0d8409", size = 111856, upload-time = "2025-07-28T19:56:49.504Z" }
sdist = { url = "https://files.pythonhosted.org/packages/48/4d/70d209fdebf0377db233f80dfdf26ca2bc25d2b2e89d4882e0edccd2227f/boto3-1.40.1.tar.gz", hash = "sha256:985ed4bf64729807f870eadbc46ad98baf93096917f7194ec39d743ff75b3f1d", size = 111817, upload-time = "2025-08-01T19:24:18.017Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/15/c5/27f50a31317041dc3ad79d62f37d5fcfb3f349c2fba8ea3e81de169db870/boto3-1.39.15-py3-none-any.whl", hash = "sha256:38fc54576b925af0075636752de9974e172c8a2cf7133400e3e09b150d20fb6a", size = 139901, upload-time = "2025-07-28T19:56:47.381Z" },
{ url = "https://files.pythonhosted.org/packages/97/0e/f0cb4f71c40ba07e6ed5b47699a737a080d3c4f4b7b26657d5671de48621/boto3-1.40.1-py3-none-any.whl", hash = "sha256:7c007d5c8ee549e9fcad0927536502da199b27891006ef515330f429aca9671f", size = 139880, upload-time = "2025-08-01T19:24:16.581Z" },
]
[[package]]
name = "botocore"
version = "1.39.15"
version = "1.40.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jmespath" },
{ name = "python-dateutil" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2f/e2/8cd9560e7e44cf977dc0cc2e48da7634e78b7104ae6e47f4e1dfc1093965/botocore-1.39.15.tar.gz", hash = "sha256:2aa29a717f14f8c7ca058c2e297aaed0aa10ecea24b91514eee802814d1b7600", size = 14237556, upload-time = "2025-07-28T19:56:39.397Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c6/d2/d914999f4a128f0f840f2a9cc8327cd98aa661d6b33b331a81a8111ab970/botocore-1.40.1.tar.gz", hash = "sha256:bdf30e2c0e8cdb939d81fc243182a6d1dd39c416694b406c5f2ea079b1c2f3f5", size = 14280398, upload-time = "2025-08-01T19:24:08.599Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7b/6e/f25b8633e7ab2008de4c27466c9bc39e32dc73816619ffebbea12936135a/botocore-1.39.15-py3-none-any.whl", hash = "sha256:eb9cfe918ebfbfb8654e1b153b29f0c129d586d2c0d7fb4032731d49baf04cff", size = 13894884, upload-time = "2025-07-28T19:56:33.715Z" },
{ url = "https://files.pythonhosted.org/packages/d4/c1/aa7922c9bf74b6d6594d2430af6f854d234faff23187e269aaba89c326c8/botocore-1.40.1-py3-none-any.whl", hash = "sha256:e039774b55fbd6fe59f0f4fea51d156a2433bd4d8faa64fc1b87aee9a03f415d", size = 13940950, upload-time = "2025-08-01T19:24:03.889Z" },
]
[[package]]
@@ -1691,7 +1691,7 @@ wheels = [
[[package]]
name = "jsii"
version = "1.112.0"
version = "1.113.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
@@ -1702,9 +1702,9 @@ dependencies = [
{ name = "typeguard" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ad/3e/270b5236035fc7bb2cdd7f55ea25f85489d35d971870cbec32c3d9e99d7f/jsii-1.112.0.tar.gz", hash = "sha256:6b7d19f361c2565b76828ecbe8cbed8b8d6028a82aa98a46b206a4ee5083157e", size = 624533, upload-time = "2025-05-07T14:45:52.574Z" }
sdist = { url = "https://files.pythonhosted.org/packages/37/9b/ff11800e2edc2860c9eddd7ea7c7a8849f69cbb16b1aae803dae7dafa86e/jsii-1.113.0.tar.gz", hash = "sha256:2dedea9d6006af53467a7a67f1d35a56ab3f75a3d6ed4b4536fffc3e1d1fe476", size = 623541, upload-time = "2025-07-31T12:55:42.888Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/44/af/8554b632e2b82f37a7422782aba5db2a1fbff4887faa7ec850818def8407/jsii-1.112.0-py3-none-any.whl", hash = "sha256:6510c223074d9b206fd0570849a791e4d9ecfff7ffe68428de73870cea9f55a1", size = 600681, upload-time = "2025-05-07T14:45:51.136Z" },
{ url = "https://files.pythonhosted.org/packages/4f/59/bbbdcc7e0adc32e2362dbb2398949ac013f79dc3468cdf2b5ac411b0f5e8/jsii-1.113.0-py3-none-any.whl", hash = "sha256:62377c651554234ea945693f7c03cb96a969ba425a686950c88d43b0d4d76b07", size = 599669, upload-time = "2025-07-31T12:55:40.874Z" },
]
[[package]]
@@ -2154,42 +2154,42 @@ source = { git = "https://github.com/vsoch/oci-python?rev=ceb4fcc090851717a3069d
[[package]]
name = "opentelemetry-api"
version = "1.35.0"
version = "1.36.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "importlib-metadata" },
{ name = "typing-extensions" },
]
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" }
sdist = { url = "https://files.pythonhosted.org/packages/27/d2/c782c88b8afbf961d6972428821c302bd1e9e7bc361352172f0ca31296e2/opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0", size = 64780, upload-time = "2025-07-29T15:12:06.02Z" }
wheels = [
{ 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" },
{ url = "https://files.pythonhosted.org/packages/bb/ee/6b08dde0a022c463b88f55ae81149584b125a42183407dc1045c486cc870/opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c", size = 65564, upload-time = "2025-07-29T15:11:47.998Z" },
]
[[package]]
name = "opentelemetry-sdk"
version = "1.35.0"
version = "1.36.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/9a/cf/1eb2ed2ce55e0a9aa95b3007f26f55c7943aeef0a783bb006bdd92b3299e/opentelemetry_sdk-1.35.0.tar.gz", hash = "sha256:2a400b415ab68aaa6f04e8a6a9f6552908fb3090ae2ff78d6ae0c597ac581954", size = 160871, upload-time = "2025-07-11T12:23:39.566Z" }
sdist = { url = "https://files.pythonhosted.org/packages/4c/85/8567a966b85a2d3f971c4d42f781c305b2b91c043724fa08fd37d158e9dc/opentelemetry_sdk-1.36.0.tar.gz", hash = "sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581", size = 162557, upload-time = "2025-07-29T15:12:16.76Z" }
wheels = [
{ 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" },
{ url = "https://files.pythonhosted.org/packages/0b/59/7bed362ad1137ba5886dac8439e84cd2df6d087be7c09574ece47ae9b22c/opentelemetry_sdk-1.36.0-py3-none-any.whl", hash = "sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb", size = 119995, upload-time = "2025-07-29T15:12:03.181Z" },
]
[[package]]
name = "opentelemetry-semantic-conventions"
version = "0.56b0"
version = "0.57b0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "typing-extensions" },
]
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" }
sdist = { url = "https://files.pythonhosted.org/packages/7e/31/67dfa252ee88476a29200b0255bda8dfc2cf07b56ad66dc9a6221f7dc787/opentelemetry_semantic_conventions-0.57b0.tar.gz", hash = "sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32", size = 124225, upload-time = "2025-07-29T15:12:17.873Z" }
wheels = [
{ 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" },
{ url = "https://files.pythonhosted.org/packages/05/75/7d591371c6c39c73de5ce5da5a2cc7b72d1d1cd3f8f4638f553c01c37b11/opentelemetry_semantic_conventions-0.57b0-py3-none-any.whl", hash = "sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78", size = 201627, upload-time = "2025-07-29T15:12:04.174Z" },
]
[[package]]

2
web/.gitignore vendored
View File

@@ -25,8 +25,6 @@ lib-cov
# Coverage directory used by tools like istanbul
coverage
playwright-report
test-results
*.lcov
# nyc test coverage

View File

@@ -15,6 +15,7 @@ export function addCommands(browser) {
/**
* @this {HTMLElement}
*/
// @ts-ignore
function () {
this.focus();
@@ -28,6 +29,7 @@ export function addCommands(browser) {
/**
* @this {HTMLElement}
*/
// @ts-ignore
function () {
this.blur();

View File

@@ -1,90 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { LocatorContext } from "#e2e/selectors/types";
import { ConsoleLogger } from "#logger/node";
import { expect, Locator } from "@playwright/test";
import { kebabCase } from "change-case";
export type LocatorMatchers = ReturnType<typeof expect<Locator>>;
export interface LocatorProxy extends Pick<Locator, keyof Locator> {
$: Locator;
expect: LocatorMatchers;
}
// Type helpers to extract the shape of the proxy
export type DeepLocatorProxy<T> =
Disposable & T extends Record<string, any>
? T extends HTMLElement
? LocatorProxy
: {
[K in keyof T]: DeepLocatorProxy<T[K]>;
}
: LocatorProxy;
export function createLocatorProxy<T extends Record<string, any>>(
ctx: LocatorContext,
initialPathPrefix: string[] = [],
dataAttribute: string = "test-id",
): DeepLocatorProxy<T> {
dataAttribute = kebabCase(dataAttribute);
function createProxy(path: string[] = initialPathPrefix): any {
const proxyCache = new Map<string, LocatorProxy>();
return new Proxy({} as any, {
get(_, property: string) {
// Build the current path
const currentPath = [...path, property];
// Convert the path to kebab-case and join with hyphens
const selectorValue = currentPath.map((segment) => kebabCase(segment)).join("-");
const selector = `[data-${dataAttribute}="${selectorValue}"]`;
// Create a locator for the current selector
const locator = ctx.locator(selector);
if (proxyCache.has(selector)) {
ConsoleLogger.debug(`Using cached locator for ${selector}`);
return proxyCache.get(selector)!;
}
// Return a new proxy that also behaves like a Locator
// This allows us to either continue chaining or use Locator methods
const nextProxy = new Proxy(locator, {
get(target, prop) {
if (typeof prop === "string") {
// The user is likely trying to access a property on the page.
if (prop === "$") {
return target as any;
}
if (prop === "expect") {
return expect(target);
}
}
// If the property exists on the Locator, use it
if (prop in target) {
const value = (target as any)[prop];
// Bind methods to the locator instance
if (typeof value === "function") {
return value.bind(target);
}
return value;
}
// Otherwise, continue building the path
return createProxy(currentPath)[prop];
},
});
proxyCache.set(selector, nextProxy as LocatorProxy);
return nextProxy;
},
});
}
return createProxy() as DeepLocatorProxy<T>;
}

View File

@@ -1,175 +0,0 @@
import { PageFixture } from "#e2e/fixtures/PageFixture";
import type { LocatorContext } from "#e2e/selectors/types";
import { expect, Page } from "@playwright/test";
export class FormFixture extends PageFixture {
static fixtureName = "Form";
//#region Selector Methods
//#endregion
//#region Field Methods
/**
* Set the value of a text input.
*
* @param fieldName The name of the form element.
* @param value the value to set.
*/
public fill = async (
fieldName: string,
value: string,
parent: LocatorContext = this.page,
): Promise<void> => {
const control = parent
.getByRole("textbox", {
name: fieldName,
})
.or(
parent.getByRole("spinbutton", {
name: fieldName,
}),
)
.first();
await expect(control, `Field (${fieldName}) should be visible`).toBeVisible();
await control.fill(value);
};
/**
* Set the value of a radio or checkbox input.
*
* @param fieldName The name of the form element.
* @param value the value to set.
*/
public setInputCheck = async (
fieldName: string,
value: boolean = true,
parent: LocatorContext = this.page,
): Promise<void> => {
const control = parent.locator("ak-switch-input", {
hasText: fieldName,
});
await control.scrollIntoViewIfNeeded();
await expect(control, `Field (${fieldName}) should be visible`).toBeVisible();
const currentChecked = await control
.getAttribute("checked")
.then((value) => value !== null);
if (currentChecked === value) {
return;
}
await control.click();
};
/**
* Set the value of a radio or checkbox input.
*
* @param fieldName The name of the form element.
* @param pattern the value to set.
*/
public setRadio = async (
groupName: string,
fieldName: string,
parent: LocatorContext = this.page,
): Promise<void> => {
const group = parent.getByRole("group", { name: groupName });
await expect(group, `Field "${groupName}" should be visible`).toBeVisible();
const control = parent.getByRole("radio", { name: fieldName });
await control.setChecked(true, {
force: true,
});
};
/**
* Set the value of a search select input.
*
* @param fieldLabel The name of the search select element.
* @param pattern The text to match against the search select entry.
*/
public selectSearchValue = async (
fieldLabel: string,
pattern: string | RegExp,
parent: LocatorContext = this.page,
): Promise<void> => {
const control = parent.getByRole("textbox", { name: fieldLabel });
await expect(
control,
`Search select control (${fieldLabel}) should be visible`,
).toBeVisible();
const fieldName = await control.getAttribute("name");
if (!fieldName) {
throw new Error(`Unable to find name attribute on search select (${fieldLabel})`);
}
// Find the search select input control and activate it.
await control.click();
const button = this.page
// ---
.locator(`div[data-managed-for*="${fieldName}"] button`, {
hasText: pattern,
});
if (!button) {
throw new Error(
`Unable to find an ak-search-select entry matching ${fieldLabel}:${pattern.toString()}`,
);
}
await button.click();
await this.page.keyboard.press("Tab");
await control.blur();
};
public setFormGroup = async (
pattern: string | RegExp,
value: boolean = true,
parent: LocatorContext = this.page,
) => {
const control = parent
.locator("ak-form-group", {
hasText: pattern,
})
.first();
const currentOpen = await control.getAttribute("open").then((value) => value !== null);
if (currentOpen === value) {
this.logger.debug(`Form group ${pattern} is already ${value ? "open" : "closed"}`);
return;
}
this.logger.debug(`Toggling form group ${pattern} to ${value ? "open" : "closed"}`);
await control.click();
if (value) {
await expect(control).toHaveAttribute("open");
} else {
await expect(control).not.toHaveAttribute("open");
}
};
//#endregion
//#region Lifecycle
constructor(page: Page, testName: string) {
super({ page, testName });
}
//#endregion
}

View File

@@ -1,30 +0,0 @@
import { ConsoleLogger, FixtureLogger } from "#logger/node";
import { Page } from "@playwright/test";
export interface PageFixtureOptions {
page: Page;
testName: string;
}
export abstract class PageFixture {
/**
* The name of the fixture.
*
* Used for logging.
*/
static fixtureName: string;
protected readonly logger: FixtureLogger;
protected readonly page: Page;
protected readonly testName: string;
constructor({ page, testName }: PageFixtureOptions) {
this.page = page;
this.testName = testName;
const Constructor = this.constructor as typeof PageFixture;
this.logger = ConsoleLogger.fixture(Constructor.fixtureName, this.testName);
}
}

View File

@@ -1,42 +0,0 @@
import { PageFixture } from "#e2e/fixtures/PageFixture";
import type { LocatorContext } from "#e2e/selectors/types";
import { Page } from "@playwright/test";
export type GetByRoleParameters = Parameters<Page["getByRole"]>;
export type ARIARole = GetByRoleParameters[0];
export type ARIAOptions = GetByRoleParameters[1];
export type ClickByName = (name: string) => Promise<void>;
export type ClickByRole = (
role: ARIARole,
options?: ARIAOptions,
context?: LocatorContext,
) => Promise<void>;
export class PointerFixture extends PageFixture {
public static fixtureName = "Pointer";
public click = (
name: string,
optionsOrRole?: ARIAOptions | ARIARole,
context: LocatorContext = this.page,
): Promise<void> => {
if (typeof optionsOrRole === "string") {
return context.getByRole(optionsOrRole, { name }).click();
}
const options = {
...optionsOrRole,
name,
};
return (
context
// ---
.getByRole("button", options)
.or(context.getByRole("link", options))
.click()
);
};
}

View File

@@ -1,119 +0,0 @@
import { PageFixture } from "#e2e/fixtures/PageFixture";
import { expect, Page } from "@playwright/test";
export const GOOD_USERNAME = "test-admin@goauthentik.io";
export const GOOD_PASSWORD = "test-runner";
export const BAD_USERNAME = "bad-username@bad-login.io";
export const BAD_PASSWORD = "-this-is-a-bad-password-";
export interface LoginInit {
username?: string;
password?: string;
to?: URL | string;
}
export class SessionFixture extends PageFixture {
static fixtureName = "Session";
public static readonly pathname = "/if/flow/default-authentication-flow/";
//#region Selectors
public $identificationStage = this.page.locator("ak-stage-identification");
/**
* The username field on the login page.
*/
public $usernameField = this.$identificationStage.locator('input[name="uidField"]');
/**
* The button to continue with the login process,
* typically to the password flow stage.
*/
public $submitUsernameStageButton = this.$identificationStage.locator('button[type="submit"]');
public $passwordStage = this.page.locator("ak-stage-password");
public $passwordField = this.$passwordStage.locator('input[name="password"]');
/**
* The button to submit the the login flow,
* typically redirecting to the authenticated interface.
*/
public $submitPasswordStageButton = this.$passwordStage.locator('button[type="submit"]');
/**
* A possible authentication failure message.
*/
public $authFailureMessage = this.page.locator(".pf-m-error");
//#endregion
constructor(page: Page, testName: string) {
super({ page, testName });
}
//#region Specific interactions
public async submitUsernameStage(username: string) {
this.logger.info("Submitting username stage", username);
await this.$usernameField.fill(username);
await expect(this.$submitUsernameStageButton).toBeEnabled();
await this.$submitUsernameStageButton.click();
}
public async submitPasswordStage(password: string) {
this.logger.info("Submitting password stage");
await this.$passwordField.fill(password);
await expect(this.$submitPasswordStageButton).toBeEnabled();
await this.$submitPasswordStageButton.click();
}
public checkAuthenticated = async (): Promise<boolean> => {
// TODO: Check if the user is authenticated via API
return true;
};
/**
* Log into the application.
*/
public async login({
username = GOOD_USERNAME,
password = GOOD_PASSWORD,
to = SessionFixture.pathname,
}: LoginInit = {}) {
this.logger.info("Logging in...");
const initialURL = new URL(this.page.url());
if (initialURL.pathname === SessionFixture.pathname) {
this.logger.info("Skipping navigation because we're already in a authentication flow");
} else {
await this.page.goto(to.toString());
}
await this.submitUsernameStage(username);
await this.$passwordField.waitFor({ state: "visible" });
await this.submitPasswordStage(password);
const expectedPathname = typeof to === "string" ? to : to.pathname;
await this.page.waitForURL(`**${expectedPathname}`);
}
//#endregion
//#region Navigation
public async toLoginPage() {
await this.page.goto(SessionFixture.pathname);
}
}

View File

@@ -1,56 +0,0 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { createLocatorProxy, DeepLocatorProxy } from "#e2e/elements/proxy";
import { FormFixture } from "#e2e/fixtures/FormFixture";
import { PointerFixture } from "#e2e/fixtures/PointerFixture";
import { SessionFixture } from "#e2e/fixtures/SessionFixture";
import { createOUIDNameEngine } from "#e2e/selectors/ouid";
import { test as base } from "@playwright/test";
export { expect } from "@playwright/test";
type TestIDLocatorProxy = DeepLocatorProxy<TestIDSelectorMap>;
interface E2EFixturesTestScope {
/**
* A proxy to retrieve elements by test ID.
*
* ```ts
* const $button = $.button;
* ```
*/
$: TestIDLocatorProxy;
session: SessionFixture;
pointer: PointerFixture;
form: FormFixture;
}
interface E2EWorkerScope {
selectorRegistration: void;
}
export const test = base.extend<E2EFixturesTestScope, E2EWorkerScope>({
selectorRegistration: [
async ({ playwright }, use) => {
await playwright.selectors.register("ouid", createOUIDNameEngine);
await use();
},
{ auto: true, scope: "worker" },
],
$: async ({ page }, use) => {
await use(createLocatorProxy<TestIDSelectorMap>(page));
},
session: async ({ page }, use, { title }) => {
await use(new SessionFixture(page, title));
},
form: async ({ page }, use, { title }) => {
await use(new FormFixture(page, title));
},
pointer: async ({ page }, use, { title }) => {
await use(new PointerFixture({ page, testName: title }));
},
});

View File

@@ -1,44 +0,0 @@
/* eslint-disable no-console */
type SelectorRoot = Document | ShadowRoot;
export function createOUIDNameEngine() {
const attributeName = "data-ouid-component-name";
console.log("Creating OUID selector engine!!");
return {
// Returns all elements matching given selector in the root's subtree.
queryAll(scope: SelectorRoot, componentName: string) {
const result: Element[] = [];
const match = (element: Element) => {
const name = element.getAttribute(attributeName);
if (name === componentName) {
result.push(element);
}
};
const query = (root: Element | ShadowRoot | Document) => {
const shadows: ShadowRoot[] = [];
if ((root as Element).shadowRoot) {
shadows.push((root as Element).shadowRoot!);
}
for (const element of root.querySelectorAll("*")) {
match(element);
if (element.shadowRoot) {
shadows.push(element.shadowRoot);
}
}
shadows.forEach(query);
};
query(scope);
return result;
},
};
}

View File

@@ -1,13 +0,0 @@
import type { Locator } from "@playwright/test";
export type LocatorContext = Pick<
Locator,
| "locator"
| "getByRole"
| "getByTestId"
| "getByText"
| "getByLabel"
| "getByAltText"
| "getByTitle"
| "getByPlaceholder"
>;

View File

@@ -1,60 +0,0 @@
import { IDGenerator } from "@goauthentik/core/id";
import {
adjectives,
colors,
Config as NameConfig,
uniqueNamesGenerator,
} from "unique-names-generator";
/**
* Given a dictionary of words, slice the dictionary to only include words that start with the given letter.
*/
export function alliterate(dictionary: string[], letter: string): string[] {
let firstIndex = 0;
for (let i = 0; i < dictionary.length; i++) {
if (dictionary[i][0] === letter) {
firstIndex = i;
break;
}
}
let lastIndex = firstIndex;
for (let i = firstIndex; i < dictionary.length; i++) {
if (dictionary[i][0] !== letter) {
lastIndex = i;
break;
}
}
return dictionary.slice(firstIndex, lastIndex);
}
export function createRandomName({
seed = IDGenerator.randomID(),
...config
}: Partial<NameConfig> = {}) {
const randomLetterIndex =
typeof seed === "number"
? seed
: Array.from(seed).reduce((acc, char) => acc + char.charCodeAt(0), 0);
const letter = adjectives[randomLetterIndex % adjectives.length][0];
const availableAdjectives = alliterate(adjectives, letter);
const availableColors = alliterate(colors, letter);
const name = uniqueNamesGenerator({
dictionaries: [availableAdjectives, availableAdjectives, availableColors],
style: "capital",
separator: " ",
length: 3,
seed,
...config,
});
return name;
}

View File

@@ -1,102 +0,0 @@
/**
* Application logger.
*
* @import { LoggerOptions, Logger, Level, ChildLoggerOptions } from "pino"
* @import { PrettyOptions } from "pino-pretty"
*/
import { pino } from "pino";
//#region Constants
/**
* Default options for creating a Pino logger.
*
* @category Logger
* @satisfies {LoggerOptions<never, false>}
*/
export const DEFAULT_PINO_LOGGER_OPTIONS = {
enabled: true,
level: "info",
transport: {
target: "./transport.js",
options: /** @satisfies {PrettyOptions} */ ({
colorize: true,
}),
},
};
//#endregion
//#region Functions
/**
* Read the log level from the environment.
* @return {Level}
*/
export function readLogLevel() {
return process.env.AK_LOG_LEVEL || DEFAULT_PINO_LOGGER_OPTIONS.level;
}
/**
* @typedef {Logger} FixtureLogger
*/
/**
* @this {Logger}
* @param {string} fixtureName
* @param {string} [testName]
* @param {ChildLoggerOptions} [options]
* @returns {FixtureLogger}
*/
function createFixtureLogger(fixtureName, testName, options) {
return this.child(
{ name: fixtureName },
{
msgPrefix: `[${testName}] `,
...options,
},
);
}
/**
* @typedef {object} CustomLoggerMethods
* @property {typeof createFixtureLogger} fixture
*/
/**
* @typedef {Logger & CustomLoggerMethods} ConsoleLogger
*/
/**
* A singleton logger instance for Node.js.
*
* ```js
* import { ConsoleLogger } from "#logger/node";
*
* ConsoleLogger.info("Hello, world!");
* ```
*
* @runtime node
* @type {ConsoleLogger}
*/
export const ConsoleLogger = Object.assign(
pino({
...DEFAULT_PINO_LOGGER_OPTIONS,
level: readLogLevel(),
}),
{ fixture: createFixtureLogger },
);
/**
* @typedef {ReturnType<ConsoleLogger['child']>} ChildConsoleLogger
*/
//#region Aliases
export const info = ConsoleLogger.info.bind(ConsoleLogger);
export const debug = ConsoleLogger.debug.bind(ConsoleLogger);
export const warn = ConsoleLogger.warn.bind(ConsoleLogger);
export const error = ConsoleLogger.error.bind(ConsoleLogger);
//#endregion

View File

@@ -1,22 +0,0 @@
/**
* @file Pretty transport for Pino
*
* @import { PrettyOptions } from "pino-pretty"
*/
import PinoPretty from "pino-pretty";
/**
* @param {PrettyOptions} options
*/
function prettyTransporter(options) {
const pretty = PinoPretty({
...options,
ignore: "pid,hostname",
translateTime: "SYS:HH:MM:ss",
});
return pretty;
}
export default prettyTransporter;

4337
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -24,8 +24,8 @@
"pseudolocalize": "node ./scripts/pseudolocalize.mjs",
"storybook": "storybook dev -p 6006",
"storybook:build": "wireit",
"test": "vitest",
"test:e2e": "playwright test",
"test": "wireit",
"test:e2e": "wireit",
"test:e2e:watch": "wireit",
"test:watch": "wireit",
"tsc": "wireit",
@@ -69,9 +69,6 @@
"#flow/*": "./src/flow/*.js",
"#locales/*": "./src/locales/*.js",
"#stories/*": "./src/stories/*.js",
"#tests/*": "./tests/*.js",
"#e2e": "./e2e/index.ts",
"#e2e/*": "./e2e/*.ts",
"#*/browser": {
"types": "./out/*/browser.d.ts",
"import": "./*/browser.js"
@@ -94,10 +91,10 @@
"@codemirror/legacy-modes": "^6.5.1",
"@codemirror/theme-one-dark": "^6.1.3",
"@eslint/js": "^9.31.0",
"@floating-ui/dom": "^1.7.2",
"@floating-ui/dom": "^1.7.3",
"@formatjs/intl-listformat": "^7.7.11",
"@fortawesome/fontawesome-free": "^7.0.0",
"@goauthentik/api": "^2025.6.4-1753714826",
"@goauthentik/api": "^2025.6.4-1754241870",
"@goauthentik/core": "^1.0.0",
"@goauthentik/esbuild-plugin-live-reload": "^1.1.0",
"@goauthentik/eslint-config": "^1.0.5",
@@ -116,13 +113,12 @@
"@openlayers-elements/maps": "^0.4.0",
"@patternfly/elements": "^4.1.0",
"@patternfly/patternfly": "^4.224.2",
"@playwright/test": "^1.54.1",
"@sentry/browser": "^9.42.1",
"@sentry/browser": "^10.0.0",
"@spotlightjs/spotlight": "^3.0.1",
"@storybook/addon-docs": "^9.0.18",
"@storybook/addon-links": "^9.0.18",
"@storybook/web-components": "^9.0.18",
"@storybook/web-components-vite": "^9.0.18",
"@storybook/addon-docs": "^9.1.0",
"@storybook/addon-links": "^9.1.0",
"@storybook/web-components": "^9.1.0",
"@storybook/web-components-vite": "^9.1.0",
"@types/codemirror": "^5.60.16",
"@types/grecaptcha": "^3.0.9",
"@types/guacamole-common-js": "^1.5.3",
@@ -132,17 +128,11 @@
"@types/react-dom": "^19.1.6",
"@typescript-eslint/eslint-plugin": "^8.38.0",
"@typescript-eslint/parser": "^8.38.0",
"@vitest/browser": "^3.2.4",
"@wdio/browser-runner": "^9.18.4",
"@wdio/cli": "9.15",
"@wdio/spec-reporter": "^9.15.0",
"@web/test-runner": "^0.20.2",
"@webcomponents/webcomponentsjs": "^2.8.0",
"base64-js": "^1.5.1",
"change-case": "^5.4.4",
"chart.js": "^4.5.0",
"chartjs-adapter-date-fns": "^3.0.0",
"chromedriver": "^138.0.4",
"codemirror": "^6.0.2",
"construct-style-sheets-polyfill": "^3.1.0",
"core-js": "^3.44.0",
@@ -168,9 +158,6 @@
"md-front-matter": "^1.0.4",
"mermaid": "^11.9.0",
"npm-run-all": "^4.1.5",
"pino": "^9.7.0",
"pino-pretty": "^13.0.0",
"playwright": "^1.54.1",
"prettier": "^3.6.2",
"pseudolocale": "^2.1.0",
"rapidoc": "^9.3.8",
@@ -191,10 +178,7 @@
"turnstile-types": "^1.2.3",
"typescript": "^5.8.3",
"typescript-eslint": "^8.38.0",
"unique-names-generator": "^4.7.1",
"unist-util-visit": "^5.0.0",
"vite": "^7.0.6",
"vitest": "^3.2.4",
"webcomponent-qr-code": "^1.3.0",
"wireit": "^0.14.12",
"yaml": "^2.8.0"
@@ -203,9 +187,13 @@
"@esbuild/darwin-arm64": "^0.25.4",
"@esbuild/linux-arm64": "^0.25.4",
"@esbuild/linux-x64": "^0.25.4",
"@rollup/rollup-darwin-arm64": "^4.46.1",
"@rollup/rollup-linux-arm64-gnu": "^4.46.1",
"@rollup/rollup-linux-x64-gnu": "^4.46.1"
"@rollup/rollup-darwin-arm64": "^4.46.2",
"@rollup/rollup-linux-arm64-gnu": "^4.46.2",
"@rollup/rollup-linux-x64-gnu": "^4.46.2",
"@wdio/browser-runner": "^9.18.4",
"@wdio/cli": "^9.18.4",
"@wdio/spec-reporter": "^9.18.0",
"@web/test-runner": "^0.20.2"
},
"wireit": {
"build": {
@@ -278,7 +266,7 @@
"command": "lit-analyzer src"
},
"lint:types:tests": {
"command": "tsc --noEmit -p tsconfig.test.json"
"command": "tsc --noEmit -p ./tests"
},
"lint:types": {
"command": "tsc -p .",
@@ -327,7 +315,7 @@
],
"env": {
"CI": "true",
"TS_NODE_PROJECT": "tsconfig.test.json"
"TS_NODE_PROJECT": "./tests/tsconfig.test.json"
}
},
"test:e2e:watch": {
@@ -336,7 +324,7 @@
"build"
],
"env": {
"TS_NODE_PROJECT": "tsconfig.test.json"
"TS_NODE_PROJECT": "./tests/tsconfig.test.json"
}
},
"test:watch": {

View File

@@ -23,7 +23,7 @@
"formdata-polyfill": "^4.0.10",
"jquery": "^3.7.1",
"prettier": "^3.5.3",
"rollup": "^4.46.1",
"rollup": "^4.46.2",
"rollup-plugin-copy": "^3.5.0",
"weakmap-polyfill": "^2.0.4"
},

View File

@@ -1,94 +0,0 @@
/**
* @file Playwright configuration.
*
* @see https://playwright.dev/docs/test-configuration
*
* @import { LogFn, Logger } from "pino"
*/
import { ConsoleLogger } from "#logger/node";
import { defineConfig, devices } from "@playwright/test";
const CI = !!process.env.CI;
/**
* @type {Map<string, Logger>}
*/
const LoggerCache = new Map();
const baseURL = process.env.AK_TEST_RUNNER_PAGE_URL ?? "http://localhost:9000";
export default defineConfig({
testDir: "./test/browser",
fullyParallel: true,
forbidOnly: CI,
retries: CI ? 2 : 0,
workers: CI ? 1 : undefined,
reporter: CI
? "github"
: [
// ---
["list", { printSteps: true }],
["html", { open: "never" }],
],
use: {
testIdAttribute: "data-test-id",
baseURL,
trace: "on-first-retry",
launchOptions: {
logger: {
isEnabled() {
return true;
},
log: (name, severity, message, args) => {
let logger = LoggerCache.get(name);
if (!logger) {
logger = ConsoleLogger.child({
name: `Playwright ${name.toUpperCase()}`,
});
LoggerCache.set(name, logger);
}
/**
* @type {LogFn}
*/
let log;
switch (severity) {
case "verbose":
log = logger.debug;
break;
case "warning":
log = logger.warn;
break;
case "error":
log = logger.error;
break;
default:
log = logger.info;
break;
}
if (name === "api") {
log = logger.debug;
}
log.call(logger, message.toString(), args);
},
},
},
},
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
},
},
],
});

View File

@@ -50,12 +50,13 @@ export class CoreGroupSearch extends CustomListenerElement(AKElement) {
search!: SearchSelect<Group>;
@property({ type: String })
public name?: string | null;
name: string | null | undefined;
selectedGroup?: Group;
constructor() {
super();
this.selected = this.selected.bind(this);
this.handleSearchUpdate = this.handleSearchUpdate.bind(this);
}
@@ -82,9 +83,9 @@ export class CoreGroupSearch extends CustomListenerElement(AKElement) {
this.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true }));
}
selected = (group: Group) => {
selected(group: Group) {
return this.group === group.pk;
};
}
render() {
return html`

View File

@@ -40,13 +40,7 @@ export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement)
search!: SearchSelect<CertificateKeyPair>;
@property({ type: String })
public name?: string | null;
@property({ type: String })
public label?: string | undefined;
@property({ type: String })
public placeholder?: string | undefined;
name: string | null | undefined;
/**
* Set to `true` to allow certificates without private key to show up. When set to `false`,
@@ -54,7 +48,7 @@ export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement)
* @attr
*/
@property({ type: Boolean, attribute: "nokey" })
public noKey = false;
noKey = false;
/**
* Set this to true if, should there be only one certificate available, you want the system to
@@ -63,12 +57,16 @@ export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement)
* @attr
*/
@property({ type: Boolean, attribute: "singleton" })
public singleton = false;
singleton = false;
/**
* @todo Document this.
*/
public selectedKeypair?: CertificateKeyPair;
selectedKeypair?: CertificateKeyPair;
constructor() {
super();
this.selected = this.selected.bind(this);
this.fetchObjects = this.fetchObjects.bind(this);
this.handleSearchUpdate = this.handleSearchUpdate.bind(this);
}
get value() {
return this.selectedKeypair ? renderValue(this.selectedKeypair) : null;
@@ -87,13 +85,13 @@ export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement)
}
}
handleSearchUpdate = (ev: CustomEvent) => {
handleSearchUpdate(ev: CustomEvent) {
ev.stopPropagation();
this.selectedKeypair = ev.detail.value;
this.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true }));
};
}
fetchObjects = async (query?: string): Promise<CertificateKeyPair[]> => {
async fetchObjects(query?: string): Promise<CertificateKeyPair[]> {
const args: CryptoCertificatekeypairsListRequest = {
ordering: "name",
hasKey: !this.noKey,
@@ -106,21 +104,19 @@ export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement)
args,
);
return certificates.results;
};
}
selected = (item: CertificateKeyPair, items: CertificateKeyPair[]) => {
selected(item: CertificateKeyPair, items: CertificateKeyPair[]) {
return (
(this.singleton && !this.certificate && items.length === 1) ||
(!!this.certificate && this.certificate === item.pk)
);
};
}
render() {
return html`
<ak-search-select
name=${ifDefined(this.name ?? undefined)}
label=${ifDefined(this.label ?? undefined)}
placeholder=${ifDefined(this.placeholder)}
.fetchObjects=${this.fetchObjects}
.renderElement=${renderElement}
.value=${renderValue}

View File

@@ -3,7 +3,6 @@ import "#elements/forms/SearchSelect/index";
import { DEFAULT_CONFIG } from "#common/api/config";
import { AKElement } from "#elements/Base";
import type { HorizontalFormElement } from "#elements/forms/HorizontalFormElement";
import { SearchSelect } from "#elements/forms/SearchSelect/index";
import { CustomListenerElement } from "#elements/utils/eventEmitter";
@@ -12,7 +11,6 @@ import { RenderFlowOption } from "#admin/flows/utils";
import type { Flow, FlowsInstancesListRequest } from "@goauthentik/api";
import { FlowsApi, FlowsInstancesListDesignationEnum } from "@goauthentik/api";
import { msg } from "@lit/localize";
import { html } from "lit";
import { property, query } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
@@ -35,17 +33,17 @@ export function getFlowValue(flow: Flow | undefined): string | undefined {
* A wrapper around SearchSelect that understands the basic semantics of querying about Flows. This
* code eliminates the long blocks of unreadable invocation that were embedded in every provider, as well as in
* sources, brands, and applications.
*
*/
export abstract class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement) {
//#region Properties
export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement) {
/**
* The type of flow we're looking for.
*
* @attr
*/
@property({ type: String })
public flowType?: FlowsInstancesListDesignationEnum;
flowType?: FlowsInstancesListDesignationEnum;
/**
* The id of the current flow, if any. For stages where the flow is already defined.
@@ -53,7 +51,7 @@ export abstract class FlowSearch<T extends Flow> extends CustomListenerElement(A
* @attr
*/
@property({ type: String })
public currentFlow?: string | undefined;
currentFlow?: string | undefined;
/**
* If true, it is not valid to leave the flow blank.
@@ -61,7 +59,10 @@ export abstract class FlowSearch<T extends Flow> extends CustomListenerElement(A
* @attr
*/
@property({ type: Boolean })
public required?: boolean = false;
required?: boolean = false;
@query("ak-search-select")
search!: SearchSelect<T>;
/**
* When specified and the object instance does not have a flow selected, auto-select the flow with the given slug.
@@ -69,81 +70,60 @@ export abstract class FlowSearch<T extends Flow> extends CustomListenerElement(A
* @attr
*/
@property()
public defaultFlowSlug?: string;
defaultFlowSlug?: string;
@property({ type: String })
public name?: string;
name: string | null | undefined;
/**
* The label of the input, for forms.
*
* @attr
*/
@property({ type: String })
public label?: string;
/**
* The textual placeholder for the search's <input> object, if currently empty. Used as the
* native <input> object's `placeholder` field.
*
* @attr
*/
@property({ type: String })
public placeholder = msg("Select a flow...");
@query("ak-search-select")
protected search!: SearchSelect<T>;
protected selectedFlow?: T;
selectedFlow?: T;
get value() {
return this.selectedFlow ? getFlowValue(this.selectedFlow) : null;
}
protected searchUpdateListener = (event: CustomEvent) => {
event.stopPropagation();
this.selectedFlow = event.detail.value;
constructor() {
super();
this.fetchObjects = this.fetchObjects.bind(this);
this.selected = this.selected.bind(this);
this.handleSearchUpdate = this.handleSearchUpdate.bind(this);
}
handleSearchUpdate(ev: CustomEvent) {
ev.stopPropagation();
this.selectedFlow = ev.detail.value;
this.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true }));
};
}
protected fetchObjects = (query?: string): Promise<Flow[]> => {
async fetchObjects(query?: string): Promise<Flow[]> {
const args: FlowsInstancesListRequest = {
ordering: "slug",
designation: this.flowType,
...(query ? { search: query } : {}),
...(query !== undefined ? { search: query } : {}),
};
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(args).then((flows) => flows.results);
};
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(args);
return flows.results;
}
/* This is the most commonly overridden method of this class. About half of the Flow Searches
* use this method, but several have more complex needs, such as relating to the brand, or just
* returning false.
*/
protected selected(flow: Flow): boolean {
selected(flow: Flow): boolean {
let selected = this.currentFlow === flow.pk;
if (!this.currentFlow && this.defaultFlowSlug && flow.slug === this.defaultFlowSlug) {
return true;
selected = true;
}
return this.currentFlow === flow.pk;
return selected;
}
connectedCallback() {
super.connectedCallback();
const horizontalContainer = this.closest<HorizontalFormElement>(
"ak-form-element-horizontal[name]",
);
const horizontalContainer = this.closest("ak-form-element-horizontal[name]");
if (!horizontalContainer) {
throw new Error("This search can only be used in a named ak-form-element-horizontal");
}
const name = horizontalContainer.getAttribute("name");
const myName = this.getAttribute("name");
if (name !== null && name !== myName) {
this.setAttribute("name", name);
}
@@ -157,10 +137,8 @@ export abstract class FlowSearch<T extends Flow> extends CustomListenerElement(A
.renderElement=${renderElement}
.renderDescription=${renderDescription}
.value=${getFlowValue}
placeholder=${ifDefined(this.placeholder)}
label=${ifDefined(this.label)}
name=${ifDefined(this.name)}
@ak-change=${this.searchUpdateListener}
name=${ifDefined(this.name ?? undefined)}
@ak-change=${this.handleSearchUpdate}
?blankable=${!this.required}
>
</ak-search-select>

View File

@@ -19,9 +19,14 @@ export class AkBrandedFlowSearch<T extends Flow> extends FlowSearch<T> {
* @attr
*/
@property({ attribute: false, type: String })
public brandFlow?: string;
brandFlow?: string;
protected override selected(flow: Flow): boolean {
constructor() {
super();
this.selected = this.selected.bind(this);
}
selected(flow: Flow): boolean {
return super.selected(flow) || flow.pk === this.brandFlow;
}
}

View File

@@ -24,7 +24,7 @@ export class AkFlowSearchNoDefault<T extends Flow> extends FlowSearch<T> {
.renderElement=${renderElement}
.renderDescription=${renderDescription}
.value=${getFlowValue}
@ak-change=${this.searchUpdateListener}
@ak-change=${this.handleSearchUpdate}
?blankable=${!this.required}
>
</ak-search-select>

View File

@@ -18,8 +18,9 @@ export class AkSourceFlowSearch<T extends Flow> extends FlowSearch<T> {
*
* @attr
*/
@property({ type: String })
public fallback?: string;
fallback: string | undefined;
/**
* The primary key of the Source (not the Flow). Mostly the instancePk itself, used to affirm
@@ -28,11 +29,16 @@ export class AkSourceFlowSearch<T extends Flow> extends FlowSearch<T> {
* @attr
*/
@property({ type: String })
public instanceId?: string;
instanceId: string | undefined;
constructor() {
super();
this.selected = this.selected.bind(this);
}
// If there's no instance or no currentFlowId for it and the flow resembles the fallback,
// otherwise defer to the parent class.
protected override selected(flow: Flow): boolean {
selected(flow: Flow): boolean {
return (
(!this.instanceId && !this.currentFlow && flow.slug === this.fallback) ||
super.selected(flow)

View File

@@ -10,25 +10,35 @@ import { html, nothing } from "lit";
import { customElement, property } from "lit/decorators.js";
@customElement("ak-license-notice")
export class AkLicenceNotice extends WithLicenseSummary(AKElement) {
export class AKLicenceNotice extends WithLicenseSummary(AKElement) {
static styles = [$PFBase];
@property()
notice = msg("Enterprise only");
public label = msg("Enterprise only");
@property()
public description = msg("Learn more about the enterprise license.");
render() {
return this.hasEnterpriseLicense
? nothing
: html`
<ak-alert class="pf-c-radio__description" inline plain>
<a href="#/enterprise/licenses">${this.notice}</a>
</ak-alert>
`;
if (this.hasEnterpriseLicense) {
return nothing;
}
return html`
<ak-alert class="pf-c-radio__description" inline plain>
<a
aria-label="${this.label}"
aria-description="${this.description}"
href="#/enterprise/licenses"
>${this.label}</a
>
</ak-alert>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-license-notice": AkLicenceNotice;
"ak-license-notice": AKLicenceNotice;
}
}

View File

@@ -140,8 +140,6 @@ export function renderForm(
.errorMessages=${errors?.certificate ?? []}
>
<ak-crypto-certificate-search
label=${msg("Certificate")}
placeholder=${msg("Select a certificate...")}
certificate=${ifDefined(provider?.certificate ?? nothing)}
name="certificate"
>

View File

@@ -124,7 +124,6 @@ export function renderForm(
) {
return html` <ak-text-input
name="name"
placeholder=${msg("Provider name")}
label=${msg("Name")}
value=${ifDefined(provider?.name)}
required
@@ -136,8 +135,6 @@ export function renderForm(
required
>
<ak-flow-search
label=${msg("Authorization flow")}
placeholder=${msg("Select an authorization flow...")}
flowType=${FlowsInstancesListDesignationEnum.Authorization}
.currentFlow=${provider?.authorizationFlow}
required
@@ -200,8 +197,6 @@ export function renderForm(
<ak-form-element-horizontal label=${msg("Signing Key")} name="signingKey">
<!-- NOTE: 'null' cast to 'undefined' on signingKey to satisfy Lit requirements -->
<ak-crypto-certificate-search
label=${msg("Signing Key")}
placeholder=${msg("Select a signing key...")}
certificate=${ifDefined(provider?.signingKey ?? undefined)}
singleton
></ak-crypto-certificate-search>
@@ -210,8 +205,6 @@ export function renderForm(
<ak-form-element-horizontal label=${msg("Encryption Key")} name="encryptionKey">
<!-- NOTE: 'null' cast to 'undefined' on encryptionKey to satisfy Lit requirements -->
<ak-crypto-certificate-search
label=${msg("Encryption Key")}
placeholder=${msg("Select an encryption key...")}
certificate=${ifDefined(provider?.encryptionKey ?? undefined)}
></ak-crypto-certificate-search>
<p class="pf-c-form__helper-text">${msg("Key used to encrypt the tokens.")}</p>
@@ -226,8 +219,6 @@ export function renderForm(
label=${msg("Authentication flow")}
>
<ak-flow-search
label=${msg("Authentication flow")}
placeholder=${msg("Select an authentication flow...")}
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authenticationFlow}
></ak-flow-search>
@@ -243,8 +234,6 @@ export function renderForm(
required
>
<ak-flow-search
label=${msg("Invalidation flow")}
placeholder=${msg("Select an invalidation flow...")}
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
.currentFlow=${provider?.invalidationFlow}
defaultFlowSlug="default-provider-invalidation-flow"

View File

@@ -26,7 +26,6 @@ import {
RbacPermissionsAssignedByUsersListModelEnum,
User,
} from "@goauthentik/api";
import { IDGenerator } from "@goauthentik/core/id";
import MDProviderOAuth2 from "~docs/add-secure-apps/providers/oauth2/index.mdx";
@@ -268,16 +267,12 @@ export class OAuth2ProviderViewPage extends AKElement {
<div class="pf-c-card__body">
<form class="pf-c-form">
<div class="pf-c-form__group">
<label
class="pf-c-form__label"
for="${IDGenerator.elementID("providerInfo")}"
>
<label class="pf-c-form__label">
<span class="pf-c-form__label-text"
>${msg("OpenID Configuration URL")}</span
>
</label>
<input
id="${IDGenerator.elementID("providerInfo")}"
class="pf-c-form-control"
readonly
type="text"
@@ -285,16 +280,12 @@ export class OAuth2ProviderViewPage extends AKElement {
/>
</div>
<div class="pf-c-form__group">
<label
class="pf-c-form__label"
for="${IDGenerator.elementID("issuer")}"
>
<label class="pf-c-form__label">
<span class="pf-c-form__label-text"
>${msg("OpenID Configuration Issuer")}</span
>
</label>
<input
id="${IDGenerator.elementID("issuer")}"
class="pf-c-form-control"
readonly
type="text"
@@ -303,16 +294,12 @@ export class OAuth2ProviderViewPage extends AKElement {
</div>
<hr class="pf-c-divider" />
<div class="pf-c-form__group">
<label
class="pf-c-form__label"
for="${IDGenerator.elementID("authorize")}"
>
<label class="pf-c-form__label">
<span class="pf-c-form__label-text"
>${msg("Authorize URL")}</span
>
</label>
<input
id="${IDGenerator.elementID("authorize")}"
class="pf-c-form-control"
readonly
type="text"
@@ -320,14 +307,10 @@ export class OAuth2ProviderViewPage extends AKElement {
/>
</div>
<div class="pf-c-form__group">
<label
class="pf-c-form__label"
for="${IDGenerator.elementID("token")}"
>
<label class="pf-c-form__label">
<span class="pf-c-form__label-text">${msg("Token URL")}</span>
</label>
<input
id="${IDGenerator.elementID("token")}"
class="pf-c-form-control"
readonly
type="text"
@@ -335,16 +318,12 @@ export class OAuth2ProviderViewPage extends AKElement {
/>
</div>
<div class="pf-c-form__group">
<label
class="pf-c-form__label"
for="${IDGenerator.elementID("userInfo")}"
>
<label class="pf-c-form__label">
<span class="pf-c-form__label-text"
>${msg("Userinfo URL")}</span
>
</label>
<input
id="${IDGenerator.elementID("userInfo")}"
class="pf-c-form-control"
readonly
type="text"
@@ -352,14 +331,10 @@ export class OAuth2ProviderViewPage extends AKElement {
/>
</div>
<div class="pf-c-form__group">
<label
class="pf-c-form__label"
for="${IDGenerator.elementID("logout")}"
>
<label class="pf-c-form__label">
<span class="pf-c-form__label-text">${msg("Logout URL")}</span>
</label>
<input
id="${IDGenerator.elementID("logout")}"
class="pf-c-form-control"
readonly
type="text"
@@ -367,14 +342,10 @@ export class OAuth2ProviderViewPage extends AKElement {
/>
</div>
<div class="pf-c-form__group">
<label
class="pf-c-form__label"
for="${IDGenerator.elementID("jwks")}"
>
<label class="pf-c-form__label">
<span class="pf-c-form__label-text">${msg("JWKS URL")}</span>
</label>
<input
id="${IDGenerator.elementID("jwks")}"
class="pf-c-form-control"
readonly
type="text"
@@ -420,12 +391,9 @@ export class OAuth2ProviderViewPage extends AKElement {
${renderDescriptionList(
[
[
html`<label for="${IDGenerator.elementID("preview-user")}"
>${msg("Preview for user")}</label
>`,
msg("Preview for user"),
html`
<ak-search-select
id="${IDGenerator.elementID("preview-user")}"
.fetchObjects=${async (query?: string): Promise<User[]> => {
const args: CoreUsersListRequest = {
ordering: "username",

View File

@@ -45,7 +45,6 @@ export function renderForm(
<ak-text-input
name="name"
label=${msg("Name")}
placeholder=${msg("Provider name")}
value=${ifDefined(provider?.name)}
.errorMessages=${errors?.name ?? []}
required
@@ -59,8 +58,6 @@ export function renderForm(
.errorMessages=${errors?.authorizationFlow ?? []}
>
<ak-branded-flow-search
label=${msg("Authentication flow")}
placeholder=${msg("Select an authentication flow...")}
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authorizationFlow}
.brandFlow=${brand?.flowAuthentication}

View File

@@ -16,6 +16,7 @@ import {
FlowsInstancesListDesignationEnum,
PropertymappingsApi,
PropertymappingsProviderSamlListRequest,
SAMLNameIDPolicyEnum,
SAMLPropertyMapping,
SAMLProvider,
SpBindingEnum,
@@ -314,6 +315,54 @@ export function renderForm(
"When using IDP-initiated logins, the relay state will be set to this value.",
)}
></ak-text-input>
<ak-form-element-horizontal
label=${msg("Default NameID Policy")}
required
name="defaultNameIdPolicy"
>
<select class="pf-c-form-control">
<option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
?selected=${provider?.defaultNameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
>
${msg("Persistent")}
</option>
<option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
?selected=${provider?.defaultNameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
>
${msg("Email address")}
</option>
<option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
?selected=${provider?.defaultNameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
>
${msg("Windows")}
</option>
<option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
?selected=${provider?.defaultNameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
>
${msg("X509 Subject")}
</option>
<option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
?selected=${provider?.defaultNameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
>
${msg("Transient")}
</option>
</select>
<p class="pf-c-form__helper-text">
${msg(
"Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).",
)}
</p>
</ak-form-element-horizontal>
<ak-radio-input
name="digestAlgorithm"

View File

@@ -74,8 +74,8 @@ export class InitialPermissionsForm extends ModelForm<InitialPermissions, string
if (query !== undefined) {
args.search = query;
}
const users = await new RbacApi(DEFAULT_CONFIG).rbacRolesList(args);
return users.results;
const roles = await new RbacApi(DEFAULT_CONFIG).rbacRolesList(args);
return roles.results;
}}
.renderElement=${(role: Role): string => {
return role.name;

View File

@@ -23,7 +23,7 @@ import {
DigestAlgorithmEnum,
FlowsInstancesListDesignationEnum,
GroupMatchingModeEnum,
NameIdPolicyEnum,
SAMLNameIDPolicyEnum,
SAMLSource,
SignatureAlgorithmEnum,
SourcesApi,
@@ -351,37 +351,37 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
>
<select class="pf-c-form-control">
<option
value=${NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
?selected=${this.instance?.nameIdPolicy ===
NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
>
${msg("Persistent")}
</option>
<option
value=${NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
?selected=${this.instance?.nameIdPolicy ===
NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
>
${msg("Email address")}
</option>
<option
value=${NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
?selected=${this.instance?.nameIdPolicy ===
NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
>
${msg("Windows")}
</option>
<option
value=${NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
?selected=${this.instance?.nameIdPolicy ===
NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
>
${msg("X509 Subject")}
</option>
<option
value=${NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
?selected=${this.instance?.nameIdPolicy ===
NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
>
${msg("Transient")}
</option>

View File

@@ -35,11 +35,6 @@
--ak-navbar--height: 7rem;
}
.pf-c-form__group {
--pf-c-form--m-horizontal__group-label--md--GridColumnWidth: minmax(max-content, 9.375rem);
column-gap: var(--pf-global--spacer--md);
}
@supports selector(::-webkit-scrollbar) {
::-webkit-scrollbar {
width: 5px;

View File

@@ -292,7 +292,7 @@ export function applyDocumentTheme(hint: CSSColorSchemeValue | UIThemeHint = "au
* @todo Can this be handled with a Lit Mixin?
*/
export function rootInterface<T extends HTMLElement = HTMLElement>(): T {
const element = document.body.querySelector<T>("[data-test-id=interface-root]");
const element = document.body.querySelector<T>("[data-ak-interface-root]");
if (!element) {
throw new Error(

View File

@@ -1,14 +1,10 @@
import { SlottedTemplateResult } from "#elements/types";
import { html, nothing, TemplateResult } from "lit";
import { classMap } from "lit/directives/class-map.js";
import { map } from "lit/directives/map.js";
export type DescriptionPair = [
term: SlottedTemplateResult,
desc: SlottedTemplateResult | undefined,
];
export type DescriptionRecord = { term: string; desc: SlottedTemplateResult | undefined };
export type DescriptionDesc = string | TemplateResult | undefined | typeof nothing;
export type DescriptionPair = [string, DescriptionDesc];
export type DescriptionRecord = { term: string; desc: DescriptionDesc };
interface DescriptionConfig {
horizontal?: boolean;

View File

@@ -175,6 +175,7 @@ export class NavigationButtons extends AKElement {
return html`<img
class="pf-c-page__header-tools-item pf-c-avatar pf-m-hidden pf-m-visible-on-xl"
src=${ifDefined(this.me?.user.avatar)}
aria-hidden="true"
alt="${msg("Avatar image")}"
/>`;
}
@@ -189,7 +190,7 @@ export class NavigationButtons extends AKElement {
}
render() {
return html`<div class="pf-c-page__header-tools">
return html`<div role="presentation" class="pf-c-page__header-tools">
<div class="pf-c-page__header-tools-group">
${this.renderApiDrawerTrigger()}
<!-- -->

View File

@@ -88,7 +88,7 @@ export class AKPageNavbar
color: var(--ak-dark-foreground);
}
navbar {
.main-content {
border-bottom: var(--pf-global--BorderWidth--sm);
border-bottom-style: solid;
border-bottom-color: var(--pf-global--BorderColor--100);
@@ -350,7 +350,12 @@ export class AKPageNavbar
renderIcon() {
if (this.icon) {
if (this.iconImage && !this.icon.startsWith("fa://")) {
return html`<img class="accent-icon pf-icon" src="${this.icon}" alt="page icon" />`;
return html`<img
aria-hidden="true"
class="accent-icon pf-icon"
src="${this.icon}"
alt="page icon"
/>`;
}
const icon = this.icon.replaceAll("fa://", "fa ");
@@ -362,9 +367,9 @@ export class AKPageNavbar
render(): TemplateResult {
return html` <slot></slot>
<navbar aria-label="Main" class="navbar">
<aside class="brand ${this.open ? "" : "pf-m-collapsed"}">
<a href="#/">
<div role="banner" aria-label="Main" class="main-content">
<aside role="presentation" class="brand ${this.open ? "" : "pf-m-collapsed"}">
<a aria-label="${msg("Home")}" href="#/">
<div class="logo">
<img
src=${themeImage(this.brandingLogo)}
@@ -375,31 +380,35 @@ export class AKPageNavbar
</a>
</aside>
<button
aria-controls="global-nav"
class="sidebar-trigger pf-c-button pf-m-plain"
@click=${this.#toggleSidebar}
aria-label=${msg("Toggle sidebar")}
aria-label=${this.open ? msg("Collapse navigation") : msg("Expand navigation")}
aria-expanded=${this.open ? "true" : "false"}
>
<i class="fas fa-bars"></i>
<i aria-hidden="true" class="fas fa-bars"></i>
</button>
<section
class="items primary pf-c-content ${this.description ? "block-sibling" : ""}"
>
<h1 class="page-title">
<div class="items primary pf-c-content ${this.description ? "block-sibling" : ""}">
<h1 aria-labelledby="page-navbar-heading" class="page-title">
${this.hasIcon
? html`<slot name="icon">${this.renderIcon()}</slot>`
? html`<slot aria-hidden="true" name="icon">${this.renderIcon()}</slot>`
: nothing}
${this.header}
<span id="page-navbar-heading">${this.header}</span>
</h1>
</section>
</div>
${this.description
? html`<section class="items page-description pf-c-content">
? html`<div
role="heading"
aria-level="2"
aria-label="${this.description}"
class="items page-description pf-c-content"
>
<p>${this.description}</p>
</section>`
</div>`
: nothing}
<section class="items secondary">
<div class="items secondary">
<div class="pf-c-page__header-tools-group">
<ak-nav-buttons .uiConfig=${this.uiConfig} .me=${this.session}>
<a
@@ -411,8 +420,8 @@ export class AKPageNavbar
</a>
</ak-nav-buttons>
</div>
</section>
</navbar>`;
</div>
</div>`;
}
//#endregion

View File

@@ -28,6 +28,6 @@ export abstract class Interface extends AKElement {
public connectedCallback(): void {
super.connectedCallback();
this.dataset.testId = "interface-root";
this.dataset.akInterfaceRoot = this.tagName.toLowerCase();
}
}

View File

@@ -103,7 +103,7 @@ type ContentValue = SlottedTemplateResult | undefined;
*/
export function akLoadingOverlay(
properties: ILoadingOverlay = {},
content: string | ILoadingOverlayContent = {},
content: ILoadingOverlayContent = {},
) {
// `heading` here is an Object.key of ILoadingOverlayContent, not the obsolete
// slot-name.

View File

@@ -58,10 +58,6 @@ export class HorizontalFormElement extends AKElement {
grid-template-columns:
var(--pf-c-form--m-horizontal__group-label--md--GridColumnWidth)
var(--pf-c-form--m-horizontal__group-control--md--GridColumnWidth);
&[data-flow-direction="row"] {
grid-template-columns: 1fr;
}
}
.pf-c-form__group-label {

View File

@@ -31,99 +31,73 @@ export interface ISearchSelectBase<T> {
emptyOption: string;
}
export abstract class SearchSelectBase<T>
extends AkControlElement<string>
implements ISearchSelectBase<T>
{
export class SearchSelectBase<T> extends AkControlElement<string> implements ISearchSelectBase<T> {
static styles = [PFBase];
//#region Properties
// A function which takes the query state object (accepting that it may be empty) and returns a
// new collection of objects.
fetchObjects!: (query?: string) => Promise<T[]>;
/**
* A function which takes the query state object (accepting that it may be empty)
* and returns a
* new collection of objects.
*/
public abstract fetchObjects: (query?: string) => Promise<T[]>;
// A function passed to this object that extracts a string representation of items of the
// collection under search.
renderElement!: (element: T) => string;
/**
* A function passed to this object that extracts a string representation of items of the
* collection under search.
*/
public abstract renderElement: (element: T) => string;
// A function passed to this object that extracts an HTML representation of additional
// information for items of the collection under search.
renderDescription?: (element: T) => string | TemplateResult;
/**
* A function passed to this object that extracts an HTML representation of additional
* information for items of the collection under search.
*/
public abstract renderDescription?: (element: T) => string | TemplateResult;
// A function which returns the currently selected object's primary key, used for serialization
// into forms.
value!: (element: T | undefined) => string;
/**
* A function which returns the currently selected object's primary key, used for serialization
* into forms.
*/
public abstract value: (element?: T) => string;
// A function passed to this object that determines an object in the collection under search
// should be automatically selected. Only used when the search itself is responsible for
// fetching the data; sets an initial default value.
selected?: (element: T, elements: T[]) => boolean;
/**
* A function passed to this object that determines an object in the collection under search
* should be automatically selected. Only used when the search itself is responsible for
* fetching the data; sets an initial default value.
*/
public abstract selected?: (element: T, elements: T[]) => boolean;
/**
* A function passed to this object (or using the default below) that groups objects in the
* collection under search into categories.
*/
public groupBy: (items: T[]) => [string, T[]][] = (items) => {
return groupBy(items, () => "");
// A function passed to this object (or using the default below) that groups objects in the
// collection under search into categories.
groupBy: (items: T[]) => [string, T[]][] = (items: T[]): [string, T[]][] => {
return groupBy(items, () => {
return "";
});
};
// Whether or not the dropdown component can be left blank
@property({ type: Boolean })
public blankable = false;
blankable = false;
// An initial string to filter the search contents, and the value of the input which further
// serves to restrict the search
@property()
public query?: string;
query?: string;
// The objects currently available under search
@property({ attribute: false })
public objects?: T[];
objects?: T[];
// The currently selected object
@property({ attribute: false })
public selectedObject?: T;
selectedObject?: T;
// Used to inform the form of the name of the object
@property()
public name?: string;
// Used to inform the form of the input label.
@property()
public label?: string;
name?: string;
// The textual placeholder for the search's <input> object, if currently empty. Used as the
// native <input> object's `placeholder` field.
@property()
public placeholder: string = msg("Select an object.");
placeholder: string = msg("Select an object.");
// A textual string representing "The user has affirmed they want to leave the selection blank."
// Only used if `blankable` above is true.
@property()
public emptyOption = "---------";
emptyOption = "---------";
//#endregion
//#region State
#loading = false;
isFetchingData = false;
@state()
protected error?: APIError;
//#endregion
error?: APIError;
public toForm(): string {
if (!this.objects) {
@@ -147,29 +121,26 @@ export abstract class SearchSelectBase<T>
}
public async updateData() {
if (this.#loading) {
if (this.isFetchingData) {
return Promise.resolve();
}
this.#loading = true;
this.isFetchingData = true;
this.dispatchEvent(new Event("loading"));
return this.fetchObjects(this.query)
.then((nextObjects) => {
if (this.selected) {
for (const obj of nextObjects) {
if (this.selected(obj, nextObjects)) {
this.selectedObject = obj;
this.dispatchChangeEvent(this.selectedObject);
}
nextObjects.forEach((obj) => {
if (this.selected && this.selected(obj, nextObjects || [])) {
this.selectedObject = obj;
this.dispatchChangeEvent(this.selectedObject);
}
}
});
this.objects = nextObjects;
this.#loading = false;
this.isFetchingData = false;
})
.catch(async (error: unknown) => {
this.#loading = false;
this.isFetchingData = false;
this.objects = undefined;
const parsedError = await parseAPIResponseError(error);
@@ -192,10 +163,9 @@ export abstract class SearchSelectBase<T>
this.removeEventListener(EVENT_REFRESH, this.updateData);
}
#searchListener = (event: InputEvent) => {
private onSearch(event: InputEvent) {
const value = (event.target as SearchSelectView).rawValue;
if (!value) {
if (value === undefined) {
this.selectedObject = undefined;
return;
}
@@ -204,7 +174,7 @@ export abstract class SearchSelectBase<T>
this.updateData()?.then(() => {
this.dispatchChangeEvent(this.selectedObject);
});
};
}
private onSelect(event: InputEvent) {
const value = (event.target as SearchSelectView).value;
@@ -288,22 +258,21 @@ export abstract class SearchSelectBase<T>
.options=${options}
value=${ifDefined(value)}
?blankable=${this.blankable}
label=${ifDefined(this.label)}
name=${ifDefined(this.name)}
placeholder=${this.placeholder}
emptyOption=${ifDefined(this.blankable ? this.emptyOption : undefined)}
@input=${this.#searchListener}
@input=${this.onSearch}
@change=${this.onSelect}
></ak-search-select-view> `;
}
public override updated(changed: PropertyValues<this>) {
if (!this.#loading && changed.has("objects")) {
if (!this.isFetchingData && changed.has("objects")) {
this.dispatchEvent(new Event("ready"));
}
// It is not safe for automated tests to interact with this component while it is fetching
// data.
if (!this.#loading) {
if (!this.isFetchingData) {
this.setAttribute("data-ouia-component-safe", "true");
}
}

View File

@@ -47,26 +47,18 @@ export interface ISearchSelectEz<T> extends ISearchSelectBase<T> {
export class SearchSelectEz<T> extends SearchSelectBase<T> implements ISearchSelectEz<T> {
static styles = [...SearchSelectBase.styles];
public fetchObjects!: (query?: string) => Promise<T[]>;
public renderElement!: (element: T) => string;
public renderDescription?: ((element: T) => string | TemplateResult) | undefined;
public value!: (element?: T | undefined) => string;
public selected?: ((element: T, elements: T[]) => boolean) | undefined;
@property({ type: Object, attribute: false })
public config!: ISearchSelectApi<T>;
config!: ISearchSelectApi<T>;
public override connectedCallback() {
connectedCallback() {
this.fetchObjects = this.config.fetchObjects;
this.renderElement = this.config.renderElement;
this.renderDescription = this.config.renderDescription;
this.value = this.config.value;
this.selected = this.config.selected;
if (this.config.groupBy) {
if (this.config.groupBy !== undefined) {
this.groupBy = this.config.groupBy;
}
super.connectedCallback();
}
}

View File

@@ -5,11 +5,12 @@ import { findFlatOptions, findOptionsSubset, groupOptions, optionsToFlat } from
import { ListSelect } from "#elements/ak-list-select/ak-list-select";
import { AKElement } from "#elements/Base";
import { bound } from "#elements/decorators/bound";
import type { GroupedOptions, SelectOption, SelectOptions } from "#elements/types";
import { randomId } from "#elements/utils/randomId";
import { msg } from "@lit/localize";
import { CSSResult, html, nothing, PropertyValues } from "lit";
import { html, nothing, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { createRef, ref, Ref } from "lit/directives/ref.js";
@@ -69,9 +70,7 @@ export interface ISearchSelectView {
*/
@customElement("ak-search-select-view")
export class SearchSelectView extends AKElement implements ISearchSelectView {
static styles: CSSResult[] = [PFBase, PFForm, PFFormControl, PFSelect];
//#region Properties
static styles = [PFBase, PFForm, PFFormControl, PFSelect];
/**
* The options collection. The simplest variant is just [key, label, optional<description>]. See
@@ -80,16 +79,16 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
* @prop
*/
@property({ type: Array, attribute: false })
public set options(options: SelectOptions) {
this.#options = groupOptions(options);
this.#flatOptions = optionsToFlat(this.#options);
set options(options: SelectOptions) {
this._options = groupOptions(options);
this.flatOptions = optionsToFlat(this._options);
}
public get options() {
return this.#options;
get options() {
return this._options;
}
#options!: GroupedOptions;
_options!: GroupedOptions;
/**
* The current value. Must be one of the keys in the options group above.
@@ -97,7 +96,7 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
* @prop
*/
@property({ type: String, reflect: true })
public value?: string;
value?: string;
/**
* Whether or not the dropdown is open
@@ -105,7 +104,7 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
* @attr
*/
@property({ type: Boolean, reflect: true })
public open = false;
open = false;
/**
* If set to true, this object MAY return undefined in no value is passed in and none is set
@@ -114,7 +113,7 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
* @attr
*/
@property({ type: Boolean })
public blankable = false;
blankable = false;
/**
* If not managed, make the matcher case-sensitive during interaction. If managed,
@@ -123,23 +122,15 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
* @attr
*/
@property({ type: Boolean, attribute: "case-sensitive" })
public caseSensitive = false;
caseSensitive = false;
/**
* The name of the input, for forms.
* The name of the input, for forms
*
* @attr
*/
@property({ type: String })
public name?: string;
/**
* The label of the input, for forms.
*
* @attr
*/
@property({ type: String })
public label?: string;
name?: string;
/**
* The textual placeholder for the search's <input> object, if currently empty. Used as the
@@ -148,7 +139,7 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
* @attr
*/
@property({ type: String })
public placeholder: string = msg("Select an object.");
placeholder: string = msg("Select an object.");
/**
* If true, the component only sends an input message up to a parent component. If false, the
@@ -158,7 +149,7 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
*@attr
*/
@property({ type: Boolean })
public managed = false;
managed = false;
/**
* A textual string representing "The user has affirmed they want to leave the selection blank."
@@ -167,50 +158,36 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
* @attr
*/
@property()
public emptyOption = "---------";
emptyOption = "---------";
//#endregion
// Handle the behavior of the drop-down when the :host scrolls off the page.
scrollHandler?: () => void;
//#region State
// observer: IntersectionObserver;
@state()
protected displayValue = "";
displayValue = "";
// Tracks when the inputRef is populated, so we can safely reschedule the
// render of the dropdown with respect to it.
@state()
protected inputRefIsAvailable = false;
inputRefIsAvailable = false;
/**
* Permanent identity with the portal so focus events can be checked.
*/
#menuRef: Ref<ListSelect> = createRef();
menuRef: Ref<ListSelect> = createRef();
/**
* Permanent identify for the input object, so the floating portal can find where to anchor
* itself.
*/
#inputRef: Ref<HTMLInputElement> = createRef();
inputRef: Ref<HTMLInputElement> = createRef();
/**
* Maps a value from the portal to labels to be put into the <input> field>
* Maps a value from the portal to labels to be put into the <input> field>
*/
#flatOptions: [string, SelectOption][] = [];
//#endregion
//#region Lifecycle
public override updated() {
this.setAttribute("data-ouia-component-safe", "true");
}
public override firstUpdated() {
// Route around Lit's scheduling algorithm complaining about re-renders
window.setTimeout(() => {
this.inputRefIsAvailable = Boolean(this.#inputRef?.value);
}, 0);
}
flatOptions: [string, SelectOption][] = [];
connectedCallback() {
super.connectedCallback();
@@ -226,26 +203,24 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
// TODO
}
//#endregion
//#region Event Listeners
#clickListener = (_ev: Event) => {
@bound
onClick(_ev: Event) {
this.open = !this.open;
this.#inputRef.value?.focus();
};
this.inputRef.value?.focus();
}
setFromMatchList(value: string | undefined) {
if (value === undefined) {
return;
}
const probableValue = this.#flatOptions.find((option) => option[0] === this.value);
if (probableValue && this.#inputRef.value) {
this.#inputRef.value.value = probableValue[1][1];
const probableValue = this.flatOptions.find((option) => option[0] === this.value);
if (probableValue && this.inputRef.value) {
this.inputRef.value.value = probableValue[1][1];
}
}
#searchKeydownListener = (event: KeyboardEvent) => {
@bound
onKeydown(event: KeyboardEvent) {
if (event.code === "Escape") {
event.stopPropagation();
this.open = false;
@@ -256,44 +231,45 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
if (event.code === "Tab" && this.open) {
event.preventDefault();
this.setFromMatchList(this.value);
this.#menuRef.value?.currentElement?.focus();
this.menuRef.value?.currentElement?.focus();
}
};
}
#blurListener = (event: FocusEvent) => {
@bound
onListBlur(event: FocusEvent) {
// If we lost focus but the menu got it, don't do anything;
const relatedTarget = event.relatedTarget as HTMLElement | undefined;
if (
relatedTarget &&
(this.contains(relatedTarget) ||
this.renderRoot.contains(relatedTarget) ||
this.#menuRef.value?.contains(relatedTarget) ||
this.#menuRef.value?.renderRoot.contains(relatedTarget))
this.menuRef.value?.contains(relatedTarget) ||
this.menuRef.value?.renderRoot.contains(relatedTarget))
) {
return;
}
this.open = false;
if (!this.value) {
if (this.#inputRef.value) {
this.#inputRef.value.value = "";
if (this.value === undefined) {
if (this.inputRef.value) {
this.inputRef.value.value = "";
}
this.setValue(undefined);
}
};
}
setValue(newValue: string | undefined) {
this.value = newValue;
this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
this.dispatchEvent(new Event("change", { bubbles: true, composed: true })); // prettier-ignore
}
findValueForInput() {
const value = this.#inputRef.value?.value;
const value = this.inputRef.value?.value;
if (value === undefined || value.trim() === "") {
this.setValue(undefined);
return;
}
const matchesFound = findFlatOptions(this.#flatOptions, value);
const matchesFound = findFlatOptions(this.flatOptions, value);
if (matchesFound.length > 0) {
const newValue = matchesFound[0][0];
if (newValue === value) {
@@ -305,46 +281,47 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
}
}
#inputListener = (_ev: InputEvent) => {
@bound
onInput(_ev: InputEvent) {
if (!this.managed) {
this.findValueForInput();
this.requestUpdate();
}
this.open = true;
};
}
#listKeydownListener = (event: KeyboardEvent) => {
@bound
onListKeydown(event: KeyboardEvent) {
if (event.key === "Escape") {
this.open = false;
this.#inputRef.value?.focus();
this.inputRef.value?.focus();
}
if (event.key === "Tab" && event.shiftKey) {
event.preventDefault();
this.#inputRef.value?.focus();
this.inputRef.value?.focus();
}
};
}
#changeListener = (event: InputEvent) => {
@bound
onListChange(event: InputEvent) {
if (!event.target) {
return;
}
const value = (event.target as HTMLInputElement).value;
if (value) {
if (value !== undefined) {
const newDisplayValue = this.findDisplayForValue(value);
if (this.#inputRef.value) {
this.#inputRef.value.value = newDisplayValue ?? "";
if (this.inputRef.value) {
this.inputRef.value.value = newDisplayValue ?? "";
}
} else if (this.#inputRef.value) {
this.#inputRef.value.value = "";
} else if (this.inputRef.value) {
this.inputRef.value.value = "";
}
this.open = false;
this.setValue(value);
};
//#endregion
}
findDisplayForValue(value: string) {
const newDisplayValue = this.#flatOptions.find((option) => option[0] === value);
const newDisplayValue = this.flatOptions.find((option) => option[0] === value);
return newDisplayValue ? newDisplayValue[1][1] : undefined;
}
@@ -363,17 +340,15 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
}
get rawValue() {
return this.#inputRef.value?.value ?? "";
return this.inputRef.value?.value ?? "";
}
get managedOptions() {
return this.managed
? this.#options
: findOptionsSubset(this.#options, this.rawValue, this.caseSensitive);
? this._options
: findOptionsSubset(this._options, this.rawValue, this.caseSensitive);
}
//#region Render
public override render() {
const emptyOption = this.blankable ? this.emptyOption : undefined;
const open = this.open;
@@ -386,15 +361,13 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
autocomplete="off"
class="pf-c-form-control pf-c-select__toggle-typeahead"
type="text"
${ref(this.#inputRef)}
${ref(this.inputRef)}
placeholder=${this.placeholder}
aria-label=${ifDefined(this.label)}
name=${ifDefined(this.name)}
spellcheck="false"
@input=${this.#inputListener}
@click=${this.#clickListener}
@blur=${this.#blurListener}
@keydown=${this.#searchKeydownListener}
@input=${this.onInput}
@click=${this.onClick}
@blur=${this.onListBlur}
@keydown=${this.onKeydown}
value=${this.displayValue}
/>
</div>
@@ -404,25 +377,34 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
? html`
<ak-portal
name=${ifDefined(this.name)}
.anchor=${this.#inputRef.value}
.anchor=${this.inputRef.value}
?open=${open}
>
<ak-list-select
id="menu-${this.getAttribute("data-ouia-component-id")}"
${ref(this.#menuRef)}
${ref(this.menuRef)}
.options=${this.managedOptions}
value=${ifDefined(this.value)}
@change=${this.#changeListener}
@blur=${this.#blurListener}
@change=${this.onListChange}
@blur=${this.onListBlur}
emptyOption=${ifDefined(emptyOption)}
@keydown=${this.#listKeydownListener}
@keydown=${this.onListKeydown}
></ak-list-select>
</ak-portal>
`
: nothing}`;
}
//#endregion
public override updated() {
this.setAttribute("data-ouia-component-safe", "true");
}
public override firstUpdated() {
// Route around Lit's scheduling algorithm complaining about re-renders
window.setTimeout(() => {
this.inputRefIsAvailable = Boolean(this.inputRef?.value);
}, 0);
}
}
declare global {

View File

@@ -44,28 +44,44 @@ export interface ISearchSelect<T> extends ISearchSelectBase<T> {
* consequence of the user typing or when selecting from the list.
*
*/
@customElement("ak-search-select")
export class SearchSelect<T> extends SearchSelectBase<T> implements ISearchSelect<T> {
static styles = [...SearchSelectBase.styles];
// A function which takes the query state object (accepting that it may be empty) and returns a
// new collection of objects.
@property({ attribute: false })
public fetchObjects!: (query?: string) => Promise<T[]>;
fetchObjects!: (query?: string) => Promise<T[]>;
// A function passed to this object that extracts a string representation of items of the
// collection under search.
@property({ attribute: false })
public renderElement!: (element: T) => string;
renderElement!: (element: T) => string;
// A function passed to this object that extracts an HTML representation of additional
// information for items of the collection under search.
@property({ attribute: false })
public renderDescription?: (element: T) => string | TemplateResult;
renderDescription?: (element: T) => string | TemplateResult;
// A function which returns the currently selected object's primary key, used for serialization
// into forms.
@property({ attribute: false })
public value!: (element?: T) => string;
value!: (element: T | undefined) => string;
// A function passed to this object that determines an object in the collection under search
// should be automatically selected. Only used when the search itself is responsible for
// fetching the data; sets an initial default value.
@property({ attribute: false })
public selected?: (element: T, elements: T[]) => boolean;
selected?: (element: T, elements: T[]) => boolean;
// A function passed to this object (or using the default below) that groups objects in the
// collection under search into categories.
@property({ attribute: false })
public groupBy: (items: T[]) => [string, T[]][] = (items: T[]): [string, T[]][] => {
return groupBy(items, () => "");
groupBy: (items: T[]) => [string, T[]][] = (items: T[]): [string, T[]][] => {
return groupBy(items, () => {
return "";
});
};
}

View File

@@ -580,7 +580,7 @@ export class FlowExecutor
</div>
</div>
</div>
${(this.inspectorAvailable ?? !this.inspectorOpen)
${this.inspectorAvailable || !this.inspectorOpen
? html`<button
class="inspector-toggle pf-c-button pf-m-primary"
@click=${() => {

View File

@@ -184,7 +184,7 @@ export class LibraryPage extends AKElement {
render() {
return html`<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
<div class="pf-c-content header">
<h1 role="heading" aria-level="1" data-test-id="page-heading">
<h1 role="heading" aria-level="1" id="library-page-title">
${msg("My applications")}
</h1>
${this.uiConfig.searchEnabled ? this.renderSearch() : nothing}
@@ -193,13 +193,3 @@ export class LibraryPage extends AKElement {
</main>`;
}
}
declare global {
interface PageTestIDMap {
heading: HTMLHeadingElement;
}
interface TestIDSelectorMap {
page: PageTestIDMap;
}
}

View File

@@ -1,217 +0,0 @@
import { expect, test } from "#e2e";
import { createRandomName } from "#e2e/utils/generators";
import { ConsoleLogger } from "#logger/node";
import { IDGenerator } from "@goauthentik/core/id";
import { series } from "@goauthentik/core/promises";
test.describe("Provider Wizard", () => {
const providerNames = new Map<string, string>();
//#region Lifecycle
test.beforeEach("Configure Providers", async ({ page, session }, { testId }) => {
const seed = IDGenerator.randomID(6);
const providerName = `${createRandomName({ seed })} (${seed})`;
providerNames.set(testId, providerName);
const wizard = page.getByRole("dialog", { name: "New provider" });
await test.step("Authenticate", async () => {
await session.login({
to: "/if/admin/#/core/providers",
});
});
await test.step("Navigate to provider wizard", async () => {
await expect(wizard, "Wizard is initially closed").toBeHidden();
await page.getByRole("button", { name: "New Provider" }).click();
await expect(wizard, "Wizard opens after clicking on New Provider").toBeVisible();
await expect(
page.getByRole("listbox", { name: "Select a provider type" }),
"Wizard opens with a list of provider types",
).toBeVisible();
await expect(
wizard.getByRole("navigation").getByRole("button", {
name: /next|finish/i,
}),
"Wizard can't be navigated to next step",
).toBeDisabled();
});
});
test.afterEach("Verification", async ({ page }, { testId }) => {
//#region Confirm provider
const providerName = providerNames.get(testId)!;
const $provider = await test.step("Find provider via search", async () => {
const searchInput = page.getByRole("search").getByPlaceholder("Search for providers");
await searchInput.fill(providerName);
// We have to wait for the provider to appear in the table,
// but several UI elements will be rendered asynchronously.
// We attempt several times to find the provider to avoid flakiness.
const tries = 10;
let found = false;
for (let i = 0; i < tries; i++) {
await searchInput.press("Enter");
await searchInput.blur();
const $rowEntry = page.getByRole("row", {
name: providerName,
});
ConsoleLogger.info(
`${i + 1}/${tries} Waiting for provider ${providerName} to appear in the table`,
);
found = await $rowEntry
.waitFor({
timeout: 1500,
})
.then(() => true)
.catch(() => false);
if (found) {
ConsoleLogger.info(`Provider ${providerName} found in the table`);
return $rowEntry;
}
}
throw new Error(`Provider ${providerName} not found in the table`);
});
await expect($provider, "Provider is visible").toBeVisible();
//#endregion
});
//#endregion
//#region OAuth2
test("Simple OAuth2 Provider", async ({ form, pointer }, testInfo) => {
const providerName = providerNames.get(testInfo.testId)!;
const { fill, selectSearchValue } = form;
const { click } = pointer;
await series(
[click, "OAuth2/OpenID", "option"],
[click, "Next"],
[fill, "Provider name", providerName],
[
selectSearchValue,
"Authorization flow",
/default-provider-authorization-explicit-consent/,
],
[click, "Finish"],
);
});
test("Complete OAuth2 Provider", async ({ page, form, pointer }, testInfo) => {
const providerName = providerNames.get(testInfo.testId)!;
const { fill, selectSearchValue, setFormGroup, setRadio, setInputCheck } = form;
const { click } = pointer;
const $clientSecretInput = page.getByRole("textbox", { name: "Client Secret" });
await series(
[click, "OAuth2/OpenID", "option"],
[click, "Next"],
[fill, "Provider name", providerName],
[
selectSearchValue,
"Authorization flow",
/default-provider-authorization-explicit-consent/,
],
[setFormGroup, "Protocol settings", true],
[setRadio, "Client Type", "Public"],
[
expect(
$clientSecretInput,
"Client Secret should be hidden when Client Type is Public",
).toBeHidden,
],
[setRadio, "Client Type", "Confidential"],
[
expect(
$clientSecretInput,
"Client Secret should be visible when Client Type is Confidential",
).toBeVisible,
],
[selectSearchValue, "Signing Key", /authentik Self-signed Certificate/],
[selectSearchValue, "Encryption Key", /authentik Self-signed Certificate/],
[setFormGroup, "Advanced flow settings", true],
[selectSearchValue, "Authentication flow", /default-source-authentication/],
[selectSearchValue, "Invalidation flow", /default-invalidation-flow/],
[setFormGroup, "Advanced protocol settings", true],
[fill, "Access code validity", "minutes=2"],
[fill, "Access token validity", "minutes=10"],
[fill, "Refresh token validity", "days=40"],
[setInputCheck, "Include claims in id_token", false],
[setRadio, "Subject mode", "Based on the User's username"],
[setRadio, "Issuer mode", "Same identifier is used for all providers"],
[setFormGroup, "Machine-to-Machine authentication settings", true],
[click, "Finish", "button", page.getByRole("dialog", { name: "New Provider" })],
);
});
//#endregion
//#region LDAP
test("Complete LDAP Provider", async ({ page, pointer, form }, testInfo) => {
const providerName = providerNames.get(testInfo.testId)!;
const { fill, setFormGroup, selectSearchValue, setInputCheck, setRadio } = form;
const { click } = pointer;
await series(
[click, "LDAP", "option"],
[click, "Next"],
[fill, "Provider name", providerName],
[setFormGroup, "Flow settings", true],
[setFormGroup, "Protocol settings", true],
[selectSearchValue, "Bind flow", /default-authentication-flow/],
[fill, "Base DN", "DC=ldap-2,DC=goauthentik,DC=io"],
[selectSearchValue, "Certificate", /authentik Self-signed Certificate/],
[fill, "TLS Server name", "goauthentik.io"],
[fill, "UID start number", "2001"],
[fill, "GID start number", "4001"],
[setRadio, "Search mode", "Direct querying"],
[setRadio, "Bind mode", "Direct binding"],
[setInputCheck, "MFA Support", false],
[click, "Finish", "button", page.getByRole("dialog", { name: "New Provider" })],
);
});
//#endregion
//#region RADIUS
test("Complete RADIUS Provider", async ({ page, pointer, form }, testInfo) => {
const providerName = providerNames.get(testInfo.testId)!;
const { fill, selectSearchValue } = form;
const { click } = pointer;
await series(
[click, "RADIUS", "option"],
[click, "Next"],
[fill, "Provider name", providerName],
[selectSearchValue, "Authentication flow", /default-authentication-flow/],
[click, "Finish", "button", page.getByRole("dialog", { name: "New Provider" })],
);
});
//#endregion
});

View File

@@ -1,35 +0,0 @@
import { expect, test } from "#e2e";
import {
BAD_PASSWORD,
BAD_USERNAME,
GOOD_PASSWORD,
GOOD_USERNAME,
} from "#e2e/fixtures/SessionFixture";
test.beforeEach(async ({ session }) => {
await session.toLoginPage();
});
test.describe("Session management", () => {
test("Login with valid credentials", async ({ session, $ }) => {
await session.login({ username: GOOD_USERNAME, password: GOOD_PASSWORD });
await $.page.heading.expect.toHaveText("My applications");
});
test("Reject bad username", async ({ session }) => {
await session.submitUsernameStage(BAD_USERNAME);
await session.submitPasswordStage(GOOD_PASSWORD);
await expect(session.$authFailureMessage).toBeVisible();
await expect(session.$authFailureMessage).toHaveText("Invalid password");
});
test("Reject bad password", async ({ session }) => {
await session.submitUsernameStage(GOOD_USERNAME);
await session.submitPasswordStage(BAD_PASSWORD);
await expect(session.$authFailureMessage).toBeVisible();
await expect(session.$authFailureMessage).toHaveText("Invalid password");
});
});

View File

@@ -1,87 +0,0 @@
/**
* @file Vitest browser utilities for Lit.
*
* @import { LocatorSelectors } from '@vitest/browser/context'
* @import { PrettyDOMOptions } from '@vitest/browser/utils'
* @import { RenderOptions as LitRenderOptions } from 'lit'
*/
import { debug, getElementLocatorSelectors } from "@vitest/browser/utils";
import { render as renderLit } from "lit";
/**
* @implements {Disposable}
*/
export class LitViteContext {
/**
* @type {Set<Disposable>}
*/
static #resources = new Set();
/**
* @param {unknown} template
* @param {HTMLElement} [container]
* @param {LitRenderOptions} [options]
*
* @returns {LitViteContext}
*/
static render = (template, container = document.createElement("div"), options) => {
const context = new LitViteContext(container);
context.render(template, options);
return context;
};
static [Symbol.dispose] = () => {
this.#resources.forEach((resource) => resource[Symbol.dispose]());
this.#resources.clear();
};
static cleanup = () => {
return this[Symbol.dispose]();
};
/**
* @param {unknown} template
* @param {LitRenderOptions} [options]
*/
render(template, options) {
return renderLit(template, this.container, options);
}
/**
* @type {HTMLElement} container
*/
container;
/**
* @type {LocatorSelectors}
*/
$;
/**
* @param {HTMLElement} container
*/
constructor(container) {
this.container = container;
this.$ = getElementLocatorSelectors(container);
}
toFragment() {
return document.createRange().createContextualFragment(this.container.innerHTML);
}
/**
* @param {number} [maxLength]
* @param {PrettyDOMOptions} [options]
*/
debug(maxLength, options) {
return debug(this.container, maxLength, options);
}
[Symbol.dispose] = () => {
this.container.remove();
LitViteContext.#resources.delete(this);
};
}

View File

@@ -1,12 +0,0 @@
import { LitViteContext } from "./rendering.js";
import { page } from "@vitest/browser/context";
import { beforeEach } from "vitest";
page.extend({
// @ts-ignore
renderLit: LitViteContext.render,
[Symbol.for("vitest:component-cleanup")]: LitViteContext.cleanup,
});
beforeEach(() => LitViteContext.cleanup());

View File

@@ -1,9 +0,0 @@
import { expect, test } from "vitest";
function sum(a: number, b: number) {
return a + b;
}
test("adds 1 + 2 to equal 3", () => {
expect(sum(1, 2)).toBe(3);
});

View File

@@ -1,5 +1,6 @@
{
"compilerOptions": {
"strict": true,
"baseUrl": ".",
"moduleResolution": "node",
"module": "ESNext",

View File

@@ -2,11 +2,5 @@
{
"extends": "./tsconfig.json",
"exclude": [
// ---
"src/**/*.test.ts",
"src/**/*.comp.ts",
"./**/*.stories.ts",
"./tests"
]
"exclude": ["src/**/*.test.ts", "./tests"]
}

View File

@@ -1,5 +1,24 @@
// @file TSConfig used by the web package during build.
// @file TSConfig used during tests.
{
"extends": "./tsconfig.json"
"compilerOptions": {
"baseUrl": ".",
"types": ["node", "webdriverio/async", "@wdio/cucumber-framework", "expect-webdriverio"],
"target": "esnext",
"module": "esnext",
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true,
"lib": [
"ES5",
"ES2015",
"ES2016",
"ES2017",
"ES2018",
"ES2019",
"ES2020",
"ESNext",
"DOM",
"DOM.Iterable",
"WebWorker"
]
}
}

23
web/types/node.d.ts vendored
View File

@@ -14,13 +14,12 @@ declare module "module" {
* const relativeDirname = dirname(fileURLToPath(import.meta.url));
* ```
*/
var __dirname: string;
}
}
declare module "process" {
import { Level } from "pino";
global {
namespace NodeJS {
interface ProcessEnv {
@@ -31,26 +30,6 @@ declare module "process" {
* @see {@link https://nodejs.org/en/learn/getting-started/nodejs-the-difference-between-development-and-production | The difference between development and production}
*/
readonly NODE_ENV?: "development" | "production";
/**
* Whether or not we are running on a CI server.
*/
readonly CI?: string;
/**
* The application log level.
*/
readonly AK_LOG_LEVEL?: Level;
/**
* The base URL of web server to run the tests against.
*
* Typically this is `http://localhost:9000`.
*
* @format url
*/
readonly AK_TEST_RUNNER_PAGE_URL?: string;
/**
* @todo Determine where this is used and if it is needed,
* give it a better name.

View File

@@ -1,5 +1,3 @@
/// <reference types="vitest/config" />
import { createBundleDefinitions } from "#bundler/utils/node";
import { inlineCSSPlugin } from "#bundler/vite-plugin-lit-css/node";
@@ -11,41 +9,4 @@ export default defineConfig({
// ---
inlineCSSPlugin(),
],
test: {
dir: "./test",
exclude: [
"**/node_modules/**",
"**/dist/**",
"**/out/**",
"**/.{idea,git,cache,output,temp}/**",
"**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*",
],
projects: [
{
test: {
include: ["./unit/**/*.{test,spec}.ts", "**/*.unit.{test,spec}.ts"],
name: "unit",
environment: "node",
},
},
{
test: {
setupFiles: ["./test/lit/setup.js"],
include: ["./browser/**/*.{test,spec}.ts", "**/*.browser.{test,spec}.ts"],
name: "browser",
browser: {
enabled: true,
provider: "playwright",
instances: [
{
browser: "chromium",
},
],
},
},
},
],
},
});

View File

@@ -6620,7 +6620,7 @@ Bindings zu Gruppen/Benutzern werden mit dem Benutzer des Ereignisses abgegliche
</trans-unit>
<trans-unit id="saf63d34c8601dd41">
<source><x id="0" equiv-text="${prompt.label}"/></source>
<source><x id="0" equiv-text="${name}"/></source>
<target>
<x id="0" equiv-text="${prompt.label}"/>
</target>
@@ -9667,10 +9667,6 @@ Bindings zu Gruppen/Benutzern werden mit dem Benutzer des Ereignisses abgegliche
<source>Number of previous passwords to check</source>
<target>Anzahl der vorherigen Passwörter, die geprüft werden sollen</target>
</trans-unit>
<trans-unit id="sdd66c5a2e706fb81">
<source>Toggle sidebar</source>
<target>Seitenleiste umschalten</target>
</trans-unit>
<trans-unit id="s7d4ec232535a36f0">
<source>Choose a Provider</source>
<target>Wähle einen Provider</target>
@@ -9976,6 +9972,48 @@ Bindings zu Gruppen/Benutzern werden mit dem Benutzer des Ereignisses abgegliche
</trans-unit>
<trans-unit id="s5e13dff03b580216">
<source>Previous executions logs</source>
</trans-unit>
<trans-unit id="s6abb1cd87fe0114e">
<source>Home</source>
</trans-unit>
<trans-unit id="se58e6ed983bf34b0">
<source>Collapse navigation</source>
</trans-unit>
<trans-unit id="sc6ef25894ed00175">
<source>Expand navigation</source>
</trans-unit>
<trans-unit id="s148b5e365440a7c1">
<source>Table pagination</source>
</trans-unit>
<trans-unit id="s5d929ff1619ac0c9">
<source>Search</source>
</trans-unit>
<trans-unit id="sd2c2366d13599d8c">
<source>Table actions</source>
</trans-unit>
<trans-unit id="s3d195621e562d805">
<source>Select row</source>
</trans-unit>
<trans-unit id="s572d21b6a41e24fa">
<source>Table of <x id="0" equiv-text="${this.label}"/></source>
</trans-unit>
<trans-unit id="sa25b60b4fac481aa">
<source>Table content</source>
</trans-unit>
<trans-unit id="s5eba8fa19126f70a">
<source>Learn more about the enterprise license.</source>
</trans-unit>
<trans-unit id="s9db1679f3b234d4e">
<source>Search for providers…</source>
</trans-unit>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body>
</file>

View File

@@ -5299,7 +5299,7 @@ Bindings to groups/users are checked against the user of the event.</source>
<target>Change password</target>
</trans-unit>
<trans-unit id="saf63d34c8601dd41">
<source><x id="0" equiv-text="${prompt.label}"/></source>
<source><x id="0" equiv-text="${name}"/></source>
<target>
<x id="0" equiv-text="${prompt.label}"/>
</target>
@@ -7590,9 +7590,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s79b3fcd40dd63921">
<source>Number of previous passwords to check</source>
</trans-unit>
<trans-unit id="sdd66c5a2e706fb81">
<source>Toggle sidebar</source>
</trans-unit>
<trans-unit id="s7d4ec232535a36f0">
<source>Choose a Provider</source>
</trans-unit>
@@ -7853,6 +7850,48 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s5e13dff03b580216">
<source>Previous executions logs</source>
</trans-unit>
<trans-unit id="s6abb1cd87fe0114e">
<source>Home</source>
</trans-unit>
<trans-unit id="se58e6ed983bf34b0">
<source>Collapse navigation</source>
</trans-unit>
<trans-unit id="sc6ef25894ed00175">
<source>Expand navigation</source>
</trans-unit>
<trans-unit id="s148b5e365440a7c1">
<source>Table pagination</source>
</trans-unit>
<trans-unit id="s5d929ff1619ac0c9">
<source>Search</source>
</trans-unit>
<trans-unit id="sd2c2366d13599d8c">
<source>Table actions</source>
</trans-unit>
<trans-unit id="s3d195621e562d805">
<source>Select row</source>
</trans-unit>
<trans-unit id="s572d21b6a41e24fa">
<source>Table of <x id="0" equiv-text="${this.label}"/></source>
</trans-unit>
<trans-unit id="sa25b60b4fac481aa">
<source>Table content</source>
</trans-unit>
<trans-unit id="s5eba8fa19126f70a">
<source>Learn more about the enterprise license.</source>
</trans-unit>
<trans-unit id="s9db1679f3b234d4e">
<source>Search for providers…</source>
</trans-unit>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body>
</file>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" ?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<?xml version="1.0"?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file target-language="es" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="s4caed5b7a7e5d89b">
@@ -596,9 +596,9 @@
</trans-unit>
<trans-unit id="saa0e2675da69651b">
<source>The URL &quot;<x id="0" equiv-text="${this.url}"/>&quot; was not found.</source>
<target>El URL &quot;
<x id="0" equiv-text="${this.url}"/>&quot; no fue encontrado.</target>
<source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
<target>El URL "
<x id="0" equiv-text="${this.url}"/>" no fue encontrado.</target>
</trans-unit>
<trans-unit id="s58cd9c2fe836d9c6">
@@ -1693,7 +1693,7 @@
</trans-unit>
<trans-unit id="sa90b7809586c35ce">
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon &quot;fa-test&quot;.</source>
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
<target>Ingrese una URL completa, una ruta relativa o use 'fa: //fa-test' para usar el ícono Font Awesome «fa-test».</target>
</trans-unit>
@@ -3732,10 +3732,10 @@ no se aprueba cuando una o ambas de las opciones seleccionadas son iguales o sup
</trans-unit>
<trans-unit id="sa95a538bfbb86111">
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> &quot;<x id="1" equiv-text="${this.obj?.name}"/>&quot;?</source>
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
<target>¿Estás seguro de que deseas actualizar
<x id="0" equiv-text="${this.objectLabel}"/>&quot;
<x id="1" equiv-text="${this.obj?.name}"/>&quot;?</target>
<x id="0" equiv-text="${this.objectLabel}"/>"
<x id="1" equiv-text="${this.obj?.name}"/>"?</target>
</trans-unit>
<trans-unit id="sc92d7cfb6ee1fec6">
@@ -4790,8 +4790,8 @@ no se aprueba cuando una o ambas de las opciones seleccionadas son iguales o sup
</trans-unit>
<trans-unit id="sdf1d8edef27236f0">
<source>A &quot;roaming&quot; authenticator, like a YubiKey</source>
<target>Un autenticador &quot;roaming&quot;, como una YubiKey</target>
<source>A "roaming" authenticator, like a YubiKey</source>
<target>Un autenticador "roaming", como una YubiKey</target>
</trans-unit>
<trans-unit id="sfffba7b23d8fb40c">
@@ -5149,8 +5149,8 @@ no se aprueba cuando una o ambas de las opciones seleccionadas son iguales o sup
</trans-unit>
<trans-unit id="s1608b2f94fa0dbd4">
<source>If set to a duration above 0, the user will have the option to choose to &quot;stay signed in&quot;, which will extend their session by the time specified here.</source>
<target>Si se establece en una duración mayor a 0, el usuario tendrá la opción de &quot;mantener la sesión iniciada&quot;, lo que extenderá su sesión por el tiempo especificado aquí.</target>
<source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source>
<target>Si se establece en una duración mayor a 0, el usuario tendrá la opción de "mantener la sesión iniciada", lo que extenderá su sesión por el tiempo especificado aquí.</target>
</trans-unit>
<trans-unit id="s542a71bb8f41e057">
@@ -6625,7 +6625,7 @@ Las vinculaciones a grupos/usuarios se verifican en función del usuario del eve
</trans-unit>
<trans-unit id="saf63d34c8601dd41">
<source><x id="0" equiv-text="${prompt.label}"/></source>
<source><x id="0" equiv-text="${name}"/></source>
<target>
<x id="0" equiv-text="${prompt.label}"/>
</target>
@@ -7398,7 +7398,7 @@ Las vinculaciones a grupos/usuarios se verifican en función del usuario del eve
<target>Usuario creado correctamente y agregado al grupo <x id="0" equiv-text="${this.group.name}"/></target>
</trans-unit>
<trans-unit id="s824e0943a7104668">
<source>This user will be added to the group &quot;<x id="0" equiv-text="${this.targetGroup.name}"/>&quot;.</source>
<source>This user will be added to the group "<x id="0" equiv-text="${this.targetGroup.name}"/>".</source>
<target>Este usuario se agregará al grupo. &amp;quot;<x id="0" equiv-text="${this.targetGroup.name}"/>&amp;quot;.</target>
</trans-unit>
<trans-unit id="s62e7f6ed7d9cb3ca">
@@ -8660,7 +8660,7 @@ Las vinculaciones a grupos/usuarios se verifican en función del usuario del eve
<target>Sincronizar Grupo</target>
</trans-unit>
<trans-unit id="s2d5f69929bb7221d">
<source><x id="0" equiv-text="${p.name}"/> (&quot;<x id="1" equiv-text="${p.fieldKey}"/>&quot;, of type <x id="2" equiv-text="${p.type}"/>)</source>
<source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
<target><x id="0" equiv-text="${p.name}"/> (&amp;quot;<x id="1" equiv-text="${p.fieldKey}"/>&amp;quot;, of type <x id="2" equiv-text="${p.type}"/>)</target>
</trans-unit>
<trans-unit id="s25bacc19d98b444e">
@@ -8908,8 +8908,8 @@ Las vinculaciones a grupos/usuarios se verifican en función del usuario del eve
<target>URI de redirección válidas tras un flujo de autorización exitoso. Especifique también aquí los orígenes de los flujos implícitos.</target>
</trans-unit>
<trans-unit id="s4c49d27de60a532b">
<source>To allow any redirect URI, set the mode to Regex and the value to &quot;.*&quot;. Be aware of the possible security implications this can have.</source>
<target>Para permitir cualquier URI de redirección, configure el modo en Expresión Regular y el valor en &quot;.*&quot;. Tenga en cuenta las posibles implicaciones de seguridad que esto puede tener.</target>
<source>To allow any redirect URI, set the mode to Regex and the value to ".*". Be aware of the possible security implications this can have.</source>
<target>Para permitir cualquier URI de redirección, configure el modo en Expresión Regular y el valor en ".*". Tenga en cuenta las posibles implicaciones de seguridad que esto puede tener.</target>
</trans-unit>
<trans-unit id="sa52bf79fe1ccb13e">
<source>Federated OIDC Sources</source>
@@ -9655,8 +9655,8 @@ Si se deja vacío, AuthnContextClassRef se establecerá según los métodos de a
<target>Cómo realizar la autenticación durante un flujo de solicitud de token de código de autorización</target>
</trans-unit>
<trans-unit id="s844baf19a6c4a9b4">
<source>Enable &quot;Remember me on this device&quot;</source>
<target>Habilita &quot;Recordarme en este dispositivo&quot;</target>
<source>Enable "Remember me on this device"</source>
<target>Habilita "Recordarme en este dispositivo"</target>
</trans-unit>
<trans-unit id="sfa72bca733f40692">
<source>When enabled, the user can save their username in a cookie, allowing them to skip directly to entering their password.</source>
@@ -9674,10 +9674,6 @@ Si se deja vacío, AuthnContextClassRef se establecerá según los métodos de a
<source>Number of previous passwords to check</source>
<target>Número de contraseñas anteriores a verificar</target>
</trans-unit>
<trans-unit id="sdd66c5a2e706fb81">
<source>Toggle sidebar</source>
<target>Alternar barra lateral</target>
</trans-unit>
<trans-unit id="s7d4ec232535a36f0">
<source>Choose a Provider</source>
<target>Elige un Proveedor</target>
@@ -10024,7 +10020,49 @@ El valor de este campo se compara con el atributo de pertenencia del usuario.</t
</trans-unit>
<trans-unit id="s5e13dff03b580216">
<source>Previous executions logs</source>
</trans-unit>
<trans-unit id="s6abb1cd87fe0114e">
<source>Home</source>
</trans-unit>
<trans-unit id="se58e6ed983bf34b0">
<source>Collapse navigation</source>
</trans-unit>
<trans-unit id="sc6ef25894ed00175">
<source>Expand navigation</source>
</trans-unit>
<trans-unit id="s148b5e365440a7c1">
<source>Table pagination</source>
</trans-unit>
<trans-unit id="s5d929ff1619ac0c9">
<source>Search</source>
</trans-unit>
<trans-unit id="sd2c2366d13599d8c">
<source>Table actions</source>
</trans-unit>
<trans-unit id="s3d195621e562d805">
<source>Select row</source>
</trans-unit>
<trans-unit id="s572d21b6a41e24fa">
<source>Table of <x id="0" equiv-text="${this.label}"/></source>
</trans-unit>
<trans-unit id="sa25b60b4fac481aa">
<source>Table content</source>
</trans-unit>
<trans-unit id="s5eba8fa19126f70a">
<source>Learn more about the enterprise license.</source>
</trans-unit>
<trans-unit id="s9db1679f3b234d4e">
<source>Search for providers…</source>
</trans-unit>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body>
</file>
</xliff>
</xliff>

View File

@@ -6623,7 +6623,7 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
</trans-unit>
<trans-unit id="saf63d34c8601dd41">
<source><x id="0" equiv-text="${prompt.label}"/></source>
<source><x id="0" equiv-text="${name}"/></source>
<target>
<x id="0" equiv-text="${prompt.label}"/>
</target>
@@ -9671,10 +9671,6 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<source>Number of previous passwords to check</source>
<target>Nombre d'anciens mots de passe à vérifier</target>
</trans-unit>
<trans-unit id="sdd66c5a2e706fb81">
<source>Toggle sidebar</source>
<target>Afficher/masquer la barre latérale</target>
</trans-unit>
<trans-unit id="s7d4ec232535a36f0">
<source>Choose a Provider</source>
<target>Choisir un fournisseur</target>
@@ -9994,6 +9990,48 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
</trans-unit>
<trans-unit id="s5e13dff03b580216">
<source>Previous executions logs</source>
</trans-unit>
<trans-unit id="s6abb1cd87fe0114e">
<source>Home</source>
</trans-unit>
<trans-unit id="se58e6ed983bf34b0">
<source>Collapse navigation</source>
</trans-unit>
<trans-unit id="sc6ef25894ed00175">
<source>Expand navigation</source>
</trans-unit>
<trans-unit id="s148b5e365440a7c1">
<source>Table pagination</source>
</trans-unit>
<trans-unit id="s5d929ff1619ac0c9">
<source>Search</source>
</trans-unit>
<trans-unit id="sd2c2366d13599d8c">
<source>Table actions</source>
</trans-unit>
<trans-unit id="s3d195621e562d805">
<source>Select row</source>
</trans-unit>
<trans-unit id="s572d21b6a41e24fa">
<source>Table of <x id="0" equiv-text="${this.label}"/></source>
</trans-unit>
<trans-unit id="sa25b60b4fac481aa">
<source>Table content</source>
</trans-unit>
<trans-unit id="s5eba8fa19126f70a">
<source>Learn more about the enterprise license.</source>
</trans-unit>
<trans-unit id="s9db1679f3b234d4e">
<source>Search for providers…</source>
</trans-unit>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body>
</file>

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