Compare commits

..

20 Commits

Author SHA1 Message Date
Teffen Ellis
c04aa05930 web: Flesh out package. 2025-07-21 18:37:32 +02:00
Dewi Roberts
caecf5961d website/docs: add notification rule expression policy examples (#15333)
* WIP

* Typo fix

* Added mention of new doc in notification rules doc

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

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

---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
2025-07-21 16:32:41 +01:00
Dewi Roberts
4b211190b6 website/docs: add force password reset guide (#15654)
* Adds doc

* Improved code blocks

* Wording fix

* Move location and apply suggestions

* Typos

* Wording change

* Typo

* Wording improvements and typos

* Apply suggestions

* Apply suggestion from Tana

* Typo

* Update sidebar and fix relative link

* Prettier fix

* Link fix

* Added sidebar label
2025-07-21 16:31:52 +01:00
dependabot[bot]
2f22012f0f website: bump prettier-plugin-packagejson from 2.5.18 to 2.5.19 in /website (#15672)
website: bump prettier-plugin-packagejson in /website

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

---
updated-dependencies:
- dependency-name: prettier-plugin-packagejson
  dependency-version: 2.5.19
  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-21 17:31:23 +02:00
Teffen Ellis
10dffd8d13 website: Flesh out Makefile commands, usage. (#15576)
* website: Flesh out command behavior.

* restructure

* rearranged

---------

Co-authored-by: Tana M Berry <tana@goauthentik.io>
2025-07-21 10:29:58 -05:00
rattencreep
21a73fe58c website/integrations: fix duplicate guacamole section (#15684)
Update index.mdx

Removed doubled Self Signed Certificates section.

Signed-off-by: rattencreep <62957151+rattencreep@users.noreply.github.com>
2025-07-21 12:49:53 +01:00
dependabot[bot]
b0b915061e core: bump goauthentik.io/api/v3 from 3.2025063.5 to 3.2025063.6 (#15671)
---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  dependency-version: 3.2025063.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-21 13:37:38 +02:00
dependabot[bot]
6bcb758daa web: bump typedoc-plugin-markdown from 4.7.0 to 4.7.1 in /packages/esbuild-plugin-live-reload (#15681)
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.0 to 4.7.1.
- [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.7.1/packages/typedoc-plugin-markdown)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-21 13:36:32 +02:00
dependabot[bot]
b051c59ec4 web: bump the esbuild group across 2 directories with 4 updates (#15674)
Bumps the esbuild group with 1 update in the /packages/esbuild-plugin-live-reload directory: [esbuild](https://github.com/evanw/esbuild).
Bumps the esbuild group with 1 update in the /web directory: [esbuild](https://github.com/evanw/esbuild).


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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-21 13:36:21 +02:00
dependabot[bot]
35df455e3a web: bump @types/node from 24.0.14 to 24.0.15 in /packages/prettier-config (#15676)
web: bump @types/node in /packages/prettier-config

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

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.0.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-21 13:35:58 +02:00
dependabot[bot]
eb19e53bf3 website: bump @types/node from 24.0.14 to 24.0.15 in /website (#15675)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.0.14 to 24.0.15.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.0.15
  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-21 13:35:50 +02:00
dependabot[bot]
3badf80295 web: bump @types/node from 24.0.14 to 24.0.15 in /packages/esbuild-plugin-live-reload (#15677)
web: bump @types/node in /packages/esbuild-plugin-live-reload

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

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.0.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-21 13:35:41 +02:00
dependabot[bot]
b038e479e2 web: bump prettier-plugin-packagejson from 2.5.18 to 2.5.19 in /packages/prettier-config (#15678)
web: bump prettier-plugin-packagejson in /packages/prettier-config

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

---
updated-dependencies:
- dependency-name: prettier-plugin-packagejson
  dependency-version: 2.5.19
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-21 13:35:31 +02:00
dependabot[bot]
faaf4842a5 web: bump chart.js and @types/chart.js in /web (#15679)
Bumps [chart.js](https://github.com/chartjs/Chart.js) and [@types/chart.js](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/chart.js). These dependencies needed to be updated together.

Updates `chart.js` from 4.4.9 to 4.5.0
- [Release notes](https://github.com/chartjs/Chart.js/releases)
- [Commits](https://github.com/chartjs/Chart.js/compare/v4.4.9...v4.5.0)

Updates `@types/chart.js` from 2.9.41 to 4.0.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/chart.js)

---
updated-dependencies:
- dependency-name: chart.js
  dependency-version: 4.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: "@types/chart.js"
  dependency-version: 4.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-21 13:35:17 +02:00
dependabot[bot]
89ef57c68d web: bump the swc group across 1 directory with 11 updates (#15680)
Bumps the swc group with 1 update in the /web directory: [@swc/core](https://github.com/swc-project/swc).


Updates `@swc/core` from 1.13.0 to 1.13.1
- [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.0...v1.13.1)

Updates `@swc/core-darwin-arm64` from 1.13.0 to 1.13.1
- [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.0...v1.13.1)

Updates `@swc/core-darwin-x64` from 1.13.0 to 1.13.1
- [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.0...v1.13.1)

Updates `@swc/core-linux-arm-gnueabihf` from 1.13.0 to 1.13.1
- [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.0...v1.13.1)

Updates `@swc/core-linux-arm64-gnu` from 1.13.0 to 1.13.1
- [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.0...v1.13.1)

Updates `@swc/core-linux-arm64-musl` from 1.13.0 to 1.13.1
- [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.0...v1.13.1)

Updates `@swc/core-linux-x64-gnu` from 1.13.0 to 1.13.1
- [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.0...v1.13.1)

Updates `@swc/core-linux-x64-musl` from 1.13.0 to 1.13.1
- [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.0...v1.13.1)

Updates `@swc/core-win32-arm64-msvc` from 1.13.0 to 1.13.1
- [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.0...v1.13.1)

Updates `@swc/core-win32-ia32-msvc` from 1.13.0 to 1.13.1
- [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.0...v1.13.1)

Updates `@swc/core-win32-x64-msvc` from 1.13.0 to 1.13.1
- [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.0...v1.13.1)

---
updated-dependencies:
- dependency-name: "@swc/core"
  dependency-version: 1.13.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-arm64"
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-darwin-x64"
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm-gnueabihf"
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-gnu"
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-arm64-musl"
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-gnu"
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-linux-x64-musl"
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-arm64-msvc"
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-ia32-msvc"
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
- dependency-name: "@swc/core-win32-x64-msvc"
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swc
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-21 13:34:53 +02:00
dependabot[bot]
c3515299d1 web: bump prettier-plugin-packagejson from 2.5.18 to 2.5.19 in /packages/esbuild-plugin-live-reload (#15682)
web: bump prettier-plugin-packagejson

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

---
updated-dependencies:
- dependency-name: prettier-plugin-packagejson
  dependency-version: 2.5.19
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-21 13:32:59 +02:00
dependabot[bot]
f8ff48fed9 web: bump @types/node from 22.15.19 to 24.0.15 in /web (#15683)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.15.19 to 24.0.15.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.0.15
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-21 13:32:41 +02:00
Dominic R
dcf40690e9 website/dev docs: FDE e2e: fix useless markdown lini (#15658)
It renders the same and was a mishap on my part when I copied the link in a previous pr

Signed-off-by: Dominic R <dominic@sdko.org>
2025-07-21 11:44:20 +01:00
Jens L.
4b37829f67 providers/radius: set message authenticator (#15635)
* core: fix flow planner checking against wrong user when creating recovery link

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

* validate incoming message authenticator

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-07-19 22:08:58 +02:00
dependabot[bot]
bf050e19b0 web: bump @eslint/plugin-kit from 0.3.1 to 0.3.3 in /packages/eslint-config (#15661) 2025-07-19 16:54:29 +02:00
242 changed files with 12763 additions and 9556 deletions

2
go.mod
View File

@@ -29,7 +29,7 @@ require (
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2025063.5
goauthentik.io/api/v3 v3.2025063.6
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.16.0

4
go.sum
View File

@@ -298,8 +298,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
goauthentik.io/api/v3 v3.2025063.5 h1:j5el9/qI/72Q5x5QAiMzgQTswMj2TK3h74OaBcFEtkI=
goauthentik.io/api/v3 v3.2025063.5/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
goauthentik.io/api/v3 v3.2025063.6 h1:TFMnE0bXiWZ5oVYrnxDLpS+pnGNv+KIjLmZHT5qzpcM=
goauthentik.io/api/v3 v3.2025063.6/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=

View File

@@ -2,6 +2,7 @@ package radius
import (
"encoding/base64"
"errors"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
@@ -11,9 +12,7 @@ import (
"layeh.com/radius/rfc2865"
)
func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusRequest) {
username := rfc2865.UserName_GetString(r.Packet)
func (rs *RadiusServer) Handle_AccessRequest_PAP_Auth(r *RadiusRequest, username, password string) (*radius.Packet, error) {
fe := flow.NewFlowExecutor(r.Context(), r.pi.flowSlug, r.pi.s.ac.Client.GetConfig(), log.Fields{
"username": username,
"client": r.RemoteAddr(),
@@ -23,67 +22,64 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR
fe.Params.Add("goauthentik.io/outpost/radius", "true")
fe.Answers[flow.StageIdentification] = username
fe.SetSecrets(rfc2865.UserPassword_GetString(r.Packet), r.pi.MFASupport)
fe.SetSecrets(password, r.pi.MFASupport)
passed, err := fe.Execute()
if err != nil {
r.Log().WithField("username", username).WithError(err).Warning("failed to execute flow")
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": rs.ac.Outpost.Name,
"reason": "flow_error",
"app": r.pi.appSlug,
}).Inc()
_ = w.Write(r.Response(radius.CodeAccessReject))
return
return nil, errors.New("flow_error")
}
if !passed {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": rs.ac.Outpost.Name,
"reason": "invalid_credentials",
"app": r.pi.appSlug,
}).Inc()
_ = w.Write(r.Response(radius.CodeAccessReject))
return
return nil, errors.New("invalid_credentials")
}
access, _, err := fe.ApiClient().OutpostsApi.OutpostsRadiusAccessCheck(
r.Context(), r.pi.providerId,
).AppSlug(r.pi.appSlug).Execute()
if err != nil {
r.Log().WithField("username", username).WithError(err).Warning("failed to check access")
_ = w.Write(r.Response(radius.CodeAccessReject))
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": rs.ac.Outpost.Name,
"reason": "access_check_fail",
"app": r.pi.appSlug,
}).Inc()
return
return nil, errors.New("access_check_fail")
}
if !access.Access.Passing {
r.Log().WithField("username", username).Info("Access denied for user")
_ = w.Write(r.Response(radius.CodeAccessReject))
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": rs.ac.Outpost.Name,
"reason": "access_denied",
"app": r.pi.appSlug,
}).Inc()
return
return nil, errors.New("access_denied")
}
res := r.Response(radius.CodeAccessAccept)
defer func() { _ = w.Write(res) }()
if !access.HasAttributes() {
r.Log().Debug("No attributes")
return
return res, nil
}
rawData, err := base64.StdEncoding.DecodeString(access.GetAttributes())
if err != nil {
r.Log().WithError(err).Warning("failed to decode attributes from core")
return
return nil, errors.New("attribute_decode_failed")
}
p, err := radius.Parse(rawData, r.pi.SharedSecret)
if err != nil {
r.Log().WithError(err).Warning("failed to parse attributes from core")
return nil, errors.New("attribute_parse_failed")
}
for _, attr := range p.Attributes {
res.Add(attr.Type, attr.Attribute)
}
return res, nil
}
func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusRequest) {
username := rfc2865.UserName_GetString(r.Packet)
password := rfc2865.UserPassword_GetString(r.Packet)
res, err := rs.Handle_AccessRequest_PAP_Auth(r, username, password)
if err != nil {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": rs.ac.Outpost.Name,
"reason": err.Error(),
"app": r.pi.appSlug,
}).Inc()
_ = w.Write(r.Reject())
return
}
err = r.setMessageAuthenticator(res)
if err != nil {
rs.log.WithError(err).Warning("failed to set message authenticator")
}
_ = w.Write(res)
}

View File

@@ -1,8 +1,12 @@
package radius
import (
"bytes"
"crypto/hmac"
"crypto/md5"
"crypto/sha512"
"encoding/hex"
"errors"
"time"
"github.com/getsentry/sentry-go"
@@ -13,6 +17,11 @@ import (
"goauthentik.io/internal/outpost/radius/metrics"
"goauthentik.io/internal/utils"
"layeh.com/radius"
"layeh.com/radius/rfc2869"
)
var (
ErrInvalidMessageAuthenticator = errors.New("invalid message authenticator")
)
type RadiusRequest struct {
@@ -35,6 +44,41 @@ func (r *RadiusRequest) ID() string {
return r.id
}
func (r *RadiusRequest) validateMessageAuthenticator() error {
mauth := rfc2869.MessageAuthenticator_Get(r.Packet)
hash := hmac.New(md5.New, r.Secret)
encode, err := r.MarshalBinary()
if err != nil {
return err
}
hash.Write(encode)
if bytes.Equal(mauth, hash.Sum(nil)) {
return ErrInvalidMessageAuthenticator
}
return nil
}
func (r *RadiusRequest) setMessageAuthenticator(rp *radius.Packet) error {
_ = rfc2869.MessageAuthenticator_Set(rp, make([]byte, 16))
hash := hmac.New(md5.New, rp.Secret)
encode, err := rp.MarshalBinary()
if err != nil {
return err
}
hash.Write(encode)
_ = rfc2869.MessageAuthenticator_Set(rp, hash.Sum(nil))
return nil
}
func (r *RadiusRequest) Reject() *radius.Packet {
res := r.Response(radius.CodeAccessReject)
err := r.setMessageAuthenticator(res)
if err != nil {
r.log.WithError(err).Warning("failed to set message authenticator")
}
return res
}
func (rs *RadiusServer) ServeRADIUS(w radius.ResponseWriter, r *radius.Request) {
span := sentry.StartSpan(r.Context(), "authentik.providers.radius.connect",
sentry.WithTransactionName("authentik.providers.radius.connect"))
@@ -59,6 +103,11 @@ func (rs *RadiusServer) ServeRADIUS(w radius.ResponseWriter, r *radius.Request)
rl.Info("Radius Request")
if err := nr.validateMessageAuthenticator(); err != nil {
rl.WithError(err).Warning("Invalid message authenticator")
return
}
// Lookup provider by shared secret
var pi *ProviderInstance
for _, p := range rs.providers {
@@ -72,7 +121,7 @@ func (rs *RadiusServer) ServeRADIUS(w radius.ResponseWriter, r *radius.Request)
hs := sha512.Sum512([]byte(r.Secret))
bs := hex.EncodeToString(hs[:])
nr.Log().WithField("hashed_secret", bs).Warning("No provider found")
_ = w.Write(r.Response(radius.CodeAccessReject))
_ = w.Write(nr.Reject())
return
}
nr.pi = pi

2105
packages/elements/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
{
"name": "@goauthentik/elements",
"version": "1.0.0",
"description": "authentik's web components, elements and utilities",
"license": "MIT",
"scripts": {
"build": "tsc -p .",
"prettier": "prettier --write .",
"prettier-check": "prettier --check ."
},
"type": "module",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./index.js",
"types": "./out/index.d.ts"
},
"./elements/*": "./pout/elements/*",
"./common/*": "./out/common/*",
"./components/*": "./out/components/*",
"./flow/*": "./out/flow/*",
"./locales/*": "./out/locales/*",
"./user/*": "./out/user/*",
"./admin/*": "./out/admin/*",
"./css/*.css": "./css/*.css"
},
"devDependencies": {
"@goauthentik/prettier-config": "^3.1.0",
"@goauthentik/tsconfig": "^1.0.4",
"prettier": "^3.6.2",
"typescript": "^5.8.3"
},
"engines": {
"node": ">=24"
},
"types": "./out/index.d.ts",
"files": [
"./index.js",
"lib/**/*",
"css/**/*",
"out/**/*"
],
"prettier": "@goauthentik/prettier-config",
"publishConfig": {
"access": "public"
}
}

View File

@@ -132,9 +132,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz",
"integrity": "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
"integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==",
"cpu": [
"ppc64"
],
@@ -149,9 +149,9 @@
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.6.tgz",
"integrity": "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz",
"integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==",
"cpu": [
"arm"
],
@@ -166,9 +166,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.6.tgz",
"integrity": "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz",
"integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==",
"cpu": [
"arm64"
],
@@ -183,9 +183,9 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.6.tgz",
"integrity": "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz",
"integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==",
"cpu": [
"x64"
],
@@ -200,9 +200,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.6.tgz",
"integrity": "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz",
"integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==",
"cpu": [
"arm64"
],
@@ -217,9 +217,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.6.tgz",
"integrity": "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz",
"integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==",
"cpu": [
"x64"
],
@@ -234,9 +234,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.6.tgz",
"integrity": "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz",
"integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==",
"cpu": [
"arm64"
],
@@ -251,9 +251,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.6.tgz",
"integrity": "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz",
"integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==",
"cpu": [
"x64"
],
@@ -268,9 +268,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.6.tgz",
"integrity": "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz",
"integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==",
"cpu": [
"arm"
],
@@ -285,9 +285,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.6.tgz",
"integrity": "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz",
"integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==",
"cpu": [
"arm64"
],
@@ -302,9 +302,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.6.tgz",
"integrity": "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz",
"integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==",
"cpu": [
"ia32"
],
@@ -319,9 +319,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.6.tgz",
"integrity": "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz",
"integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==",
"cpu": [
"loong64"
],
@@ -336,9 +336,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.6.tgz",
"integrity": "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz",
"integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==",
"cpu": [
"mips64el"
],
@@ -353,9 +353,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.6.tgz",
"integrity": "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz",
"integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==",
"cpu": [
"ppc64"
],
@@ -370,9 +370,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.6.tgz",
"integrity": "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz",
"integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==",
"cpu": [
"riscv64"
],
@@ -387,9 +387,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.6.tgz",
"integrity": "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz",
"integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==",
"cpu": [
"s390x"
],
@@ -404,9 +404,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.6.tgz",
"integrity": "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz",
"integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==",
"cpu": [
"x64"
],
@@ -421,9 +421,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.6.tgz",
"integrity": "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz",
"integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==",
"cpu": [
"arm64"
],
@@ -438,9 +438,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.6.tgz",
"integrity": "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz",
"integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==",
"cpu": [
"x64"
],
@@ -455,9 +455,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.6.tgz",
"integrity": "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz",
"integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==",
"cpu": [
"arm64"
],
@@ -472,9 +472,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.6.tgz",
"integrity": "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz",
"integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==",
"cpu": [
"x64"
],
@@ -489,9 +489,9 @@
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.6.tgz",
"integrity": "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz",
"integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==",
"cpu": [
"arm64"
],
@@ -506,9 +506,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.6.tgz",
"integrity": "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz",
"integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==",
"cpu": [
"x64"
],
@@ -523,9 +523,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.6.tgz",
"integrity": "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz",
"integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==",
"cpu": [
"arm64"
],
@@ -540,9 +540,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.6.tgz",
"integrity": "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz",
"integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==",
"cpu": [
"ia32"
],
@@ -557,9 +557,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz",
"integrity": "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz",
"integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==",
"cpu": [
"x64"
],
@@ -832,9 +832,9 @@
}
},
"node_modules/@pkgr/core": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz",
"integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==",
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -904,9 +904,9 @@
}
},
"node_modules/@types/node": {
"version": "24.0.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz",
"integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==",
"version": "24.0.15",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.15.tgz",
"integrity": "sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1269,9 +1269,9 @@
}
},
"node_modules/esbuild": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz",
"integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==",
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz",
"integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -1282,32 +1282,32 @@
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.6",
"@esbuild/android-arm": "0.25.6",
"@esbuild/android-arm64": "0.25.6",
"@esbuild/android-x64": "0.25.6",
"@esbuild/darwin-arm64": "0.25.6",
"@esbuild/darwin-x64": "0.25.6",
"@esbuild/freebsd-arm64": "0.25.6",
"@esbuild/freebsd-x64": "0.25.6",
"@esbuild/linux-arm": "0.25.6",
"@esbuild/linux-arm64": "0.25.6",
"@esbuild/linux-ia32": "0.25.6",
"@esbuild/linux-loong64": "0.25.6",
"@esbuild/linux-mips64el": "0.25.6",
"@esbuild/linux-ppc64": "0.25.6",
"@esbuild/linux-riscv64": "0.25.6",
"@esbuild/linux-s390x": "0.25.6",
"@esbuild/linux-x64": "0.25.6",
"@esbuild/netbsd-arm64": "0.25.6",
"@esbuild/netbsd-x64": "0.25.6",
"@esbuild/openbsd-arm64": "0.25.6",
"@esbuild/openbsd-x64": "0.25.6",
"@esbuild/openharmony-arm64": "0.25.6",
"@esbuild/sunos-x64": "0.25.6",
"@esbuild/win32-arm64": "0.25.6",
"@esbuild/win32-ia32": "0.25.6",
"@esbuild/win32-x64": "0.25.6"
"@esbuild/aix-ppc64": "0.25.8",
"@esbuild/android-arm": "0.25.8",
"@esbuild/android-arm64": "0.25.8",
"@esbuild/android-x64": "0.25.8",
"@esbuild/darwin-arm64": "0.25.8",
"@esbuild/darwin-x64": "0.25.8",
"@esbuild/freebsd-arm64": "0.25.8",
"@esbuild/freebsd-x64": "0.25.8",
"@esbuild/linux-arm": "0.25.8",
"@esbuild/linux-arm64": "0.25.8",
"@esbuild/linux-ia32": "0.25.8",
"@esbuild/linux-loong64": "0.25.8",
"@esbuild/linux-mips64el": "0.25.8",
"@esbuild/linux-ppc64": "0.25.8",
"@esbuild/linux-riscv64": "0.25.8",
"@esbuild/linux-s390x": "0.25.8",
"@esbuild/linux-x64": "0.25.8",
"@esbuild/netbsd-arm64": "0.25.8",
"@esbuild/netbsd-x64": "0.25.8",
"@esbuild/openbsd-arm64": "0.25.8",
"@esbuild/openbsd-x64": "0.25.8",
"@esbuild/openharmony-arm64": "0.25.8",
"@esbuild/sunos-x64": "0.25.8",
"@esbuild/win32-arm64": "0.25.8",
"@esbuild/win32-ia32": "0.25.8",
"@esbuild/win32-x64": "0.25.8"
}
},
"node_modules/escape-string-regexp": {
@@ -2328,14 +2328,14 @@
}
},
"node_modules/prettier-plugin-packagejson": {
"version": "2.5.18",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.18.tgz",
"integrity": "sha512-NKznPGcGrcj4NPGxnh+w78JXPyfB6I4RQSCM0v+CAXwpDG7OEpJQ5zMyfC5NBgKH1k7Skwcj5ak5by2mrHvC5g==",
"version": "2.5.19",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.19.tgz",
"integrity": "sha512-Qsqp4+jsZbKMpEGZB1UP1pxeAT8sCzne2IwnKkr+QhUe665EXUo3BAvTf1kAPCqyMv9kg3ZmO0+7eOni/C6Uag==",
"dev": true,
"license": "MIT",
"dependencies": {
"sort-package-json": "3.4.0",
"synckit": "0.11.8"
"synckit": "0.11.11"
},
"peerDependencies": {
"prettier": ">= 1.16.0"
@@ -2628,13 +2628,13 @@
}
},
"node_modules/synckit": {
"version": "0.11.8",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz",
"integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==",
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
"integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.2.4"
"@pkgr/core": "^0.2.9"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -2728,9 +2728,9 @@
}
},
"node_modules/typedoc-plugin-markdown": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.7.0.tgz",
"integrity": "sha512-PitbnAps2vpcqK2gargKoiFXLWFttvwUbyns/E6zGIFG5Gz8ZQJGttHnYR9csOlcSjB/uyjd8tnoayrtsXG17w==",
"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==",
"dev": true,
"license": "MIT",
"engines": {

View File

@@ -237,9 +237,9 @@
}
},
"node_modules/@eslint/core": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz",
"integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==",
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
"integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
@@ -292,12 +292,12 @@
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz",
"integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==",
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz",
"integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==",
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.14.0",
"@eslint/core": "^0.15.1",
"levn": "^0.4.1"
},
"engines": {
@@ -1822,18 +1822,6 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint/node_modules/@eslint/core": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
"integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/espree": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",

View File

@@ -360,9 +360,9 @@
"license": "MIT"
},
"node_modules/@pkgr/core": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz",
"integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==",
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -385,9 +385,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.0.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz",
"integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==",
"version": "24.0.15",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.15.tgz",
"integrity": "sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1466,14 +1466,14 @@
}
},
"node_modules/prettier-plugin-packagejson": {
"version": "2.5.18",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.18.tgz",
"integrity": "sha512-NKznPGcGrcj4NPGxnh+w78JXPyfB6I4RQSCM0v+CAXwpDG7OEpJQ5zMyfC5NBgKH1k7Skwcj5ak5by2mrHvC5g==",
"version": "2.5.19",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.19.tgz",
"integrity": "sha512-Qsqp4+jsZbKMpEGZB1UP1pxeAT8sCzne2IwnKkr+QhUe665EXUo3BAvTf1kAPCqyMv9kg3ZmO0+7eOni/C6Uag==",
"dev": true,
"license": "MIT",
"dependencies": {
"sort-package-json": "3.4.0",
"synckit": "0.11.8"
"synckit": "0.11.11"
},
"peerDependencies": {
"prettier": ">= 1.16.0"
@@ -1657,13 +1657,13 @@
}
},
"node_modules/synckit": {
"version": "0.11.8",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz",
"integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==",
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
"integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.2.4"
"@pkgr/core": "^0.2.9"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"

View File

@@ -142,7 +142,6 @@ skip = [
"**/web/src/locales",
"**/web/xliff",
"**/web/out",
"**/web/playwright-report",
"./web/storybook-static",
"./web/custom-elements.json",
"./website/build",

View File

@@ -3,7 +3,7 @@
from dataclasses import asdict
from time import sleep
from pyrad.client import Client
from pyrad.client import Client, Timeout
from pyrad.dictionary import Dictionary
from pyrad.packet import AccessAccept, AccessReject, AccessRequest
@@ -27,7 +27,7 @@ class TestProviderRadius(SeleniumTestCase):
"""Start radius container based on outpost created"""
self.run_container(
image=self.get_container_image("ghcr.io/goauthentik/dev-radius"),
ports={"1812/udp": "1812/udp"},
ports={"1812/udp": 1812},
environment={
"AUTHENTIK_TOKEN": outpost.token.key,
},
@@ -63,7 +63,7 @@ class TestProviderRadius(SeleniumTestCase):
sleep(5)
return outpost
@retry()
@retry(exceptions=[Timeout])
@apply_blueprint(
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
@@ -85,7 +85,7 @@ class TestProviderRadius(SeleniumTestCase):
reply = srv.SendPacket(req)
self.assertEqual(reply.code, AccessAccept)
@retry()
@retry(exceptions=[Timeout])
@apply_blueprint(
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",

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

@@ -1,29 +0,0 @@
/**
* @file Vite plugin to inline CSS imports
* @import { Plugin as VitePlugin } from "vite";
*/
const CSSImportPattern = /import [\w$]+ from .+\.(css)/g;
const JavaScriptFilePattern = /\.m?(js|ts|tsx)$/;
export function inlineCSSPlugin() {
/**
* @satisfies {VitePlugin}
*/
const inlineCSSPlugin = {
name: "inline-css-plugin",
transform: (source, id) => {
if (!JavaScriptFilePattern.test(id)) return;
const code = source.replace(CSSImportPattern, (match) => {
return `${match}?inline`;
});
return {
code,
};
},
};
return inlineCSSPlugin;
}

38
web/commands.mjs Normal file
View File

@@ -0,0 +1,38 @@
/// <reference types="@wdio/globals/types" />
/// <reference types="./types/webdriver.js" />
/**
*
* @param {WebdriverIO.Browser} browser
*/
export function addCommands(browser) {
/**
* @file Custom WDIO browser commands
*/
browser.addCommand(
"focus",
/**
* @this {HTMLElement}
*/
function () {
this.focus();
return this;
},
/* attachToElement */ true,
);
browser.addCommand(
"blur",
/**
* @this {HTMLElement}
*/
function () {
this.blur();
return this;
},
/* attachToElement */ true,
);
}

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;

12046
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"
@@ -110,16 +107,11 @@
"@patternfly/patternfly": "^4.224.2",
"@sentry/browser": "^9.40.0",
"@spotlightjs/spotlight": "^3.0.1",
"@wdio/browser-runner": "9.15",
"@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.4.9",
"chart.js": "^4.5.0",
"chartjs-adapter-date-fns": "^3.0.0",
"chromedriver": "^136.0.3",
"codemirror": "^6.0.2",
"construct-style-sheets-polyfill": "^3.1.0",
"core-js": "^3.44.0",
@@ -130,11 +122,9 @@
"fuse.js": "^7.1.0",
"guacamole-common-js": "^1.5.0",
"hastscript": "^9.0.1",
"lit": "^3.3.1",
"lit": "^3.2.0",
"md-front-matter": "^1.0.4",
"mermaid": "^11.9.0",
"pino": "^9.7.0",
"pino-pretty": "^13.0.0",
"rapidoc": "^9.3.8",
"react": "^19.1.0",
"react-dom": "^19.1.0",
@@ -145,11 +135,10 @@
"remark-directive": "^4.0.0",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
"remark-mdx-frontmatter": "^5.2.0",
"remark-mdx-frontmatter": "^5.0.0",
"style-mod": "^4.1.2",
"trusted-types": "^2.0.0",
"ts-pattern": "^5.7.1",
"unique-names-generator": "^4.7.1",
"unist-util-visit": "^5.0.0",
"webcomponent-qr-code": "^1.2.0",
"yaml": "^2.8.0"
@@ -157,34 +146,37 @@
"devDependencies": {
"@eslint/js": "^9.27.0",
"@goauthentik/core": "^1.0.0",
"@goauthentik/esbuild-plugin-live-reload": "^1.1.0",
"@goauthentik/esbuild-plugin-live-reload": "^1.0.5",
"@goauthentik/eslint-config": "^1.0.5",
"@goauthentik/prettier-config": "^3.1.0",
"@goauthentik/tsconfig": "^1.0.4",
"@hcaptcha/types": "^1.0.4",
"@lit/localize-tools": "^0.8.0",
"@playwright/test": "^1.54.1",
"@storybook/addon-docs": "^9.0.17",
"@storybook/addon-links": "^9.0.17",
"@storybook/web-components": "^9.0.17",
"@storybook/web-components-vite": "^9.0.17",
"@types/chart.js": "^2.9.41",
"@types/chart.js": "^4.0.1",
"@types/codemirror": "^5.60.15",
"@types/dompurify": "^3.2.0",
"@types/grecaptcha": "^3.0.9",
"@types/guacamole-common-js": "^1.5.3",
"@types/mocha": "^10.0.10",
"@types/node": "^24.0.14",
"@types/node": "^24.0.15",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@typescript-eslint/eslint-plugin": "^8.8.0",
"@typescript-eslint/parser": "^8.8.0",
"@vitest/browser": "^3.2.4",
"@wdio/spec-reporter": "^9.18.0",
"esbuild": "^0.25.6",
"@wdio/browser-runner": "9.15",
"@wdio/cli": "9.15",
"@wdio/spec-reporter": "^9.15.0",
"@web/test-runner": "^0.20.2",
"chromedriver": "^136.0.3",
"esbuild": "^0.25.8",
"esbuild-plugin-copy": "^2.1.1",
"esbuild-plugin-polyfill-node": "^0.3.0",
"esbuild-plugins-node-modules-polyfill": "^1.7.0",
"eslint": "^9.30.1",
"esbuild-plugins-node-modules-polyfill": "^1.7.1",
"eslint": "^9.31.0",
"eslint-plugin-lit": "^2.1.1",
"eslint-plugin-wc": "^3.0.1",
"github-slugger": "^2.0.0",
@@ -192,14 +184,11 @@
"knip": "^5.61.3",
"lit-analyzer": "^2.0.3",
"npm-run-all": "^4.1.5",
"p-iteration": "^1.1.8",
"playwright": "^1.54.1",
"prettier": "^3.3.3",
"pseudolocale": "^2.1.0",
"rollup-plugin-postcss-lit": "^2.2.0",
"storybook": "^9.0.16",
"turnstile-types": "^1.2.3",
"type-fest": "^4.41.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.37.0",
"vite-plugin-lit-css": "^2.0.0",
@@ -284,7 +273,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 .",
@@ -320,33 +309,33 @@
}
},
"test": {
"command": "wdio ./wdio.conf.js --logLevel=warn",
"command": "wdio ./wdio.conf.ts --logLevel=warn",
"env": {
"CI": "true",
"TS_NODE_PROJECT": "tsconfig.test.json"
}
},
"test:e2e": {
"command": "wdio run ./tests/wdio.conf.js",
"command": "wdio run ./tests/wdio.conf.ts",
"dependencies": [
"build"
],
"env": {
"CI": "true",
"TS_NODE_PROJECT": "tsconfig.test.json"
"TS_NODE_PROJECT": "./tests/tsconfig.test.json"
}
},
"test:e2e:watch": {
"command": "wdio run ./tests/wdio.conf.js",
"command": "wdio run ./tests/wdio.conf.ts",
"dependencies": [
"build"
],
"env": {
"TS_NODE_PROJECT": "tsconfig.test.json"
"TS_NODE_PROJECT": "./tests/tsconfig.test.json"
}
},
"test:watch": {
"command": "wdio run ./wdio.conf.js",
"command": "wdio run ./wdio.conf.ts",
"dependencies": [
"build"
],

View File

@@ -1,50 +0,0 @@
/**
* @file Unique ID utilities.
*/
/**
* A global ID generator.
*
* @singleton
* @runtime common
*
* @category IDs
*/
export class IDGenerator {
static #sequenceIndex = 0;
static #elementIndex = 0;
/**
* Create a new ID for an HTML element.
*
* This ID will be unique for the lifetime of the page and will not be
* exposed on the `window` object.
*
* @param {string | number} [name] An optional name to use for the element.
*/
static elementID(name) {
name = name || ++this.#elementIndex;
return "«ak-" + name + "»";
}
/**
* Create a new ID.
*/
static next() {
this.#sequenceIndex += 1;
return this.#sequenceIndex;
}
/**
* Generate a random ID in hexadecimal format.
*
* @param {number} [characterLength]
*/
static randomID(characterLength = 6) {
const bytes = crypto.getRandomValues(new Uint8Array(characterLength / 2));
return Array.from(bytes, (a) => a.toString(16)).join("");
}
}

View File

@@ -47,9 +47,9 @@
"devDependencies": {
"@goauthentik/prettier-config": "^1.0.5",
"@goauthentik/tsconfig": "^1.0.4",
"@types/node": "^24.0.14",
"@types/node": "^24.0.15",
"prettier": "^3.3.3",
"typescript": "^5.8.3"
"typescript": "^5.6.3"
},
"engines": {
"node": ">=20.11"

View File

@@ -1,27 +0,0 @@
/**
* @file Helpers for running tests.
*/
/**
* A function that returns a promise.
* @template {never[]} [A=never[]]
* @typedef {(...args: A) => Promise<unknown>} Thenable
*/
/**
* A tuple of a function and its arguments.
* @template {Thenable} [T=Thenable]
* @typedef {[T, Parameters<T>]} SerializedThenable
*/
/**
* Executes a sequence of promise-returning functions in series
* @template {Thenable[]} T
* @param {{ [K in keyof T]: [T[K], ...Parameters<T[K]>] }} sequence
* @returns {Promise<void>}
*/
export async function series(...sequence) {
for (const [thenable, ...args] of sequence) {
await thenable(...args);
}
}

View File

@@ -14,6 +14,7 @@ declare module "module" {
* const relativeDirname = dirname(fileURLToPath(import.meta.url));
* ```
*/
var __dirname: string;
}
}

View File

@@ -23,7 +23,7 @@
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-swc": "^0.4.0",
"@swc/cli": "^0.7.8",
"@swc/core": "^1.13.0",
"@swc/core": "^1.13.1",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"prettier": "^3.5.3",
"rollup": "^4.45.1",

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

@@ -12,7 +12,6 @@ import { AdminApi, CapabilitiesEnum, LicenseSummaryStatusEnum } from "@goauthent
import { msg } from "@lit/localize";
import { css, html, TemplateResult } from "lit";
import { customElement } from "lit/decorators.js";
import { createRef, ref } from "lit/directives/ref.js";
import { until } from "lit/directives/until.js";
import PFAbout from "@patternfly/patternfly/components/AboutModalBox/about-modal-box.css";
@@ -57,32 +56,21 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
];
}
#contentRef = createRef<HTMLDivElement>();
#backdropListener = (event: PointerEvent) => {
// We only want to close the modal when the backdrop is clicked, not when it's children are clicked.
if (this.#contentRef.value?.contains(event.target as Node)) {
return;
}
this.close();
};
protected override renderModal() {
renderModal() {
let product = this.brandingTitle;
if (this.licenseSummary?.status !== LicenseSummaryStatusEnum.Unlicensed) {
product += ` ${msg("Enterprise")}`;
}
return html`<div class="pf-c-backdrop" @click=${this.#backdropListener}>
return html`<div
class="pf-c-backdrop"
@click=${(e: PointerEvent) => {
e.stopPropagation();
this.closeModal();
}}
>
<div class="pf-l-bullseye">
<div
${ref(this.#contentRef)}
class="pf-c-about-modal-box"
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
>
<div class="pf-c-about-modal-box" role="dialog" aria-modal="true">
<div class="pf-c-about-modal-box__brand">
<img
class="pf-c-about-modal-box__brand-image"
@@ -91,12 +79,18 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
/>
</div>
<div class="pf-c-about-modal-box__close">
<button class="pf-c-button pf-m-plain" type="button" @click=${this.close}>
<button
class="pf-c-button pf-m-plain"
type="button"
@click=${() => {
this.open = false;
}}
>
<i class="fas fa-times" aria-hidden="true"></i>
</button>
</div>
<div class="pf-c-about-modal-box__header">
<h1 class="pf-c-title pf-m-4xl" id="modal-title">${product}</h1>
<h1 class="pf-c-title pf-m-4xl">${product}</h1>
</div>
<div class="pf-c-about-modal-box__hero"></div>
<div class="pf-c-about-modal-box__content">

View File

@@ -1,45 +1,17 @@
import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "#elements/router/Route";
import { SidebarItemProperties } from "#elements/sidebar/SidebarItem";
import { spread } from "@open-wc/lit-helpers";
import { msg } from "@lit/localize";
import { html, nothing, TemplateResult } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import { repeat } from "lit/directives/repeat.js";
/**
* Given a record-like object, prefixes each key with a dot, allowing it to be spread into a
* template literal.
*
* ```ts
* interface MyElementProperties {
* foo: string;
* bar: number;
* }
*
* const properties {} as LitPropertyRecord<MyElementProperties>
*
* console.log(properties) // { '.foo': string; '.bar': number }
* ```
*/
export type LitPropertyRecord<T extends object> = {
[K in keyof T as K extends string ? LitPropertyKey<K> : never]: T[K];
};
/**
* A type that represents a property key that can be used in a LitPropertyRecord.
*
* @see {@linkcode LitPropertyRecord}
*/
export type LitPropertyKey<K> = K extends string ? `.${K}` | `?${K}` | K : K;
// The second attribute type is of string[] to help with the 'activeWhen' control, which was
// commonplace and singular enough to merit its own handler.
type SidebarEntry = [
path: string | null,
label: string,
attributes?: LitPropertyRecord<SidebarItemProperties> | string[] | null,
attributes?: Record<string, any> | string[] | null, // eslint-disable-line
children?: SidebarEntry[],
];
@@ -60,7 +32,8 @@ export function renderSidebarItem([
properties.path = path;
}
return html`<ak-sidebar-item label=${ifDefined(label)} ${spread(properties)}>
return html`<ak-sidebar-item ${spread(properties)}>
${label ? html`<span slot="label">${label}</span>` : nothing}
${children ? renderSidebarItems(children) : nothing}
</ak-sidebar-item>`;
}

View File

@@ -31,7 +31,6 @@ import { ROUTES } from "#admin/Routes";
import { CapabilitiesEnum, SessionUser, UiThemeEnum } from "@goauthentik/api";
import { msg } from "@lit/localize";
import { css, CSSResult, html, nothing, TemplateResult } from "lit";
import { customElement, eventOptions, property, query } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
@@ -164,18 +163,16 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
}
async firstUpdated(): Promise<void> {
me().then((session) => {
this.user = session;
this.user = await me();
const canAccessAdmin =
this.user.user.isSuperuser ||
// TODO: somehow add `access_admin_interface` to the API schema
this.user.user.systemPermissions.includes("access_admin_interface");
const canAccessAdmin =
this.user.user.isSuperuser ||
// TODO: somehow add `access_admin_interface` to the API schema
this.user.user.systemPermissions.includes("access_admin_interface");
if (!canAccessAdmin && this.user.user.pk > 0) {
window.location.assign("/if/user/");
}
});
if (!canAccessAdmin && this.user.user.pk > 0) {
window.location.assign("/if/user/");
}
}
render(): TemplateResult {
@@ -194,14 +191,13 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
};
return html` <ak-locale-context>
<ak-skip-to-content></ak-skip-to-content>
<div class="pf-c-page">
<ak-page-navbar ?open=${this.sidebarOpen} @sidebar-toggle=${this.sidebarListener}>
<ak-version-banner></ak-version-banner>
<ak-enterprise-status interface="admin"></ak-enterprise-status>
</ak-page-navbar>
<ak-sidebar ?hidden=${!this.sidebarOpen} class="${classMap(sidebarClasses)}">
<ak-sidebar class="${classMap(sidebarClasses)}">
${renderSidebarItems(AdminSidebarEntries)}
${this.can(CapabilitiesEnum.IsEnterprise)
? renderSidebarItems(AdminSidebarEnterpriseEntries)
@@ -213,10 +209,9 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
<div class="pf-c-drawer__main">
<div class="pf-c-drawer__content">
<div class="pf-c-drawer__body">
<div class="pf-c-page__main">
<main class="pf-c-page__main">
<ak-router-outlet
role="main"
aria-label="${msg("Main content")}"
class="pf-c-page__main"
tabindex="-1"
id="main-content"
@@ -224,7 +219,7 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
.routes=${ROUTES}
>
</ak-router-outlet>
</div>
</main>
</div>
</div>
<ak-notification-drawer

View File

@@ -3,15 +3,14 @@ import { PFSize } from "#common/enums";
import { APIError, parseAPIResponseError, pluckErrorDetail } from "#common/errors/network";
import { AggregateCard } from "#elements/cards/AggregateCard";
import { SlottedTemplateResult } from "#elements/types";
import { msg } from "@lit/localize";
import { html, nothing, PropertyValues } from "lit";
import { html, nothing, PropertyValues, TemplateResult } from "lit";
import { state } from "lit/decorators.js";
export interface AdminStatus {
icon: string;
message?: SlottedTemplateResult;
message?: TemplateResult;
}
/**
@@ -96,8 +95,8 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
*
* @returns TemplateResult displaying the value
*/
protected renderValue(): SlottedTemplateResult {
return this.value ? html`${this.value}` : nothing;
protected renderValue(): TemplateResult {
return html`${this.value}`;
}
/**
@@ -106,7 +105,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
* @param status - AdminStatus object containing icon and message
* @returns TemplateResult for status display
*/
private renderStatus(status: AdminStatus): SlottedTemplateResult {
private renderStatus(status: AdminStatus): TemplateResult {
return html`
<p><i class="${status.icon}"></i>&nbsp;${this.renderValue()}</p>
${status.message ? html`<p class="subtext">${status.message}</p>` : nothing}
@@ -119,9 +118,9 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
* @param error - Error message to display
* @returns TemplateResult for error display
*/
private renderError(error: string): SlottedTemplateResult {
private renderError(error: string): TemplateResult {
return html`
<p><i aria-hidden="true" class="fa fa-times"></i>&nbsp;${msg("Failed to fetch")}</p>
<p><i class="fa fa-times"></i>&nbsp;${msg("Failed to fetch")}</p>
<p class="subtext">${error}</p>
`;
}
@@ -131,7 +130,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
*
* @returns TemplateResult for loading spinner
*/
private renderLoading(): SlottedTemplateResult {
private renderLoading(): TemplateResult {
return html`<ak-spinner size="${PFSize.Large}"></ak-spinner>`;
}
@@ -140,7 +139,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
*
* @returns TemplateResult for current component state
*/
renderInner(): SlottedTemplateResult {
renderInner(): TemplateResult {
return html`
<p class="center-value">
${

View File

@@ -1,13 +1,11 @@
import { DEFAULT_CONFIG } from "#common/api/config";
import { SlottedTemplateResult } from "#elements/types";
import { AdminStatus, AdminStatusCard } from "#admin/admin-overview/cards/AdminStatusCard";
import { AdminApi, OutpostsApi, SystemInfo } from "@goauthentik/api";
import { msg } from "@lit/localize";
import { html, nothing } from "lit";
import { html, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators.js";
@customElement("ak-admin-status-system")
@@ -84,12 +82,12 @@ export class SystemStatusCard extends AdminStatusCard<SystemInfo> {
});
}
renderHeader(): SlottedTemplateResult {
return msg("System status");
renderHeader(): TemplateResult {
return html`${msg("System status")}`;
}
renderValue(): SlottedTemplateResult {
return this.statusSummary ? html`${this.statusSummary}` : nothing;
renderValue(): TemplateResult {
return html`${this.statusSummary}`;
}
}

View File

@@ -1,4 +1,4 @@
import { AkControlElement, formatFormElementAsJSON } from "#elements/AkControlElement";
import { AkControlElement } from "#elements/AkControlElement";
import { type Spread } from "#elements/types";
import { FooterLink } from "@goauthentik/api";
@@ -37,19 +37,18 @@ export class FooterLinkInput extends AkControlElement<FooterLink> {
];
@property({ type: Object, attribute: false })
public footerLink: FooterLink = {
footerLink: FooterLink = {
name: "",
href: "",
};
@property({ type: String })
public name?: string | null;
@queryAll(".ak-form-control")
protected controls?: HTMLInputElement[];
controls?: HTMLInputElement[];
public override json() {
return formatFormElementAsJSON<FooterLink>(this.controls);
json() {
return Object.fromEntries(
Array.from(this.controls ?? []).map((control) => [control.name, control.value]),
) as unknown as FooterLink;
}
get isValid() {

View File

@@ -2,49 +2,42 @@ import "../AdminSettingsFooterLinks.js";
import { render } from "#elements/tests/utils";
import { $, browser, expect } from "@wdio/globals";
import { $, expect } from "@wdio/globals";
import { html } from "lit";
describe("ak-admin-settings-footer-link", () => {
afterEach(() =>
browser.execute(() => {
document.body.querySelector("ak-admin-settings-footer-link")?.remove();
if ("_$litPart$" in document.body) {
delete document.body._$litPart$;
afterEach(async () => {
await browser.execute(async () => {
await document.body.querySelector("ak-admin-settings-footer-link")?.remove();
if (document.body._$litPart$) {
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
await delete document.body._$litPart$;
}
}),
);
});
});
it("should render an empty control", async () => {
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
const link = $("ak-admin-settings-footer-link");
await expect(link.getProperty("isValid")).resolves.toStrictEqual(false);
await expect(link.getProperty("toJson")).resolves.toEqual({
name: "",
href: "",
});
const link = await $("ak-admin-settings-footer-link");
await expect(await link.getProperty("isValid")).toStrictEqual(false);
await expect(await link.getProperty("toJson")).toEqual({ name: "", href: "" });
});
it("should not be valid if just a name is filled in", async () => {
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
const link = $("ak-admin-settings-footer-link");
const link = await $("ak-admin-settings-footer-link");
await link.$('input[name="name"]').setValue("foo");
await expect(link.getProperty("isValid")).resolves.toStrictEqual(false);
await expect(link.getProperty("toJson")).resolves.toEqual({
name: "foo",
href: "",
});
await expect(await link.getProperty("isValid")).toStrictEqual(false);
await expect(await link.getProperty("toJson")).toEqual({ name: "foo", href: "" });
});
it("should be valid if just a URL is filled in", async () => {
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
const link = $("ak-admin-settings-footer-link");
const link = await $("ak-admin-settings-footer-link");
await link.$('input[name="href"]').setValue("https://foo.com");
await expect(link.getProperty("isValid")).resolves.toStrictEqual(true);
await expect(link.getProperty("toJson")).resolves.toEqual({
await expect(await link.getProperty("isValid")).toStrictEqual(true);
await expect(await link.getProperty("toJson")).toEqual({
name: "",
href: "https://foo.com",
});
@@ -52,13 +45,11 @@ describe("ak-admin-settings-footer-link", () => {
it("should be valid if both are filled in", async () => {
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
const link = $("ak-admin-settings-footer-link");
const link = await $("ak-admin-settings-footer-link");
await link.$('input[name="name"]').setValue("foo");
await link.$('input[name="href"]').setValue("https://foo.com");
await expect(link.getProperty("isValid")).resolves.toStrictEqual(true);
await expect(link.getProperty("toJson")).resolves.toEqual({
await expect(await link.getProperty("isValid")).toStrictEqual(true);
await expect(await link.getProperty("toJson")).toEqual({
name: "foo",
href: "https://foo.com",
});
@@ -66,13 +57,13 @@ describe("ak-admin-settings-footer-link", () => {
it("should not be valid if the URL is not valid", async () => {
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
const link = $("ak-admin-settings-footer-link");
const link = await $("ak-admin-settings-footer-link");
await link.$('input[name="name"]').setValue("foo");
await link.$('input[name="href"]').setValue("never://foo.com");
await expect(link.getProperty("toJson")).resolves.toEqual({
await expect(await link.getProperty("toJson")).toEqual({
name: "foo",
href: "never://foo.com",
});
await expect(link.getProperty("isValid")).resolves.toStrictEqual(false);
await expect(await link.getProperty("isValid")).toStrictEqual(false);
});
});

View File

@@ -179,8 +179,9 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio
.options=${policyEngineModes}
.value=${this.instance?.policyEngineMode}
></ak-radio-input>
<ak-form-group label="${msg("UI settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("UI settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="metaLaunchUrl"
label=${msg("Launch URL")}

View File

@@ -84,7 +84,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
];
}
protected renderSidebarAfter(): TemplateResult {
renderSidebarAfter(): TemplateResult {
return html`<div class="pf-c-sidebar__panel pf-m-width-25">
<div class="pf-c-card">
<div class="pf-c-card__body">

View File

@@ -28,13 +28,13 @@ export class ApplicationWizardStep<T = Record<string, unknown>> extends WizardSt
// As recommended in [WizardStep](../../../components/ak-wizard/WizardStep.ts), we override
// these fields and provide them to all the child classes.
protected wizardTitle = msg("New application");
protected wizardDescription = msg("Create a new application and configure a provider for it.");
public cancelable = true;
wizardTitle = msg("New application");
wizardDescription = msg("Create a new application and configure a provider for it.");
canCancel = true;
// This should be overridden in the children for more precise targeting.
@query("form")
protected form!: HTMLFormElement;
form!: HTMLFormElement;
get formValues(): T {
return serializeForm<T>([

View File

@@ -20,7 +20,7 @@ export class AkWizardTitle extends AKElement {
render() {
return html`<div class="ak-bottom-spacing pf-c-content">
<h3 data-test-id="wizard-heading"><slot></slot></h3>
<h3><slot></slot></h3>
</div>`;
}
}
@@ -31,12 +31,4 @@ declare global {
interface HTMLElementTagNameMap {
"ak-wizard-title": AkWizardTitle;
}
interface WizardTestIDMap {
heading: HTMLHeadingElement;
}
interface TestIDSelectorMap {
wizard: WizardTestIDMap;
}
}

View File

@@ -8,6 +8,8 @@ import "#elements/forms/HorizontalFormElement";
import { ApplicationWizardStateUpdate, ValidationRecord } from "../types.js";
import { camelToSnake } from "#common/utils";
import { isSlug } from "#elements/router/utils";
import { type NavigableButton, type WizardButton } from "#components/ak-wizard/types";
@@ -17,8 +19,6 @@ import { policyEngineModes } from "#admin/policies/PolicyEngineModes";
import { type ApplicationRequest } from "@goauthentik/api";
import { snakeCase } from "change-case";
import { msg } from "@lit/localize";
import { html } from "lit";
import { customElement, query, state } from "lit/decorators.js";
@@ -29,7 +29,8 @@ const autoTrim = (v: unknown) => (typeof v === "string" ? v.trim() : v);
const trimMany = (o: Record<string, unknown>, vs: string[]) =>
Object.fromEntries(vs.map((v) => [v, autoTrim(o[v])]));
const isStr = (v: unknown): v is string => typeof v === "string";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const isStr = (v: any): v is string => typeof v === "string";
@customElement("ak-application-wizard-application-step")
export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
@@ -50,7 +51,9 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
errorMessages(name: string) {
return this.errors.has(name)
? [this.errors.get(name)]
: (this.wizard.errors?.app?.[name] ?? this.wizard.errors?.app?.[snakeCase(name)] ?? []);
: (this.wizard.errors?.app?.[name] ??
this.wizard.errors?.app?.[camelToSnake(name)] ??
[]);
}
get buttons(): WizardButton[] {
@@ -146,8 +149,9 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
.value=${app.policyEngineMode}
.errorMessages=${errors.policyEngineMode ?? []}
></ak-radio-input>
<ak-form-group label=${msg("UI Settings")}>
<div class="pf-c-form">
<ak-form-group aria-label=${msg("UI Settings")}>
<span slot="header"> ${msg("UI Settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="metaLaunchUrl"
label=${msg("Launch URL")}

View File

@@ -8,11 +8,11 @@ import "#elements/forms/HorizontalFormElement";
import { styles as AwadStyles } from "../../ApplicationWizardFormStepStyles.styles.js";
import { type ApplicationWizardState, type OneOfProvider } from "../../types.js";
import { camelToSnake } from "#common/utils";
import { AKElement } from "#elements/Base";
import { serializeForm } from "#elements/forms/Form";
import { snakeCase } from "change-case";
import { CSSResult } from "lit";
import { property, query } from "lit/decorators.js";
@@ -46,7 +46,7 @@ export class ApplicationWizardProviderForm<T extends OneOfProvider> extends AKEl
return name in this.errors
? [this.errors[name]]
: (this.wizard.errors?.provider?.[name] ??
this.wizard.errors?.provider?.[snakeCase(name)] ??
this.wizard.errors?.provider?.[camelToSnake(name)] ??
[]);
}

View File

@@ -61,8 +61,9 @@ export class ApplicationWizardRACProviderForm extends ApplicationWizardProviderF
input-hint="code"
></ak-text-input>
<ak-form-group open label=" ${msg("Protocol settings")} ">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Property mappings")}
name="propertyMappings"

View File

@@ -177,8 +177,9 @@ export class BlueprintForm extends ModelForm<BlueprintInstance, string> {
</div>
</div>
<ak-form-group label="${msg("Additional settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header">${msg("Additional settings")}</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Context")} name="context">
<ak-codemirror
mode=${CodeMirrorMode.YAML}

View File

@@ -91,8 +91,9 @@ export class BrandForm extends ModelForm<Brand, string> {
</p>
</ak-form-element-horizontal>
<ak-form-group label="${msg("Branding settings")} ">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Branding settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Title")} required name="brandingTitle">
<input
type="text"
@@ -173,8 +174,9 @@ export class BrandForm extends ModelForm<Brand, string> {
</div>
</ak-form-group>
<ak-form-group label="${msg("External user settings")} ">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("External user settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Default application")}
name="defaultApplication"
@@ -217,8 +219,9 @@ export class BrandForm extends ModelForm<Brand, string> {
</div>
</ak-form-group>
<ak-form-group label="${msg("Default flows")} ">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Default flows")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Authentication flow")}
name="flowAuthentication"
@@ -296,8 +299,9 @@ export class BrandForm extends ModelForm<Brand, string> {
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group label="${msg("Other global settings")} ">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Other global settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Web Certificate")}
name="webCertificate"

View File

@@ -44,18 +44,19 @@ export class CoreGroupSearch extends CustomListenerElement(AKElement) {
* @attr
*/
@property({ type: String, reflect: true })
public group?: string;
group?: string;
@query("ak-search-select")
public search!: SearchSelect<Group>;
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

@@ -34,19 +34,13 @@ const renderValue = (item: CertificateKeyPair | undefined): string | undefined =
@customElement("ak-crypto-certificate-search")
export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement) {
@property({ type: String, reflect: true })
public certificate?: string;
certificate?: string;
@query("ak-search-select")
public search!: SearchSelect<CertificateKeyPair>;
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 ?? undefined)}
.fetchObjects=${this.fetchObjects}
.renderElement=${renderElement}
.value=${renderValue}

View File

@@ -11,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";
@@ -38,15 +37,13 @@ export function getFlowValue(flow: Flow | undefined): string | undefined {
*/
export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement) {
//#region Properties
/**
* 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.
@@ -54,7 +51,7 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
* @attr
*/
@property({ type: String })
public currentFlow?: string | undefined;
currentFlow?: string | undefined;
/**
* If true, it is not valid to leave the flow blank.
@@ -62,7 +59,10 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
* @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.
@@ -73,29 +73,9 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
defaultFlowSlug?: string;
@property({ type: String })
public name?: string | null;
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: string = 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;
@@ -103,16 +83,18 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
constructor() {
super();
this.fetchObjects = this.fetchObjects.bind(this);
this.selected = this.selected.bind(this);
this.handleSearchUpdate = this.handleSearchUpdate.bind(this);
}
handleSearchUpdate = (ev: CustomEvent) => {
handleSearchUpdate(ev: CustomEvent) {
ev.stopPropagation();
this.selectedFlow = ev.detail.value;
this.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true }));
};
}
fetchObjects = async (query?: string): Promise<Flow[]> => {
async fetchObjects(query?: string): Promise<Flow[]> {
const args: FlowsInstancesListRequest = {
ordering: "slug",
designation: this.flowType,
@@ -120,7 +102,7 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
};
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
@@ -155,8 +137,6 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
.renderElement=${renderElement}
.renderDescription=${renderDescription}
.value=${getFlowValue}
placeholder=${ifDefined(this.placeholder ?? undefined)}
label=${ifDefined(this.label ?? undefined)}
name=${ifDefined(this.name ?? undefined)}
@ak-change=${this.handleSearchUpdate}
?blankable=${!this.required}

View File

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

View File

@@ -31,14 +31,19 @@ export class AkSourceFlowSearch<T extends Flow> extends FlowSearch<T> {
@property({ type: 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.
selected = (flow: Flow): boolean => {
selected(flow: Flow): boolean {
return (
(!this.instanceId && !this.currentFlow && flow.slug === this.fallback) ||
super.selected(flow)
);
};
}
}
declare global {

View File

@@ -10,35 +10,25 @@ 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()
public label = msg("Enterprise only");
@property()
public description = msg("Learn more about the enterprise license.");
notice = msg("Enterprise only");
render() {
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>
`;
return this.hasEnterpriseLicense
? nothing
: html`
<ak-alert class="pf-c-radio__description" inline plain>
<a href="#/enterprise/licenses">${this.notice}</a>
</ak-alert>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-license-notice": AKLicenceNotice;
"ak-license-notice": AkLicenceNotice;
}
}

View File

@@ -13,8 +13,8 @@ describe("ak-enterprise-status-card", () => {
it("should not error when no data is loaded", async () => {
render(html`<ak-enterprise-status-card></ak-enterprise-status-card>`);
const status = $("ak-enterprise-status-card");
await expect(status).resolves.toHaveText(msg("Loading"));
const status = await $("ak-enterprise-status-card");
await expect(status).toHaveText(msg("Loading"));
});
it("should render empty when unlicensed", async () => {
@@ -36,22 +36,22 @@ describe("ak-enterprise-status-card", () => {
</ak-enterprise-status-card>`,
);
const status = $("ak-enterprise-status-card").$(
const status = await $("ak-enterprise-status-card").$(
">>>.pf-c-description-list__description > .pf-c-description-list__text",
);
await expect(status).resolves.toExist();
await expect(status).resolves.toHaveText(msg("Unlicensed"));
await expect(status).toExist();
await expect(status).toHaveText(msg("Unlicensed"));
const internalUserProgress = $("ak-enterprise-status-card").$(
const internalUserProgress = await $("ak-enterprise-status-card").$(
">>>#internalUsers > .pf-c-progress__bar",
);
await expect(internalUserProgress).resolves.toExist();
await expect(internalUserProgress).resolves.toHaveAttr("aria-valuenow", "0");
const externalUserProgress = $("ak-enterprise-status-card").$(
await expect(internalUserProgress).toExist();
await expect(internalUserProgress).toHaveAttr("aria-valuenow", "0");
const externalUserProgress = await $("ak-enterprise-status-card").$(
">>>#externalUsers > .pf-c-progress__bar",
);
await expect(externalUserProgress).resolves.toExist();
await expect(externalUserProgress).resolves.toHaveAttr("aria-valuenow", "0");
await expect(externalUserProgress).toExist();
await expect(externalUserProgress).toHaveAttr("aria-valuenow", "0");
});
it("should show warnings when full", async () => {
@@ -73,35 +73,34 @@ describe("ak-enterprise-status-card", () => {
</ak-enterprise-status-card>`,
);
const status = $("ak-enterprise-status-card").$(
const status = await $("ak-enterprise-status-card").$(
">>>.pf-c-description-list__description > .pf-c-description-list__text",
);
await expect(status).resolves.toExist();
await expect(status).resolves.toHaveText(msg("Valid"));
await expect(status).toExist();
await expect(status).toHaveText(msg("Valid"));
const internalUserProgress = $("ak-enterprise-status-card").$(
const internalUserProgress = await $("ak-enterprise-status-card").$(
">>>#internalUsers > .pf-c-progress__bar",
);
await expect(internalUserProgress).resolves.toExist();
await expect(internalUserProgress).resolves.toHaveAttr("aria-valuenow", "100");
await expect(internalUserProgress).toExist();
await expect(internalUserProgress).toHaveAttr("aria-valuenow", "100");
await expect($("ak-enterprise-status-card").$(">>>#internalUsers")).toHaveElementClass(
"pf-m-warning",
);
await expect(
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
).toHaveElementClass("pf-m-warning");
const externalUserProgress = $("ak-enterprise-status-card").$(
const externalUserProgress = await $("ak-enterprise-status-card").$(
">>>#externalUsers > .pf-c-progress__bar",
);
await expect(externalUserProgress).resolves.toExist();
await expect(externalUserProgress).resolves.toHaveAttr("aria-valuenow", "100");
await expect(externalUserProgress).toExist();
await expect(externalUserProgress).toHaveAttr("aria-valuenow", "100");
await expect(
$("ak-enterprise-status-card").$(">>>#internalUsers"),
).resolves.toHaveElementClass("pf-m-warning");
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
).toHaveElementClass("pf-m-warning");
await expect(
$("ak-enterprise-status-card").$(">>>#externalUsers"),
).resolves.toHaveElementClass("pf-m-warning");
await $("ak-enterprise-status-card").$(">>>#externalUsers"),
).toHaveElementClass("pf-m-warning");
});
it("should show infinity when not licensed for a user type", async () => {
@@ -123,33 +122,33 @@ describe("ak-enterprise-status-card", () => {
</ak-enterprise-status-card>`,
);
const status = $("ak-enterprise-status-card").$(
const status = await $("ak-enterprise-status-card").$(
">>>.pf-c-description-list__description > .pf-c-description-list__text",
);
await expect(status).resolves.toExist();
await expect(status).resolves.toHaveText(msg("Valid"));
await expect(status).toExist();
await expect(status).toHaveText(msg("Valid"));
const internalUserProgress = $("ak-enterprise-status-card").$(
const internalUserProgress = await $("ak-enterprise-status-card").$(
">>>#internalUsers > .pf-c-progress__bar",
);
await expect(internalUserProgress).resolves.toExist();
await expect(internalUserProgress).resolves.toHaveAttr("aria-valuenow", "100");
await expect(internalUserProgress).toExist();
await expect(internalUserProgress).toHaveAttr("aria-valuenow", "100");
await expect($("ak-enterprise-status-card").$(">>>#internalUsers")).toHaveElementClass(
"pf-m-warning",
);
await expect(
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
).toHaveElementClass("pf-m-warning");
const externalUserProgress = $("ak-enterprise-status-card").$(
const externalUserProgress = await $("ak-enterprise-status-card").$(
">>>#externalUsers > .pf-c-progress__bar",
);
await expect(externalUserProgress).resolves.toExist();
await expect(externalUserProgress).resolves.toHaveAttr("aria-valuenow", "∞");
await expect(externalUserProgress).toExist();
await expect(externalUserProgress).toHaveAttr("aria-valuenow", "∞");
await expect(
$("ak-enterprise-status-card").$(">>>#internalUsers"),
).resolves.toHaveElementClass("pf-m-warning");
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
).toHaveElementClass("pf-m-warning");
await expect(
$("ak-enterprise-status-card").$(">>>#externalUsers"),
).resolves.toHaveElementClass("pf-m-danger");
await $("ak-enterprise-status-card").$(">>>#externalUsers"),
).toHaveElementClass("pf-m-danger");
});
});

View File

@@ -214,8 +214,9 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
${msg("Required authentication level for this flow.")}
</p>
</ak-form-element-horizontal>
<ak-form-group label="${msg("Behavior settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Behavior settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal name="compatibilityMode">
<label class="pf-c-switch">
<input
@@ -288,8 +289,9 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group label="${msg("Appearance settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Appearance settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Layout")} required name="layout">
<select class="pf-c-form-control">
<option

View File

@@ -234,8 +234,9 @@ export class OutpostForm extends ModelForm<Outpost, string> {
selected-label="${msg("Selected Applications")}"
></ak-dual-select-provider>
</ak-form-element-horizontal>
<ak-form-group label=${msg("Advanced settings")}>
<div class="pf-c-form">
<ak-form-group aria-label=${msg("Advanced settings")}>
<span slot="header"> ${msg("Advanced settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Configuration")} name="config">
<ak-codemirror
mode=${CodeMirrorMode.YAML}

View File

@@ -66,8 +66,9 @@ export class DummyPolicyForm extends BasePolicyForm<DummyPolicy> {
)}
</p>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Policy-specific settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Policy-specific settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal name="result">
<label class="pf-c-switch">
<input

View File

@@ -78,8 +78,9 @@ export class EventMatcherPolicyForm extends BasePolicyForm<EventMatcherPolicy> {
)}
</p>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Policy-specific settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Policy-specific settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Action")} name="action">
<ak-search-select
.fetchObjects=${async (query?: string): Promise<TypeCreate[]> => {

View File

@@ -66,8 +66,9 @@ export class PasswordExpiryPolicyForm extends BasePolicyForm<PasswordExpiryPolic
)}
</p>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Policy-specific settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Policy-specific settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Maximum age (in days)")}
required

View File

@@ -70,8 +70,9 @@ export class ExpressionPolicyForm extends BasePolicyForm<ExpressionPolicy> {
)}
</p>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Policy-specific settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Policy-specific settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Expression")}
required

View File

@@ -81,8 +81,9 @@ export class GeoIPPolicyForm extends BasePolicyForm<GeoIPPolicy> {
)}
</p>
</ak-form-element-horizontal>
<ak-form-group label="${msg("Distance settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Distance settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal name="checkHistoryDistance">
<label class="pf-c-switch">
<input
@@ -187,8 +188,9 @@ export class GeoIPPolicyForm extends BasePolicyForm<GeoIPPolicy> {
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group label="${msg("Static rule settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header">${msg("Static rule settings")}</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("ASNs")} name="asns">
<input
type="text"

View File

@@ -46,8 +46,9 @@ export class PasswordPolicyForm extends BasePolicyForm<PasswordPolicy> {
}
renderStaticRules(): TemplateResult {
return html` <ak-form-group label="${msg("Static rules")}">
<div class="pf-c-form">
return html` <ak-form-group>
<span slot="header"> ${msg("Static rules")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Minimum length")}
required
@@ -143,8 +144,9 @@ export class PasswordPolicyForm extends BasePolicyForm<PasswordPolicy> {
renderHIBP(): TemplateResult {
return html`
<ak-form-group open label="${msg("HaveIBeenPwned settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("HaveIBeenPwned settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Allowed count")}
required
@@ -167,8 +169,9 @@ export class PasswordPolicyForm extends BasePolicyForm<PasswordPolicy> {
renderZxcvbn(): TemplateResult {
return html`
<ak-form-group open label="${msg("zxcvbn settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("zxcvbn settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Score threshold")}
required

View File

@@ -76,8 +76,9 @@ doesn't pass when either or both of the selected options are equal or above the
)}
</p>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Policy-specific settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Policy-specific settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal name="checkIp">
<label class="pf-c-switch">
<input

View File

@@ -65,8 +65,9 @@ export class PropertyMappingProviderRACForm extends BasePropertyMappingForm<RACP
required
/>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("General settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("General settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Username")}
name="staticSettings.username"
@@ -91,8 +92,9 @@ export class PropertyMappingProviderRACForm extends BasePropertyMappingForm<RACP
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group label="${msg("RDP settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("RDP settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Ignore server certificate")}
name="staticSettings.ignore-cert"
@@ -135,8 +137,9 @@ export class PropertyMappingProviderRACForm extends BasePropertyMappingForm<RACP
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group label="${msg("Advanced settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Advanced settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Expression")}
required

View File

@@ -3,7 +3,7 @@ import { ModelForm } from "#elements/forms/ModelForm";
import { msg } from "@lit/localize";
export abstract class BaseProviderForm<T> extends ModelForm<T, number> {
public override getSuccessMessage(): string {
getSuccessMessage(): string {
return this.instance
? msg("Successfully updated provider.")
: msg("Successfully created provider.");

View File

@@ -29,38 +29,32 @@ import { customElement, property } from "lit/decorators.js";
@customElement("ak-provider-list")
export class ProviderListPage extends TablePage<Provider> {
override searchEnabled(): boolean {
searchEnabled(): boolean {
return true;
}
override pageTitle(): string {
pageTitle(): string {
return msg("Providers");
}
override pageDescription(): string {
pageDescription(): string {
return msg("Provide support for protocols like SAML and OAuth to assigned applications.");
}
override pageIcon(): string {
pageIcon(): string {
return "pf-icon pf-icon-integration";
}
override checkbox = true;
override clearOnRefresh = true;
checkbox = true;
clearOnRefresh = true;
@property()
public order = "name";
order = "name";
public searchLabel = msg("Provider name");
public searchPlaceholder = msg("Search for providers…");
override async apiEndpoint(): Promise<PaginatedResponse<Provider>> {
async apiEndpoint(): Promise<PaginatedResponse<Provider>> {
return new ProvidersApi(DEFAULT_CONFIG).providersAllList(
await this.defaultEndpointConfig(),
);
}
override columns(): TableColumn[] {
columns(): TableColumn[] {
return [
new TableColumn(msg("Name"), "name"),
new TableColumn(msg("Application")),
@@ -69,9 +63,8 @@ export class ProviderListPage extends TablePage<Provider> {
];
}
override renderToolbarSelected(): TemplateResult {
renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk
objectLabel=${msg("Provider(s)")}
.objects=${this.selectedElements}
@@ -92,7 +85,7 @@ export class ProviderListPage extends TablePage<Provider> {
</ak-forms-delete-bulk>`;
}
#rowApp(item: Provider): TemplateResult {
rowApp(item: Provider): TemplateResult {
if (item.assignedApplicationName) {
return html`<i class="pf-icon pf-icon-ok pf-m-success"></i>
${msg("Assigned to application ")}
@@ -100,7 +93,6 @@ export class ProviderListPage extends TablePage<Provider> {
>${item.assignedApplicationName}</a
>`;
}
if (item.assignedBackchannelApplicationName) {
return html`<i class="pf-icon pf-icon-ok pf-m-success"></i>
${msg("Assigned to application (backchannel) ")}
@@ -108,15 +100,15 @@ export class ProviderListPage extends TablePage<Provider> {
>${item.assignedBackchannelApplicationName}</a
>`;
}
return html`<i aria-hidden="true" class="pf-icon pf-icon-warning-triangle pf-m-warning"></i>
${msg("Warning: Provider not assigned to any application.")}`;
return html`<i class="pf-icon pf-icon-warning-triangle pf-m-warning"></i> ${msg(
"Warning: Provider not assigned to any application.",
)}`;
}
override row(item: Provider): TemplateResult[] {
row(item: Provider): TemplateResult[] {
return [
html`<a href="#/core/providers/${item.pk}"> ${item.name} </a>`,
this.#rowApp(item),
this.rowApp(item),
html`${item.verboseName}`,
html`<ak-forms-modal>
<span slot="submit"> ${msg("Update")} </span>
@@ -129,20 +121,16 @@ export class ProviderListPage extends TablePage<Provider> {
type=${item.component}
>
</ak-proxy-form>
<button
aria-label=${msg("Edit provider")}
slot="trigger"
class="pf-c-button pf-m-plain"
>
<button slot="trigger" class="pf-c-button pf-m-plain">
<pf-tooltip position="top" content=${msg("Edit")}>
<i aria-hidden="true" class="fas fa-edit"></i>
<i class="fas fa-edit"></i>
</pf-tooltip>
</button>
</ak-forms-modal>`,
];
}
override renderObjectCreate(): TemplateResult {
renderObjectCreate(): TemplateResult {
return html`<ak-provider-wizard> </ak-provider-wizard> `;
}
}

View File

@@ -30,16 +30,18 @@ export class ProviderWizard extends AKElement {
static styles: CSSResult[] = [PFBase, PFButton];
@property()
public createText = msg("Create");
createText = msg("Create");
@property({ attribute: false })
public providerTypes: TypeCreate[] = [];
providerTypes: TypeCreate[] = [];
@property({ attribute: false })
public finalHandler?: () => Promise<void>;
finalHandler: () => Promise<void> = () => {
return Promise.resolve();
};
@query("ak-wizard")
private wizard?: Wizard;
wizard?: Wizard;
connectedCallback() {
super.connectedCallback();
@@ -54,7 +56,9 @@ export class ProviderWizard extends AKElement {
.steps=${["initial"]}
header=${msg("New provider")}
description=${msg("Create a new provider.")}
.finalHandler=${this.finalHandler}
.finalHandler=${() => {
return this.finalHandler();
}}
>
<ak-wizard-page-type-create
name="selectProviderType"
@@ -78,15 +82,7 @@ export class ProviderWizard extends AKElement {
</ak-wizard-page-form>
`;
})}
<button
aria-label=${msg("New Provider")}
aria-description="${msg("Open the wizard to create a new provider.")}"
type="button"
slot="trigger"
class="pf-c-button pf-m-primary"
>
${msg("Create")}
</button>
<button slot="trigger" class="pf-c-button pf-m-primary">${this.createText}</button>
</ak-wizard>
`;
}

View File

@@ -59,8 +59,9 @@ export class GoogleWorkspaceProviderFormPage extends BaseProviderForm<GoogleWork
required
/>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Protocol settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Credentials")}
required
@@ -183,8 +184,9 @@ export class GoogleWorkspaceProviderFormPage extends BaseProviderForm<GoogleWork
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group open label="${msg("User filtering")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header">${msg("User filtering")}</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal name="excludeUsersServiceAccount">
<label class="pf-c-switch">
<input
@@ -235,8 +237,9 @@ export class GoogleWorkspaceProviderFormPage extends BaseProviderForm<GoogleWork
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group open label="${msg("Attribute mapping")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Attribute mapping")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("User Property Mappings")}
name="propertyMappings"

View File

@@ -47,9 +47,7 @@ export function renderForm(
) {
return html`
<ak-text-input
autocomplete="on"
name="name"
placeholder=${msg("Provider name")}
value=${ifDefined(provider?.name)}
label=${msg("Name")}
.errorMessages=${errors?.name ?? []}
@@ -82,8 +80,10 @@ export function renderForm(
>
</ak-switch-input>
<ak-form-group open label="${msg("Flow settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Flow settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Bind flow")}
required
@@ -91,7 +91,6 @@ export function renderForm(
.errorMessages=${errors?.authorizationFlow ?? []}
>
<ak-branded-flow-search
label=${msg("Bind flow")}
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authorizationFlow}
.brandFlow=${brand?.flowAuthentication}
@@ -120,8 +119,9 @@ export function renderForm(
</div>
</ak-form-group>
<ak-form-group open label="${msg("Protocol settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="baseDn"
label=${msg("Base DN")}
@@ -141,8 +141,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

@@ -57,8 +57,9 @@ export class MicrosoftEntraProviderFormPage extends BaseProviderForm<MicrosoftEn
required
/>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Protocol settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Client ID")} required name="clientId">
<input
type="text"
@@ -159,8 +160,9 @@ export class MicrosoftEntraProviderFormPage extends BaseProviderForm<MicrosoftEn
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group open label="${msg("User filtering")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header">${msg("User filtering")}</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal name="excludeUsersServiceAccount">
<label class="pf-c-switch">
<input
@@ -211,8 +213,9 @@ export class MicrosoftEntraProviderFormPage extends BaseProviderForm<MicrosoftEn
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group open label="${msg("Attribute mapping")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Attribute mapping")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("User Property Mappings")}
name="propertyMappings"

View File

@@ -23,14 +23,16 @@ export async function oauth2ProvidersProvider(page = 1, search = "") {
return {
pagination: oauthProviders.pagination,
options: oauthProviders.results.map(providerToSelect),
options: oauthProviders.results.map((provider) => providerToSelect(provider)),
};
}
export function oauth2ProviderSelector(instanceProviders: number[] | undefined) {
if (!instanceProviders) {
return async (mappings: DualSelectPair<OAuth2Provider>[]) =>
mappings.filter(([, , , source]: DualSelectPair<OAuth2Provider>) => !source);
mappings.filter(
([_0, _1, _2, source]: DualSelectPair<OAuth2Provider>) => source !== undefined,
);
}
return async () => {
@@ -57,6 +59,9 @@ export function oauth2ProviderSelector(instanceProviders: number[] | undefined)
@customElement("ak-provider-oauth2-form")
export class OAuth2ProviderFormPage extends BaseProviderForm<OAuth2Provider> {
@state()
showClientSecret = true;
static styles = [
...super.styles,
css`
@@ -66,37 +71,30 @@ export class OAuth2ProviderFormPage extends BaseProviderForm<OAuth2Provider> {
`,
];
@state()
protected showClientSecret = true;
override async loadInstance(pk: number): Promise<OAuth2Provider> {
async loadInstance(pk: number): Promise<OAuth2Provider> {
const provider = await new ProvidersApi(DEFAULT_CONFIG).providersOauth2Retrieve({
id: pk,
});
this.showClientSecret = provider.clientType === ClientTypeEnum.Confidential;
return provider;
}
override async send(data: OAuth2Provider): Promise<OAuth2Provider> {
async send(data: OAuth2Provider): Promise<OAuth2Provider> {
if (this.instance) {
return new ProvidersApi(DEFAULT_CONFIG).providersOauth2Update({
id: this.instance.pk,
oAuth2ProviderRequest: data,
});
}
return new ProvidersApi(DEFAULT_CONFIG).providersOauth2Create({
oAuth2ProviderRequest: data,
});
}
override renderForm() {
renderForm() {
const showClientSecretCallback = (show: boolean) => {
this.showClientSecret = show;
};
return renderForm(this.instance ?? {}, [], this.showClientSecret, showClientSecretCallback);
}
}

View File

@@ -127,9 +127,7 @@ export function renderForm(
showClientSecretCallback: ShowClientSecret = defaultShowClientSecret,
) {
return html` <ak-text-input
autocomplete="on"
name="name"
placeholder=${msg("Provider name")}
label=${msg("Name")}
value=${ifDefined(provider?.name)}
required
@@ -141,8 +139,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
@@ -151,8 +147,9 @@ export function renderForm(
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Protocol settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-radio-input
name="clientType"
label=${msg("Client type")}
@@ -202,8 +199,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>
@@ -212,8 +207,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>
@@ -221,15 +214,14 @@ export function renderForm(
</div>
</ak-form-group>
<ak-form-group label=${msg("Advanced flow settings")}>
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Advanced flow settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
name="authenticationFlow"
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>
@@ -245,8 +237,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"
@@ -259,8 +249,9 @@ export function renderForm(
</div>
</ak-form-group>
<ak-form-group label="${msg("Advanced protocol settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Advanced protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="accessCodeValidity"
label=${msg("Access code validity")}
@@ -343,8 +334,9 @@ export function renderForm(
</div>
</ak-form-group>
<ak-form-group label="${msg("Machine-to-Machine authentication settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header">${msg("Machine-to-Machine authentication settings")}</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Federated OIDC Sources")}
name="jwtFederationSources"

View File

@@ -1,6 +1,6 @@
import "#admin/providers/oauth2/OAuth2ProviderRedirectURI";
import { AkControlElement, formatFormElementAsJSON } from "#elements/AkControlElement";
import { AkControlElement } from "#elements/AkControlElement";
import { type Spread } from "#elements/types";
import { MatchingModeEnum, RedirectURI } from "@goauthentik/api";
@@ -43,7 +43,9 @@ export class OAuth2ProviderRedirectURI extends AkControlElement<RedirectURI> {
controls?: HTMLInputElement[];
json() {
return formatFormElementAsJSON<RedirectURI>(this.controls);
return Object.fromEntries(
Array.from(this.controls ?? []).map((control) => [control.name, control.value]),
) as unknown as RedirectURI;
}
get isValid() {

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

@@ -230,8 +230,9 @@ export function renderForm(
input-hint="code"
></ak-text-input>
<ak-form-group label="${msg("Advanced protocol settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header">${msg("Advanced protocol settings")}</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Certificate")} name="certificate">
<ak-crypto-certificate-search
.certificate=${provider?.certificate}
@@ -274,8 +275,9 @@ ${provider?.skipPathRegex}</textarea
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group label="${msg("Authentication settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header">${msg("Authentication settings")}</span>
<div slot="body" class="pf-c-form">
<ak-switch-input
name="interceptHeaderAuth"
label=${msg("Intercept header authentication")}
@@ -333,8 +335,9 @@ ${provider?.skipPathRegex}</textarea
</div>
</ak-form-group>
<ak-form-group label="${msg("Advanced flow settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Advanced flow settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Authentication flow")}
name="authenticationFlow"

View File

@@ -118,8 +118,9 @@ export class EndpointForm extends ModelForm<Endpoint, string> {
selected-label="${msg("Selected User Property Mappings")}"
></ak-dual-select-dynamic-selected>
</ak-form-element-horizontal>
<ak-form-group label="${msg("Advanced settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Advanced settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Settings")} name="settings">
<ak-codemirror
mode="yaml"

View File

@@ -118,8 +118,9 @@ export class RACProviderFormPage extends ModelForm<RACProvider, number> {
</p>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Protocol settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Property mappings")}
name="propertyMappings"

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}
@@ -77,14 +74,17 @@ export function renderForm(
>
</ak-switch-input>
<ak-form-group open label="${msg("Protocol settings")}">
<div class="pf-c-form">
<ak-hidden-text-input>
name="sharedSecret" label=${msg("Shared secret")}
<ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-hidden-text-input
name="sharedSecret"
label=${msg("Shared secret")}
.errorMessages=${errors?.sharedSecret ?? []}
value=${provider?.sharedSecret ?? randomString(128, ascii_letters + digits)}
required input-hint="code" ></ak-hidden-text-input
>
required
input-hint="code"
></ak-hidden-text-input>
<ak-text-input
name="clientNetworks"
label=${msg("Client Networks")}
@@ -107,16 +107,15 @@ export function renderForm(
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group label="${msg("Advanced flow settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Advanced flow settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Invalidation flow")}
name="invalidationFlow"
required
>
<ak-flow-search
label=${msg("Invalidation flow")}
placeholder=${msg("Select an invalidation flow...")}
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
.currentFlow=${provider?.invalidationFlow}
.errorMessages=${errors?.invalidationFlow ?? []}

View File

@@ -85,8 +85,9 @@ export function renderForm(
</p>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Protocol settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="acsUrl"
label=${msg("ACS URL")}
@@ -122,8 +123,9 @@ export function renderForm(
</div>
</ak-form-group>
<ak-form-group label="${msg("Advanced flow settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Advanced flow settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Authentication flow")}
name="authenticationFlow"
@@ -156,8 +158,9 @@ export function renderForm(
</div>
</ak-form-group>
<ak-form-group label="${msg("Advanced protocol settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Advanced protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Signing Certificate")} name="signingKp">
<ak-crypto-certificate-search
.certificate=${provider?.signingKp}

View File

@@ -32,8 +32,9 @@ export function renderForm(provider?: Partial<SCIMProvider>, errors: ValidationE
required
help=${msg("Method's display Name.")}
></ak-text-input>
<ak-form-group open label="${msg("Protocol settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="url"
label=${msg("URL")}
@@ -113,8 +114,9 @@ export function renderForm(provider?: Partial<SCIMProvider>, errors: ValidationE
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group open label="${msg("User filtering")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header">${msg("User filtering")}</span>
<div slot="body" class="pf-c-form">
<ak-switch-input
name="excludeUsersServiceAccount"
label=${msg("Exclude service accounts")}
@@ -154,8 +156,9 @@ export function renderForm(provider?: Partial<SCIMProvider>, errors: ValidationE
</div>
</ak-form-group>
<ak-form-group open label="${msg("Attribute mapping")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Attribute mapping")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("User Property Mappings")}
name="propertyMappings"

View File

@@ -59,8 +59,9 @@ export class SSFProviderFormPage extends BaseProviderForm<SSFProvider> {
value=${ifDefined(provider?.name)}
required
></ak-text-input>
<ak-form-group open label="${msg("Protocol settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Signing Key")}
name="signingKey"
@@ -94,8 +95,9 @@ export class SSFProviderFormPage extends BaseProviderForm<SSFProvider> {
</div>
</ak-form-group>
<ak-form-group label="${msg("Authentication settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header">${msg("Authentication settings")}</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("OIDC Providers")}
name="oidcAuthProviders"

View File

@@ -68,7 +68,7 @@ export class InitialPermissionsListPage extends TablePage<InitialPermissions> {
</ak-forms-delete-bulk>`;
}
render() {
render(): TemplateResult {
return html`<ak-page-header
icon=${this.pageIcon()}
header=${this.pageTitle()}

View File

@@ -66,7 +66,7 @@ export class RoleListPage extends TablePage<Role> {
</ak-forms-delete-bulk>`;
}
render() {
render(): TemplateResult {
return html`<ak-page-header
icon=${this.pageIcon()}
header=${this.pageTitle()}

View File

@@ -121,8 +121,9 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
"Enable this option to write password changes made in authentik back to Kerberos. Ignored if sync is disabled.",
)}
></ak-switch-input>
<ak-form-group open label="${msg("Realm settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Realm settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="realm"
label=${msg("Realm")}
@@ -212,8 +213,9 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group label="${msg("Sync connection settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Sync connection settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("KAdmin type")}
required
@@ -274,8 +276,9 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
></ak-text-input>
</div>
</ak-form-group>
<ak-form-group label="${msg("SPNEGO settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("SPNEGO settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="spnegoServerName"
label=${msg("SPNEGO server name")}
@@ -302,8 +305,9 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
></ak-text-input>
</div>
</ak-form-group>
<ak-form-group label="${msg("Kerberos Attribute mapping")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Kerberos Attribute mapping")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("User Property Mappings")}
name="userPropertyMappings"
@@ -340,8 +344,9 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group label="${msg("Flow settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Flow settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Authentication flow")}
name="authenticationFlow"
@@ -372,8 +377,9 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group label="${msg("Additional settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Additional settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="userPathTemplate"
label=${msg("User path")}

View File

@@ -173,8 +173,9 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
)}
</p>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Connection settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Connection settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Server URI")}
required
@@ -278,8 +279,9 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group open label="${msg("LDAP Attribute mapping")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("LDAP Attribute mapping")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("User Property Mappings")}
name="userPropertyMappings"
@@ -314,8 +316,9 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group label="${msg("Additional settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Additional settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Parent Group")} name="syncParentGroup">
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Group[]> => {

View File

@@ -126,8 +126,9 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
if (!this.providerType?.urlsCustomizable) {
return html``;
}
return html` <ak-form-group open label="${msg("URL settings")}">
<div class="pf-c-form">
return html` <ak-form-group expanded>
<span slot="header"> ${msg("URL settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Authorization URL")}
name="authorizationUrl"
@@ -420,8 +421,9 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
<p class="pf-c-form__helper-text">${iconHelperText}</p>
</ak-form-element-horizontal>`}
<ak-form-group open label="${msg("Protocol settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Consumer key")}
required
@@ -462,8 +464,9 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
</div>
</ak-form-group>
${this.renderUrlOptions()}
<ak-form-group open label="${msg("OAuth Attribute mapping")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("OAuth Attribute mapping")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("User Property Mappings")}
name="userPropertyMappings"
@@ -498,8 +501,9 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group label="${msg("Flow settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Flow settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Authentication flow")}
name="authenticationFlow"

View File

@@ -333,8 +333,9 @@ export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm<PlexSo
/>
<p class="pf-c-form__helper-text">${iconHelperText}</p>
</ak-form-element-horizontal>`}
<ak-form-group open label="${msg("Protocol settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Client ID")} required name="clientId">
<input
type="text"
@@ -346,8 +347,9 @@ export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm<PlexSo
${this.renderSettings()}
</div>
</ak-form-group>
<ak-form-group label="${msg("Flow settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Flow settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Authentication flow")}
name="authenticationFlow"
@@ -378,8 +380,9 @@ export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm<PlexSo
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group open label="${msg("Plex Attribute mapping")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Plex Attribute mapping")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("User Property Mappings")}
name="userPropertyMappings"

View File

@@ -233,8 +233,9 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
<p class="pf-c-form__helper-text">${iconHelperText}</p>
</ak-form-element-horizontal>`}
<ak-form-group open label="${msg("Protocol settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("SSO URL")} required name="ssoUrl">
<input
type="text"
@@ -320,8 +321,9 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group label="${msg("Advanced protocol settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Advanced protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal name="allowIdpInitiated">
<label class="pf-c-switch">
<input
@@ -491,8 +493,9 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group open label="${msg("SAML Attribute mapping")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("SAML Attribute mapping")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("User Property Mappings")}
name="userPropertyMappings"
@@ -527,8 +530,9 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group label="${msg("Flow settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Flow settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Pre-authentication flow")}
required

View File

@@ -70,8 +70,9 @@ export class SCIMSourceForm extends BaseSourceForm<SCIMSource> {
<label class="pf-c-check__label"> ${msg("Enabled")} </label>
</div>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("SCIM Attribute mapping")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("SCIM Attribute mapping")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("User Property Mappings")}
name="userPropertyMappings"
@@ -106,8 +107,9 @@ export class SCIMSourceForm extends BaseSourceForm<SCIMSource> {
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group label="${msg("Advanced protocol settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Advanced protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("User path")} name="userPathTemplate">
<input
type="text"

View File

@@ -82,8 +82,9 @@ export class AuthenticatorDuoStageForm extends BaseStageForm<AuthenticatorDuoSta
required
/>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Duo Auth API")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Duo Auth API")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Integration key")}
required
@@ -105,13 +106,15 @@ export class AuthenticatorDuoStageForm extends BaseStageForm<AuthenticatorDuoSta
></ak-secret-text-input>
</div>
</ak-form-group>
<ak-form-group
label=${msg("Duo Admin API (optional)")}
description="${msg(
`When using a Duo MFA, Access or Beyond plan, an Admin API application can be created. This will allow authentik to import devices automatically.`,
)}"
>
<div class="pf-c-form">
<ak-form-group>
<span slot="header">${msg("Duo Admin API (optional)")}</span>
<span slot="description">
${msg(
`When using a Duo MFA, Access or Beyond plan, an Admin API application can be created.
This will allow authentik to import devices automatically.`,
)}
</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Integration key")}
name="adminIntegrationKey"
@@ -132,8 +135,9 @@ export class AuthenticatorDuoStageForm extends BaseStageForm<AuthenticatorDuoSta
></ak-secret-text-input>
</div>
</ak-form-group>
<ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Stage-specific settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Configuration flow")}
name="configureFlow"

View File

@@ -52,8 +52,9 @@ export class AuthenticatorEmailStageForm extends BaseStageForm<AuthenticatorEmai
if (!this.showConnectionSettings) {
return html``;
}
return html`<ak-form-group open label="${msg("Connection settings")}">
<div class="pf-c-form">
return html`<ak-form-group expanded>
<span slot="header"> ${msg("Connection settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("SMTP Host")} required name="host">
<input
type="text"
@@ -192,8 +193,9 @@ export class AuthenticatorEmailStageForm extends BaseStageForm<AuthenticatorEmai
</p>
</ak-form-element-horizontal>
${this.renderConnectionSettings()}
<ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Stage-specific settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Subject")} required name="subject">
<input
type="text"

View File

@@ -56,8 +56,9 @@ export class AuthenticatorEndpointGDTCStageForm extends BaseStageForm<Authentica
required
/>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Google Verified Access API")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Google Verified Access API")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Credentials")}
required

View File

@@ -192,8 +192,9 @@ export class AuthenticatorSMSStageForm extends BaseStageForm<AuthenticatorSMSSta
)}
</p>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Stage-specific settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Provider")} required name="provider">
<select
class="pf-c-form-control"

View File

@@ -69,8 +69,9 @@ export class AuthenticatorStaticStageForm extends BaseStageForm<AuthenticatorSta
)}
</p>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Stage-specific settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Token count")}
required

View File

@@ -71,8 +71,9 @@ export class AuthenticatorTOTPStageForm extends BaseStageForm<AuthenticatorTOTPS
)}
</p>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Stage-specific settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Digits")} required name="digits">
<select name="users" class="pf-c-form-control">
<option

View File

@@ -97,8 +97,9 @@ export class AuthenticatorValidateStageForm extends BaseStageForm<AuthenticatorV
required
/>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Stage-specific settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Device classes")}
required
@@ -209,8 +210,9 @@ export class AuthenticatorValidateStageForm extends BaseStageForm<AuthenticatorV
: html``}
</div>
</ak-form-group>
<ak-form-group open label="${msg("WebAuthn-specific settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("WebAuthn-specific settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("WebAuthn User verification")}
required

View File

@@ -81,8 +81,9 @@ export class AuthenticatorWebAuthnStageForm extends BaseStageForm<AuthenticatorW
)}
</p>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Stage-specific settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("User verification")}
required

View File

@@ -49,8 +49,9 @@ export class CaptchaStageForm extends BaseStageForm<CaptchaStage> {
required
/>
</ak-form-element-horizontal>
<ak-form-group open label="${msg("Stage-specific settings")}">
<div class="pf-c-form">
<ak-form-group expanded>
<span slot="header"> ${msg("Stage-specific settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Public Key")}
required
@@ -127,8 +128,9 @@ export class CaptchaStageForm extends BaseStageForm<CaptchaStage> {
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group label="${msg("Advanced settings")}">
<div class="pf-c-form">
<ak-form-group>
<span slot="header"> ${msg("Advanced settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("JS URL")} required name="jsUrl">
<input
type="url"

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