mirror of
https://github.com/goauthentik/authentik
synced 2026-05-05 14:42:22 +02:00
Compare commits
68 Commits
website/do
...
api--set-A
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3d9165e3d | ||
|
|
cd0440c40c | ||
|
|
138feeb116 | ||
|
|
38ae472f6c | ||
|
|
922e9b6de2 | ||
|
|
7d0656c6fa | ||
|
|
0bbe415b5b | ||
|
|
e52c1b2bdc | ||
|
|
5064167f28 | ||
|
|
bca0f51b53 | ||
|
|
67c197e5a5 | ||
|
|
32b17da699 | ||
|
|
c75eed630a | ||
|
|
9f17d6df96 | ||
|
|
13c8ad5c56 | ||
|
|
28209c03e2 | ||
|
|
f47cf08d8a | ||
|
|
d69433b314 | ||
|
|
849a6053ad | ||
|
|
abdbe0269f | ||
|
|
55384c384a | ||
|
|
06fd68f076 | ||
|
|
d35ab99b2d | ||
|
|
a3b0180049 | ||
|
|
88a545f4fb | ||
|
|
ba62507fc2 | ||
|
|
82fc2e2c80 | ||
|
|
80b3739640 | ||
|
|
1258e1eada | ||
|
|
96ed17e760 | ||
|
|
4b17468b6e | ||
|
|
c834681251 | ||
|
|
9edd7cfbda | ||
|
|
4851179522 | ||
|
|
685f920de2 | ||
|
|
3b4d51b0c5 | ||
|
|
a1098d00b7 | ||
|
|
0d4984b964 | ||
|
|
38330df1f9 | ||
|
|
8b03c36d5a | ||
|
|
07a53a101c | ||
|
|
a3db2ce6a3 | ||
|
|
5487cdb874 | ||
|
|
2d5160d09b | ||
|
|
973fe0bd65 | ||
|
|
58b5e605de | ||
|
|
626e23b87a | ||
|
|
3559beba9c | ||
|
|
0b6d3a2850 | ||
|
|
56ca192391 | ||
|
|
6df62aaa2a | ||
|
|
ca344a64c4 | ||
|
|
a0cdd81f71 | ||
|
|
8eff4c7e0b | ||
|
|
d241a0e8f1 | ||
|
|
ebfc01fcda | ||
|
|
4b0e8a411b | ||
|
|
9bf6595fc6 | ||
|
|
5c07e845d2 | ||
|
|
4f76232e7c | ||
|
|
846f8a7e30 | ||
|
|
fa1c3490c3 | ||
|
|
a35edf7d0f | ||
|
|
9d4d5b7133 | ||
|
|
8d91a76bc9 | ||
|
|
6910428a93 | ||
|
|
cb181d388a | ||
|
|
aad4b6f925 |
2
.github/actions/setup/action.yml
vendored
2
.github/actions/setup/action.yml
vendored
@@ -64,7 +64,7 @@ runs:
|
|||||||
rustflags: ""
|
rustflags: ""
|
||||||
- name: Setup rust dependencies
|
- name: Setup rust dependencies
|
||||||
if: ${{ contains(inputs.dependencies, 'rust') }}
|
if: ${{ contains(inputs.dependencies, 'rust') }}
|
||||||
uses: taiki-e/install-action@481c34c1cf3a84c68b5e46f4eccfc82af798415a # v2
|
uses: taiki-e/install-action@b5fddbb5361bce8a06fb168c9d403a6cc552b084 # v2
|
||||||
with:
|
with:
|
||||||
tool: cargo-deny cargo-machete cargo-llvm-cov nextest
|
tool: cargo-deny cargo-machete cargo-llvm-cov nextest
|
||||||
- name: Setup node (web)
|
- name: Setup node (web)
|
||||||
|
|||||||
2
.github/workflows/_reusable-docker-build.yml
vendored
2
.github/workflows/_reusable-docker-build.yml
vendored
@@ -90,7 +90,7 @@ jobs:
|
|||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- uses: int128/docker-manifest-create-action@7df7f9e221d927eaadf87db231ddf728047308a4 # v2
|
- uses: int128/docker-manifest-create-action@fa55f72001a6c74b0f4997dca65c70d334905180 # v2
|
||||||
id: build
|
id: build
|
||||||
with:
|
with:
|
||||||
tags: ${{ matrix.tag }}
|
tags: ${{ matrix.tag }}
|
||||||
|
|||||||
16
.github/workflows/ci-main.yml
vendored
16
.github/workflows/ci-main.yml
vendored
@@ -282,10 +282,18 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
job:
|
job:
|
||||||
- name: basic
|
- name: oidc_basic
|
||||||
glob: tests/openid_conformance/test_basic.py
|
glob: tests/openid_conformance/test_oidc_basic.py
|
||||||
- name: implicit
|
- name: oidc_implicit
|
||||||
glob: tests/openid_conformance/test_implicit.py
|
glob: tests/openid_conformance/test_oidc_implicit.py
|
||||||
|
- name: oidc_rp-initiated
|
||||||
|
glob: tests/openid_conformance/test_oidc_rp_initiated.py
|
||||||
|
- name: oidc_frontchannel
|
||||||
|
glob: tests/openid_conformance/test_oidc_frontchannel.py
|
||||||
|
- name: oidc_backchannel
|
||||||
|
glob: tests/openid_conformance/test_oidc_backchannel.py
|
||||||
|
- name: ssf_transmitter
|
||||||
|
glob: tests/openid_conformance/test_ssf_transmitter.py
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
|
||||||
- name: Setup authentik env
|
- name: Setup authentik env
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -229,6 +229,11 @@ source_docs/
|
|||||||
|
|
||||||
### Golang ###
|
### Golang ###
|
||||||
/vendor/
|
/vendor/
|
||||||
|
server
|
||||||
|
proxy
|
||||||
|
ldap
|
||||||
|
rac
|
||||||
|
radius
|
||||||
|
|
||||||
### Docker ###
|
### Docker ###
|
||||||
tests/openid_conformance/exports/*.zip
|
tests/openid_conformance/exports/*.zip
|
||||||
|
|||||||
108
Cargo.lock
generated
108
Cargo.lock
generated
@@ -17,18 +17,6 @@ version = "2.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ahash"
|
|
||||||
version = "0.8.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"once_cell",
|
|
||||||
"version_check",
|
|
||||||
"zerocopy",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.4"
|
version = "1.1.4"
|
||||||
@@ -203,6 +191,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"which",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1014,6 +1003,17 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "evmap"
|
||||||
|
version = "11.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b8874945f036109c72242964c1174cf99434e30cfa45bf45fedc983f50046f8"
|
||||||
|
dependencies = [
|
||||||
|
"hashbag",
|
||||||
|
"left-right",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "eyre"
|
name = "eyre"
|
||||||
version = "0.6.12"
|
version = "0.6.12"
|
||||||
@@ -1230,6 +1230,21 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generator"
|
||||||
|
version = "0.8.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"rustversion",
|
||||||
|
"windows-link",
|
||||||
|
"windows-result",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.7"
|
version = "0.14.7"
|
||||||
@@ -1311,6 +1326,12 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbag"
|
||||||
|
version = "0.1.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7040a10f52cba493ddb09926e15d10a9d8a28043708a405931fe4c6f19fac064"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.5"
|
version = "0.15.5"
|
||||||
@@ -1868,6 +1889,17 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "left-right"
|
||||||
|
version = "0.11.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f0c21e4c8ff95f487fb34e6f9182875f42c84cef966d29216bf115d9bba835a"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
"loom",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.183"
|
version = "0.2.183"
|
||||||
@@ -1939,6 +1971,19 @@ version = "0.4.29"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "loom"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"generator",
|
||||||
|
"scoped-tls",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lru-slab"
|
name = "lru-slab"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -1978,21 +2023,22 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "metrics"
|
name = "metrics"
|
||||||
version = "0.24.3"
|
version = "0.24.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8"
|
checksum = "ff56c2e7dce6bd462e3b8919986a617027481b1dcc703175b58cf9dd98a2f071"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
|
||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
|
"rapidhash",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "metrics-exporter-prometheus"
|
name = "metrics-exporter-prometheus"
|
||||||
version = "0.18.1"
|
version = "0.18.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3589659543c04c7dc5526ec858591015b87cd8746583b51b48ef4353f99dbcda"
|
checksum = "1db0d8f1fc9e62caebd0319e11eaec5822b0186c171568f0480b46a0137f9108"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
"evmap",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"metrics",
|
"metrics",
|
||||||
"metrics-util",
|
"metrics-util",
|
||||||
@@ -2813,6 +2859,15 @@ dependencies = [
|
|||||||
"rand_core 0.9.5",
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rapidhash"
|
||||||
|
version = "4.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59"
|
||||||
|
dependencies = [
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "raw-cpuid"
|
name = "raw-cpuid"
|
||||||
version = "11.6.0"
|
version = "11.6.0"
|
||||||
@@ -2871,9 +2926,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.13.2"
|
version = "0.13.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
|
checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -3105,6 +3160,12 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scoped-tls"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -4515,6 +4576,15 @@ dependencies = [
|
|||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "which"
|
||||||
|
version = "8.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81995fafaaaf6ae47a7d0cc83c67caf92aeb7e5331650ae6ff856f7c0c60c459"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "whoami"
|
name = "whoami"
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
|
|||||||
@@ -43,15 +43,15 @@ hyper-unix-socket = "= 0.6.1"
|
|||||||
hyper-util = "= 0.1.20"
|
hyper-util = "= 0.1.20"
|
||||||
ipnet = { version = "= 2.12.0", features = ["serde"] }
|
ipnet = { version = "= 2.12.0", features = ["serde"] }
|
||||||
json-subscriber = "= 0.2.8"
|
json-subscriber = "= 0.2.8"
|
||||||
metrics = "= 0.24.3"
|
metrics = "= 0.24.5"
|
||||||
metrics-exporter-prometheus = { version = "= 0.18.1", default-features = false }
|
metrics-exporter-prometheus = { version = "= 0.18.3", default-features = false }
|
||||||
nix = { version = "= 0.31.2", features = ["hostname", "signal"] }
|
nix = { version = "= 0.31.2", features = ["hostname", "signal"] }
|
||||||
notify = "= 8.2.0"
|
notify = "= 8.2.0"
|
||||||
pin-project-lite = "= 0.2.17"
|
pin-project-lite = "= 0.2.17"
|
||||||
pyo3 = "= 0.28.3"
|
pyo3 = "= 0.28.3"
|
||||||
pyo3-build-config = "= 0.28.3"
|
pyo3-build-config = "= 0.28.3"
|
||||||
regex = "= 1.12.3"
|
regex = "= 1.12.3"
|
||||||
reqwest = { version = "= 0.13.2", features = [
|
reqwest = { version = "= 0.13.3", features = [
|
||||||
"form",
|
"form",
|
||||||
"json",
|
"json",
|
||||||
"multipart",
|
"multipart",
|
||||||
@@ -113,6 +113,7 @@ tracing-subscriber = { version = "= 0.3.23", features = [
|
|||||||
] }
|
] }
|
||||||
url = "= 2.5.8"
|
url = "= 2.5.8"
|
||||||
uuid = { version = "= 1.23.1", features = ["serde", "v4"] }
|
uuid = { version = "= 1.23.1", features = ["serde", "v4"] }
|
||||||
|
which = "= 8.0.2"
|
||||||
|
|
||||||
ak-axum = { package = "authentik-axum", version = "2026.5.0-rc1", path = "./packages/ak-axum" }
|
ak-axum = { package = "authentik-axum", version = "2026.5.0-rc1", path = "./packages/ak-axum" }
|
||||||
ak-client = { package = "authentik-client", version = "2026.5.0-rc1", path = "./packages/client-rust" }
|
ak-client = { package = "authentik-client", version = "2026.5.0-rc1", path = "./packages/client-rust" }
|
||||||
@@ -282,6 +283,7 @@ sqlx = { workspace = true, optional = true }
|
|||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
|
which.workspace = true
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
11
Makefile
11
Makefile
@@ -109,14 +109,11 @@ i18n-extract: core-i18n-extract web-i18n-extract ## Extract strings that requir
|
|||||||
aws-cfn:
|
aws-cfn:
|
||||||
cd lifecycle/aws && npm i && $(UV) run npm run aws-cfn
|
cd lifecycle/aws && npm i && $(UV) run npm run aws-cfn
|
||||||
|
|
||||||
run-server: ## Run the main authentik server process
|
run: ## Run the main authentik server and worker processes
|
||||||
$(UV) run ak server
|
$(UV) run ak allinone
|
||||||
|
|
||||||
run-worker: ## Run the main authentik worker process
|
run-watch: ## Run the authentik server and worker, with auto reloading
|
||||||
$(UV) run ak worker
|
watchexec --on-busy-update=restart --stop-signal=SIGINT --exts py,rs,go --no-meta --notify -- $(UV) run ak allinone
|
||||||
|
|
||||||
run-worker-watch: ## Run the authentik worker, with auto reloading
|
|
||||||
watchexec --on-busy-update=restart --stop-signal=SIGINT --exts py,rs --no-meta --notify -- $(UV) run ak worker
|
|
||||||
|
|
||||||
core-i18n-extract:
|
core-i18n-extract:
|
||||||
$(UV) run ak makemessages \
|
$(UV) run ak makemessages \
|
||||||
|
|||||||
@@ -32,19 +32,19 @@ from authentik.rbac.decorators import permission_required
|
|||||||
class UserAgentDeviceDict(TypedDict):
|
class UserAgentDeviceDict(TypedDict):
|
||||||
"""User agent device"""
|
"""User agent device"""
|
||||||
|
|
||||||
brand: str
|
brand: str | None = None
|
||||||
family: str
|
family: str
|
||||||
model: str
|
model: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class UserAgentOSDict(TypedDict):
|
class UserAgentOSDict(TypedDict):
|
||||||
"""User agent os"""
|
"""User agent os"""
|
||||||
|
|
||||||
family: str
|
family: str
|
||||||
major: str
|
major: str | None = None
|
||||||
minor: str
|
minor: str | None = None
|
||||||
patch: str
|
patch: str | None = None
|
||||||
patch_minor: str
|
patch_minor: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class UserAgentBrowserDict(TypedDict):
|
class UserAgentBrowserDict(TypedDict):
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"""authentik core signals"""
|
"""authentik core signals"""
|
||||||
|
|
||||||
from asgiref.sync import async_to_sync
|
|
||||||
from channels.layers import get_channel_layer
|
from channels.layers import get_channel_layer
|
||||||
from django.contrib.auth.signals import user_logged_in
|
from django.contrib.auth.signals import user_logged_in
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
@@ -59,7 +58,7 @@ def user_logged_in_session(sender, request: HttpRequest, user: User, **_):
|
|||||||
layer = get_channel_layer()
|
layer = get_channel_layer()
|
||||||
device_cookie = request.COOKIES.get("authentik_device")
|
device_cookie = request.COOKIES.get("authentik_device")
|
||||||
if device_cookie:
|
if device_cookie:
|
||||||
async_to_sync(layer.group_send)(
|
layer.group_send_blocking(
|
||||||
build_device_group(device_cookie),
|
build_device_group(device_cookie),
|
||||||
{"type": "event.session.authenticated"},
|
{"type": "event.session.authenticated"},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Generated by Django 5.2.12 on 2026-04-04 16:58
|
# Generated by Django 5.2.12 on 2026-04-04 16:58
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@@ -40,4 +41,109 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="stream",
|
||||||
|
name="events_requested",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.TextField(
|
||||||
|
choices=[
|
||||||
|
(
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/session-revoked",
|
||||||
|
"Caep Session Revoked",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/token-claims-change",
|
||||||
|
"Caep Token Claims Change",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/credential-change",
|
||||||
|
"Caep Credential Change",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/assurance-level-change",
|
||||||
|
"Caep Assurance Level Change",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/device-compliance-change",
|
||||||
|
"Caep Device Compliance Change",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/session-established",
|
||||||
|
"Caep Session Established",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/session-presented",
|
||||||
|
"Caep Session Presented",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/risk-level-change",
|
||||||
|
"Caep Risk Level Change",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"https://schemas.openid.net/secevent/ssf/event-type/verification",
|
||||||
|
"Set Verification",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
default=list,
|
||||||
|
size=None,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="stream",
|
||||||
|
name="status",
|
||||||
|
field=models.TextField(
|
||||||
|
choices=[
|
||||||
|
("enabled", "Enabled"),
|
||||||
|
("paused", "Paused"),
|
||||||
|
("disabled", "Disabled"),
|
||||||
|
("disabled_deleted", "Disabled Deleted"),
|
||||||
|
],
|
||||||
|
default="enabled",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="streamevent",
|
||||||
|
name="type",
|
||||||
|
field=models.TextField(
|
||||||
|
choices=[
|
||||||
|
(
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/session-revoked",
|
||||||
|
"Caep Session Revoked",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/token-claims-change",
|
||||||
|
"Caep Token Claims Change",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/credential-change",
|
||||||
|
"Caep Credential Change",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/assurance-level-change",
|
||||||
|
"Caep Assurance Level Change",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/device-compliance-change",
|
||||||
|
"Caep Device Compliance Change",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/session-established",
|
||||||
|
"Caep Session Established",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/session-presented",
|
||||||
|
"Caep Session Presented",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/risk-level-change",
|
||||||
|
"Caep Risk Level Change",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"https://schemas.openid.net/secevent/ssf/event-type/verification",
|
||||||
|
"Set Verification",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -24,8 +24,31 @@ class EventTypes(models.TextChoices):
|
|||||||
"""SSF Event types supported by authentik"""
|
"""SSF Event types supported by authentik"""
|
||||||
|
|
||||||
CAEP_SESSION_REVOKED = "https://schemas.openid.net/secevent/caep/event-type/session-revoked"
|
CAEP_SESSION_REVOKED = "https://schemas.openid.net/secevent/caep/event-type/session-revoked"
|
||||||
|
"""https://openid.net/specs/openid-caep-1_0-final.html#section-3.1"""
|
||||||
|
CAEP_TOKEN_CLAIMS_CHANGE = (
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/token-claims-change"
|
||||||
|
)
|
||||||
|
"""https://openid.net/specs/openid-caep-1_0-final.html#section-3.2"""
|
||||||
CAEP_CREDENTIAL_CHANGE = "https://schemas.openid.net/secevent/caep/event-type/credential-change"
|
CAEP_CREDENTIAL_CHANGE = "https://schemas.openid.net/secevent/caep/event-type/credential-change"
|
||||||
|
"""https://openid.net/specs/openid-caep-1_0-final.html#section-3.3"""
|
||||||
|
CAEP_ASSURANCE_LEVEL_CHANGE = (
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/assurance-level-change"
|
||||||
|
)
|
||||||
|
"""https://openid.net/specs/openid-caep-1_0-final.html#section-3.4"""
|
||||||
|
CAEP_DEVICE_COMPLIANCE_CHANGE = (
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/device-compliance-change"
|
||||||
|
)
|
||||||
|
"""https://openid.net/specs/openid-caep-1_0-final.html#section-3.5"""
|
||||||
|
CAEP_SESSION_ESTABLISHED = (
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/session-established"
|
||||||
|
)
|
||||||
|
"""https://openid.net/specs/openid-caep-1_0-final.html#section-3.6"""
|
||||||
|
CAEP_SESSION_PRESENTED = "https://schemas.openid.net/secevent/caep/event-type/session-presented"
|
||||||
|
"""https://openid.net/specs/openid-caep-1_0-final.html#section-3.7"""
|
||||||
|
CAEP_RISK_LEVEL_CHANGE = "https://schemas.openid.net/secevent/caep/event-type/risk-level-change"
|
||||||
|
"""https://openid.net/specs/openid-caep-1_0-final.html#section-3.8"""
|
||||||
SET_VERIFICATION = "https://schemas.openid.net/secevent/ssf/event-type/verification"
|
SET_VERIFICATION = "https://schemas.openid.net/secevent/ssf/event-type/verification"
|
||||||
|
"""https://openid.net/specs/openid-sharedsignals-framework-1_0.html#section-8.1.4.1"""
|
||||||
|
|
||||||
|
|
||||||
class DeliveryMethods(models.TextChoices):
|
class DeliveryMethods(models.TextChoices):
|
||||||
@@ -46,10 +69,12 @@ class SSFEventStatus(models.TextChoices):
|
|||||||
|
|
||||||
|
|
||||||
class StreamStatus(models.TextChoices):
|
class StreamStatus(models.TextChoices):
|
||||||
|
"""SSF Stream status"""
|
||||||
|
|
||||||
ENABLED = "enabled"
|
ENABLED = "enabled"
|
||||||
PAUSED = "paused"
|
PAUSED = "paused"
|
||||||
DISABLED = "disabled"
|
DISABLED = "disabled"
|
||||||
|
DISABLED_DELETED = "disabled_deleted"
|
||||||
|
|
||||||
|
|
||||||
class SSFProvider(TasksModel, BackchannelProvider):
|
class SSFProvider(TasksModel, BackchannelProvider):
|
||||||
|
|||||||
@@ -108,13 +108,13 @@ def send_ssf_event(stream_uuid: UUID, event_data: dict[str, Any]):
|
|||||||
event.save()
|
event.save()
|
||||||
self.info("Event successfully sent", status=response.status_code)
|
self.info("Event successfully sent", status=response.status_code)
|
||||||
# Cleanup, if we were the last pending message for this stream and it has been deleted
|
# Cleanup, if we were the last pending message for this stream and it has been deleted
|
||||||
# (status=StreamStatus.DISABLED), then we can delete the stream
|
# (status=StreamStatus.DISABLED_DELETED), then we can delete the stream
|
||||||
if (
|
if (
|
||||||
not StreamEvent.objects.filter(
|
not StreamEvent.objects.filter(
|
||||||
stream=stream,
|
stream=stream,
|
||||||
status__in=[SSFEventStatus.PENDING_FAILED, SSFEventStatus.PENDING_NEW],
|
status__in=[SSFEventStatus.PENDING_FAILED, SSFEventStatus.PENDING_NEW],
|
||||||
).exists()
|
).exists()
|
||||||
and stream.status == StreamStatus.DISABLED
|
and stream.status == StreamStatus.DISABLED_DELETED
|
||||||
):
|
):
|
||||||
LOGGER.info(
|
LOGGER.info(
|
||||||
"Deleting inactive stream as all pending messages were sent.", stream=stream
|
"Deleting inactive stream as all pending messages were sent.", stream=stream
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class TestSSFAuth(APITestCase):
|
|||||||
self.assertEqual(event.status, SSFEventStatus.PENDING_FAILED)
|
self.assertEqual(event.status, SSFEventStatus.PENDING_FAILED)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
event.payload["events"],
|
event.payload["events"],
|
||||||
{"https://schemas.openid.net/secevent/ssf/event-type/verification": {"state": None}},
|
{"https://schemas.openid.net/secevent/ssf/event-type/verification": {}},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_stream_add_oidc(self):
|
def test_stream_add_oidc(self):
|
||||||
@@ -115,7 +115,7 @@ class TestSSFAuth(APITestCase):
|
|||||||
self.assertEqual(event.status, SSFEventStatus.PENDING_FAILED)
|
self.assertEqual(event.status, SSFEventStatus.PENDING_FAILED)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
event.payload["events"],
|
event.payload["events"],
|
||||||
{"https://schemas.openid.net/secevent/ssf/event-type/verification": {"state": None}},
|
{"https://schemas.openid.net/secevent/ssf/event-type/verification": {}},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_token_invalid(self):
|
def test_token_invalid(self):
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class TestStream(APITestCase):
|
|||||||
self.assertEqual(event.status, SSFEventStatus.PENDING_FAILED)
|
self.assertEqual(event.status, SSFEventStatus.PENDING_FAILED)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
event.payload["events"],
|
event.payload["events"],
|
||||||
{"https://schemas.openid.net/secevent/ssf/event-type/verification": {"state": None}},
|
{"https://schemas.openid.net/secevent/ssf/event-type/verification": {}},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_stream_add_poll(self):
|
def test_stream_add_poll(self):
|
||||||
@@ -96,7 +96,7 @@ class TestStream(APITestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(res.status_code, 204)
|
self.assertEqual(res.status_code, 204)
|
||||||
stream.refresh_from_db()
|
stream.refresh_from_db()
|
||||||
self.assertEqual(stream.status, StreamStatus.DISABLED)
|
self.assertEqual(stream.status, StreamStatus.DISABLED_DELETED)
|
||||||
|
|
||||||
def test_stream_get(self):
|
def test_stream_get(self):
|
||||||
"""get stream"""
|
"""get stream"""
|
||||||
@@ -225,3 +225,26 @@ class TestStream(APITestCase):
|
|||||||
HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}",
|
HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}",
|
||||||
)
|
)
|
||||||
self.assertEqual(res.status_code, 404)
|
self.assertEqual(res.status_code, 404)
|
||||||
|
|
||||||
|
def test_stream_status_update(self):
|
||||||
|
stream = Stream.objects.create(provider=self.provider)
|
||||||
|
res = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"authentik_providers_ssf:stream-status",
|
||||||
|
kwargs={"application_slug": self.application.slug},
|
||||||
|
),
|
||||||
|
data={
|
||||||
|
"stream_id": str(stream.pk),
|
||||||
|
"status": StreamStatus.DISABLED,
|
||||||
|
},
|
||||||
|
HTTP_AUTHORIZATION=f"Bearer {self.provider.token.key}",
|
||||||
|
)
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
stream.refresh_from_db()
|
||||||
|
self.assertJSONEqual(
|
||||||
|
res.content,
|
||||||
|
{
|
||||||
|
"stream_id": str(stream.pk),
|
||||||
|
"status": str(stream.status),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class TestTasks(APITestCase):
|
|||||||
)
|
)
|
||||||
event_data = stream.prepare_event_payload(
|
event_data = stream.prepare_event_payload(
|
||||||
EventTypes.SET_VERIFICATION,
|
EventTypes.SET_VERIFICATION,
|
||||||
{"state": None},
|
{},
|
||||||
sub_id={"format": "opaque", "id": str(stream.uuid)},
|
sub_id={"format": "opaque", "id": str(stream.uuid)},
|
||||||
)
|
)
|
||||||
with Mocker() as mocker:
|
with Mocker() as mocker:
|
||||||
@@ -46,7 +46,7 @@ class TestTasks(APITestCase):
|
|||||||
)
|
)
|
||||||
jwt = decode_complete(mocker.request_history[0].body, options={"verify_signature": False})
|
jwt = decode_complete(mocker.request_history[0].body, options={"verify_signature": False})
|
||||||
self.assertEqual(jwt["header"]["typ"], "secevent+jwt")
|
self.assertEqual(jwt["header"]["typ"], "secevent+jwt")
|
||||||
self.assertIsNone(jwt["payload"]["events"][EventTypes.SET_VERIFICATION]["state"])
|
self.assertEqual(jwt["payload"]["events"][EventTypes.SET_VERIFICATION], {})
|
||||||
|
|
||||||
def test_push_auth(self):
|
def test_push_auth(self):
|
||||||
auth = generate_id()
|
auth = generate_id()
|
||||||
@@ -58,7 +58,7 @@ class TestTasks(APITestCase):
|
|||||||
)
|
)
|
||||||
event_data = stream.prepare_event_payload(
|
event_data = stream.prepare_event_payload(
|
||||||
EventTypes.SET_VERIFICATION,
|
EventTypes.SET_VERIFICATION,
|
||||||
{"state": None},
|
{},
|
||||||
sub_id={"format": "opaque", "id": str(stream.uuid)},
|
sub_id={"format": "opaque", "id": str(stream.uuid)},
|
||||||
)
|
)
|
||||||
with Mocker() as mocker:
|
with Mocker() as mocker:
|
||||||
@@ -72,7 +72,7 @@ class TestTasks(APITestCase):
|
|||||||
)
|
)
|
||||||
jwt = decode_complete(mocker.request_history[0].body, options={"verify_signature": False})
|
jwt = decode_complete(mocker.request_history[0].body, options={"verify_signature": False})
|
||||||
self.assertEqual(jwt["header"]["typ"], "secevent+jwt")
|
self.assertEqual(jwt["header"]["typ"], "secevent+jwt")
|
||||||
self.assertIsNone(jwt["payload"]["events"][EventTypes.SET_VERIFICATION]["state"])
|
self.assertEqual(jwt["payload"]["events"][EventTypes.SET_VERIFICATION], {})
|
||||||
|
|
||||||
def test_push_stream_disable(self):
|
def test_push_stream_disable(self):
|
||||||
auth = generate_id()
|
auth = generate_id()
|
||||||
@@ -81,11 +81,11 @@ class TestTasks(APITestCase):
|
|||||||
delivery_method=DeliveryMethods.RFC_PUSH,
|
delivery_method=DeliveryMethods.RFC_PUSH,
|
||||||
endpoint_url="http://localhost/ssf-push",
|
endpoint_url="http://localhost/ssf-push",
|
||||||
authorization_header=auth,
|
authorization_header=auth,
|
||||||
status=StreamStatus.DISABLED,
|
status=StreamStatus.DISABLED_DELETED,
|
||||||
)
|
)
|
||||||
event_data = stream.prepare_event_payload(
|
event_data = stream.prepare_event_payload(
|
||||||
EventTypes.SET_VERIFICATION,
|
EventTypes.SET_VERIFICATION,
|
||||||
{"state": None},
|
{},
|
||||||
sub_id={"format": "opaque", "id": str(stream.uuid)},
|
sub_id={"format": "opaque", "id": str(stream.uuid)},
|
||||||
)
|
)
|
||||||
with Mocker() as mocker:
|
with Mocker() as mocker:
|
||||||
@@ -95,7 +95,7 @@ class TestTasks(APITestCase):
|
|||||||
).get_result(block=True, timeout=1)
|
).get_result(block=True, timeout=1)
|
||||||
jwt = decode_complete(mocker.request_history[0].body, options={"verify_signature": False})
|
jwt = decode_complete(mocker.request_history[0].body, options={"verify_signature": False})
|
||||||
self.assertEqual(jwt["header"]["typ"], "secevent+jwt")
|
self.assertEqual(jwt["header"]["typ"], "secevent+jwt")
|
||||||
self.assertIsNone(jwt["payload"]["events"][EventTypes.SET_VERIFICATION]["state"])
|
self.assertEqual(jwt["payload"]["events"][EventTypes.SET_VERIFICATION], {})
|
||||||
self.assertFalse(Stream.objects.filter(pk=stream.pk).exists())
|
self.assertFalse(Stream.objects.filter(pk=stream.pk).exists())
|
||||||
|
|
||||||
def test_push_error(self):
|
def test_push_error(self):
|
||||||
@@ -106,7 +106,7 @@ class TestTasks(APITestCase):
|
|||||||
)
|
)
|
||||||
event_data = stream.prepare_event_payload(
|
event_data = stream.prepare_event_payload(
|
||||||
EventTypes.SET_VERIFICATION,
|
EventTypes.SET_VERIFICATION,
|
||||||
{"state": None},
|
{},
|
||||||
sub_id={"format": "opaque", "id": str(stream.uuid)},
|
sub_id={"format": "opaque", "id": str(stream.uuid)},
|
||||||
)
|
)
|
||||||
with Mocker() as mocker:
|
with Mocker() as mocker:
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ class SSFView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
class SSFStreamView(SSFView):
|
class SSFStreamView(SSFView):
|
||||||
def get_object(self, any_status=False) -> Stream:
|
def get_object(self) -> Stream:
|
||||||
streams = Stream.objects.filter(provider=self.provider)
|
streams = Stream.objects.filter(provider=self.provider).exclude(
|
||||||
if not any_status:
|
status=StreamStatus.DISABLED_DELETED
|
||||||
streams = streams.filter(status__in=[StreamStatus.ENABLED, StreamStatus.PAUSED])
|
)
|
||||||
if "stream_id" in self.request.query_params:
|
if "stream_id" in self.request.query_params:
|
||||||
streams = streams.filter(pk=self.request.query_params["stream_id"])
|
streams = streams.filter(pk=self.request.query_params["stream_id"])
|
||||||
if "stream_id" in self.request.data:
|
if "stream_id" in self.request.data:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from django.http import HttpRequest
|
from django.http import Http404, HttpRequest
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from rest_framework.exceptions import PermissionDenied, ValidationError
|
from rest_framework.exceptions import PermissionDenied, ValidationError
|
||||||
from rest_framework.fields import CharField, ChoiceField, ListField, SerializerMethodField
|
from rest_framework.fields import CharField, ChoiceField, ListField, SerializerMethodField
|
||||||
@@ -106,7 +106,11 @@ class StreamResponseSerializer(PassiveSerializer):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def get_events_supported(self, instance: Stream) -> list[str]:
|
def get_events_supported(self, instance: Stream) -> list[str]:
|
||||||
return [x.value for x in EventTypes]
|
return [
|
||||||
|
EventTypes.CAEP_SESSION_REVOKED,
|
||||||
|
EventTypes.CAEP_CREDENTIAL_CHANGE,
|
||||||
|
EventTypes.SET_VERIFICATION,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class StreamView(SSFStreamView):
|
class StreamView(SSFStreamView):
|
||||||
@@ -128,10 +132,9 @@ class StreamView(SSFStreamView):
|
|||||||
LOGGER.info("Sending verification event", stream=instance)
|
LOGGER.info("Sending verification event", stream=instance)
|
||||||
send_ssf_events(
|
send_ssf_events(
|
||||||
EventTypes.SET_VERIFICATION,
|
EventTypes.SET_VERIFICATION,
|
||||||
{
|
{},
|
||||||
"state": None,
|
|
||||||
},
|
|
||||||
stream_filter={"pk": instance.uuid},
|
stream_filter={"pk": instance.uuid},
|
||||||
|
request=request,
|
||||||
sub_id={"format": "opaque", "id": str(instance.uuid)},
|
sub_id={"format": "opaque", "id": str(instance.uuid)},
|
||||||
)
|
)
|
||||||
response = StreamResponseSerializer(instance=instance, context={"request": request}).data
|
response = StreamResponseSerializer(instance=instance, context={"request": request}).data
|
||||||
@@ -159,7 +162,9 @@ class StreamView(SSFStreamView):
|
|||||||
|
|
||||||
def delete(self, request: Request, *args, **kwargs) -> Response:
|
def delete(self, request: Request, *args, **kwargs) -> Response:
|
||||||
stream = self.get_object()
|
stream = self.get_object()
|
||||||
stream.status = StreamStatus.DISABLED
|
if stream.status == StreamStatus.DISABLED_DELETED:
|
||||||
|
raise Http404
|
||||||
|
stream.status = StreamStatus.DISABLED_DELETED
|
||||||
stream.save()
|
stream.save()
|
||||||
return Response(status=204)
|
return Response(status=204)
|
||||||
|
|
||||||
@@ -175,6 +180,7 @@ class StreamVerifyView(SSFStreamView):
|
|||||||
"state": state,
|
"state": state,
|
||||||
},
|
},
|
||||||
stream_filter={"pk": stream.uuid},
|
stream_filter={"pk": stream.uuid},
|
||||||
|
request=request,
|
||||||
sub_id={"format": "opaque", "id": str(stream.uuid)},
|
sub_id={"format": "opaque", "id": str(stream.uuid)},
|
||||||
)
|
)
|
||||||
return Response(status=204)
|
return Response(status=204)
|
||||||
@@ -182,8 +188,25 @@ class StreamVerifyView(SSFStreamView):
|
|||||||
|
|
||||||
class StreamStatusView(SSFStreamView):
|
class StreamStatusView(SSFStreamView):
|
||||||
|
|
||||||
|
class StreamStatusSerializer(PassiveSerializer):
|
||||||
|
stream_id = CharField()
|
||||||
|
status = ChoiceField(choices=StreamStatus.choices)
|
||||||
|
|
||||||
def get(self, request: Request, *args, **kwargs):
|
def get(self, request: Request, *args, **kwargs):
|
||||||
stream = self.get_object(any_status=True)
|
stream = self.get_object()
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"stream_id": str(stream.pk),
|
||||||
|
"status": str(stream.status),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def post(self, request: Request, *args, **kwargs):
|
||||||
|
stream = self.get_object()
|
||||||
|
serializer = self.StreamStatusSerializer(stream, data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
stream.status = serializer.validated_data["status"]
|
||||||
|
stream.save()
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"stream_id": str(stream.pk),
|
"stream_id": str(stream.pk),
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ from inspect import currentframe
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from asgiref.sync import async_to_sync
|
|
||||||
from channels.layers import get_channel_layer
|
from channels.layers import get_channel_layer
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@@ -410,7 +409,7 @@ class NotificationTransport(TasksModel, SerializerModel):
|
|||||||
)
|
)
|
||||||
notification.save()
|
notification.save()
|
||||||
layer = get_channel_layer()
|
layer = get_channel_layer()
|
||||||
async_to_sync(layer.group_send)(
|
layer.group_send_blocking(
|
||||||
build_user_group(notification.user),
|
build_user_group(notification.user),
|
||||||
{
|
{
|
||||||
"type": "event.notification",
|
"type": "event.notification",
|
||||||
|
|||||||
@@ -53,6 +53,16 @@ class TestEndSessionView(OAuthTestCase):
|
|||||||
self.brand.flow_invalidation = self.invalidation_flow
|
self.brand.flow_invalidation = self.invalidation_flow
|
||||||
self.brand.save()
|
self.brand.save()
|
||||||
|
|
||||||
|
def _id_token_hint(self, host: str) -> str:
|
||||||
|
"""Issue a valid id_token_hint for the test provider under the given host."""
|
||||||
|
return self.provider.encode(
|
||||||
|
{
|
||||||
|
"iss": f"http://{host}/application/o/{self.app.slug}/",
|
||||||
|
"aud": self.provider.client_id,
|
||||||
|
"sub": str(self.user.pk),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def test_post_logout_redirect_uri_strict_match(self):
|
def test_post_logout_redirect_uri_strict_match(self):
|
||||||
"""Test strict URI matching redirects to flow"""
|
"""Test strict URI matching redirects to flow"""
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
@@ -61,7 +71,10 @@ class TestEndSessionView(OAuthTestCase):
|
|||||||
"authentik_providers_oauth2:end-session",
|
"authentik_providers_oauth2:end-session",
|
||||||
kwargs={"application_slug": self.app.slug},
|
kwargs={"application_slug": self.app.slug},
|
||||||
),
|
),
|
||||||
{"post_logout_redirect_uri": "http://testserver/logout"},
|
{
|
||||||
|
"post_logout_redirect_uri": "http://testserver/logout",
|
||||||
|
"id_token_hint": self._id_token_hint(self.brand.domain),
|
||||||
|
},
|
||||||
HTTP_HOST=self.brand.domain,
|
HTTP_HOST=self.brand.domain,
|
||||||
)
|
)
|
||||||
# Should redirect to the invalidation flow
|
# Should redirect to the invalidation flow
|
||||||
@@ -69,7 +82,12 @@ class TestEndSessionView(OAuthTestCase):
|
|||||||
self.assertIn(self.invalidation_flow.slug, response.url)
|
self.assertIn(self.invalidation_flow.slug, response.url)
|
||||||
|
|
||||||
def test_post_logout_redirect_uri_strict_no_match(self):
|
def test_post_logout_redirect_uri_strict_no_match(self):
|
||||||
"""Test strict URI not matching still proceeds with flow (no redirect URI in context)"""
|
"""Test strict URI not matching returns an error and does not start logout flow.
|
||||||
|
|
||||||
|
Required by OIDC RP-Initiated Logout 1.0: on an unregistered
|
||||||
|
post_logout_redirect_uri, the OP MUST NOT redirect and MUST NOT proceed with
|
||||||
|
logout that targets the RP.
|
||||||
|
"""
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
invalid_uri = "http://testserver/other"
|
invalid_uri = "http://testserver/other"
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
@@ -77,12 +95,14 @@ class TestEndSessionView(OAuthTestCase):
|
|||||||
"authentik_providers_oauth2:end-session",
|
"authentik_providers_oauth2:end-session",
|
||||||
kwargs={"application_slug": self.app.slug},
|
kwargs={"application_slug": self.app.slug},
|
||||||
),
|
),
|
||||||
{"post_logout_redirect_uri": invalid_uri},
|
{
|
||||||
|
"post_logout_redirect_uri": invalid_uri,
|
||||||
|
"id_token_hint": self._id_token_hint(self.brand.domain),
|
||||||
|
},
|
||||||
HTTP_HOST=self.brand.domain,
|
HTTP_HOST=self.brand.domain,
|
||||||
)
|
)
|
||||||
# Should still redirect to flow, but invalid URI should not be in response
|
self.assertEqual(response.status_code, 400)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertNotIn(invalid_uri, response.content.decode())
|
||||||
self.assertNotIn(invalid_uri, response.url)
|
|
||||||
|
|
||||||
def test_post_logout_redirect_uri_regex_match(self):
|
def test_post_logout_redirect_uri_regex_match(self):
|
||||||
"""Test regex URI matching redirects to flow"""
|
"""Test regex URI matching redirects to flow"""
|
||||||
@@ -92,7 +112,10 @@ class TestEndSessionView(OAuthTestCase):
|
|||||||
"authentik_providers_oauth2:end-session",
|
"authentik_providers_oauth2:end-session",
|
||||||
kwargs={"application_slug": self.app.slug},
|
kwargs={"application_slug": self.app.slug},
|
||||||
),
|
),
|
||||||
{"post_logout_redirect_uri": "https://app.example.com/logout"},
|
{
|
||||||
|
"post_logout_redirect_uri": "https://app.example.com/logout",
|
||||||
|
"id_token_hint": self._id_token_hint(self.brand.domain),
|
||||||
|
},
|
||||||
HTTP_HOST=self.brand.domain,
|
HTTP_HOST=self.brand.domain,
|
||||||
)
|
)
|
||||||
# Should redirect to the invalidation flow
|
# Should redirect to the invalidation flow
|
||||||
@@ -100,7 +123,7 @@ class TestEndSessionView(OAuthTestCase):
|
|||||||
self.assertIn(self.invalidation_flow.slug, response.url)
|
self.assertIn(self.invalidation_flow.slug, response.url)
|
||||||
|
|
||||||
def test_post_logout_redirect_uri_regex_no_match(self):
|
def test_post_logout_redirect_uri_regex_no_match(self):
|
||||||
"""Test regex URI not matching"""
|
"""Test regex URI not matching returns an error and does not start logout flow."""
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
invalid_uri = "https://malicious.com/logout"
|
invalid_uri = "https://malicious.com/logout"
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
@@ -108,12 +131,14 @@ class TestEndSessionView(OAuthTestCase):
|
|||||||
"authentik_providers_oauth2:end-session",
|
"authentik_providers_oauth2:end-session",
|
||||||
kwargs={"application_slug": self.app.slug},
|
kwargs={"application_slug": self.app.slug},
|
||||||
),
|
),
|
||||||
{"post_logout_redirect_uri": invalid_uri},
|
{
|
||||||
|
"post_logout_redirect_uri": invalid_uri,
|
||||||
|
"id_token_hint": self._id_token_hint(self.brand.domain),
|
||||||
|
},
|
||||||
HTTP_HOST=self.brand.domain,
|
HTTP_HOST=self.brand.domain,
|
||||||
)
|
)
|
||||||
# Should still proceed to flow, but invalid URI should not be in response
|
self.assertEqual(response.status_code, 400)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertNotIn(invalid_uri, response.content.decode())
|
||||||
self.assertNotIn(invalid_uri, response.url)
|
|
||||||
|
|
||||||
def test_state_parameter_appended_to_uri(self):
|
def test_state_parameter_appended_to_uri(self):
|
||||||
"""Test state parameter is appended to validated redirect URI"""
|
"""Test state parameter is appended to validated redirect URI"""
|
||||||
@@ -123,6 +148,7 @@ class TestEndSessionView(OAuthTestCase):
|
|||||||
{
|
{
|
||||||
"post_logout_redirect_uri": "http://testserver/logout",
|
"post_logout_redirect_uri": "http://testserver/logout",
|
||||||
"state": "test-state-123",
|
"state": "test-state-123",
|
||||||
|
"id_token_hint": self._id_token_hint("testserver"),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
@@ -132,6 +158,7 @@ class TestEndSessionView(OAuthTestCase):
|
|||||||
view.request = request
|
view.request = request
|
||||||
view.kwargs = {"application_slug": self.app.slug}
|
view.kwargs = {"application_slug": self.app.slug}
|
||||||
view.resolve_provider_application()
|
view.resolve_provider_application()
|
||||||
|
view.validate()
|
||||||
|
|
||||||
self.assertIn("state=test-state-123", view.post_logout_redirect_uri)
|
self.assertIn("state=test-state-123", view.post_logout_redirect_uri)
|
||||||
|
|
||||||
@@ -146,6 +173,7 @@ class TestEndSessionView(OAuthTestCase):
|
|||||||
{
|
{
|
||||||
"post_logout_redirect_uri": "http://testserver/logout",
|
"post_logout_redirect_uri": "http://testserver/logout",
|
||||||
"state": "xyz789",
|
"state": "xyz789",
|
||||||
|
"id_token_hint": self._id_token_hint(self.brand.domain),
|
||||||
},
|
},
|
||||||
HTTP_HOST=self.brand.domain,
|
HTTP_HOST=self.brand.domain,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ from urllib.parse import quote, urlparse
|
|||||||
|
|
||||||
from django.http import Http404, HttpRequest, HttpResponse
|
from django.http import Http404, HttpRequest, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
from jwt import PyJWTError
|
||||||
|
from jwt import decode as jwt_decode
|
||||||
|
|
||||||
from authentik.common.oauth.constants import (
|
from authentik.common.oauth.constants import (
|
||||||
FORBIDDEN_URI_SCHEMES,
|
FORBIDDEN_URI_SCHEMES,
|
||||||
@@ -21,11 +23,14 @@ from authentik.flows.planner import (
|
|||||||
from authentik.flows.stage import SessionEndStage
|
from authentik.flows.stage import SessionEndStage
|
||||||
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
||||||
from authentik.lib.views import bad_request_message
|
from authentik.lib.views import bad_request_message
|
||||||
from authentik.policies.views import PolicyAccessView, RequestValidationError
|
from authentik.policies.views import PolicyAccessView
|
||||||
from authentik.providers.iframe_logout import IframeLogoutStageView
|
from authentik.providers.iframe_logout import IframeLogoutStageView
|
||||||
|
from authentik.providers.oauth2.errors import TokenError
|
||||||
from authentik.providers.oauth2.models import (
|
from authentik.providers.oauth2.models import (
|
||||||
AccessToken,
|
AccessToken,
|
||||||
|
JWTAlgorithms,
|
||||||
OAuth2LogoutMethod,
|
OAuth2LogoutMethod,
|
||||||
|
OAuth2Provider,
|
||||||
RedirectURIMatchingMode,
|
RedirectURIMatchingMode,
|
||||||
)
|
)
|
||||||
from authentik.providers.oauth2.tasks import send_backchannel_logout_request
|
from authentik.providers.oauth2.tasks import send_backchannel_logout_request
|
||||||
@@ -47,21 +52,45 @@ class EndSessionView(PolicyAccessView):
|
|||||||
if not self.flow:
|
if not self.flow:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
# Parse end session parameters
|
# Parse end session parameters
|
||||||
query_dict = self.request.POST if self.request.method == "POST" else self.request.GET
|
query_dict = self.request.POST if self.request.method == "POST" else self.request.GET
|
||||||
state = query_dict.get("state")
|
state = query_dict.get("state")
|
||||||
request_redirect_uri = query_dict.get("post_logout_redirect_uri")
|
request_redirect_uri = query_dict.get("post_logout_redirect_uri")
|
||||||
|
id_token_hint = query_dict.get("id_token_hint")
|
||||||
self.post_logout_redirect_uri = None
|
self.post_logout_redirect_uri = None
|
||||||
|
|
||||||
|
# OIDC Certification: Verify id_token_hint. If invalid or missing, throw an error
|
||||||
|
if id_token_hint:
|
||||||
|
# Load a fresh provider instance that's not part of the flow
|
||||||
|
# since it'll have the cryptography Certificate that can't be pickled
|
||||||
|
provider = OAuth2Provider.objects.get(pk=self.provider.pk)
|
||||||
|
key, alg = provider.jwt_key
|
||||||
|
if alg != JWTAlgorithms.HS256:
|
||||||
|
key = provider.signing_key.public_key
|
||||||
|
try:
|
||||||
|
jwt_decode(
|
||||||
|
id_token_hint,
|
||||||
|
key,
|
||||||
|
algorithms=[alg],
|
||||||
|
audience=provider.client_id,
|
||||||
|
issuer=provider.get_issuer(self.request),
|
||||||
|
# ID Tokens are short-lived; a logout request arriving
|
||||||
|
# after expiry is still legitimate and must succeed.
|
||||||
|
options={"verify_exp": False},
|
||||||
|
)
|
||||||
|
except PyJWTError:
|
||||||
|
raise TokenError("invalid_request").with_cause(
|
||||||
|
"id_token_hint_decode_failed"
|
||||||
|
) from None
|
||||||
|
|
||||||
# Validate post_logout_redirect_uri against registered URIs
|
# Validate post_logout_redirect_uri against registered URIs
|
||||||
if request_redirect_uri:
|
if request_redirect_uri:
|
||||||
|
# OIDC Certification: id_token_hint required with post_logout_redirect_uri
|
||||||
|
if not id_token_hint:
|
||||||
|
raise TokenError("invalid_request").with_cause("id_token_hint_missing")
|
||||||
if urlparse(request_redirect_uri).scheme in FORBIDDEN_URI_SCHEMES:
|
if urlparse(request_redirect_uri).scheme in FORBIDDEN_URI_SCHEMES:
|
||||||
raise RequestValidationError(
|
raise TokenError("invalid_request").with_cause("post_logout_redirect_uri")
|
||||||
bad_request_message(
|
|
||||||
self.request,
|
|
||||||
"Forbidden URI scheme in post_logout_redirect_uri",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for allowed in self.provider.post_logout_redirect_uris:
|
for allowed in self.provider.post_logout_redirect_uris:
|
||||||
if allowed.matching_mode == RedirectURIMatchingMode.STRICT:
|
if allowed.matching_mode == RedirectURIMatchingMode.STRICT:
|
||||||
if request_redirect_uri == allowed.url:
|
if request_redirect_uri == allowed.url:
|
||||||
@@ -71,6 +100,10 @@ class EndSessionView(PolicyAccessView):
|
|||||||
if fullmatch(allowed.url, request_redirect_uri):
|
if fullmatch(allowed.url, request_redirect_uri):
|
||||||
self.post_logout_redirect_uri = request_redirect_uri
|
self.post_logout_redirect_uri = request_redirect_uri
|
||||||
break
|
break
|
||||||
|
# OIDC Certification: OP MUST NOT perform post-logout redirection
|
||||||
|
# if the supplied URI does not exactly match a registered one
|
||||||
|
if self.post_logout_redirect_uri is None:
|
||||||
|
raise TokenError("invalid_request").with_cause("invalid_post_logout_redirect_uri")
|
||||||
|
|
||||||
# Append state to the redirect URI if both are present
|
# Append state to the redirect URI if both are present
|
||||||
if self.post_logout_redirect_uri and state:
|
if self.post_logout_redirect_uri and state:
|
||||||
@@ -91,50 +124,43 @@ class EndSessionView(PolicyAccessView):
|
|||||||
"<html><body>Logout successful</body></html>", content_type="text/html", status=200
|
"<html><body>Logout successful</body></html>", content_type="text/html", status=200
|
||||||
)
|
)
|
||||||
|
|
||||||
# Otherwise, continue with normal policy checks
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
"""Dispatch the flow planner for the invalidation flow"""
|
"""Dispatch the flow planner for the invalidation flow"""
|
||||||
|
try:
|
||||||
|
self.validate()
|
||||||
|
except TokenError as exc:
|
||||||
|
return bad_request_message(
|
||||||
|
self.request,
|
||||||
|
exc.description,
|
||||||
|
)
|
||||||
planner = FlowPlanner(self.flow)
|
planner = FlowPlanner(self.flow)
|
||||||
planner.allow_empty_flows = True
|
planner.allow_empty_flows = True
|
||||||
|
|
||||||
# Build flow context with logout parameters
|
|
||||||
context = {
|
context = {
|
||||||
PLAN_CONTEXT_APPLICATION: self.application,
|
PLAN_CONTEXT_APPLICATION: self.application,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get session info for logout notifications and token invalidation
|
|
||||||
auth_session = AuthenticatedSession.from_request(request, request.user)
|
auth_session = AuthenticatedSession.from_request(request, request.user)
|
||||||
|
|
||||||
# Add validated redirect URI (with state appended) to context if available
|
|
||||||
if self.post_logout_redirect_uri:
|
if self.post_logout_redirect_uri:
|
||||||
context[PLAN_CONTEXT_POST_LOGOUT_REDIRECT_URI] = self.post_logout_redirect_uri
|
context[PLAN_CONTEXT_POST_LOGOUT_REDIRECT_URI] = self.post_logout_redirect_uri
|
||||||
# Invalidate tokens for this provider/session (RP-initiated logout:
|
|
||||||
# user stays logged into authentik, only this provider's tokens are revoked)
|
|
||||||
if request.user.is_authenticated and auth_session:
|
|
||||||
AccessToken.objects.filter(
|
|
||||||
user=request.user,
|
|
||||||
provider=self.provider,
|
|
||||||
session=auth_session,
|
|
||||||
).delete()
|
|
||||||
session_key = (
|
session_key = (
|
||||||
auth_session.session.session_key if auth_session and auth_session.session else None
|
auth_session.session.session_key if auth_session and auth_session.session else None
|
||||||
)
|
)
|
||||||
|
|
||||||
# Handle frontchannel logout
|
|
||||||
frontchannel_logout_url = None
|
frontchannel_logout_url = None
|
||||||
if self.provider.logout_method == OAuth2LogoutMethod.FRONTCHANNEL:
|
if self.provider.logout_method == OAuth2LogoutMethod.FRONTCHANNEL:
|
||||||
frontchannel_logout_url = build_frontchannel_logout_url(
|
frontchannel_logout_url = build_frontchannel_logout_url(
|
||||||
self.provider, request, session_key
|
self.provider, request, session_key
|
||||||
)
|
)
|
||||||
|
|
||||||
# Handle backchannel logout
|
|
||||||
if (
|
if (
|
||||||
self.provider.logout_method == OAuth2LogoutMethod.BACKCHANNEL
|
self.provider.logout_method == OAuth2LogoutMethod.BACKCHANNEL
|
||||||
and self.provider.logout_uri
|
and self.provider.logout_uri
|
||||||
):
|
):
|
||||||
# Find access token to get iss and sub for the logout token
|
|
||||||
access_token = AccessToken.objects.filter(
|
access_token = AccessToken.objects.filter(
|
||||||
user=request.user,
|
user=request.user,
|
||||||
provider=self.provider,
|
provider=self.provider,
|
||||||
@@ -163,9 +189,16 @@ class EndSessionView(PolicyAccessView):
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
access_tokens = AccessToken.objects.filter(
|
||||||
|
user=request.user,
|
||||||
|
provider=self.provider,
|
||||||
|
)
|
||||||
|
if auth_session:
|
||||||
|
access_tokens = access_tokens.filter(session=auth_session)
|
||||||
|
access_tokens.delete()
|
||||||
|
|
||||||
plan = planner.plan(request, context)
|
plan = planner.plan(request, context)
|
||||||
|
|
||||||
# Inject iframe logout stage if frontchannel logout is configured
|
|
||||||
if frontchannel_logout_url:
|
if frontchannel_logout_url:
|
||||||
plan.insert_stage(in_memory_stage(IframeLogoutStageView))
|
plan.insert_stage(in_memory_stage(IframeLogoutStageView))
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"""RAC Signals"""
|
"""RAC Signals"""
|
||||||
|
|
||||||
from asgiref.sync import async_to_sync
|
|
||||||
from channels.layers import get_channel_layer
|
from channels.layers import get_channel_layer
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db.models.signals import post_delete, post_save, pre_delete
|
from django.db.models.signals import post_delete, post_save, pre_delete
|
||||||
@@ -18,7 +17,7 @@ from authentik.providers.rac.models import ConnectionToken, Endpoint
|
|||||||
@receiver(pre_delete, sender=AuthenticatedSession)
|
@receiver(pre_delete, sender=AuthenticatedSession)
|
||||||
def user_session_deleted(sender, instance: AuthenticatedSession, **_):
|
def user_session_deleted(sender, instance: AuthenticatedSession, **_):
|
||||||
layer = get_channel_layer()
|
layer = get_channel_layer()
|
||||||
async_to_sync(layer.group_send)(
|
layer.group_send_blocking(
|
||||||
build_rac_client_group_session(instance.session.session_key),
|
build_rac_client_group_session(instance.session.session_key),
|
||||||
{"type": "event.disconnect", "reason": "session_logout"},
|
{"type": "event.disconnect", "reason": "session_logout"},
|
||||||
)
|
)
|
||||||
@@ -28,7 +27,7 @@ def user_session_deleted(sender, instance: AuthenticatedSession, **_):
|
|||||||
def pre_delete_connection_token_disconnect(sender, instance: ConnectionToken, **_):
|
def pre_delete_connection_token_disconnect(sender, instance: ConnectionToken, **_):
|
||||||
"""Disconnect session when connection token is deleted"""
|
"""Disconnect session when connection token is deleted"""
|
||||||
layer = get_channel_layer()
|
layer = get_channel_layer()
|
||||||
async_to_sync(layer.group_send)(
|
layer.group_send_blocking(
|
||||||
build_rac_client_group_token(instance.token),
|
build_rac_client_group_token(instance.token),
|
||||||
{"type": "event.disconnect", "reason": "token_delete"},
|
{"type": "event.disconnect", "reason": "token_delete"},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
("authentik_core", "0056_user_roles"), # must run before group field is removed
|
||||||
("authentik_rbac", "0009_remove_initialpermissions_mode"),
|
("authentik_rbac", "0009_remove_initialpermissions_mode"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -73,8 +73,16 @@ entries:
|
|||||||
redirect_uris:
|
redirect_uris:
|
||||||
- matching_mode: strict
|
- matching_mode: strict
|
||||||
url: https://localhost:8443/test/a/authentik/callback
|
url: https://localhost:8443/test/a/authentik/callback
|
||||||
|
redirect_uri_type: authorization
|
||||||
- matching_mode: strict
|
- matching_mode: strict
|
||||||
url: https://host.docker.internal:8443/test/a/authentik/callback
|
url: https://host.docker.internal:8443/test/a/authentik/callback
|
||||||
|
redirect_uri_type: authorization
|
||||||
|
- matching_mode: strict
|
||||||
|
url: https://localhost:8443/test/a/authentik/post_logout_redirect
|
||||||
|
redirect_uri_type: logout
|
||||||
|
- matching_mode: strict
|
||||||
|
url: https://host.docker.internal:8443/test/a/authentik/post_logout_redirect
|
||||||
|
redirect_uri_type: logout
|
||||||
grant_types:
|
grant_types:
|
||||||
- authorization_code
|
- authorization_code
|
||||||
- implicit
|
- implicit
|
||||||
@@ -108,8 +116,16 @@ entries:
|
|||||||
redirect_uris:
|
redirect_uris:
|
||||||
- matching_mode: strict
|
- matching_mode: strict
|
||||||
url: https://localhost:8443/test/a/authentik/callback
|
url: https://localhost:8443/test/a/authentik/callback
|
||||||
|
redirect_uri_type: authorization
|
||||||
- matching_mode: strict
|
- matching_mode: strict
|
||||||
url: https://host.docker.internal:8443/test/a/authentik/callback
|
url: https://host.docker.internal:8443/test/a/authentik/callback
|
||||||
|
redirect_uri_type: authorization
|
||||||
|
- matching_mode: strict
|
||||||
|
url: https://localhost:8443/test/a/authentik/post_logout_redirect
|
||||||
|
redirect_uri_type: logout
|
||||||
|
- matching_mode: strict
|
||||||
|
url: https://host.docker.internal:8443/test/a/authentik/post_logout_redirect
|
||||||
|
redirect_uri_type: logout
|
||||||
grant_types:
|
grant_types:
|
||||||
- authorization_code
|
- authorization_code
|
||||||
- implicit
|
- implicit
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ var healthcheckCmd = &cobra.Command{
|
|||||||
exitCode := 1
|
exitCode := 1
|
||||||
log.WithField("mode", mode).Debug("checking health")
|
log.WithField("mode", mode).Debug("checking health")
|
||||||
switch strings.ToLower(mode) {
|
switch strings.ToLower(mode) {
|
||||||
|
case "allinone":
|
||||||
|
fallthrough
|
||||||
case "server":
|
case "server":
|
||||||
exitCode = check(fmt.Sprintf("http://localhost%s-/health/live/", config.Get().Web.Path))
|
exitCode = check(fmt.Sprintf("http://localhost%s-/health/live/", config.Get().Web.Path))
|
||||||
case "worker":
|
case "worker":
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -7,7 +7,7 @@ require (
|
|||||||
beryju.io/radius-eap v0.1.0
|
beryju.io/radius-eap v0.1.0
|
||||||
github.com/avast/retry-go/v4 v4.7.0
|
github.com/avast/retry-go/v4 v4.7.0
|
||||||
github.com/coreos/go-oidc/v3 v3.18.0
|
github.com/coreos/go-oidc/v3 v3.18.0
|
||||||
github.com/getsentry/sentry-go v0.46.0
|
github.com/getsentry/sentry-go v0.46.1
|
||||||
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
||||||
github.com/go-ldap/ldap/v3 v3.4.13
|
github.com/go-ldap/ldap/v3 v3.4.13
|
||||||
github.com/go-openapi/runtime v0.29.4
|
github.com/go-openapi/runtime v0.29.4
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -20,8 +20,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/getsentry/sentry-go v0.46.0 h1:mbdDaarbUdOt9X+dx6kDdntkShLEX3/+KyOsVDTPDj0=
|
github.com/getsentry/sentry-go v0.46.1 h1:mZyQFaQYkPxAdDG4HR8gDg6j4CnKYVWt4TF92N7i3XY=
|
||||||
github.com/getsentry/sentry-go v0.46.0/go.mod h1:evVbw2qotNUdYG8KxXbAdjOQWWvWIwKxpjdZZIvcIPw=
|
github.com/getsentry/sentry-go v0.46.1/go.mod h1:evVbw2qotNUdYG8KxXbAdjOQWWvWIwKxpjdZZIvcIPw=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ function run_authentik {
|
|||||||
echo go run ./cmd/server "$@"
|
echo go run ./cmd/server "$@"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
worker)
|
allinone | worker)
|
||||||
if [[ -x "$(command -v authentik)" ]]; then
|
if [[ -x "$(command -v authentik)" ]]; then
|
||||||
echo authentik "$@"
|
echo authentik "$@"
|
||||||
else
|
else
|
||||||
@@ -105,7 +105,7 @@ elif [[ "$1" == "test-all" ]]; then
|
|||||||
prepare_debug
|
prepare_debug
|
||||||
chmod 777 /root
|
chmod 777 /root
|
||||||
check_if_root_and_run manage test authentik
|
check_if_root_and_run manage test authentik
|
||||||
elif [[ "$1" == "server" ]] || [[ "$1" == "worker" ]]; then
|
elif [[ "$1" == "allinone" ]] || [[ "$1" == "server" ]] || [[ "$1" == "worker" ]]; then
|
||||||
wait_for_db
|
wait_for_db
|
||||||
check_if_root_and_run "$@"
|
check_if_root_and_run "$@"
|
||||||
elif [[ "$1" == "healthcheck" ]]; then
|
elif [[ "$1" == "healthcheck" ]]; then
|
||||||
|
|||||||
8
lifecycle/aws/package-lock.json
generated
8
lifecycle/aws/package-lock.json
generated
@@ -9,7 +9,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"aws-cdk": "^2.1118.4",
|
"aws-cdk": "^2.1119.0",
|
||||||
"cross-env": "^10.1.0"
|
"cross-env": "^10.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -25,9 +25,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/aws-cdk": {
|
"node_modules/aws-cdk": {
|
||||||
"version": "2.1118.4",
|
"version": "2.1119.0",
|
||||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1118.4.tgz",
|
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1119.0.tgz",
|
||||||
"integrity": "sha512-wJfRQdvb+FJ2cni059mYdmjhfwhMskP+PAB59BL9jhon+jYtjy8X3pbj3uzHgAOJwNhh6jGkP8xq36Cffccbbw==",
|
"integrity": "sha512-XBxZEKH3BY4M1EX6x0qBkmOAj8viErjpww14iH6Z3z6nI0YzjZeJ05eEl7eJwzUgv7NTGagWBS9m/eDJW5+dAg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"aws-cfn": "cross-env CI=false cdk synth --version-reporting=false > template.yaml"
|
"aws-cfn": "cross-env CI=false cdk synth --version-reporting=false > template.yaml"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"aws-cdk": "^2.1118.4",
|
"aws-cdk": "^2.1119.0",
|
||||||
"cross-env": "^10.1.0"
|
"cross-env": "^10.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -28,20 +28,45 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||||||
_ = db_conn.cursor()
|
_ = db_conn.cursor()
|
||||||
|
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
if self.path == "/-/metrics/":
|
from django.db import (
|
||||||
from authentik.root.monitoring import monitoring_set
|
DatabaseError,
|
||||||
|
InterfaceError,
|
||||||
|
OperationalError,
|
||||||
|
connections,
|
||||||
|
)
|
||||||
|
from psycopg.errors import AdminShutdown
|
||||||
|
|
||||||
monitoring_set.send_robust(self)
|
from authentik.root.monitoring import monitoring_set
|
||||||
self.send_response(200)
|
|
||||||
|
DATABASE_ERRORS = (
|
||||||
|
AdminShutdown,
|
||||||
|
InterfaceError,
|
||||||
|
DatabaseError,
|
||||||
|
ConnectionError,
|
||||||
|
OperationalError,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.path == "/-/metrics/":
|
||||||
|
try:
|
||||||
|
monitoring_set.send(self)
|
||||||
|
except DATABASE_ERRORS as exc:
|
||||||
|
LOGGER.warning("failed to send monitoring_set", exc=exc)
|
||||||
|
for db_conn in connections.all():
|
||||||
|
db_conn.close()
|
||||||
|
self.send_response(503)
|
||||||
|
else:
|
||||||
|
self.send_response(200)
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
elif self.path == "/-/health/ready/":
|
elif self.path == "/-/health/ready/":
|
||||||
from django.db.utils import OperationalError
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.check_db()
|
self.check_db()
|
||||||
except OperationalError:
|
except DATABASE_ERRORS as exc:
|
||||||
|
LOGGER.warning("failed to check database health", exc=exc)
|
||||||
|
for db_conn in connections.all():
|
||||||
|
db_conn.close()
|
||||||
self.send_response(503)
|
self.send_response(503)
|
||||||
self.send_response(200)
|
else:
|
||||||
|
self.send_response(200)
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
else:
|
else:
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-04-30 00:27+0000\n"
|
"POT-Creation-Date: 2026-05-01 03:47+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -872,10 +872,6 @@ msgstr ""
|
|||||||
msgid "Grace period must be shorter than the interval."
|
msgid "Grace period must be shorter than the interval."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/lifecycle/api/rules.py
|
|
||||||
msgid "Only one type-wide rule for each object type is allowed."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: authentik/enterprise/lifecycle/models.py
|
#: authentik/enterprise/lifecycle/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Select which transports should be used to notify the reviewers. If none are "
|
"Select which transports should be used to notify the reviewers. If none are "
|
||||||
@@ -903,7 +899,8 @@ msgid "Go to {self._get_model_name()}"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/lifecycle/models.py
|
#: authentik/enterprise/lifecycle/models.py
|
||||||
msgid "Access review is due for {self.content_type.name} {str(self.object)}"
|
msgid ""
|
||||||
|
"Access review is due for {self.content_type.name.lower()} {object_label}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/lifecycle/models.py
|
#: authentik/enterprise/lifecycle/models.py
|
||||||
@@ -916,7 +913,7 @@ msgid "Access review completed for {self.content_type.name} {str(self.object)}"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/lifecycle/tasks.py
|
#: authentik/enterprise/lifecycle/tasks.py
|
||||||
msgid "Dispatch tasks to validate lifecycle rules."
|
msgid "Dispatch tasks to apply lifecycle rules."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/lifecycle/tasks.py
|
#: authentik/enterprise/lifecycle/tasks.py
|
||||||
@@ -1229,6 +1226,78 @@ msgstr ""
|
|||||||
msgid "Generate data export."
|
msgid "Generate data export."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/api.py
|
||||||
|
msgid "User to lock. If omitted, locks the current user (self-service)."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/api.py
|
||||||
|
msgid "No lockdown flow configured."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/api.py
|
||||||
|
msgid "Lockdown flow is not applicable."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/api.py
|
||||||
|
msgid "Choose the target account, then return a flow link."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/api.py
|
||||||
|
msgid "No lockdown flow configured or the flow is not applicable"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/api.py
|
||||||
|
msgid "Permission denied (when targeting another user)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/models.py
|
||||||
|
msgid "Deactivate the user account (set is_active to False)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/models.py
|
||||||
|
msgid "Set an unusable password for the user"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/models.py
|
||||||
|
msgid "Delete all active sessions for the user"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/models.py
|
||||||
|
msgid ""
|
||||||
|
"Revoke all tokens for the user (API, app password, recovery, verification, "
|
||||||
|
"OAuth)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/models.py
|
||||||
|
msgid ""
|
||||||
|
"Flow to redirect users to after self-service lockdown. This flow should not "
|
||||||
|
"require authentication since the user's session is deleted."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/models.py
|
||||||
|
msgid "Account Lockdown Stage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/models.py
|
||||||
|
msgid "Account Lockdown Stages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/stage.py
|
||||||
|
msgid "No target user specified for account lockdown"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/stage.py
|
||||||
|
msgid "You do not have permission to lock down this account."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/stage.py
|
||||||
|
msgid "Account lockdown failed for this account."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/stage.py
|
||||||
|
msgid "Self-service account lockdown requires a completion flow."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||||
msgid "Endpoint Authenticator Google Device Trust Connector Stage"
|
msgid "Endpoint Authenticator Google Device Trust Connector Stage"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -4456,6 +4525,18 @@ msgstr ""
|
|||||||
msgid "Static: Static value, displayed as-is."
|
msgid "Static: Static value, displayed as-is."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/stages/prompt/models.py
|
||||||
|
msgid "Alert (Info): Static alert box with info styling"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/stages/prompt/models.py
|
||||||
|
msgid "Alert (Warning): Static alert box with warning styling"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentik/stages/prompt/models.py
|
||||||
|
msgid "Alert (Danger): Static alert box with danger styling"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/stages/prompt/models.py
|
#: authentik/stages/prompt/models.py
|
||||||
msgid "authentik: Selection of locales authentik supports"
|
msgid "authentik: Selection of locales authentik supports"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ Relatedly
|
|||||||
Sidero
|
Sidero
|
||||||
snipeit
|
snipeit
|
||||||
sonarqube
|
sonarqube
|
||||||
|
Technitium
|
||||||
Terrakube
|
Terrakube
|
||||||
Ueberauth
|
Ueberauth
|
||||||
Veeam
|
Veeam
|
||||||
|
|||||||
Binary file not shown.
@@ -15,7 +15,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-04-08 00:28+0000\n"
|
"POT-Creation-Date: 2026-05-01 03:47+0000\n"
|
||||||
"PO-Revision-Date: 2025-12-01 19:09+0000\n"
|
"PO-Revision-Date: 2025-12-01 19:09+0000\n"
|
||||||
"Last-Translator: Sp P, 2026\n"
|
"Last-Translator: Sp P, 2026\n"
|
||||||
"Language-Team: French (France) (https://app.transifex.com/authentik/teams/119923/fr_FR/)\n"
|
"Language-Team: French (France) (https://app.transifex.com/authentik/teams/119923/fr_FR/)\n"
|
||||||
@@ -263,6 +263,18 @@ msgstr ""
|
|||||||
"fournisseurs backchannels sont retournés. Si faux, les fournisseurs "
|
"fournisseurs backchannels sont retournés. Si faux, les fournisseurs "
|
||||||
"backchannels sont exclus"
|
"backchannels sont exclus"
|
||||||
|
|
||||||
|
#: authentik/core/api/users.py
|
||||||
|
msgid "Invalid password hash format. Must be a valid Django password hash."
|
||||||
|
msgstr ""
|
||||||
|
"Format de hachage de mot de passe invalide. Cela doit être un hachage de mot"
|
||||||
|
" de passe Django valide."
|
||||||
|
|
||||||
|
#: authentik/core/api/users.py
|
||||||
|
msgid "Cannot set both password and password_hash. Use only one."
|
||||||
|
msgstr ""
|
||||||
|
"Impossible de définir à la fois password (mot de passe) et password_hash "
|
||||||
|
"(hachage de mot de passe). N'en utiliser qu'un seul."
|
||||||
|
|
||||||
#: authentik/core/api/users.py
|
#: authentik/core/api/users.py
|
||||||
msgid "No leading or trailing slashes allowed."
|
msgid "No leading or trailing slashes allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -443,6 +455,11 @@ msgid "Open launch URL in a new browser tab or window."
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Ouvrir l'URL de lancement dans une nouvelle fenêtre ou un nouvel onglet."
|
"Ouvrir l'URL de lancement dans une nouvelle fenêtre ou un nouvel onglet."
|
||||||
|
|
||||||
|
#: authentik/core/models.py
|
||||||
|
msgid "Hide this application from the user's My applications page."
|
||||||
|
msgstr ""
|
||||||
|
"Masquer cette application dans la page Mes applications de l'utilisateur."
|
||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "Application"
|
msgid "Application"
|
||||||
msgstr "Application"
|
msgstr "Application"
|
||||||
@@ -810,6 +827,14 @@ msgstr "Nonce Apple"
|
|||||||
msgid "Apple Nonces"
|
msgid "Apple Nonces"
|
||||||
msgstr "Nonces Apple"
|
msgstr "Nonces Apple"
|
||||||
|
|
||||||
|
#: authentik/endpoints/connectors/agent/models.py
|
||||||
|
msgid "Apple Independent Secure Enclave"
|
||||||
|
msgstr "Secure Enclave indépendante d'Apple"
|
||||||
|
|
||||||
|
#: authentik/endpoints/connectors/agent/models.py
|
||||||
|
msgid "Apple Independent Secure Enclaves"
|
||||||
|
msgstr "Secure Enclaves indépendantes d'Apple"
|
||||||
|
|
||||||
#: authentik/endpoints/facts.py
|
#: authentik/endpoints/facts.py
|
||||||
msgid "Operating System name, such as 'Server 2022' or 'Ubuntu'"
|
msgid "Operating System name, such as 'Server 2022' or 'Ubuntu'"
|
||||||
msgstr "Nom du système d'exploitation, comme 'Server 2022' ou 'Ubuntu'"
|
msgstr "Nom du système d'exploitation, comme 'Server 2022' ou 'Ubuntu'"
|
||||||
@@ -936,12 +961,6 @@ msgstr "Soit un groupe de réviseurs soit un réviseur doit être défini."
|
|||||||
msgid "Grace period must be shorter than the interval."
|
msgid "Grace period must be shorter than the interval."
|
||||||
msgstr "La période de grâce doit être plus courte que l'intervalle."
|
msgstr "La période de grâce doit être plus courte que l'intervalle."
|
||||||
|
|
||||||
#: authentik/enterprise/lifecycle/api/rules.py
|
|
||||||
msgid "Only one type-wide rule for each object type is allowed."
|
|
||||||
msgstr ""
|
|
||||||
"Une seule règle pour l'ensemble du type est autorisée pour chaque type "
|
|
||||||
"d'objet."
|
|
||||||
|
|
||||||
#: authentik/enterprise/lifecycle/models.py
|
#: authentik/enterprise/lifecycle/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Select which transports should be used to notify the reviewers. If none are "
|
"Select which transports should be used to notify the reviewers. If none are "
|
||||||
@@ -972,10 +991,11 @@ msgid "Go to {self._get_model_name()}"
|
|||||||
msgstr "Aller à {self._get_model_name()}"
|
msgstr "Aller à {self._get_model_name()}"
|
||||||
|
|
||||||
#: authentik/enterprise/lifecycle/models.py
|
#: authentik/enterprise/lifecycle/models.py
|
||||||
msgid "Access review is due for {self.content_type.name} {str(self.object)}"
|
msgid ""
|
||||||
|
"Access review is due for {self.content_type.name.lower()} {object_label}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"La révision d'accès est attendue pour {self.content_type.name} "
|
"La révision de l'accès doit être effectuée pour "
|
||||||
"{str(self.object)}"
|
"{self.content_type.name.lower()} {object_label}"
|
||||||
|
|
||||||
#: authentik/enterprise/lifecycle/models.py
|
#: authentik/enterprise/lifecycle/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -992,8 +1012,8 @@ msgstr ""
|
|||||||
"{str(self.object)}"
|
"{str(self.object)}"
|
||||||
|
|
||||||
#: authentik/enterprise/lifecycle/tasks.py
|
#: authentik/enterprise/lifecycle/tasks.py
|
||||||
msgid "Dispatch tasks to validate lifecycle rules."
|
msgid "Dispatch tasks to apply lifecycle rules."
|
||||||
msgstr "Déclenche les tâches pour valider les règles de cycle de vie"
|
msgstr "Déclencher les tâches pour appliquer les règles de cycle de vie"
|
||||||
|
|
||||||
#: authentik/enterprise/lifecycle/tasks.py
|
#: authentik/enterprise/lifecycle/tasks.py
|
||||||
msgid "Apply lifecycle rule."
|
msgid "Apply lifecycle rule."
|
||||||
@@ -1336,6 +1356,86 @@ msgstr "Télécharger"
|
|||||||
msgid "Generate data export."
|
msgid "Generate data export."
|
||||||
msgstr "Générer un export de données."
|
msgstr "Générer un export de données."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/api.py
|
||||||
|
msgid "User to lock. If omitted, locks the current user (self-service)."
|
||||||
|
msgstr ""
|
||||||
|
"Utilisateur à bloquer. Si non renseigné, bloque l'utilisateur actuel (libre "
|
||||||
|
"service)."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/api.py
|
||||||
|
msgid "No lockdown flow configured."
|
||||||
|
msgstr "Aucun flux de blocage configuré."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/api.py
|
||||||
|
msgid "Lockdown flow is not applicable."
|
||||||
|
msgstr "Le flux de blocage n'est pas applicable."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/api.py
|
||||||
|
msgid "Choose the target account, then return a flow link."
|
||||||
|
msgstr "Choisit le compte cible, puis renvoie un lien de flux."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/api.py
|
||||||
|
msgid "No lockdown flow configured or the flow is not applicable"
|
||||||
|
msgstr "Aucun flux de blocage configuré, ou le flux n'est pas applicable"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/api.py
|
||||||
|
msgid "Permission denied (when targeting another user)"
|
||||||
|
msgstr "Permission refusée (lors du ciblage d'un autre utilisateur)"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/models.py
|
||||||
|
msgid "Deactivate the user account (set is_active to False)"
|
||||||
|
msgstr "Désactiver le compte de l'utilisateur (définir is_active à False)."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/models.py
|
||||||
|
msgid "Set an unusable password for the user"
|
||||||
|
msgstr "Définit un mot de passe inutilisable pour cet utilisateur."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/models.py
|
||||||
|
msgid "Delete all active sessions for the user"
|
||||||
|
msgstr "Supprimer toutes les sessions actives pour cet utilisateur."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/models.py
|
||||||
|
msgid ""
|
||||||
|
"Revoke all tokens for the user (API, app password, recovery, verification, "
|
||||||
|
"OAuth)"
|
||||||
|
msgstr ""
|
||||||
|
"Révoquer tous les jetons pour cet utilisateur (API, mot de passe applicatif,"
|
||||||
|
" récupération, vérification, OAuth)"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/models.py
|
||||||
|
msgid ""
|
||||||
|
"Flow to redirect users to after self-service lockdown. This flow should not "
|
||||||
|
"require authentication since the user's session is deleted."
|
||||||
|
msgstr ""
|
||||||
|
"Flux vers lequel rediriger les utilisateurs après le blocage en libre "
|
||||||
|
"service. Ce flux ne doit pas nécessiter d'authentification car la session "
|
||||||
|
"utilisateur est supprimée."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/models.py
|
||||||
|
msgid "Account Lockdown Stage"
|
||||||
|
msgstr "Etape de blocage de compte"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/models.py
|
||||||
|
msgid "Account Lockdown Stages"
|
||||||
|
msgstr "Etapes de blocage de compte"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/stage.py
|
||||||
|
msgid "No target user specified for account lockdown"
|
||||||
|
msgstr "Aucun utilisateur ciblé défini pour le blocage de compte"
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/stage.py
|
||||||
|
msgid "You do not have permission to lock down this account."
|
||||||
|
msgstr "Vous n'avez pas la permission de bloquer ce compte."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/stage.py
|
||||||
|
msgid "Account lockdown failed for this account."
|
||||||
|
msgstr "Echec du blocage de compte pour ce compte."
|
||||||
|
|
||||||
|
#: authentik/enterprise/stages/account_lockdown/stage.py
|
||||||
|
msgid "Self-service account lockdown requires a completion flow."
|
||||||
|
msgstr ""
|
||||||
|
"Le blocage de compte en libre service nécessite un flux de finalisation."
|
||||||
|
|
||||||
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||||
msgid "Endpoint Authenticator Google Device Trust Connector Stage"
|
msgid "Endpoint Authenticator Google Device Trust Connector Stage"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1469,11 +1569,11 @@ msgstr "Évènement utilisateur"
|
|||||||
|
|
||||||
#: authentik/events/models.py
|
#: authentik/events/models.py
|
||||||
msgid "Notification Transport"
|
msgid "Notification Transport"
|
||||||
msgstr "Transport de Notification"
|
msgstr "Transport de notification"
|
||||||
|
|
||||||
#: authentik/events/models.py
|
#: authentik/events/models.py
|
||||||
msgid "Notification Transports"
|
msgid "Notification Transports"
|
||||||
msgstr "Transports de notification"
|
msgstr "Transports de notifications"
|
||||||
|
|
||||||
#: authentik/events/models.py
|
#: authentik/events/models.py
|
||||||
msgid "Notice"
|
msgid "Notice"
|
||||||
@@ -1745,6 +1845,10 @@ msgstr "Jeton du flux"
|
|||||||
msgid "Flow Tokens"
|
msgid "Flow Tokens"
|
||||||
msgstr "Jetons du flux"
|
msgstr "Jetons du flux"
|
||||||
|
|
||||||
|
#: authentik/flows/planner.py
|
||||||
|
msgid "This link is invalid or has expired. Please request a new one."
|
||||||
|
msgstr "Ce lien est invalide ou a expiré. Veuillez un demander un nouveau."
|
||||||
|
|
||||||
#: authentik/flows/views/executor.py
|
#: authentik/flows/views/executor.py
|
||||||
msgid "Invalid next URL"
|
msgid "Invalid next URL"
|
||||||
msgstr "URL suivante invalide"
|
msgstr "URL suivante invalide"
|
||||||
@@ -2772,8 +2876,12 @@ msgstr ""
|
|||||||
"restriction d'audience ne sera ajoutée."
|
"restriction d'audience ne sera ajoutée."
|
||||||
|
|
||||||
#: authentik/providers/saml/models.py
|
#: authentik/providers/saml/models.py
|
||||||
msgid "Also known as EntityID"
|
msgid ""
|
||||||
msgstr "Aussi appelé EntityID"
|
"Also known as EntityID. Providing a value overrides the default issuer "
|
||||||
|
"generated by authentik."
|
||||||
|
msgstr ""
|
||||||
|
"Aussi appelé EntityID. Fournir une valeur remplace l'émetteur par défaut "
|
||||||
|
"généré par authentik."
|
||||||
|
|
||||||
#: authentik/providers/saml/models.py
|
#: authentik/providers/saml/models.py
|
||||||
msgid "SLS URL"
|
msgid "SLS URL"
|
||||||
@@ -2994,6 +3102,10 @@ msgstr "SAML NameID pour cette session"
|
|||||||
msgid "SAML NameID format"
|
msgid "SAML NameID format"
|
||||||
msgstr "Format SAML NameID"
|
msgstr "Format SAML NameID"
|
||||||
|
|
||||||
|
#: authentik/providers/saml/models.py
|
||||||
|
msgid "SAML Issuer used for this session"
|
||||||
|
msgstr "Émetteur SAML utilisé pour cette session"
|
||||||
|
|
||||||
#: authentik/providers/saml/models.py
|
#: authentik/providers/saml/models.py
|
||||||
msgid "SAML Session"
|
msgid "SAML Session"
|
||||||
msgstr "Session SAML"
|
msgstr "Session SAML"
|
||||||
@@ -3026,6 +3138,10 @@ msgstr "Salesforce"
|
|||||||
msgid "Webex"
|
msgid "Webex"
|
||||||
msgstr "Webex"
|
msgstr "Webex"
|
||||||
|
|
||||||
|
#: authentik/providers/scim/models.py
|
||||||
|
msgid "vCenter"
|
||||||
|
msgstr "vCenter"
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py
|
#: authentik/providers/scim/models.py
|
||||||
msgid "Group filters used to define sync-scope for groups."
|
msgid "Group filters used to define sync-scope for groups."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -3749,8 +3865,8 @@ msgid ""
|
|||||||
"Which servers a user has to be a member of to be granted access. Empty list "
|
"Which servers a user has to be a member of to be granted access. Empty list "
|
||||||
"allows every server."
|
"allows every server."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"De quels serveurs un utilisateur doit être membre afin d'être autorisé. Une "
|
"De quels serveurs un utilisateur doit être membre afin d'obtenir l'accès. "
|
||||||
"liste vide autorise tous les serveurs."
|
"Une liste vide autorise tous les serveurs."
|
||||||
|
|
||||||
#: authentik/sources/plex/models.py
|
#: authentik/sources/plex/models.py
|
||||||
msgid "Allow friends to authenticate, even if you don't share a server."
|
msgid "Allow friends to authenticate, even if you don't share a server."
|
||||||
@@ -4455,11 +4571,11 @@ msgstr "Activer les utilisateurs à la complétion de l'étape."
|
|||||||
|
|
||||||
#: authentik/stages/email/models.py
|
#: authentik/stages/email/models.py
|
||||||
msgid "Email Stage"
|
msgid "Email Stage"
|
||||||
msgstr "Étape Courriel"
|
msgstr "Étape de Courriel"
|
||||||
|
|
||||||
#: authentik/stages/email/models.py
|
#: authentik/stages/email/models.py
|
||||||
msgid "Email Stages"
|
msgid "Email Stages"
|
||||||
msgstr "Étapes Courriel"
|
msgstr "Étapes de Courriel"
|
||||||
|
|
||||||
#: authentik/stages/email/stage.py
|
#: authentik/stages/email/stage.py
|
||||||
msgid "Successfully verified Email."
|
msgid "Successfully verified Email."
|
||||||
@@ -4933,6 +5049,19 @@ msgstr ""
|
|||||||
msgid "Static: Static value, displayed as-is."
|
msgid "Static: Static value, displayed as-is."
|
||||||
msgstr "Statique : valeur statique, affichée comme telle."
|
msgstr "Statique : valeur statique, affichée comme telle."
|
||||||
|
|
||||||
|
#: authentik/stages/prompt/models.py
|
||||||
|
msgid "Alert (Info): Static alert box with info styling"
|
||||||
|
msgstr "Alerte (Info) : message d'alerte statique au format information"
|
||||||
|
|
||||||
|
#: authentik/stages/prompt/models.py
|
||||||
|
msgid "Alert (Warning): Static alert box with warning styling"
|
||||||
|
msgstr ""
|
||||||
|
"Alerte (Avertissement) : message d'alerte statique au format avertissement"
|
||||||
|
|
||||||
|
#: authentik/stages/prompt/models.py
|
||||||
|
msgid "Alert (Danger): Static alert box with danger styling"
|
||||||
|
msgstr "Alerte (Danger) : message d'alerte statique au format danger"
|
||||||
|
|
||||||
#: authentik/stages/prompt/models.py
|
#: authentik/stages/prompt/models.py
|
||||||
msgid "authentik: Selection of locales authentik supports"
|
msgid "authentik: Selection of locales authentik supports"
|
||||||
msgstr "authentik : sélection des locales prises en charges par authentik"
|
msgstr "authentik : sélection des locales prises en charges par authentik"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Utilities to run an axum server.
|
//! Utilities to run an axum server.
|
||||||
|
|
||||||
use std::{net, os::unix};
|
use std::{net, os::unix, path::PathBuf};
|
||||||
|
|
||||||
use ak_common::arbiter::{Arbiter, Tasks};
|
use ak_common::arbiter::{Arbiter, Tasks};
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
@@ -21,26 +21,20 @@ async fn run_plain(
|
|||||||
name: &str,
|
name: &str,
|
||||||
router: Router,
|
router: Router,
|
||||||
addr: net::SocketAddr,
|
addr: net::SocketAddr,
|
||||||
allow_failure: bool,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
info!(addr = addr.to_string(), "starting {name} server");
|
info!(addr = addr.to_string(), "starting {name} server");
|
||||||
|
|
||||||
let handle = Handle::new();
|
let handle = Handle::new();
|
||||||
arbiter.add_net_handle(handle.clone()).await;
|
arbiter.add_net_handle(handle.clone()).await;
|
||||||
|
|
||||||
let res = axum_server::Server::bind(addr)
|
axum_server::Server::bind(addr)
|
||||||
.acceptor(CatchPanicAcceptor::new(
|
.acceptor(CatchPanicAcceptor::new(
|
||||||
ProxyProtocolAcceptor::new().acceptor(DefaultAcceptor::new()),
|
ProxyProtocolAcceptor::new().acceptor(DefaultAcceptor::new()),
|
||||||
arbiter.clone(),
|
arbiter.clone(),
|
||||||
))
|
))
|
||||||
.handle(handle)
|
.handle(handle)
|
||||||
.serve(router.into_make_service_with_connect_info::<net::SocketAddr>())
|
.serve(router.into_make_service_with_connect_info::<net::SocketAddr>())
|
||||||
.await;
|
.await?;
|
||||||
if res.is_err() && allow_failure {
|
|
||||||
arbiter.shutdown().await;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
res?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -49,60 +43,59 @@ async fn run_plain(
|
|||||||
///
|
///
|
||||||
/// `name` is only used for observability purposes and should describe which module is starting the
|
/// `name` is only used for observability purposes and should describe which module is starting the
|
||||||
/// server.
|
/// server.
|
||||||
///
|
|
||||||
/// `allow_failure` allows the server to fail silently.
|
|
||||||
pub fn start_plain(
|
pub fn start_plain(
|
||||||
tasks: &mut Tasks,
|
tasks: &mut Tasks,
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
router: Router,
|
router: Router,
|
||||||
addr: net::SocketAddr,
|
addr: net::SocketAddr,
|
||||||
allow_failure: bool,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let arbiter = tasks.arbiter();
|
let arbiter = tasks.arbiter();
|
||||||
tasks
|
tasks
|
||||||
.build_task()
|
.build_task()
|
||||||
.name(&format!("{}::run_plain({name}, {addr})", module_path!()))
|
.name(&format!("{}::run_plain({name}, {addr})", module_path!()))
|
||||||
.spawn(run_plain(arbiter, name, router, addr, allow_failure))?;
|
.spawn(run_plain(arbiter, name, router, addr))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct UnixSocketGuard(PathBuf);
|
||||||
|
|
||||||
|
impl Drop for UnixSocketGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
trace!(path = ?self.0, "removing socket");
|
||||||
|
if let Err(err) = std::fs::remove_file(&self.0) {
|
||||||
|
trace!(?err, "failed to remove socket, ignoring");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn run_unix(
|
pub(crate) async fn run_unix(
|
||||||
arbiter: Arbiter,
|
arbiter: Arbiter,
|
||||||
name: &str,
|
name: &str,
|
||||||
router: Router,
|
router: Router,
|
||||||
addr: unix::net::SocketAddr,
|
addr: unix::net::SocketAddr,
|
||||||
allow_failure: bool,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
info!(?addr, "starting {name} server");
|
info!(?addr, "starting {name} server");
|
||||||
|
|
||||||
let handle = Handle::new();
|
let handle = Handle::new();
|
||||||
arbiter.add_unix_handle(handle.clone()).await;
|
arbiter.add_unix_handle(handle.clone()).await;
|
||||||
|
|
||||||
if !allow_failure && let Some(path) = addr.as_pathname() {
|
let _guard = if let Some(path) = addr.as_pathname() {
|
||||||
trace!(?addr, "removing socket");
|
trace!(?addr, "removing socket");
|
||||||
if let Err(err) = std::fs::remove_file(path) {
|
if let Err(err) = std::fs::remove_file(path) {
|
||||||
trace!(?err, "failed to remove socket, ignoring");
|
trace!(?err, "failed to remove socket, ignoring");
|
||||||
}
|
}
|
||||||
}
|
Some(UnixSocketGuard(path.to_owned()))
|
||||||
let res = axum_server::Server::bind(addr.clone())
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
axum_server::Server::bind(addr.clone())
|
||||||
.acceptor(CatchPanicAcceptor::new(
|
.acceptor(CatchPanicAcceptor::new(
|
||||||
DefaultAcceptor::new(),
|
DefaultAcceptor::new(),
|
||||||
arbiter.clone(),
|
arbiter.clone(),
|
||||||
))
|
))
|
||||||
.handle(handle)
|
.handle(handle)
|
||||||
.serve(router.into_make_service())
|
.serve(router.into_make_service())
|
||||||
.await;
|
.await?;
|
||||||
if !allow_failure && let Some(path) = addr.as_pathname() {
|
|
||||||
trace!(?addr, "removing socket");
|
|
||||||
if let Err(err) = std::fs::remove_file(path) {
|
|
||||||
trace!(?err, "failed to remove socket, ignoring");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if res.is_err() && allow_failure {
|
|
||||||
arbiter.shutdown().await;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
res?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -111,20 +104,17 @@ pub(crate) async fn run_unix(
|
|||||||
///
|
///
|
||||||
/// `name` is only used for observability purposes and should describe which module is starting the
|
/// `name` is only used for observability purposes and should describe which module is starting the
|
||||||
/// server.
|
/// server.
|
||||||
///
|
|
||||||
/// `allow_failure` allows the server to fail silently.
|
|
||||||
pub fn start_unix(
|
pub fn start_unix(
|
||||||
tasks: &mut Tasks,
|
tasks: &mut Tasks,
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
router: Router,
|
router: Router,
|
||||||
addr: unix::net::SocketAddr,
|
addr: unix::net::SocketAddr,
|
||||||
allow_failure: bool,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let arbiter = tasks.arbiter();
|
let arbiter = tasks.arbiter();
|
||||||
tasks
|
tasks
|
||||||
.build_task()
|
.build_task()
|
||||||
.name(&format!("{}::run_unix({name}, {addr:?})", module_path!()))
|
.name(&format!("{}::run_unix({name}, {addr:?})", module_path!()))
|
||||||
.spawn(run_unix(arbiter, name, router, addr, allow_failure))?;
|
.spawn(run_unix(arbiter, name, router, addr))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export interface AuthenticatedSessionUserAgentDevice {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof AuthenticatedSessionUserAgentDevice
|
* @memberof AuthenticatedSessionUserAgentDevice
|
||||||
*/
|
*/
|
||||||
brand: string;
|
brand: string | null;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
@@ -35,7 +35,7 @@ export interface AuthenticatedSessionUserAgentDevice {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof AuthenticatedSessionUserAgentDevice
|
* @memberof AuthenticatedSessionUserAgentDevice
|
||||||
*/
|
*/
|
||||||
model: string;
|
model: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -29,25 +29,25 @@ export interface AuthenticatedSessionUserAgentOs {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof AuthenticatedSessionUserAgentOs
|
* @memberof AuthenticatedSessionUserAgentOs
|
||||||
*/
|
*/
|
||||||
major: string;
|
major: string | null;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof AuthenticatedSessionUserAgentOs
|
* @memberof AuthenticatedSessionUserAgentOs
|
||||||
*/
|
*/
|
||||||
minor: string;
|
minor: string | null;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof AuthenticatedSessionUserAgentOs
|
* @memberof AuthenticatedSessionUserAgentOs
|
||||||
*/
|
*/
|
||||||
patch: string;
|
patch: string | null;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof AuthenticatedSessionUserAgentOs
|
* @memberof AuthenticatedSessionUserAgentOs
|
||||||
*/
|
*/
|
||||||
patchMinor: string;
|
patchMinor: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
12
packages/client-ts/src/models/EventsRequestedEnum.ts
generated
12
packages/client-ts/src/models/EventsRequestedEnum.ts
generated
@@ -19,8 +19,20 @@
|
|||||||
export const EventsRequestedEnum = {
|
export const EventsRequestedEnum = {
|
||||||
HttpsSchemasOpenidNetSeceventCaepEventTypeSessionRevoked:
|
HttpsSchemasOpenidNetSeceventCaepEventTypeSessionRevoked:
|
||||||
"https://schemas.openid.net/secevent/caep/event-type/session-revoked",
|
"https://schemas.openid.net/secevent/caep/event-type/session-revoked",
|
||||||
|
HttpsSchemasOpenidNetSeceventCaepEventTypeTokenClaimsChange:
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/token-claims-change",
|
||||||
HttpsSchemasOpenidNetSeceventCaepEventTypeCredentialChange:
|
HttpsSchemasOpenidNetSeceventCaepEventTypeCredentialChange:
|
||||||
"https://schemas.openid.net/secevent/caep/event-type/credential-change",
|
"https://schemas.openid.net/secevent/caep/event-type/credential-change",
|
||||||
|
HttpsSchemasOpenidNetSeceventCaepEventTypeAssuranceLevelChange:
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/assurance-level-change",
|
||||||
|
HttpsSchemasOpenidNetSeceventCaepEventTypeDeviceComplianceChange:
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/device-compliance-change",
|
||||||
|
HttpsSchemasOpenidNetSeceventCaepEventTypeSessionEstablished:
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/session-established",
|
||||||
|
HttpsSchemasOpenidNetSeceventCaepEventTypeSessionPresented:
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/session-presented",
|
||||||
|
HttpsSchemasOpenidNetSeceventCaepEventTypeRiskLevelChange:
|
||||||
|
"https://schemas.openid.net/secevent/caep/event-type/risk-level-change",
|
||||||
HttpsSchemasOpenidNetSeceventSsfEventTypeVerification:
|
HttpsSchemasOpenidNetSeceventSsfEventTypeVerification:
|
||||||
"https://schemas.openid.net/secevent/ssf/event-type/verification",
|
"https://schemas.openid.net/secevent/ssf/event-type/verification",
|
||||||
UnknownDefaultOpenApi: "11184809",
|
UnknownDefaultOpenApi: "11184809",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const SSFStreamStatusEnum = {
|
|||||||
Enabled: "enabled",
|
Enabled: "enabled",
|
||||||
Paused: "paused",
|
Paused: "paused",
|
||||||
Disabled: "disabled",
|
Disabled: "disabled",
|
||||||
|
DisabledDeleted: "disabled_deleted",
|
||||||
UnknownDefaultOpenApi: "11184809",
|
UnknownDefaultOpenApi: "11184809",
|
||||||
} as const;
|
} as const;
|
||||||
export type SSFStreamStatusEnum = (typeof SSFStreamStatusEnum)[keyof typeof SSFStreamStatusEnum];
|
export type SSFStreamStatusEnum = (typeof SSFStreamStatusEnum)[keyof typeof SSFStreamStatusEnum];
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ dependencies = [
|
|||||||
"argon2-cffi==25.1.0",
|
"argon2-cffi==25.1.0",
|
||||||
"cachetools==7.0.6",
|
"cachetools==7.0.6",
|
||||||
"channels==4.3.2",
|
"channels==4.3.2",
|
||||||
"cryptography==47.0.0",
|
"cryptography==48.0.0",
|
||||||
"dacite==1.9.2",
|
"dacite==1.9.2",
|
||||||
"deepmerge==2.0",
|
"deepmerge==2.0",
|
||||||
"defusedxml==0.7.1",
|
"defusedxml==0.7.1",
|
||||||
@@ -46,9 +46,9 @@ dependencies = [
|
|||||||
"lxml==6.1.0",
|
"lxml==6.1.0",
|
||||||
"msgraph-sdk==1.56.0",
|
"msgraph-sdk==1.56.0",
|
||||||
"opencontainers==0.0.15",
|
"opencontainers==0.0.15",
|
||||||
"packaging==26.1",
|
"packaging==26.2",
|
||||||
"paramiko==4.0.0",
|
"paramiko==4.0.0",
|
||||||
"psycopg[c,pool]==3.3.3",
|
"psycopg[c,pool]==3.3.4",
|
||||||
"pydantic-scim==0.0.8",
|
"pydantic-scim==0.0.8",
|
||||||
"pydantic==2.13.3",
|
"pydantic==2.13.3",
|
||||||
"pyjwt==2.11.0",
|
"pyjwt==2.11.0",
|
||||||
@@ -66,7 +66,7 @@ dependencies = [
|
|||||||
"ua-parser==1.0.2",
|
"ua-parser==1.0.2",
|
||||||
"unidecode==1.4.0",
|
"unidecode==1.4.0",
|
||||||
"urllib3<3",
|
"urllib3<3",
|
||||||
"uvicorn[standard]==0.45.0",
|
"uvicorn[standard]==0.46.0",
|
||||||
"watchdog==6.0.0",
|
"watchdog==6.0.0",
|
||||||
"webauthn==2.7.1",
|
"webauthn==2.7.1",
|
||||||
"wsproto==1.3.2",
|
"wsproto==1.3.2",
|
||||||
@@ -76,7 +76,7 @@ dependencies = [
|
|||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"aws-cdk-lib==2.250.0",
|
"aws-cdk-lib==2.251.0",
|
||||||
"bandit==1.9.4",
|
"bandit==1.9.4",
|
||||||
"black==26.3.1",
|
"black==26.3.1",
|
||||||
"bpython==0.26",
|
"bpython==0.26",
|
||||||
|
|||||||
13
schema.yml
13
schema.yml
@@ -34608,10 +34608,12 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
brand:
|
brand:
|
||||||
type: string
|
type: string
|
||||||
|
nullable: true
|
||||||
family:
|
family:
|
||||||
type: string
|
type: string
|
||||||
model:
|
model:
|
||||||
type: string
|
type: string
|
||||||
|
nullable: true
|
||||||
required:
|
required:
|
||||||
- brand
|
- brand
|
||||||
- family
|
- family
|
||||||
@@ -34624,12 +34626,16 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
major:
|
major:
|
||||||
type: string
|
type: string
|
||||||
|
nullable: true
|
||||||
minor:
|
minor:
|
||||||
type: string
|
type: string
|
||||||
|
nullable: true
|
||||||
patch:
|
patch:
|
||||||
type: string
|
type: string
|
||||||
|
nullable: true
|
||||||
patch_minor:
|
patch_minor:
|
||||||
type: string
|
type: string
|
||||||
|
nullable: true
|
||||||
required:
|
required:
|
||||||
- family
|
- family
|
||||||
- major
|
- major
|
||||||
@@ -39031,7 +39037,13 @@ components:
|
|||||||
EventsRequestedEnum:
|
EventsRequestedEnum:
|
||||||
enum:
|
enum:
|
||||||
- https://schemas.openid.net/secevent/caep/event-type/session-revoked
|
- https://schemas.openid.net/secevent/caep/event-type/session-revoked
|
||||||
|
- https://schemas.openid.net/secevent/caep/event-type/token-claims-change
|
||||||
- https://schemas.openid.net/secevent/caep/event-type/credential-change
|
- https://schemas.openid.net/secevent/caep/event-type/credential-change
|
||||||
|
- https://schemas.openid.net/secevent/caep/event-type/assurance-level-change
|
||||||
|
- https://schemas.openid.net/secevent/caep/event-type/device-compliance-change
|
||||||
|
- https://schemas.openid.net/secevent/caep/event-type/session-established
|
||||||
|
- https://schemas.openid.net/secevent/caep/event-type/session-presented
|
||||||
|
- https://schemas.openid.net/secevent/caep/event-type/risk-level-change
|
||||||
- https://schemas.openid.net/secevent/ssf/event-type/verification
|
- https://schemas.openid.net/secevent/ssf/event-type/verification
|
||||||
type: string
|
type: string
|
||||||
ExpiringBaseGrantModel:
|
ExpiringBaseGrantModel:
|
||||||
@@ -55618,6 +55630,7 @@ components:
|
|||||||
- enabled
|
- enabled
|
||||||
- paused
|
- paused
|
||||||
- disabled
|
- disabled
|
||||||
|
- disabled_deleted
|
||||||
type: string
|
type: string
|
||||||
Schedule:
|
Schedule:
|
||||||
type: object
|
type: object
|
||||||
|
|||||||
27
src/main.rs
27
src/main.rs
@@ -23,10 +23,23 @@ struct Cli {
|
|||||||
#[derive(Debug, FromArgs, PartialEq)]
|
#[derive(Debug, FromArgs, PartialEq)]
|
||||||
#[argh(subcommand)]
|
#[argh(subcommand)]
|
||||||
enum Command {
|
enum Command {
|
||||||
|
#[cfg(feature = "core")]
|
||||||
|
AllInOne(AllInOne),
|
||||||
|
#[cfg(feature = "core")]
|
||||||
|
Server(server::Cli),
|
||||||
#[cfg(feature = "core")]
|
#[cfg(feature = "core")]
|
||||||
Worker(worker::Cli),
|
Worker(worker::Cli),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, FromArgs, PartialEq)]
|
||||||
|
/// Run both the authentik server and worker.
|
||||||
|
#[argh(subcommand, name = "allinone")]
|
||||||
|
#[expect(
|
||||||
|
clippy::empty_structs_with_brackets,
|
||||||
|
reason = "argh doesn't support unit structs"
|
||||||
|
)]
|
||||||
|
pub(crate) struct AllInOne {}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let tracing_crude = ak_tracing::install_crude();
|
let tracing_crude = ak_tracing::install_crude();
|
||||||
info!(version = authentik_full_version(), "authentik is starting");
|
info!(version = authentik_full_version(), "authentik is starting");
|
||||||
@@ -34,6 +47,10 @@ fn main() -> Result<()> {
|
|||||||
let cli: Cli = argh::from_env();
|
let cli: Cli = argh::from_env();
|
||||||
|
|
||||||
match &cli.command {
|
match &cli.command {
|
||||||
|
#[cfg(feature = "core")]
|
||||||
|
Command::AllInOne(_) => Mode::set(Mode::AllInOne)?,
|
||||||
|
#[cfg(feature = "core")]
|
||||||
|
Command::Server(_) => Mode::set(Mode::Server)?,
|
||||||
#[cfg(feature = "core")]
|
#[cfg(feature = "core")]
|
||||||
Command::Worker(_) => Mode::set(Mode::Worker)?,
|
Command::Worker(_) => Mode::set(Mode::Worker)?,
|
||||||
}
|
}
|
||||||
@@ -76,6 +93,16 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
|
#[cfg(feature = "core")]
|
||||||
|
Command::AllInOne(_) => {
|
||||||
|
server::start(server::Cli::default(), &mut tasks).await?;
|
||||||
|
let workers = worker::start(worker::Cli::default(), &mut tasks)?;
|
||||||
|
metrics.workers.store(Some(workers));
|
||||||
|
}
|
||||||
|
#[cfg(feature = "core")]
|
||||||
|
Command::Server(args) => {
|
||||||
|
server::start(args, &mut tasks).await?;
|
||||||
|
}
|
||||||
#[cfg(feature = "core")]
|
#[cfg(feature = "core")]
|
||||||
Command::Worker(args) => {
|
Command::Worker(args) => {
|
||||||
let workers = worker::start(args, &mut tasks)?;
|
let workers = worker::start(args, &mut tasks)?;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use std::{env::temp_dir, os::unix, path::PathBuf, sync::Arc};
|
|||||||
|
|
||||||
use ak_axum::{router::wrap_router, server};
|
use ak_axum::{router::wrap_router, server};
|
||||||
use ak_common::{
|
use ak_common::{
|
||||||
|
Mode,
|
||||||
arbiter::{Arbiter, Tasks},
|
arbiter::{Arbiter, Tasks},
|
||||||
config,
|
config,
|
||||||
};
|
};
|
||||||
@@ -77,25 +78,20 @@ pub(crate) fn start(tasks: &mut Tasks) -> Result<Arc<Metrics>> {
|
|||||||
.name(&format!("{}::run_upkeep", module_path!()))
|
.name(&format!("{}::run_upkeep", module_path!()))
|
||||||
.spawn(run_upkeep(arbiter, Arc::clone(&metrics)))?;
|
.spawn(run_upkeep(arbiter, Arc::clone(&metrics)))?;
|
||||||
|
|
||||||
for addr in config::get().listen.metrics.iter().copied() {
|
// Only run HTTP server in worker mode, in server or allinone mode, they're handled by the
|
||||||
server::start_plain(
|
// server.
|
||||||
|
if Mode::get() == Mode::Worker {
|
||||||
|
for addr in config::get().listen.metrics.iter().copied() {
|
||||||
|
server::start_plain(tasks, "metrics", router.clone(), addr)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
server::start_unix(
|
||||||
tasks,
|
tasks,
|
||||||
"metrics",
|
"metrics",
|
||||||
router.clone(),
|
router,
|
||||||
addr,
|
unix::net::SocketAddr::from_pathname(socket_path())?,
|
||||||
config::get().debug, /* Allow failure in case the server is running on the same
|
|
||||||
* machine, like in dev */
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
server::start_unix(
|
|
||||||
tasks,
|
|
||||||
"metrics",
|
|
||||||
router,
|
|
||||||
unix::net::SocketAddr::from_pathname(socket_path())?,
|
|
||||||
config::get().debug, /* Allow failure in case the server is running on the same machine,
|
|
||||||
* like in dev */
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(metrics)
|
Ok(metrics)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,124 @@
|
|||||||
use std::{env::temp_dir, path::PathBuf};
|
use std::{env::temp_dir, path::PathBuf, process::Stdio, sync::Arc};
|
||||||
|
|
||||||
|
use ak_common::{Arbiter, Tasks, config};
|
||||||
|
use argh::FromArgs;
|
||||||
|
use eyre::{Result, eyre};
|
||||||
|
use nix::{
|
||||||
|
sys::signal::{Signal, kill},
|
||||||
|
unistd::Pid,
|
||||||
|
};
|
||||||
|
use tokio::{
|
||||||
|
process::{Child, Command},
|
||||||
|
sync::Mutex,
|
||||||
|
time::{Duration, sleep, timeout},
|
||||||
|
};
|
||||||
|
use tracing::{info, warn};
|
||||||
|
|
||||||
|
#[derive(Debug, Default, FromArgs, PartialEq, Eq)]
|
||||||
|
/// Run the authentik server.
|
||||||
|
#[argh(subcommand, name = "server")]
|
||||||
|
#[expect(
|
||||||
|
clippy::empty_structs_with_brackets,
|
||||||
|
reason = "argh doesn't support unit structs"
|
||||||
|
)]
|
||||||
|
pub(crate) struct Cli {}
|
||||||
|
|
||||||
pub(crate) fn socket_path() -> PathBuf {
|
pub(crate) fn socket_path() -> PathBuf {
|
||||||
temp_dir().join("authentik.sock")
|
temp_dir().join("authentik.sock")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Server {
|
||||||
|
server: Mutex<Child>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Server {
|
||||||
|
async fn new() -> Result<Self> {
|
||||||
|
info!("starting server");
|
||||||
|
|
||||||
|
let server = if config::get().debug && which::which("authentik-server").is_err() {
|
||||||
|
let build_status = Command::new("go")
|
||||||
|
.args(["build", "-o", "server", "./cmd/server"])
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.status()
|
||||||
|
.await?;
|
||||||
|
if !build_status.success() {
|
||||||
|
return Err(eyre!("golang server failed to compile"));
|
||||||
|
}
|
||||||
|
Command::new("./server")
|
||||||
|
.kill_on_drop(true)
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.spawn()?
|
||||||
|
} else {
|
||||||
|
Command::new("authentik-server")
|
||||||
|
.kill_on_drop(true)
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.spawn()?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
server: Mutex::new(server),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn shutdown(&self) -> Result<()> {
|
||||||
|
info!("shutting down server");
|
||||||
|
let mut server = self.server.lock().await;
|
||||||
|
if let Some(id) = server.id() {
|
||||||
|
kill(Pid::from_raw(id.cast_signed()), Signal::SIGINT)?;
|
||||||
|
}
|
||||||
|
timeout(Duration::from_secs(1), server.wait()).await??;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn is_alive(&self) -> bool {
|
||||||
|
let try_wait = self.server.lock().await.try_wait();
|
||||||
|
match try_wait {
|
||||||
|
Ok(Some(code)) => {
|
||||||
|
warn!(?code, "server has exited");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Ok(None) => true,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(
|
||||||
|
?err,
|
||||||
|
"failed to check the status of server process, ignoring"
|
||||||
|
);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn watch_server(arbiter: Arbiter, server: Arc<Server>) -> Result<()> {
|
||||||
|
info!("starting server watcher");
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
() = sleep(Duration::from_secs(5)) => {
|
||||||
|
if !server.is_alive().await {
|
||||||
|
return Err(eyre!("server has exited unexpectedly"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
() = arbiter.shutdown() => {
|
||||||
|
server.shutdown().await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn start(_cli: Cli, tasks: &mut Tasks) -> Result<Arc<Server>> {
|
||||||
|
let arbiter = tasks.arbiter();
|
||||||
|
|
||||||
|
let server = Arc::new(Server::new().await?);
|
||||||
|
|
||||||
|
tasks
|
||||||
|
.build_task()
|
||||||
|
.name(&format!("{}::watch_server", module_path!()))
|
||||||
|
.spawn(watch_server(arbiter.clone(), Arc::clone(&server)))?;
|
||||||
|
|
||||||
|
Ok(server)
|
||||||
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ const INITIAL_WORKER_ID: usize = 1000;
|
|||||||
static INITIAL_WORKER_READY: AtomicBool = AtomicBool::new(false);
|
static INITIAL_WORKER_READY: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
pub(crate) struct Worker {
|
pub(crate) struct Worker {
|
||||||
|
worker_id: usize,
|
||||||
worker: Child,
|
worker: Child,
|
||||||
client: Client<UnixSocketConnector<PathBuf>, Body>,
|
client: Client<UnixSocketConnector<PathBuf>, Body>,
|
||||||
socket_path: PathBuf,
|
socket_path: PathBuf,
|
||||||
@@ -75,6 +76,7 @@ impl Worker {
|
|||||||
.build(UnixSocketConnector::new(socket_path.clone()));
|
.build(UnixSocketConnector::new(socket_path.clone()));
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
worker_id,
|
||||||
worker: cmd
|
worker: cmd
|
||||||
.kill_on_drop(true)
|
.kill_on_drop(true)
|
||||||
.stdin(Stdio::null())
|
.stdin(Stdio::null())
|
||||||
@@ -108,7 +110,7 @@ impl Worker {
|
|||||||
self.shutdown(Signal::SIGINT).await
|
self.shutdown(Signal::SIGINT).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip(self), fields(worker_id = self.worker_id))]
|
||||||
fn is_alive(&mut self) -> bool {
|
fn is_alive(&mut self) -> bool {
|
||||||
let try_wait = self.worker.try_wait();
|
let try_wait = self.worker.try_wait();
|
||||||
match try_wait {
|
match try_wait {
|
||||||
@@ -133,34 +135,52 @@ impl Worker {
|
|||||||
result.is_ok()
|
result.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip(self), fields(worker_id = self.worker_id))]
|
||||||
async fn health_live(&self) -> Result<bool> {
|
async fn health_live(&self) -> Result<bool> {
|
||||||
|
trace!("sending health live request to worker");
|
||||||
let req = Request::builder()
|
let req = Request::builder()
|
||||||
.method("GET")
|
.method("GET")
|
||||||
.uri("http://localhost:8000/-/health/live/")
|
.uri("http://localhost:8000/-/health/live/")
|
||||||
.header(HOST, "localhost")
|
.header(HOST, "localhost")
|
||||||
.body(Body::from(""))?;
|
.body(Body::from(""))?;
|
||||||
Ok(self.client.request(req).await?.status().is_success())
|
Ok(self
|
||||||
|
.client
|
||||||
|
.request(req)
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| warn!(?err, "failed to send health live request to worker"))?
|
||||||
|
.status()
|
||||||
|
.is_success())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip(self), fields(worker_id = self.worker_id))]
|
||||||
async fn health_ready(&self) -> Result<bool> {
|
async fn health_ready(&self) -> Result<bool> {
|
||||||
|
trace!("sending health ready request to worker");
|
||||||
let req = Request::builder()
|
let req = Request::builder()
|
||||||
.method("GET")
|
.method("GET")
|
||||||
.uri("http://localhost:8000/-/health/ready/")
|
.uri("http://localhost:8000/-/health/ready/")
|
||||||
.header(HOST, "localhost")
|
.header(HOST, "localhost")
|
||||||
.body(Body::from(""))?;
|
.body(Body::from(""))?;
|
||||||
Ok(self.client.request(req).await?.status().is_success())
|
Ok(self
|
||||||
|
.client
|
||||||
|
.request(req)
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| warn!(?err, "failed to send health ready request to worker"))?
|
||||||
|
.status()
|
||||||
|
.is_success())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip(self), fields(worker_id = self.worker_id))]
|
||||||
async fn notify_metrics(&self) -> Result<()> {
|
async fn notify_metrics(&self) -> Result<()> {
|
||||||
|
trace!("sending metrics request to worker");
|
||||||
let req = Request::builder()
|
let req = Request::builder()
|
||||||
.method("GET")
|
.method("GET")
|
||||||
.uri("http://localhost:8000/-/metrics/")
|
.uri("http://localhost:8000/-/metrics/")
|
||||||
.header(HOST, "localhost")
|
.header(HOST, "localhost")
|
||||||
.body(Body::from(""))?;
|
.body(Body::from(""))?;
|
||||||
self.client.request(req).await?;
|
self.client
|
||||||
|
.request(req)
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| warn!(?err, "failed to send metrics request to worker"))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -323,14 +343,7 @@ pub(crate) fn start(_cli: Cli, tasks: &mut Tasks) -> Result<Arc<Workers>> {
|
|||||||
let router = healthcheck::build_router(Arc::clone(&workers));
|
let router = healthcheck::build_router(Arc::clone(&workers));
|
||||||
|
|
||||||
for addr in config::get().listen.http.iter().copied() {
|
for addr in config::get().listen.http.iter().copied() {
|
||||||
ak_axum::server::start_plain(
|
ak_axum::server::start_plain(tasks, "worker", router.clone(), addr)?;
|
||||||
tasks,
|
|
||||||
"worker",
|
|
||||||
router.clone(),
|
|
||||||
addr,
|
|
||||||
config::get().debug, /* Allow failure in case the server is running on the same
|
|
||||||
* machine, like in dev. */
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ak_axum::server::start_unix(
|
ak_axum::server::start_unix(
|
||||||
@@ -338,8 +351,6 @@ pub(crate) fn start(_cli: Cli, tasks: &mut Tasks) -> Result<Arc<Workers>> {
|
|||||||
"worker",
|
"worker",
|
||||||
router,
|
router,
|
||||||
unix::net::SocketAddr::from_pathname(socket_path())?,
|
unix::net::SocketAddr::from_pathname(socket_path())?,
|
||||||
config::get().debug, /* Allow failure in case the server is running on the same
|
|
||||||
* machine, like in dev. */
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
|
from os import unlink, write
|
||||||
from sys import stderr
|
from sys import stderr
|
||||||
|
from tempfile import mkstemp
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from channels.testing import ChannelsLiveServerTestCase
|
from channels.testing import ChannelsLiveServerTestCase
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
from docker.types import Healthcheck
|
||||||
from dramatiq import get_broker
|
from dramatiq import get_broker
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
from yaml import safe_dump
|
||||||
|
|
||||||
from authentik.core.apps import Setup
|
from authentik.core.apps import Setup
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
@@ -47,6 +53,84 @@ class E2ETestMixin(DockerTestCase):
|
|||||||
print("::endgroup::", file=stderr)
|
print("::endgroup::", file=stderr)
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
|
|
||||||
|
def url(self, view: str, query: dict | None = None, **kwargs) -> str:
|
||||||
|
"""reverse `view` with `**kwargs` into full URL using live_server_url"""
|
||||||
|
url = self.live_server_url + reverse(view, kwargs=kwargs)
|
||||||
|
if query:
|
||||||
|
return url + "?" + urlencode(query)
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
class SSLLiveMixin(DockerTestCase):
|
||||||
|
"""Mixin to provide an SSL-enabled webserver for integration/e2e tests that require it.
|
||||||
|
|
||||||
|
Overrides `live_server_url` and as such other all usual helper functions will return an HTTPS
|
||||||
|
URL. Certificate is self-signed and random on each run."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self._setup_traefik()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super().tearDown()
|
||||||
|
unlink(self._traefik_config)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def live_server_url(self):
|
||||||
|
return f"https://{self.host}:{self._traefik_port}"
|
||||||
|
|
||||||
|
def _setup_traefik(self):
|
||||||
|
config = {
|
||||||
|
"http": {
|
||||||
|
"routers": {
|
||||||
|
"authentik": {
|
||||||
|
"rule": "PathPrefix(`/`)",
|
||||||
|
"entryPoints": ["websecure"],
|
||||||
|
"service": "authentik",
|
||||||
|
"tls": {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"authentik": {"loadBalancer": {"servers": [{"url": super().live_server_url}]}}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fd, self._traefik_config = mkstemp()
|
||||||
|
write(fd, safe_dump(config).encode())
|
||||||
|
traefik = self.run_container(
|
||||||
|
image="docker.io/library/traefik:3.1",
|
||||||
|
command=[
|
||||||
|
"--providers.file.filename=/etc/traefik/dynamic.yml",
|
||||||
|
"--providers.file.watch=true",
|
||||||
|
"--entrypoints.websecure.address=:9443",
|
||||||
|
"--log.level=DEBUG",
|
||||||
|
"--api=true",
|
||||||
|
"--api.dashboard=true",
|
||||||
|
"--api.insecure=true",
|
||||||
|
"--ping=true",
|
||||||
|
],
|
||||||
|
healthcheck=Healthcheck(
|
||||||
|
test=["CMD", "traefik", "healthcheck", "--ping"],
|
||||||
|
interval=5 * 1_000 * 1_000_000,
|
||||||
|
start_period=1 * 1_000 * 1_000_000,
|
||||||
|
),
|
||||||
|
ports={
|
||||||
|
"9443": None,
|
||||||
|
},
|
||||||
|
volumes={
|
||||||
|
self._traefik_config: {
|
||||||
|
"bind": "/etc/traefik/dynamic.yml",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
# {
|
||||||
|
# "8443/tcp": [
|
||||||
|
# {"HostIp": "0.0.0.0", "HostPort": "8443"},
|
||||||
|
# {"HostIp": "::", "HostPort": "8443"},
|
||||||
|
# ],
|
||||||
|
# }
|
||||||
|
self._traefik_port = traefik.ports["9443/tcp"][0]["HostPort"]
|
||||||
|
|
||||||
|
|
||||||
class E2ETestCase(E2ETestMixin, StaticLiveServerTestCase):
|
class E2ETestCase(E2ETestMixin, StaticLiveServerTestCase):
|
||||||
"""E2E Test case with django static live server"""
|
"""E2E Test case with django static live server"""
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
from os import makedirs
|
from os import makedirs
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
|
|
||||||
from authentik.blueprints.tests import apply_blueprint, reconcile_app
|
from authentik.blueprints.tests import apply_blueprint, reconcile_app
|
||||||
from authentik.providers.oauth2.models import OAuth2Provider
|
from authentik.providers.oauth2.models import OAuth2Provider
|
||||||
|
from tests.live import SSLLiveMixin
|
||||||
from tests.openid_conformance.conformance import Conformance
|
from tests.openid_conformance.conformance import Conformance
|
||||||
from tests.selenium import SeleniumTestCase
|
from tests.selenium import SeleniumTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestOpenIDConformance(SeleniumTestCase):
|
class TestOpenIDConformance(SSLLiveMixin, SeleniumTestCase):
|
||||||
|
|
||||||
conformance: Conformance
|
conformance: Conformance
|
||||||
|
|
||||||
@@ -59,32 +61,28 @@ class TestOpenIDConformance(SeleniumTestCase):
|
|||||||
},
|
},
|
||||||
"consent": {},
|
"consent": {},
|
||||||
}
|
}
|
||||||
self.test_variant = {
|
|
||||||
"server_metadata": "discovery",
|
|
||||||
"client_registration": "static_client",
|
|
||||||
}
|
|
||||||
|
|
||||||
def run_test(self, test_name: str, test_plan_config: dict):
|
def run_test(
|
||||||
# Create a Conformance instance...
|
self, test_name: str, test_plan_config: dict[str, Any], test_variant: dict[str, Any]
|
||||||
|
):
|
||||||
self.conformance = Conformance(f"https://{self.host}:8443/", None, verify_ssl=False)
|
self.conformance = Conformance(f"https://{self.host}:8443/", None, verify_ssl=False)
|
||||||
|
|
||||||
test_plan = self.conformance.create_test_plan(
|
test_plan = self.conformance.create_test_plan(
|
||||||
test_name,
|
test_name,
|
||||||
test_plan_config,
|
test_plan_config,
|
||||||
self.test_variant,
|
test_variant,
|
||||||
)
|
)
|
||||||
plan_id = test_plan["id"]
|
plan_id = test_plan["id"]
|
||||||
for test in test_plan["modules"]:
|
for test in test_plan["modules"]:
|
||||||
with self.subTest(test["testModule"], **test["variant"]):
|
# Fetch name and variant of the next test to run
|
||||||
# Fetch name and variant of the next test to run
|
module_name = test["testModule"]
|
||||||
module_name = test["testModule"]
|
variant = test["variant"]
|
||||||
variant = test["variant"]
|
module_instance = self.conformance.create_test_from_plan_with_variant(
|
||||||
module_instance = self.conformance.create_test_from_plan_with_variant(
|
plan_id, module_name, variant
|
||||||
plan_id, module_name, variant
|
)
|
||||||
)
|
module_id = module_instance["id"]
|
||||||
module_id = module_instance["id"]
|
self.run_single_test(module_id)
|
||||||
self.run_single_test(module_id)
|
self.conformance.wait_for_state(module_id, ["FINISHED"], timeout=self.wait_timeout)
|
||||||
self.conformance.wait_for_state(module_id, ["FINISHED"], timeout=self.wait_timeout)
|
|
||||||
sleep(2)
|
sleep(2)
|
||||||
self.conformance.export_html(plan_id, Path(__file__).parent / "exports")
|
self.conformance.export_html(plan_id, Path(__file__).parent / "exports")
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ services:
|
|||||||
mongodb:
|
mongodb:
|
||||||
image: mongo:6.0.13
|
image: mongo:6.0.13
|
||||||
nginx:
|
nginx:
|
||||||
image: ghcr.io/beryju/oidc-conformance-suite-nginx:v5.1.41
|
image: ghcr.io/beryju/oidc-conformance-suite-nginx:v5.1.43
|
||||||
ports:
|
ports:
|
||||||
- "8443:8443"
|
- "8443:8443"
|
||||||
- "8444:8444"
|
- "8444:8444"
|
||||||
depends_on:
|
depends_on:
|
||||||
- server
|
- server
|
||||||
server:
|
server:
|
||||||
image: ghcr.io/beryju/oidc-conformance-suite-server:v5.1.41
|
image: ghcr.io/beryju/oidc-conformance-suite-server:v5.1.43
|
||||||
ports:
|
ports:
|
||||||
- "9999:9999"
|
- "9999:9999"
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
@@ -19,8 +19,8 @@ services:
|
|||||||
-Xdebug -Xrunjdwp:transport=dt_socket,address=*:9999,server=y,suspend=n
|
-Xdebug -Xrunjdwp:transport=dt_socket,address=*:9999,server=y,suspend=n
|
||||||
-jar /server/fapi-test-suite.jar
|
-jar /server/fapi-test-suite.jar
|
||||||
-Djdk.tls.maxHandshakeMessageSize=65536
|
-Djdk.tls.maxHandshakeMessageSize=65536
|
||||||
--fintechlabs.base_url=https://host.docker.internal:8443
|
--fintechlabs.base_url=https://localhost:8443
|
||||||
--fintechlabs.base_mtls_url=https://host.docker.internal:8444
|
--fintechlabs.base_mtls_url=https://localhost:8444
|
||||||
--fintechlabs.devmode=true
|
--fintechlabs.devmode=true
|
||||||
--fintechlabs.startredir=true
|
--fintechlabs.startredir=true
|
||||||
links:
|
links:
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
from tests.decorators import retry
|
|
||||||
from tests.openid_conformance.base import TestOpenIDConformance
|
|
||||||
|
|
||||||
|
|
||||||
class TestOpenIDConformanceBasic(TestOpenIDConformance):
|
|
||||||
|
|
||||||
@retry()
|
|
||||||
def test_oidcc_basic_certification_test(self):
|
|
||||||
test_plan_name = "oidcc-basic-certification-test-plan"
|
|
||||||
self.run_test(test_plan_name, self.test_plan_config)
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
from tests.decorators import retry
|
|
||||||
from tests.openid_conformance.base import TestOpenIDConformance
|
|
||||||
|
|
||||||
|
|
||||||
class TestOpenIDConformanceImplicit(TestOpenIDConformance):
|
|
||||||
|
|
||||||
@retry()
|
|
||||||
def test_oidcc_implicit_certification_test_plan(self):
|
|
||||||
test_plan_name = "oidcc-implicit-certification-test-plan"
|
|
||||||
self.run_test(test_plan_name, self.test_plan_config)
|
|
||||||
39
tests/openid_conformance/test_oidc_backchannel.py
Normal file
39
tests/openid_conformance/test_oidc_backchannel.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import urllib3
|
||||||
|
|
||||||
|
from authentik.flows.models import Flow
|
||||||
|
from authentik.lib.utils.http import get_http_session as real_get_http_session
|
||||||
|
from authentik.providers.oauth2.models import OAuth2LogoutMethod, OAuth2Provider
|
||||||
|
from tests.decorators import retry
|
||||||
|
from tests.openid_conformance.base import TestOpenIDConformance
|
||||||
|
|
||||||
|
|
||||||
|
def _insecure_http_session():
|
||||||
|
session = real_get_http_session()
|
||||||
|
session.verify = False
|
||||||
|
return session
|
||||||
|
|
||||||
|
|
||||||
|
@patch("authentik.providers.oauth2.tasks.get_http_session", _insecure_http_session)
|
||||||
|
class TestOpenIDConformanceBackchannel(TestOpenIDConformance):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
OAuth2Provider.objects.filter(name__startswith="oidc-conformance-").update(
|
||||||
|
invalidation_flow=Flow.objects.get(slug="default-invalidation-flow"),
|
||||||
|
logout_method=OAuth2LogoutMethod.BACKCHANNEL,
|
||||||
|
logout_uri="https://localhost:8443/test/a/authentik/backchannel_logout",
|
||||||
|
)
|
||||||
|
# We are unable to use https for this at the current time
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
|
@retry()
|
||||||
|
def test_oidcc_backchannel_logout_certification_test_plan(self):
|
||||||
|
self.run_test(
|
||||||
|
"oidcc-backchannel-rp-initiated-logout-certification-test-plan",
|
||||||
|
self.test_plan_config,
|
||||||
|
{
|
||||||
|
"client_registration": "static_client",
|
||||||
|
"response_type": "code",
|
||||||
|
},
|
||||||
|
)
|
||||||
16
tests/openid_conformance/test_oidc_basic.py
Normal file
16
tests/openid_conformance/test_oidc_basic.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from tests.decorators import retry
|
||||||
|
from tests.openid_conformance.base import TestOpenIDConformance
|
||||||
|
|
||||||
|
|
||||||
|
class TestOpenIDConformanceBasic(TestOpenIDConformance):
|
||||||
|
|
||||||
|
@retry()
|
||||||
|
def test_oidcc_basic_certification_test(self):
|
||||||
|
self.run_test(
|
||||||
|
"oidcc-basic-certification-test-plan",
|
||||||
|
self.test_plan_config,
|
||||||
|
{
|
||||||
|
"server_metadata": "discovery",
|
||||||
|
"client_registration": "static_client",
|
||||||
|
},
|
||||||
|
)
|
||||||
26
tests/openid_conformance/test_oidc_frontchannel.py
Normal file
26
tests/openid_conformance/test_oidc_frontchannel.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from authentik.flows.models import Flow
|
||||||
|
from authentik.providers.oauth2.models import OAuth2LogoutMethod, OAuth2Provider
|
||||||
|
from tests.decorators import retry
|
||||||
|
from tests.openid_conformance.base import TestOpenIDConformance
|
||||||
|
|
||||||
|
|
||||||
|
class TestOpenIDConformanceFrontchannel(TestOpenIDConformance):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
OAuth2Provider.objects.filter(name__startswith="oidc-conformance-").update(
|
||||||
|
invalidation_flow=Flow.objects.get(slug="default-invalidation-flow"),
|
||||||
|
logout_method=OAuth2LogoutMethod.FRONTCHANNEL,
|
||||||
|
logout_uri="https://localhost:8443/test/a/authentik/frontchannel_logout",
|
||||||
|
)
|
||||||
|
|
||||||
|
@retry()
|
||||||
|
def test_oidcc_frontchannel_logout_certification_test_plan(self):
|
||||||
|
self.run_test(
|
||||||
|
"oidcc-frontchannel-rp-initiated-logout-certification-test-plan",
|
||||||
|
self.test_plan_config,
|
||||||
|
{
|
||||||
|
"client_registration": "static_client",
|
||||||
|
"response_type": "code",
|
||||||
|
},
|
||||||
|
)
|
||||||
16
tests/openid_conformance/test_oidc_implicit.py
Normal file
16
tests/openid_conformance/test_oidc_implicit.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from tests.decorators import retry
|
||||||
|
from tests.openid_conformance.base import TestOpenIDConformance
|
||||||
|
|
||||||
|
|
||||||
|
class TestOpenIDConformanceImplicit(TestOpenIDConformance):
|
||||||
|
|
||||||
|
@retry()
|
||||||
|
def test_oidcc_implicit_certification_test_plan(self):
|
||||||
|
self.run_test(
|
||||||
|
"oidcc-implicit-certification-test-plan",
|
||||||
|
self.test_plan_config,
|
||||||
|
{
|
||||||
|
"server_metadata": "discovery",
|
||||||
|
"client_registration": "static_client",
|
||||||
|
},
|
||||||
|
)
|
||||||
24
tests/openid_conformance/test_oidc_rp_initiated.py
Normal file
24
tests/openid_conformance/test_oidc_rp_initiated.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from authentik.flows.models import Flow
|
||||||
|
from authentik.providers.oauth2.models import OAuth2Provider
|
||||||
|
from tests.decorators import retry
|
||||||
|
from tests.openid_conformance.base import TestOpenIDConformance
|
||||||
|
|
||||||
|
|
||||||
|
class TestOpenIDConformanceRPInitiated(TestOpenIDConformance):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
OAuth2Provider.objects.filter(name__startswith="oidc-conformance-").update(
|
||||||
|
invalidation_flow=Flow.objects.get(slug="default-invalidation-flow"),
|
||||||
|
)
|
||||||
|
|
||||||
|
@retry()
|
||||||
|
def test_oidcc_rp_initiated_certification_test_plan(self):
|
||||||
|
self.run_test(
|
||||||
|
"oidcc-rp-initiated-logout-certification-test-plan",
|
||||||
|
self.test_plan_config,
|
||||||
|
{
|
||||||
|
"client_registration": "static_client",
|
||||||
|
"response_type": "code",
|
||||||
|
},
|
||||||
|
)
|
||||||
49
tests/openid_conformance/test_ssf_transmitter.py
Normal file
49
tests/openid_conformance/test_ssf_transmitter.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
from authentik.core.models import Application
|
||||||
|
from authentik.crypto.models import CertificateKeyPair
|
||||||
|
from authentik.enterprise.providers.ssf.models import SSFProvider
|
||||||
|
from authentik.lib.generators import generate_id
|
||||||
|
from tests.decorators import retry
|
||||||
|
from tests.live import SSLLiveMixin
|
||||||
|
from tests.openid_conformance.base import TestOpenIDConformance
|
||||||
|
|
||||||
|
|
||||||
|
class TestOpenIDConformanceSSFTransmitter(TestOpenIDConformance, SSLLiveMixin):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.provider = SSFProvider.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
|
signing_key=CertificateKeyPair.objects.get(name="authentik Self-signed Certificate"),
|
||||||
|
backchannel_application=Application.objects.get(slug="oidc-conformance-1"),
|
||||||
|
push_verify_certificates=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
@retry()
|
||||||
|
def test_openid_ssf_transmitter_test_plan(self):
|
||||||
|
iss = self.url(
|
||||||
|
"authentik_providers_ssf:configuration",
|
||||||
|
application_slug="oidc-conformance-1",
|
||||||
|
)
|
||||||
|
self.run_test(
|
||||||
|
"openid-ssf-transmitter-test-plan",
|
||||||
|
{
|
||||||
|
"alias": "authentik",
|
||||||
|
"description": "authentik",
|
||||||
|
"ssf": {
|
||||||
|
"transmitter": {
|
||||||
|
"issuer": iss,
|
||||||
|
"configuration_metadata_endpoint": iss,
|
||||||
|
"access_token": self.provider.token.key,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
test_variant={
|
||||||
|
"client_auth_type": "client_secret_post",
|
||||||
|
"ssf_server_metadata": "static",
|
||||||
|
"server_metadata": "static",
|
||||||
|
"ssf_auth_mode": "static",
|
||||||
|
"ssf_delivery_mode": "push",
|
||||||
|
"ssf_profile": "caep_interop",
|
||||||
|
"client_registration": "static_client",
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -5,10 +5,8 @@ from json import JSONDecodeError, dumps, loads
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import gettempdir
|
from tempfile import gettempdir
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from urllib.parse import urlencode
|
|
||||||
|
|
||||||
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
||||||
from django.urls import reverse
|
|
||||||
from docker.models.containers import Container
|
from docker.models.containers import Container
|
||||||
from requests import RequestException
|
from requests import RequestException
|
||||||
from selenium import webdriver
|
from selenium import webdriver
|
||||||
@@ -131,13 +129,6 @@ class SeleniumTestMixin(E2ETestMixin):
|
|||||||
f"HTML: {self.driver.page_source[:1000]}"
|
f"HTML: {self.driver.page_source[:1000]}"
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
def url(self, view: str, query: dict | None = None, **kwargs) -> str:
|
|
||||||
"""reverse `view` with `**kwargs` into full URL using live_server_url"""
|
|
||||||
url = self.live_server_url + reverse(view, kwargs=kwargs)
|
|
||||||
if query:
|
|
||||||
return url + "?" + urlencode(query)
|
|
||||||
return url
|
|
||||||
|
|
||||||
def if_user_url(self, path: str | None = None) -> str:
|
def if_user_url(self, path: str | None = None) -> str:
|
||||||
"""same as self.url() but show URL in shell"""
|
"""same as self.url() but show URL in shell"""
|
||||||
url = self.url("authentik_core:if-user")
|
url = self.url("authentik_core:if-user")
|
||||||
|
|||||||
138
uv.lock
generated
138
uv.lock
generated
@@ -318,7 +318,7 @@ requires-dist = [
|
|||||||
{ name = "argon2-cffi", specifier = "==25.1.0" },
|
{ name = "argon2-cffi", specifier = "==25.1.0" },
|
||||||
{ name = "cachetools", specifier = "==7.0.6" },
|
{ name = "cachetools", specifier = "==7.0.6" },
|
||||||
{ name = "channels", specifier = "==4.3.2" },
|
{ name = "channels", specifier = "==4.3.2" },
|
||||||
{ name = "cryptography", specifier = "==47.0.0" },
|
{ name = "cryptography", specifier = "==48.0.0" },
|
||||||
{ name = "dacite", specifier = "==1.9.2" },
|
{ name = "dacite", specifier = "==1.9.2" },
|
||||||
{ name = "deepmerge", specifier = "==2.0" },
|
{ name = "deepmerge", specifier = "==2.0" },
|
||||||
{ name = "defusedxml", specifier = "==0.7.1" },
|
{ name = "defusedxml", specifier = "==0.7.1" },
|
||||||
@@ -355,9 +355,9 @@ requires-dist = [
|
|||||||
{ name = "lxml", specifier = "==6.1.0" },
|
{ name = "lxml", specifier = "==6.1.0" },
|
||||||
{ name = "msgraph-sdk", specifier = "==1.56.0" },
|
{ name = "msgraph-sdk", specifier = "==1.56.0" },
|
||||||
{ name = "opencontainers", git = "https://github.com/vsoch/oci-python?rev=ceb4fcc090851717a3069d78e85ceb1e86c2740c" },
|
{ name = "opencontainers", git = "https://github.com/vsoch/oci-python?rev=ceb4fcc090851717a3069d78e85ceb1e86c2740c" },
|
||||||
{ name = "packaging", specifier = "==26.1" },
|
{ name = "packaging", specifier = "==26.2" },
|
||||||
{ name = "paramiko", specifier = "==4.0.0" },
|
{ name = "paramiko", specifier = "==4.0.0" },
|
||||||
{ name = "psycopg", extras = ["c", "pool"], specifier = "==3.3.3" },
|
{ name = "psycopg", extras = ["c", "pool"], specifier = "==3.3.4" },
|
||||||
{ name = "pydantic", specifier = "==2.13.3" },
|
{ name = "pydantic", specifier = "==2.13.3" },
|
||||||
{ name = "pydantic-scim", specifier = "==0.0.8" },
|
{ name = "pydantic-scim", specifier = "==0.0.8" },
|
||||||
{ name = "pyjwt", specifier = "==2.11.0" },
|
{ name = "pyjwt", specifier = "==2.11.0" },
|
||||||
@@ -375,7 +375,7 @@ requires-dist = [
|
|||||||
{ name = "ua-parser", specifier = "==1.0.2" },
|
{ name = "ua-parser", specifier = "==1.0.2" },
|
||||||
{ name = "unidecode", specifier = "==1.4.0" },
|
{ name = "unidecode", specifier = "==1.4.0" },
|
||||||
{ name = "urllib3", specifier = "<3" },
|
{ name = "urllib3", specifier = "<3" },
|
||||||
{ name = "uvicorn", extras = ["standard"], specifier = "==0.45.0" },
|
{ name = "uvicorn", extras = ["standard"], specifier = "==0.46.0" },
|
||||||
{ name = "watchdog", specifier = "==6.0.0" },
|
{ name = "watchdog", specifier = "==6.0.0" },
|
||||||
{ name = "webauthn", specifier = "==2.7.1" },
|
{ name = "webauthn", specifier = "==2.7.1" },
|
||||||
{ name = "wsproto", specifier = "==1.3.2" },
|
{ name = "wsproto", specifier = "==1.3.2" },
|
||||||
@@ -385,7 +385,7 @@ requires-dist = [
|
|||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "aws-cdk-lib", specifier = "==2.250.0" },
|
{ name = "aws-cdk-lib", specifier = "==2.251.0" },
|
||||||
{ name = "bandit", specifier = "==1.9.4" },
|
{ name = "bandit", specifier = "==1.9.4" },
|
||||||
{ name = "black", specifier = "==26.3.1" },
|
{ name = "black", specifier = "==26.3.1" },
|
||||||
{ name = "bpython", specifier = "==0.26" },
|
{ name = "bpython", specifier = "==0.26" },
|
||||||
@@ -481,21 +481,21 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-cdk-cloud-assembly-schema"
|
name = "aws-cdk-cloud-assembly-schema"
|
||||||
version = "53.9.0"
|
version = "53.20.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "jsii" },
|
{ name = "jsii" },
|
||||||
{ name = "publication" },
|
{ name = "publication" },
|
||||||
{ name = "typeguard" },
|
{ name = "typeguard" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/7c/95/afd7bd310b7eb64bfa51700e283a297a57692d8ca5a3a289d9bfe6ca92f6/aws_cdk_cloud_assembly_schema-53.9.0.tar.gz", hash = "sha256:0a9a537c6cdfebbf3e97f250aaff92d811a6be94394a4673784a50660889a3cb", size = 210905, upload-time = "2026-03-26T18:44:01.266Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/bf/8a/3b94fbba0d8ca4123eb015ea12a1c8fc5193a1eddcc4d69d31b9575546c8/aws_cdk_cloud_assembly_schema-53.20.0.tar.gz", hash = "sha256:c5d884f7211fd18cc0ce8c4349902ab6a6b3cd8f3c2259c56616a59218c221eb", size = 212292, upload-time = "2026-04-30T11:34:29.29Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/6e/d0/4dbd2020f8fa6991f6469ed1851981f3160965393ea4cef19a59bf8c938b/aws_cdk_cloud_assembly_schema-53.9.0-py3-none-any.whl", hash = "sha256:41e75541d3ea3d46edbe67ade491d974f0472ac62c3856596e3029fe384a9a44", size = 210649, upload-time = "2026-03-26T18:43:59.951Z" },
|
{ url = "https://files.pythonhosted.org/packages/b5/eb/7bf100ad3603d5fbbebb49b3d48add58abda59c3fa44a4a7eae40da5b8d0/aws_cdk_cloud_assembly_schema-53.20.0-py3-none-any.whl", hash = "sha256:b68ea0754ec830751d4a375ebe84d4c077dc488a2498c3607a2f998bc7e91d73", size = 212140, upload-time = "2026-04-30T11:34:27.044Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-cdk-lib"
|
name = "aws-cdk-lib"
|
||||||
version = "2.250.0"
|
version = "2.251.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aws-cdk-asset-awscli-v1" },
|
{ name = "aws-cdk-asset-awscli-v1" },
|
||||||
@@ -506,9 +506,9 @@ dependencies = [
|
|||||||
{ name = "publication" },
|
{ name = "publication" },
|
||||||
{ name = "typeguard" },
|
{ name = "typeguard" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/d5/7b/98c66b6ea6d4f84b70d86ec391bbcd2856a82b384a97adc223f36b36dfd6/aws_cdk_lib-2.250.0.tar.gz", hash = "sha256:6e5cb8def9208a45cede1376a81d7508b3889879ccc7e9cddaa4fd807da0b144", size = 49146123, upload-time = "2026-04-14T21:43:07.309Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/b8/6c/d60d96e1848aabf1882e6a1d30a27de4a592affc9437d6918848f0e06497/aws_cdk_lib-2.251.0.tar.gz", hash = "sha256:ed69e7ea6896c62ac2ce01857083601baf541d5d875370bee6d213d641e8921e", size = 49353237, upload-time = "2026-04-24T23:21:04.805Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/e9/e7/b64389b59215a42d0d7538a1d9a8006edb2eefb297ccbdcd4c55ff2cffba/aws_cdk_lib-2.250.0-py3-none-any.whl", hash = "sha256:427c9a062f350c16e301326fd6ca0440428f9cc0e85421aaa69a9afa57228acc", size = 49827287, upload-time = "2026-04-14T21:42:21.21Z" },
|
{ url = "https://files.pythonhosted.org/packages/d2/fb/ab682b518e3ca5d18b23b252832e0fade4e6617a2c0f2b0ae0d8d2e74312/aws_cdk_lib-2.251.0-py3-none-any.whl", hash = "sha256:a684f3461d096443ac688adbf559abe1af2d50dd5c8e0fa7dbf4a5f361702db8", size = 50035969, upload-time = "2026-04-24T23:20:18.952Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -917,55 +917,55 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cryptography"
|
name = "cryptography"
|
||||||
version = "47.0.0"
|
version = "48.0.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/ef/b2/7ffa7fe8207a8c42147ffe70c3e360b228160c1d85dc3faff16aaa3244c0/cryptography-47.0.0.tar.gz", hash = "sha256:9f8e55fe4e63613a5e1cc5819030f27b97742d720203a087802ce4ce9ceb52bb", size = 830863, upload-time = "2026-04-24T19:54:57.056Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/a4/98/40dfe932134bdcae4f6ab5927c87488754bf9eb79297d7e0070b78dd58e9/cryptography-47.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:160ad728f128972d362e714054f6ba0067cab7fb350c5202a9ae8ae4ce3ef1a0", size = 7912214, upload-time = "2026-04-24T19:53:03.864Z" },
|
{ url = "https://files.pythonhosted.org/packages/df/3d/01f6dd9190170a5a241e0e98c2d04be3664a9e6f5b9b872cde63aff1c3dd/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6", size = 8001587, upload-time = "2026-05-04T22:57:36.803Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/34/c6/2733531243fba725f58611b918056b277692f1033373dcc8bd01af1c05d4/cryptography-47.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b9a8943e359b7615db1a3ba587994618e094ff3d6fa5a390c73d079ce18b3973", size = 4644617, upload-time = "2026-04-24T19:53:06.909Z" },
|
{ url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/00/e3/b27be1a670a9b87f855d211cf0e1174a5d721216b7616bd52d8581d912ed/cryptography-47.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5c15764f261394b22aef6b00252f5195f46f2ca300bec57149474e2538b31f8", size = 4668186, upload-time = "2026-04-24T19:53:09.053Z" },
|
{ url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/81/b9/8443cfe5d17d482d348cee7048acf502bb89a51b6382f06240fd290d4ca3/cryptography-47.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9c59ab0e0fa3a180a5a9c59f3a5abe3ef90d474bc56d7fadfbe80359491b615b", size = 4651244, upload-time = "2026-04-24T19:53:11.217Z" },
|
{ url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5d/5e/13ed0cdd0eb88ba159d6dd5ebfece8cb901dbcf1ae5ac4072e28b55d3153/cryptography-47.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:34b4358b925a5ea3e14384ca781a2c0ef7ac219b57bb9eacc4457078e2b19f92", size = 5252906, upload-time = "2026-04-24T19:53:13.532Z" },
|
{ url = "https://files.pythonhosted.org/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/64/16/ed058e1df0f33d440217cd120d41d5dda9dd215a80b8187f68483185af82/cryptography-47.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0024b87d47ae2399165a6bfb20d24888881eeab83ae2566d62467c5ff0030ce7", size = 4701842, upload-time = "2026-04-24T19:53:15.618Z" },
|
{ url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/02/e0/3d30986b30fdbd9e969abbdf8ba00ed0618615144341faeb57f395a084fe/cryptography-47.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:1e47422b5557bb82d3fff997e8d92cff4e28b9789576984f08c248d2b3535d93", size = 4289313, upload-time = "2026-04-24T19:53:17.755Z" },
|
{ url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/df/fd/32db38e3ad0cb331f0691cb4c7a8a6f176f679124dee746b3af6633db4d9/cryptography-47.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6f29f36582e6151d9686235e586dd35bb67491f024767d10b842e520dc6a07ac", size = 4650964, upload-time = "2026-04-24T19:53:20.062Z" },
|
{ url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/86/53/5395d944dfd48cb1f67917f533c609c34347185ef15eb4308024c876f274/cryptography-47.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a9b761f012a943b7de0e828843c5688d0de94a0578d44d6c85a1bae32f87791f", size = 5207817, upload-time = "2026-04-24T19:53:22.498Z" },
|
{ url = "https://files.pythonhosted.org/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/34/4f/e5711b28e1901f7d480a2b1b688b645aa4c77c73f10731ed17e7f7db3f0d/cryptography-47.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4e1de79e047e25d6e9f8cea71c86b4a53aced64134f0f003bbcbf3655fd172c8", size = 4701544, upload-time = "2026-04-24T19:53:24.356Z" },
|
{ url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/22/22/c8ddc25de3010fc8da447648f5a092c40e7a8fadf01dd6d255d9c0b9373d/cryptography-47.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef6b3634087f18d2155b1e8ce264e5345a753da2c5fa9815e7d41315c90f8318", size = 4783536, upload-time = "2026-04-24T19:53:26.665Z" },
|
{ url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/66/b6/d4a68f4ea999c6d89e8498579cba1c5fcba4276284de7773b17e4fa69293/cryptography-47.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:11dbb9f50a0f1bb9757b3d8c27c1101780efb8f0bdecfb12439c22a74d64c001", size = 4926106, upload-time = "2026-04-24T19:53:28.686Z" },
|
{ url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/54/ed/5f524db1fade9c013aa618e1c99c6ed05e8ffc9ceee6cda22fed22dda3f4/cryptography-47.0.0-cp311-abi3-win32.whl", hash = "sha256:7fda2f02c9015db3f42bb8a22324a454516ed10a8c29ca6ece6cdbb5efe2a203", size = 3258581, upload-time = "2026-04-24T19:53:31.058Z" },
|
{ url = "https://files.pythonhosted.org/packages/04/70/e5a1b41d325f797f39427aa44ef8baf0be500065ab6d8e10369d850d4a4f/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4", size = 3294868, upload-time = "2026-05-04T22:58:06.467Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/dc/1b901990b174786569029f67542b3edf72ac068b6c3c8683c17e6a2f5363/cryptography-47.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:f5c3296dab66202f1b18a91fa266be93d6aa0c2806ea3d67762c69f60adc71aa", size = 3775309, upload-time = "2026-04-24T19:53:33.054Z" },
|
{ url = "https://files.pythonhosted.org/packages/f4/ac/8ac51b4a5fc5932eb7ee5c517ba7dc8cd834f0048962b6b352f00f41ebf9/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7", size = 3817107, upload-time = "2026-05-04T22:58:08.845Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/14/88/7aa18ad9c11bc87689affa5ce4368d884b517502d75739d475fc6f4a03c7/cryptography-47.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:be12cb6a204f77ed968bcefe68086eb061695b540a3dd05edac507a3111b25f0", size = 7904299, upload-time = "2026-04-24T19:53:35.003Z" },
|
{ url = "https://files.pythonhosted.org/packages/6b/84/70e3feea9feea87fd7cbe77efb2712ae1e3e6edf10749dc6e95f4e60e455/cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec", size = 7986556, upload-time = "2026-05-04T22:58:11.172Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/07/55/c18f75724544872f234678fdedc871391722cb34a2aee19faa9f63100bb2/cryptography-47.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2ebd84adf0728c039a3be2700289378e1c164afc6748df1a5ed456767bef9ba7", size = 4631180, upload-time = "2026-04-24T19:53:37.517Z" },
|
{ url = "https://files.pythonhosted.org/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ee/65/31a5cc0eaca99cec5bafffe155d407115d96136bb161e8b49e0ef73f09a7/cryptography-47.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f68d6fbc7fbbcfb0939fea72c3b96a9f9a6edfc0e1b1d29778a2066030418b1", size = 4653529, upload-time = "2026-04-24T19:53:39.775Z" },
|
{ url = "https://files.pythonhosted.org/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e5/bc/641c0519a495f3bfd0421b48d7cd325c4336578523ccd76ea322b6c29c7a/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:6651d32eff255423503aa276739da98c30f26c40cbeffcc6048e0d54ef704c0c", size = 4638570, upload-time = "2026-04-24T19:53:42.129Z" },
|
{ url = "https://files.pythonhosted.org/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2b/f2/300327b0a47f6dc94dd8b71b57052aefe178bb51745073d73d80604f11ab/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3fb8fa48075fad7193f2e5496135c6a76ac4b2aa5a38433df0a539296b377829", size = 5238019, upload-time = "2026-04-24T19:53:44.577Z" },
|
{ url = "https://files.pythonhosted.org/packages/ca/b4/fc334ed8cfd705aca282fe4d8f5ae64a8e0f74932e9feecb344610cf6e4d/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c", size = 5282526, upload-time = "2026-05-04T22:58:20.75Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e9/5a/5b5cf994391d4bf9d9c7efd4c66aabe4d95227256627f8fea6cff7dfadbd/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11438c7518132d95f354fa01a4aa2f806d172a061a7bed18cf18cbdacdb204d7", size = 4686832, upload-time = "2026-04-24T19:53:47.015Z" },
|
{ url = "https://files.pythonhosted.org/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/dc/2c/ae950e28fd6475c852fc21a44db3e6b5bcc1261d1e370f2b6e42fa800fef/cryptography-47.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8c1a736bbb3288005796c3f7ccb9453360d7fed483b13b9f468aea5171432923", size = 4269301, upload-time = "2026-04-24T19:53:48.97Z" },
|
{ url = "https://files.pythonhosted.org/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/67/fb/6a39782e150ffe5cc1b0018cb6ddc48bf7ca62b498d7539ffc8a758e977d/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:f1557695e5c2b86e204f6ce9470497848634100787935ab7adc5397c54abd7ab", size = 4638110, upload-time = "2026-04-24T19:53:51.011Z" },
|
{ url = "https://files.pythonhosted.org/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/8e/d7/0b3c71090a76e5c203164a47688b697635ece006dcd2499ab3a4dbd3f0bd/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:f9a034b642b960767fb343766ae5ba6ad653f2e890ddd82955aef288ffea8736", size = 5194988, upload-time = "2026-04-24T19:53:52.962Z" },
|
{ url = "https://files.pythonhosted.org/packages/dd/a6/825010a291b4438aecc1f568bc428189fc1175515223632477c07dc0a6df/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336", size = 5237657, upload-time = "2026-05-04T22:58:29.848Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/63/33/63a961498a9df51721ab578c5a2622661411fc520e00bd83b0cc64eb20c4/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:b1c76fca783aa7698eb21eb14f9c4aa09452248ee54a627d125025a43f83e7a7", size = 4686563, upload-time = "2026-04-24T19:53:55.274Z" },
|
{ url = "https://files.pythonhosted.org/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b7/bf/5ee5b145248f92250de86145d1c1d6edebbd57a7fe7caa4dedb5d4cf06a1/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4f7722c97826770bab8ae92959a2e7b20a5e9e9bf4deae68fd86c3ca457bab52", size = 4770094, upload-time = "2026-04-24T19:53:57.753Z" },
|
{ url = "https://files.pythonhosted.org/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/92/43/21d220b2da5d517773894dacdcdb5c682c28d3fffce65548cb06e87d5501/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:09f6d7bf6724f8db8b32f11eccf23efc8e759924bc5603800335cf8859a3ddbd", size = 4913811, upload-time = "2026-04-24T19:54:00.236Z" },
|
{ url = "https://files.pythonhosted.org/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/31/98/dc4ad376ac5f1a1a7d4a83f7b0c6f2bcad36b5d2d8f30aeb482d3a7d9582/cryptography-47.0.0-cp314-cp314t-win32.whl", hash = "sha256:6eebcaf0df1d21ce1f90605c9b432dd2c4f4ab665ac29a40d5e3fc68f51b5e63", size = 3237158, upload-time = "2026-04-24T19:54:02.606Z" },
|
{ url = "https://files.pythonhosted.org/packages/75/54/cc6d0f3deac3e81c7f847e8a189a12b6cdd65059b43dad25d4316abd849a/cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f", size = 3270954, upload-time = "2026-05-04T22:58:38.791Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/bc/da/97f62d18306b5133468bc3f8cc73a3111e8cdc8cf8d3e69474d6e5fd2d1b/cryptography-47.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:51c9313e90bd1690ec5a75ed047c27c0b8e6c570029712943d6116ef9a90620b", size = 3758706, upload-time = "2026-04-24T19:54:04.433Z" },
|
{ url = "https://files.pythonhosted.org/packages/49/67/cc947e288c0758a4e5473d1dcb743037ab7785541265a969240b8885441a/cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12", size = 3797313, upload-time = "2026-05-04T22:58:40.746Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e0/34/a4fae8ae7c3bc227460c9ae43f56abf1b911da0ec29e0ebac53bb0a4b6b7/cryptography-47.0.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:14432c8a9bcb37009784f9594a62fae211a2ae9543e96c92b2a8e4c3cd5cd0c4", size = 7904072, upload-time = "2026-04-24T19:54:06.411Z" },
|
{ url = "https://files.pythonhosted.org/packages/f2/63/61d4a4e1c6b6bab6ce1e213cd36a24c415d90e76d78c5eb8577c5541d2e8/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86", size = 7983482, upload-time = "2026-05-04T22:58:43.769Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/01/64/d7b1e54fdb69f22d24a64bb3e88dc718b31c7fb10ef0b9691a3cf7eeea6e/cryptography-47.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:07efe86201817e7d3c18781ca9770bc0db04e1e48c994be384e4602bc38f8f27", size = 4635767, upload-time = "2026-04-24T19:54:08.519Z" },
|
{ url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/8b/7b/cca826391fb2a94efdcdfe4631eb69306ee1cff0b22f664a412c90713877/cryptography-47.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b45761c6ec22b7c726d6a829558777e32d0f1c8be7c3f3480f9c912d5ee8a10", size = 4654350, upload-time = "2026-04-24T19:54:10.795Z" },
|
{ url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4c/65/4b57bcc823f42a991627c51c2f68c9fd6eb1393c1756aac876cba2accae2/cryptography-47.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:edd4da498015da5b9f26d38d3bfc2e90257bfa9cbed1f6767c282a0025ae649b", size = 4643394, upload-time = "2026-04-24T19:54:13.275Z" },
|
{ url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f4/c4/2c5fbeea70adbbca2bbae865e1d605d6a4a7f8dbd9d33eaf69645087f06c/cryptography-47.0.0-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9af828c0d5a65c70ec729cd7495a4bf1a67ecb66417b8f02ff125ab8a6326a74", size = 5225777, upload-time = "2026-04-24T19:54:15.18Z" },
|
{ url = "https://files.pythonhosted.org/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/7e/b8/ac57107ef32749d2b244e36069bb688792a363aaaa3acc9e3cf84c130315/cryptography-47.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:256d07c78a04d6b276f5df935a9923275f53bd1522f214447fdf365494e2d515", size = 4688771, upload-time = "2026-04-24T19:54:17.835Z" },
|
{ url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/56/fc/9f1de22ff8be99d991f240a46863c52d475404c408886c5a38d2b5c3bb26/cryptography-47.0.0-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:5d0e362ff51041b0c0d219cc7d6924d7b8996f57ce5712bdcef71eb3c65a59cc", size = 4270753, upload-time = "2026-04-24T19:54:19.963Z" },
|
{ url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/00/68/d70c852797aa68e8e48d12e5a87170c43f67bb4a59403627259dd57d15de/cryptography-47.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:1581aef4219f7ca2849d0250edaa3866212fb74bf5667284f46aa92f9e65c1ca", size = 4642911, upload-time = "2026-04-24T19:54:21.818Z" },
|
{ url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a5/51/661cbee74f594c5d97ff82d34f10d5551c085ca4668645f4606ebd22bd5d/cryptography-47.0.0-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a49a3eb5341b9503fa3000a9a0db033161db90d47285291f53c2a9d2cd1b7f76", size = 5181411, upload-time = "2026-04-24T19:54:24.376Z" },
|
{ url = "https://files.pythonhosted.org/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/94/87/f2b6c374a82cf076cfa1416992ac8e8ec94d79facc37aec87c1a5cb72352/cryptography-47.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2207a498b03275d0051589e326b79d4cf59985c99031b05bb292ac52631c37fe", size = 4688262, upload-time = "2026-04-24T19:54:26.946Z" },
|
{ url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/14/e2/8b7462f4acf21ec509616f0245018bb197194ab0b65c2ea21a0bdd53c0eb/cryptography-47.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a02675e2fabd0c0fc04c868b8781863cbf1967691543c22f5470500ff840b31", size = 4775506, upload-time = "2026-04-24T19:54:28.926Z" },
|
{ url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/70/75/158e494e4c08dc05e039da5bb48553826bd26c23930cf8d3cd5f21fa8921/cryptography-47.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80887c5cbd1774683cb126f0ab4184567f080071d5acf62205acb354b4b753b7", size = 4912060, upload-time = "2026-04-24T19:54:30.869Z" },
|
{ url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/06/bd/0a9d3edbf5eadbac926d7b9b3cd0c4be584eeeae4a003d24d9eda4affbbd/cryptography-47.0.0-cp38-abi3-win32.whl", hash = "sha256:ed67ea4e0cfb5faa5bc7ecb6e2b8838f3807a03758eec239d6c21c8769355310", size = 3248487, upload-time = "2026-04-24T19:54:33.494Z" },
|
{ url = "https://files.pythonhosted.org/packages/b8/23/6e6f32143ab5d8b36ca848a502c4bcd477ae75b9e1677e3530d669062578/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd", size = 3279117, upload-time = "2026-05-04T22:59:12.019Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/60/80/5681af756d0da3a599b7bdb586fac5a1540f1bcefd2717a20e611ddade45/cryptography-47.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:835d2d7f47cdc53b3224e90810fb1d36ca94ea29cc1801fb4c1bc43876735769", size = 3755737, upload-time = "2026-04-24T19:54:35.408Z" },
|
{ url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2583,11 +2583,11 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "26.1"
|
version = "26.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" },
|
{ url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2733,14 +2733,14 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "psycopg"
|
name = "psycopg"
|
||||||
version = "3.3.3"
|
version = "3.3.4"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/d3/b6/379d0a960f8f435ec78720462fd94c4863e7a31237cf81bf76d0af5883bf/psycopg-3.3.3.tar.gz", hash = "sha256:5e9a47458b3c1583326513b2556a2a9473a1001a56c9efe9e587245b43148dd9", size = 165624, upload-time = "2026-02-18T16:52:16.546Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/db/2f/cb91e5502ec9de1de6f1b76cfbf69531932725361168bb06963620c77e2e/psycopg-3.3.4.tar.gz", hash = "sha256:e21207764952cff81b6b8bdacad9a3939f2793367fdac2987b3aac36a651b5bc", size = 165799, upload-time = "2026-05-01T23:31:55.179Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/c8/5b/181e2e3becb7672b502f0ed7f16ed7352aca7c109cfb94cf3878a9186db9/psycopg-3.3.3-py3-none-any.whl", hash = "sha256:f96525a72bcfade6584ab17e89de415ff360748c766f0106959144dcbb38c698", size = 212768, upload-time = "2026-02-18T16:46:27.365Z" },
|
{ url = "https://files.pythonhosted.org/packages/5c/e0/7b3dee031daae7743609ce3c746565d4a3ed7c2c186479eb48e34e838c64/psycopg-3.3.4-py3-none-any.whl", hash = "sha256:b6bbc25ccf05c8fad3b061d9db2ef0909a555171b84b07f29458a447253d679a", size = 213001, upload-time = "2026-05-01T23:20:50.816Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.optional-dependencies]
|
[package.optional-dependencies]
|
||||||
@@ -2753,9 +2753,9 @@ pool = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "psycopg-c"
|
name = "psycopg-c"
|
||||||
version = "3.3.3"
|
version = "3.3.4"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/cb/a0/8feb0ca8c7c20a8b9ac4d46b335ddd57e48e593b714262f006880f34fee5/psycopg_c-3.3.3.tar.gz", hash = "sha256:86ef6f4424348247828e83fb0882c9f8acb33e64d0a5ce66c1b4a5107ee73edd", size = 631965, upload-time = "2026-02-18T16:52:18.084Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/21/7c/c08364f2eab2913e4068b3b955d963e7a3491986a85429990969525def30/psycopg_c-3.3.4.tar.gz", hash = "sha256:ed8106128b2d04359c185fc9641b4409abfce4d0b6fb1d1ff6800646e27f1a22", size = 647111, upload-time = "2026-05-01T23:31:58.032Z" }
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "psycopg-pool"
|
name = "psycopg-pool"
|
||||||
@@ -2947,14 +2947,14 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyopenssl"
|
name = "pyopenssl"
|
||||||
version = "26.1.0"
|
version = "26.2.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "cryptography" },
|
{ name = "cryptography" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/8c/a8/26d36401e3ab8eed9030ad33f381da7856fcfad5691780fccd1b019718fc/pyopenssl-26.1.0.tar.gz", hash = "sha256:737f0a2275c5bc54f3b02137687e1a765931fb3949b9a92a825e4d33b9eec08b", size = 186181, upload-time = "2026-04-24T20:23:48.115Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/1a/51/27a5ad5f939d08f690a326ef9582cda7140555180db71695f6fb747d6a36/pyopenssl-26.2.0.tar.gz", hash = "sha256:8c6fcecd1183a7fc897548dfe388b0cdb7f37e018200d8409cf33959dbe35387", size = 182195, upload-time = "2026-05-04T23:06:09.72Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/a8/41/52f3a3e812b816a91e89aa504199d8bf989a1f873192b10762be66cf2009/pyopenssl-26.1.0-py3-none-any.whl", hash = "sha256:115563879b2c8ccb207975705d3e491434d8c9d7c79667c902ecbf5f3bbd2ece", size = 58109, upload-time = "2026-04-24T20:23:46.273Z" },
|
{ url = "https://files.pythonhosted.org/packages/73/b8/a0e2790ae249d6f38c9f66de7a211621a7ab2650217bcd04e1262f578a56/pyopenssl-26.2.0-py3-none-any.whl", hash = "sha256:4f9d971bc5298b8bc1fab282803da04bf000c755d4ad9d99b52de2569ca19a70", size = 55823, upload-time = "2026-05-04T23:06:08.395Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3808,15 +3808,15 @@ socks = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uvicorn"
|
name = "uvicorn"
|
||||||
version = "0.45.0"
|
version = "0.46.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
{ name = "h11" },
|
{ name = "h11" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/62b0d9a2cfc8b4de6771322dae30f2db76c66dae9ec32e94e176a44ad563/uvicorn-0.45.0.tar.gz", hash = "sha256:3fe650df136c5bd2b9b06efc5980636344a2fbb840e9ddd86437d53144fa335d", size = 87818, upload-time = "2026-04-21T10:43:46.815Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/1f/93/041fca8274050e40e6791f267d82e0e2e27dd165627bd640d3e0e378d877/uvicorn-0.46.0.tar.gz", hash = "sha256:fb9da0926999cc6cb22dc7cd71a94a632f078e6ae47ff683c5c420750fb7413d", size = 88758, upload-time = "2026-04-23T07:16:00.151Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/c1/88/d0f7512465b166a4e931ccf7e77792be60fb88466a43964c7566cbaff752/uvicorn-0.45.0-py3-none-any.whl", hash = "sha256:2db26f588131aeec7439de00f2dd52d5f210710c1f01e407a52c90b880d1fd4f", size = 69838, upload-time = "2026-04-21T10:43:45.029Z" },
|
{ url = "https://files.pythonhosted.org/packages/31/a3/5b1562db76a5a488274b2332a97199b32d0442aca0ed193697fd47786316/uvicorn-0.46.0-py3-none-any.whl", hash = "sha256:bbebbcbed972d162afca128605223022bedd345b7bc7855ce66deb31487a9048", size = 70926, upload-time = "2026-04-23T07:15:58.355Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.optional-dependencies]
|
[package.optional-dependencies]
|
||||||
|
|||||||
290
web/package-lock.json
generated
290
web/package-lock.json
generated
@@ -21,7 +21,7 @@
|
|||||||
"@codemirror/theme-one-dark": "^6.1.3",
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
"@eslint/js": "^9.39.3",
|
"@eslint/js": "^9.39.3",
|
||||||
"@floating-ui/dom": "^1.7.6",
|
"@floating-ui/dom": "^1.7.6",
|
||||||
"@formatjs/intl-listformat": "^8.3.2",
|
"@formatjs/intl-listformat": "^8.3.4",
|
||||||
"@fortawesome/fontawesome-free": "^7.2.0",
|
"@fortawesome/fontawesome-free": "^7.2.0",
|
||||||
"@goauthentik/api": "0.0.0",
|
"@goauthentik/api": "0.0.0",
|
||||||
"@goauthentik/core": "^1.0.0",
|
"@goauthentik/core": "^1.0.0",
|
||||||
@@ -43,11 +43,11 @@
|
|||||||
"@patternfly/elements": "^4.4.0",
|
"@patternfly/elements": "^4.4.0",
|
||||||
"@patternfly/patternfly": "^4.224.2",
|
"@patternfly/patternfly": "^4.224.2",
|
||||||
"@playwright/test": "^1.59.1",
|
"@playwright/test": "^1.59.1",
|
||||||
"@sentry/browser": "^10.49.0",
|
"@sentry/browser": "^10.50.0",
|
||||||
"@storybook/addon-docs": "^10.3.5",
|
"@storybook/addon-docs": "^10.3.6",
|
||||||
"@storybook/addon-links": "^10.3.5",
|
"@storybook/addon-links": "^10.3.6",
|
||||||
"@storybook/web-components": "^10.3.5",
|
"@storybook/web-components": "^10.3.6",
|
||||||
"@storybook/web-components-vite": "^10.3.5",
|
"@storybook/web-components-vite": "^10.3.6",
|
||||||
"@types/codemirror": "^5.60.17",
|
"@types/codemirror": "^5.60.17",
|
||||||
"@types/grecaptcha": "^3.0.9",
|
"@types/grecaptcha": "^3.0.9",
|
||||||
"@types/guacamole-common-js": "^1.5.5",
|
"@types/guacamole-common-js": "^1.5.5",
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
"country-flag-icons": "^1.6.16",
|
"country-flag-icons": "^1.6.16",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"deepmerge-ts": "^7.1.5",
|
"deepmerge-ts": "^7.1.5",
|
||||||
"dompurify": "^3.4.1",
|
"dompurify": "^3.4.2",
|
||||||
"esbuild": "^0.28.0",
|
"esbuild": "^0.28.0",
|
||||||
"eslint": "^9.39.3",
|
"eslint": "^9.39.3",
|
||||||
"eslint-plugin-lit": "^2.2.1",
|
"eslint-plugin-lit": "^2.2.1",
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
"globals": "^17.5.0",
|
"globals": "^17.5.0",
|
||||||
"guacamole-common-js": "^1.5.0",
|
"guacamole-common-js": "^1.5.0",
|
||||||
"hastscript": "^9.0.1",
|
"hastscript": "^9.0.1",
|
||||||
"knip": "^6.6.3",
|
"knip": "^6.7.0",
|
||||||
"lex": "^2025.11.0",
|
"lex": "^2025.11.0",
|
||||||
"lit": "^3.3.2",
|
"lit": "^3.3.2",
|
||||||
"lit-analyzer": "^2.0.3",
|
"lit-analyzer": "^2.0.3",
|
||||||
@@ -1305,27 +1305,27 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@formatjs/fast-memoize": {
|
"node_modules/@formatjs/fast-memoize": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.3.tgz",
|
||||||
"integrity": "sha512-vPnriihkfK0lzoQGaXq+qXH23VsYyansRTkTgo2aTG0k1NjLFyZimFVdfj4C9JkSE5dm7CEngcQ5TTc1yAyBfQ==",
|
"integrity": "sha512-Ocd1vPuD68rW6BJDuAOtnnc1GPeVepY5kZXML1psGVFQ+1Q8CfkftT3Tnam+Mxx97Pz08jIEDCotl/GV+Naccg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@formatjs/intl-listformat": {
|
"node_modules/@formatjs/intl-listformat": {
|
||||||
"version": "8.3.2",
|
"version": "8.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-8.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-8.3.4.tgz",
|
||||||
"integrity": "sha512-PH8V1YVSm+AcrQ1D9Th5wxWo74vDs1V1rRGz9PCFm1HTBtZLVMN3P/+tDcpizQBvvdGNwUqHBOdxN5ZZtlq2bQ==",
|
"integrity": "sha512-q7WskvO6C/Cyq7ryyM9maDL2FJzt6u39MMBrxmTHZtpTMZukG5Lw0kl9sZaCOR9tYP34xOdWp4JNUrfrkdLGXQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/intl-localematcher": "0.8.3"
|
"@formatjs/intl-localematcher": "0.8.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@formatjs/intl-localematcher": {
|
"node_modules/@formatjs/intl-localematcher": {
|
||||||
"version": "0.8.3",
|
"version": "0.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.5.tgz",
|
||||||
"integrity": "sha512-pHUjWb9NuhnMs8+PxQdzBtZRFJHlGhrURGAbm6Ltwl82BFajeuiIR3jblSa7ia3r62rXe/0YtVpUG3xWr5bFCA==",
|
"integrity": "sha512-TEW/NR367c3PcQ2AXfkNig9jC740+qbkM0LgKl7UCE7Xtv7C5Uk1mvlu86MjQZBmscUai8HSWjcEETpwaVvJ6A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/fast-memoize": "3.1.2"
|
"@formatjs/fast-memoize": "3.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fortawesome/fontawesome-free": {
|
"node_modules/@fortawesome/fontawesome-free": {
|
||||||
@@ -3593,75 +3593,75 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@sentry-internal/browser-utils": {
|
"node_modules/@sentry-internal/browser-utils": {
|
||||||
"version": "10.49.0",
|
"version": "10.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.50.0.tgz",
|
||||||
"integrity": "sha512-n0QRx0Ysx6mPfIydTkz7VP0FmwM+/EqMZiRqdsU3aTYsngE9GmEDV0OL1bAy6a8N/C1xf9vntkuAtj6N/8Z51w==",
|
"integrity": "sha512-42bxyRTxnCmYlWnvz4CxikuQNanw8UNma2WJrtxJ0f1MAJV2GhQGSHDLnA+lvFlmiz6qct3pfen/NXGyOTegTA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/core": "10.49.0"
|
"@sentry/core": "10.50.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry-internal/feedback": {
|
"node_modules/@sentry-internal/feedback": {
|
||||||
"version": "10.49.0",
|
"version": "10.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.50.0.tgz",
|
||||||
"integrity": "sha512-JNsUBGv0faCFE7MeZUH99Y9lU9qq3LBALbLxpE1x7ngNrQnVYRlcFgdqaD/btNBKr8awjYL8gmcSkHBWskGqLQ==",
|
"integrity": "sha512-0k9XZF0wn86f77mIO2U3gNNyDZooy139CnEanRzHinrN106vVzvBZ6TUEQoHtoO1fqQxr+nWWVrqV/PXUqk47w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/core": "10.49.0"
|
"@sentry/core": "10.50.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry-internal/replay": {
|
"node_modules/@sentry-internal/replay": {
|
||||||
"version": "10.49.0",
|
"version": "10.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.50.0.tgz",
|
||||||
"integrity": "sha512-IEy4lwHVMiRE3JAcn+kFKjsTgalDOCSTf20SoFd+nkt6rN/k1RDyr4xpdfF//Kj3UdeTmbuibYjK5H/FLhhnGg==",
|
"integrity": "sha512-51FYNfnvVLAWw1rrEWPFfwHuMRb9mkVCFGA4J9/un7SpeGBsQDziGB0Di4fsCxI7+EdSBpfLHPF0csKtCCw0oQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry-internal/browser-utils": "10.49.0",
|
"@sentry-internal/browser-utils": "10.50.0",
|
||||||
"@sentry/core": "10.49.0"
|
"@sentry/core": "10.50.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry-internal/replay-canvas": {
|
"node_modules/@sentry-internal/replay-canvas": {
|
||||||
"version": "10.49.0",
|
"version": "10.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.50.0.tgz",
|
||||||
"integrity": "sha512-7D/NrgH1Qwx5trDYaaTSSJmCb1yVQQLqFG4G/S9x2ltzl9876lSGJL8UeW8ReNQgF3CDAcwbmm/9aXaVSBUNZA==",
|
"integrity": "sha512-jx6RKBmcJSWdI92qDGS/sBv1w+7Cww879Z/moX7bw7ipHa/Ts3iDcB3rgZwvhmi17U+mvYsbJeL2DXkPo3TjPw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry-internal/replay": "10.49.0",
|
"@sentry-internal/replay": "10.50.0",
|
||||||
"@sentry/core": "10.49.0"
|
"@sentry/core": "10.50.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/browser": {
|
"node_modules/@sentry/browser": {
|
||||||
"version": "10.49.0",
|
"version": "10.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.50.0.tgz",
|
||||||
"integrity": "sha512-bGCHc+wK2Dx67YoSbmtlt04alqWfQ+dasD/GVipVOq50gvw/BBIDHTEWRJEjACl+LrvszeY54V+24p8z4IgysA==",
|
"integrity": "sha512-1f6rAvET6myiTaSeYqvaaBwvq1LfxqWjAPIoAW/NVC9bPMkeEcuvgDajHrnZMrBeWoJ81NMyoLkyX+iOc7MoFA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry-internal/browser-utils": "10.49.0",
|
"@sentry-internal/browser-utils": "10.50.0",
|
||||||
"@sentry-internal/feedback": "10.49.0",
|
"@sentry-internal/feedback": "10.50.0",
|
||||||
"@sentry-internal/replay": "10.49.0",
|
"@sentry-internal/replay": "10.50.0",
|
||||||
"@sentry-internal/replay-canvas": "10.49.0",
|
"@sentry-internal/replay-canvas": "10.50.0",
|
||||||
"@sentry/core": "10.49.0"
|
"@sentry/core": "10.50.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/core": {
|
"node_modules/@sentry/core": {
|
||||||
"version": "10.49.0",
|
"version": "10.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.50.0.tgz",
|
||||||
"integrity": "sha512-UaFeum3LUM1mB0d67jvKnqId1yWQjyqmaDV6kWngG03x+jqXb08tJdGpSoxjXZe13jFBbiBL/wKDDYIK7rCK4g==",
|
"integrity": "sha512-J4A+vzUO3adl0TkFCjaN1+4miamrjHiEIYuLHiuu1lmAjq5WIVw32ObvAh4yMwNtxyaEMosTrrh5M6f12XSJFg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -3698,15 +3698,15 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@storybook/addon-docs": {
|
"node_modules/@storybook/addon-docs": {
|
||||||
"version": "10.3.5",
|
"version": "10.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.3.6.tgz",
|
||||||
"integrity": "sha512-WuHbxia/o5TX4Rg/IFD0641K5qId/Nk0dxhmAUNoFs5L0+yfZUwh65XOBbzXqrkYmYmcVID4v7cgDRmzstQNkA==",
|
"integrity": "sha512-TvIdADVPtauxW0LzXIpIv7X6GxwetorhyNh+6+7MHC27XSBCWVxxRUwL63YeLlHTuXsIk0quG3b1xgwVRzWOJA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"@storybook/csf-plugin": "10.3.5",
|
"@storybook/csf-plugin": "10.3.6",
|
||||||
"@storybook/icons": "^2.0.1",
|
"@storybook/icons": "^2.0.1",
|
||||||
"@storybook/react-dom-shim": "10.3.5",
|
"@storybook/react-dom-shim": "10.3.6",
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
"ts-dedent": "^2.0.0"
|
"ts-dedent": "^2.0.0"
|
||||||
@@ -3716,13 +3716,13 @@
|
|||||||
"url": "https://opencollective.com/storybook"
|
"url": "https://opencollective.com/storybook"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"storybook": "^10.3.5"
|
"storybook": "^10.3.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@storybook/addon-links": {
|
"node_modules/@storybook/addon-links": {
|
||||||
"version": "10.3.5",
|
"version": "10.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-10.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-10.3.6.tgz",
|
||||||
"integrity": "sha512-Xe2wCGZ+hpZ0cDqAIBHk+kPc8nODNbu585ghd5bLrlYJMDVXoNM/fIlkrLgjIDVbfpgeJLUEg7vldJrn+FyOLw==",
|
"integrity": "sha512-tv9Xd68qRGBAvEubaxNo3FuFq4GwuMiBriD+gLGuFK0+/u3cnkuA264aoR1v6YCH3sT3er3+MBimuyKM3jLDxg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@storybook/global": "^5.0.0"
|
"@storybook/global": "^5.0.0"
|
||||||
@@ -3733,7 +3733,7 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
"storybook": "^10.3.5"
|
"storybook": "^10.3.6"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"react": {
|
"react": {
|
||||||
@@ -3742,12 +3742,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@storybook/builder-vite": {
|
"node_modules/@storybook/builder-vite": {
|
||||||
"version": "10.3.5",
|
"version": "10.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.3.6.tgz",
|
||||||
"integrity": "sha512-i4KwCOKbhtlbQIbhm53+Kk7bMnxa0cwTn1pxmtA/x5wm1Qu7FrrBQV0V0DNjkUqzcSKo1CjspASJV/HlY0zYlw==",
|
"integrity": "sha512-gpvR/sE4BcrFtmQZ+Ker7zD23oQzoVeqD9nF6cK6yzY+Q0svJXyX2EPmFG4y+EwygD5/vNzDpP84gGMut8VRwg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@storybook/csf-plugin": "10.3.5",
|
"@storybook/csf-plugin": "10.3.6",
|
||||||
"ts-dedent": "^2.0.0"
|
"ts-dedent": "^2.0.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
@@ -3755,14 +3755,14 @@
|
|||||||
"url": "https://opencollective.com/storybook"
|
"url": "https://opencollective.com/storybook"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"storybook": "^10.3.5",
|
"storybook": "^10.3.6",
|
||||||
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@storybook/csf-plugin": {
|
"node_modules/@storybook/csf-plugin": {
|
||||||
"version": "10.3.5",
|
"version": "10.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.3.6.tgz",
|
||||||
"integrity": "sha512-qlEzNKxOjq86pvrbuMwiGD/bylnsXk1dg7ve0j77YFjEEchqtl7qTlrXvFdNaLA89GhW6D/EV6eOCu/eobPDgw==",
|
"integrity": "sha512-9kBf7VRdRqTSIYo+rPtVn5yjYYyK8kP2QhEYx3oiXvfwy4RexmbJnhk/tXa/lNiTqukA1TqaWQ2+5MqF4fu6YQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"unplugin": "^2.3.5"
|
"unplugin": "^2.3.5"
|
||||||
@@ -3774,7 +3774,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"esbuild": "*",
|
"esbuild": "*",
|
||||||
"rollup": "*",
|
"rollup": "*",
|
||||||
"storybook": "^10.3.5",
|
"storybook": "^10.3.6",
|
||||||
"vite": "*",
|
"vite": "*",
|
||||||
"webpack": "*"
|
"webpack": "*"
|
||||||
},
|
},
|
||||||
@@ -3810,9 +3810,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@storybook/react-dom-shim": {
|
"node_modules/@storybook/react-dom-shim": {
|
||||||
"version": "10.3.5",
|
"version": "10.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.3.6.tgz",
|
||||||
"integrity": "sha512-Gw8R7XZm0zSUH0XAuxlQJhmizsLzyD6x00KOlP6l7oW9eQHXGfxg3seNDG3WrSAcW07iP1/P422kuiriQlOv7g==",
|
"integrity": "sha512-/Tu1gPu+Fw+zOnAGmxRmOD30FX3a04LxcTAKflEtdpmtIMVR5bA3qpjy+f5YhoyDCecbXyKmL1OeIU2FIIZHqQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -3821,13 +3821,13 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
"storybook": "^10.3.5"
|
"storybook": "^10.3.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@storybook/web-components": {
|
"node_modules/@storybook/web-components": {
|
||||||
"version": "10.3.5",
|
"version": "10.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/web-components/-/web-components-10.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/web-components/-/web-components-10.3.6.tgz",
|
||||||
"integrity": "sha512-tSppZagFCeZ+bWsaHUvdiw17ATWgfGDBz0mFicgEj0/eNuxQH2OvXyRIQUXY39b/55TBwSGeoIX3tOW91WIqpw==",
|
"integrity": "sha512-femDZGYBGQDckL7F6ZCl2S+dNNBjvd9lp6rQrwBdbNprjctLd6d3EB4HyNM502QxtdEo7laq8y1goDu8KwIV3A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@storybook/global": "^5.0.0",
|
"@storybook/global": "^5.0.0",
|
||||||
@@ -3840,24 +3840,24 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"lit": "^2.0.0 || ^3.0.0",
|
"lit": "^2.0.0 || ^3.0.0",
|
||||||
"storybook": "^10.3.5"
|
"storybook": "^10.3.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@storybook/web-components-vite": {
|
"node_modules/@storybook/web-components-vite": {
|
||||||
"version": "10.3.5",
|
"version": "10.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/@storybook/web-components-vite/-/web-components-vite-10.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@storybook/web-components-vite/-/web-components-vite-10.3.6.tgz",
|
||||||
"integrity": "sha512-6uAw6KAUXFsAPzp8KchcMp3gatEnEAd8ylIvzoMzvsIMiHmzXwvDNmoFZnAJ2tmsQGvF4dZRDCBg7PvWdTx8Rg==",
|
"integrity": "sha512-VeDEAJuOOQV6VAqEF0pilXucS6kp+1ILJVkI+ets6Ku2D+RKeu167YrQAzh1NwzRTv0e5H0anDDNke+sWvg2dg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@storybook/builder-vite": "10.3.5",
|
"@storybook/builder-vite": "10.3.6",
|
||||||
"@storybook/web-components": "10.3.5"
|
"@storybook/web-components": "10.3.6"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/storybook"
|
"url": "https://opencollective.com/storybook"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"storybook": "^10.3.5"
|
"storybook": "^10.3.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swagger-api/apidom-ast": {
|
"node_modules/@swagger-api/apidom-ast": {
|
||||||
@@ -4556,9 +4556,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core": {
|
"node_modules/@swc/core": {
|
||||||
"version": "1.15.30",
|
"version": "1.15.32",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.30.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.32.tgz",
|
||||||
"integrity": "sha512-R8VQbQY1BZcbIF2p3gjlTCwAQzx1A194ugWfwld5y+WgVVWqVKm7eURGGOVbQVubgKWzidP2agomBbg96rZilQ==",
|
"integrity": "sha512-/eWL0n43D64QWEUHLtTE+jDqjkJhyidjkDhv6f0uJohOUAhywxQ9wXYp845DNNds0JpCdI4Uo0a9bl+vbXf+ew==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4573,18 +4573,18 @@
|
|||||||
"url": "https://opencollective.com/swc"
|
"url": "https://opencollective.com/swc"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@swc/core-darwin-arm64": "1.15.30",
|
"@swc/core-darwin-arm64": "1.15.32",
|
||||||
"@swc/core-darwin-x64": "1.15.30",
|
"@swc/core-darwin-x64": "1.15.32",
|
||||||
"@swc/core-linux-arm-gnueabihf": "1.15.30",
|
"@swc/core-linux-arm-gnueabihf": "1.15.32",
|
||||||
"@swc/core-linux-arm64-gnu": "1.15.30",
|
"@swc/core-linux-arm64-gnu": "1.15.32",
|
||||||
"@swc/core-linux-arm64-musl": "1.15.30",
|
"@swc/core-linux-arm64-musl": "1.15.32",
|
||||||
"@swc/core-linux-ppc64-gnu": "1.15.30",
|
"@swc/core-linux-ppc64-gnu": "1.15.32",
|
||||||
"@swc/core-linux-s390x-gnu": "1.15.30",
|
"@swc/core-linux-s390x-gnu": "1.15.32",
|
||||||
"@swc/core-linux-x64-gnu": "1.15.30",
|
"@swc/core-linux-x64-gnu": "1.15.32",
|
||||||
"@swc/core-linux-x64-musl": "1.15.30",
|
"@swc/core-linux-x64-musl": "1.15.32",
|
||||||
"@swc/core-win32-arm64-msvc": "1.15.30",
|
"@swc/core-win32-arm64-msvc": "1.15.32",
|
||||||
"@swc/core-win32-ia32-msvc": "1.15.30",
|
"@swc/core-win32-ia32-msvc": "1.15.32",
|
||||||
"@swc/core-win32-x64-msvc": "1.15.30"
|
"@swc/core-win32-x64-msvc": "1.15.32"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@swc/helpers": ">=0.5.17"
|
"@swc/helpers": ">=0.5.17"
|
||||||
@@ -4596,9 +4596,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-darwin-arm64": {
|
"node_modules/@swc/core-darwin-arm64": {
|
||||||
"version": "1.15.30",
|
"version": "1.15.32",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.30.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.32.tgz",
|
||||||
"integrity": "sha512-VvpP+vq08HmGYewMWvrdsxh9s2lthz/808zXm8Yu5kaqeR8Yia2b0eYXleHQ3VAjoStUDk6LzTheBW9KXYQdMA==",
|
"integrity": "sha512-/YWMvJDPu+AAwuUsM2G+DNQ/7zhodURGzdQyewEqcvgklAdDHs3LwQmLLnyn6SJl8DT8UOxkbzK+D1PmPeelRg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -4612,9 +4612,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-darwin-x64": {
|
"node_modules/@swc/core-darwin-x64": {
|
||||||
"version": "1.15.30",
|
"version": "1.15.32",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.30.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.32.tgz",
|
||||||
"integrity": "sha512-WiJA0hiZI3nwQAO6mu5RqigtWGDtth4Hiq6rbZxAaQyhIcqKIg5IoMRc1Y071lrNJn29eEDMC86Rq58xgUxlDg==",
|
"integrity": "sha512-KOTXJXdAhWL+hZ77MYP3z+4pcMFaQhQ74yqyN1uz093q0YnbxpqMtYpPISbYvMHzVRNNx5kN+9RZAXEaadhWVA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -4628,9 +4628,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
||||||
"version": "1.15.30",
|
"version": "1.15.32",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.30.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.32.tgz",
|
||||||
"integrity": "sha512-YANuFUo48kIT6plJgCD0keae9HFXfjxsbvsgevqc0hr/07X/p7sAWTFOGYEc2SXcASaK7UvuQqzlbW8pr7R79g==",
|
"integrity": "sha512-oOoxLweljlc0A4X8ybsgxV7cVaYTwBOg2iMDJcFR3Sr48C+lsv9VzSmqdK/IVIXF4W4GjLc3VqTAdSMXlfVLuQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -4644,9 +4644,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-linux-arm64-gnu": {
|
"node_modules/@swc/core-linux-arm64-gnu": {
|
||||||
"version": "1.15.30",
|
"version": "1.15.32",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.30.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.32.tgz",
|
||||||
"integrity": "sha512-VndG8jaR4ugY6u+iVOT0Q+d2fZd7sLgjPgN8W/Le+3EbZKl+cRfFxV7Eoz4gfLqhmneZPdcIzf9T3LkgkmqNLg==",
|
"integrity": "sha512-oDzEkdl6D6BAWdMtU5KGO7y3HR5fJcvByNLyEk9+ugj8nP5Ovb7P4kBcStBXc4MPExFGQryehiINMlmY8HlclA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -4660,9 +4660,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-linux-arm64-musl": {
|
"node_modules/@swc/core-linux-arm64-musl": {
|
||||||
"version": "1.15.30",
|
"version": "1.15.32",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.30.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.32.tgz",
|
||||||
"integrity": "sha512-1SYGs2l0Yyyi0pR/P/NKz/x0kqxkoiw+BXeJjLUdecSk/KasncWlJrc6hOvFSgKHOBrzgM5jwuluKtlT8dnrcA==",
|
"integrity": "sha512-omcqjoZP/b8D8PuczVoRwJieC6ibj7qIxTftNYokz4/aSmKFHvsd7nIFfPk5ZvtzncbH4AY7+Dkr/Lp2gWxYeA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -4676,9 +4676,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-linux-ppc64-gnu": {
|
"node_modules/@swc/core-linux-ppc64-gnu": {
|
||||||
"version": "1.15.30",
|
"version": "1.15.32",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.30.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.32.tgz",
|
||||||
"integrity": "sha512-TXREtiXeRhbfDFbmhnkIsXpKfzbfT73YkV2ZF6w0sfxgjC5zI2ZAbaCOq25qxvegofj2K93DtOpm9RLaBgqR2g==",
|
"integrity": "sha512-KGkTMyz/Tbn3PBNu0AVZ4GTDFKnICrYcTiNPZq8DrvK42pnFsf3GNDrIG9E5AtQlTmC0YigkWKmu0eMcfTrmgA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -4692,9 +4692,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-linux-s390x-gnu": {
|
"node_modules/@swc/core-linux-s390x-gnu": {
|
||||||
"version": "1.15.30",
|
"version": "1.15.32",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.30.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.32.tgz",
|
||||||
"integrity": "sha512-DCR2YYeyd6DQE4OuDhImouuNcjXEiEdnn1Y0DyGteugPEDvVuvYk8Xddi+4o2SgWH6jiW8/I+3emZvbep1NC+g==",
|
"integrity": "sha512-G3Aa4tVS/3OGZBkoNIwUF9F6RAy+Osb4GOlo62SinLmDiErz/ykmM7KH0wkz6l9kM8jJq1HyAM6atJTUEbBk7g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -4708,9 +4708,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-linux-x64-gnu": {
|
"node_modules/@swc/core-linux-x64-gnu": {
|
||||||
"version": "1.15.30",
|
"version": "1.15.32",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.30.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.32.tgz",
|
||||||
"integrity": "sha512-5Pizw3NgfOJ5BJOBK8TIRa59xFW2avESTOBDPTAYwZYa1JNDs+KMF9lUfjJiJLM5HiMs/wPheA9eiT0q9m2AoA==",
|
"integrity": "sha512-ERsjfGcj6CBmj3vJnGDO8m8rTvw6RqMcWo1dogOtNx3/+/0+NNpJiXDobJrr1GwInI/BHAEkvSFIH6d2LqPcUQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -4724,9 +4724,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-linux-x64-musl": {
|
"node_modules/@swc/core-linux-x64-musl": {
|
||||||
"version": "1.15.30",
|
"version": "1.15.32",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.30.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.32.tgz",
|
||||||
"integrity": "sha512-qyqydP/wyH8alcIP4a2hnGSjHLJjm9H7yDFup+CPy9oTahFgLLwnNcv5UHXqO2Qs3AIND+cls5f/Bb6hqpxdgA==",
|
"integrity": "sha512-N4Ggahe/8SUbTX50P6EdhbW9YWcgbZVb52R4cq6MK+zsoMjRq7rGvV5ztA05QnbaCYqMYx8rTY7KAIA3Crdo4Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -4740,9 +4740,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-win32-arm64-msvc": {
|
"node_modules/@swc/core-win32-arm64-msvc": {
|
||||||
"version": "1.15.30",
|
"version": "1.15.32",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.30.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.32.tgz",
|
||||||
"integrity": "sha512-CaQENgDHVGOg1mSF5sQVgvfFHG9kjMor2rkLMLeLOkfZYNj13ppnJ9+lfaBZLZUMMbnlGQnavCJb8PVBUOso7Q==",
|
"integrity": "sha512-01yN0o9jvo8xBTP12aPK2wW8b41jmOlGbDDlAnoynotc4pO6xA0zby9f1z6j++qXDpGBttLySq1omgVrlQKYcw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -4756,9 +4756,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-win32-ia32-msvc": {
|
"node_modules/@swc/core-win32-ia32-msvc": {
|
||||||
"version": "1.15.30",
|
"version": "1.15.32",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.30.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.32.tgz",
|
||||||
"integrity": "sha512-30VdLeGk6fugiUs/kUdJ/pAg7z/zpvVbR11RH60jZ0Z42WIeIniYx0rLEWN7h/pKJ3CopqsQ3RsogCAkRKiA2g==",
|
"integrity": "sha512-fLagI9XZYNpTcmlqAcp3KBtmj7E19WCmYD80Jxj1Kn5tGNa7yxNLd3NNdWxuZGUPl5iC0/KqZru7g08gF6Fsrw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -4772,9 +4772,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-win32-x64-msvc": {
|
"node_modules/@swc/core-win32-x64-msvc": {
|
||||||
"version": "1.15.30",
|
"version": "1.15.32",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.30.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.32.tgz",
|
||||||
"integrity": "sha512-4iObHPR+Q4oDY110EF5SF5eIaaVJNpMdG9C0q3Q92BsJ5y467uHz7sYQhP60WYlLFsLQ1el2YrIPUItUAQGOKg==",
|
"integrity": "sha512-gbc2bQ/T2CiR+w0OvcVKwLOFAcPZBvmWmolbwpg1E8UrpeC03DGtyMUApOHNXNYWA3SHFrYXCQtosrcMza1YFg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -8380,9 +8380,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/dompurify": {
|
"node_modules/dompurify": {
|
||||||
"version": "3.4.1",
|
"version": "3.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.2.tgz",
|
||||||
"integrity": "sha512-JahakDAIg1gyOm7dlgWSDjV4n7Ip2PKR55NIT6jrMfIgLFgWo81vdr1/QGqWtFNRqXP9UV71oVePtjqS2ebnPw==",
|
"integrity": "sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==",
|
||||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@types/trusted-types": "^2.0.7"
|
"@types/trusted-types": "^2.0.7"
|
||||||
@@ -11569,9 +11569,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/knip": {
|
"node_modules/knip": {
|
||||||
"version": "6.6.3",
|
"version": "6.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/knip/-/knip-6.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/knip/-/knip-6.7.0.tgz",
|
||||||
"integrity": "sha512-7HSf5bLx6r66+sjXwSvSiDEE9RjRzHuAkrEFLE6XXHqaPDY97tdzNvyRVF9DeusbiV72kStAFiNnhj72rxJNGQ==",
|
"integrity": "sha512-ckL51NDH1YJxnv1kNB0iUdDngB4f/e9Igz8uIqYfmNDoyOFmmk1V0WFv3LQ7/hzC63b2Z9X41gGUE9eOWrZpaA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -16598,9 +16598,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/storybook": {
|
"node_modules/storybook": {
|
||||||
"version": "10.3.5",
|
"version": "10.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/storybook/-/storybook-10.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/storybook/-/storybook-10.3.6.tgz",
|
||||||
"integrity": "sha512-uBSZu/GZa9aEIW3QMGvdQPMZWhGxSe4dyRWU8B3/Vd47Gy/XLC7tsBxRr13txmmPOEDHZR94uLuq0H50fvuqBw==",
|
"integrity": "sha512-vbSz7g/1rGMC1uAULqMZjALkIuLu2QABqfhRYhyr/11kzyesi+vAmwyJLukZP1FfecxGOgMwOh6GS0YsGpHAvQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@storybook/global": "^5.0.0",
|
"@storybook/global": "^5.0.0",
|
||||||
@@ -16625,11 +16625,15 @@
|
|||||||
"url": "https://opencollective.com/storybook"
|
"url": "https://opencollective.com/storybook"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"prettier": "^2 || ^3"
|
"prettier": "^2 || ^3",
|
||||||
|
"vite-plus": "^0.1.15"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"optional": true
|
"optional": true
|
||||||
|
},
|
||||||
|
"vite-plus": {
|
||||||
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -19248,7 +19252,7 @@
|
|||||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||||
"@rollup/plugin-swc": "^0.4.0",
|
"@rollup/plugin-swc": "^0.4.0",
|
||||||
"@swc/cli": "^0.8.1",
|
"@swc/cli": "^0.8.1",
|
||||||
"@swc/core": "^1.15.30",
|
"@swc/core": "^1.15.32",
|
||||||
"@webcomponents/template": "^1.5.1",
|
"@webcomponents/template": "^1.5.1",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"core-js": "^3.49.0",
|
"core-js": "^3.49.0",
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
"@codemirror/theme-one-dark": "^6.1.3",
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
"@eslint/js": "^9.39.3",
|
"@eslint/js": "^9.39.3",
|
||||||
"@floating-ui/dom": "^1.7.6",
|
"@floating-ui/dom": "^1.7.6",
|
||||||
"@formatjs/intl-listformat": "^8.3.2",
|
"@formatjs/intl-listformat": "^8.3.4",
|
||||||
"@fortawesome/fontawesome-free": "^7.2.0",
|
"@fortawesome/fontawesome-free": "^7.2.0",
|
||||||
"@goauthentik/api": "0.0.0",
|
"@goauthentik/api": "0.0.0",
|
||||||
"@goauthentik/core": "^1.0.0",
|
"@goauthentik/core": "^1.0.0",
|
||||||
@@ -119,11 +119,11 @@
|
|||||||
"@patternfly/elements": "^4.4.0",
|
"@patternfly/elements": "^4.4.0",
|
||||||
"@patternfly/patternfly": "^4.224.2",
|
"@patternfly/patternfly": "^4.224.2",
|
||||||
"@playwright/test": "^1.59.1",
|
"@playwright/test": "^1.59.1",
|
||||||
"@sentry/browser": "^10.49.0",
|
"@sentry/browser": "^10.50.0",
|
||||||
"@storybook/addon-docs": "^10.3.5",
|
"@storybook/addon-docs": "^10.3.6",
|
||||||
"@storybook/addon-links": "^10.3.5",
|
"@storybook/addon-links": "^10.3.6",
|
||||||
"@storybook/web-components": "^10.3.5",
|
"@storybook/web-components": "^10.3.6",
|
||||||
"@storybook/web-components-vite": "^10.3.5",
|
"@storybook/web-components-vite": "^10.3.6",
|
||||||
"@types/codemirror": "^5.60.17",
|
"@types/codemirror": "^5.60.17",
|
||||||
"@types/grecaptcha": "^3.0.9",
|
"@types/grecaptcha": "^3.0.9",
|
||||||
"@types/guacamole-common-js": "^1.5.5",
|
"@types/guacamole-common-js": "^1.5.5",
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
"country-flag-icons": "^1.6.16",
|
"country-flag-icons": "^1.6.16",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"deepmerge-ts": "^7.1.5",
|
"deepmerge-ts": "^7.1.5",
|
||||||
"dompurify": "^3.4.1",
|
"dompurify": "^3.4.2",
|
||||||
"esbuild": "^0.28.0",
|
"esbuild": "^0.28.0",
|
||||||
"eslint": "^9.39.3",
|
"eslint": "^9.39.3",
|
||||||
"eslint-plugin-lit": "^2.2.1",
|
"eslint-plugin-lit": "^2.2.1",
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
"globals": "^17.5.0",
|
"globals": "^17.5.0",
|
||||||
"guacamole-common-js": "^1.5.0",
|
"guacamole-common-js": "^1.5.0",
|
||||||
"hastscript": "^9.0.1",
|
"hastscript": "^9.0.1",
|
||||||
"knip": "^6.6.3",
|
"knip": "^6.7.0",
|
||||||
"lex": "^2025.11.0",
|
"lex": "^2025.11.0",
|
||||||
"lit": "^3.3.2",
|
"lit": "^3.3.2",
|
||||||
"lit-analyzer": "^2.0.3",
|
"lit-analyzer": "^2.0.3",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||||
"@rollup/plugin-swc": "^0.4.0",
|
"@rollup/plugin-swc": "^0.4.0",
|
||||||
"@swc/cli": "^0.8.1",
|
"@swc/cli": "^0.8.1",
|
||||||
"@swc/core": "^1.15.30",
|
"@swc/core": "^1.15.32",
|
||||||
"@webcomponents/template": "^1.5.1",
|
"@webcomponents/template": "^1.5.1",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"core-js": "^3.49.0",
|
"core-js": "^3.49.0",
|
||||||
|
|||||||
@@ -52,10 +52,6 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(AKModal)) {
|
|||||||
...AKModal.styles,
|
...AKModal.styles,
|
||||||
PFAbout,
|
PFAbout,
|
||||||
css`
|
css`
|
||||||
:host {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-c-about-modal-box {
|
.pf-c-about-modal-box {
|
||||||
--pf-c-about-modal-box--BackgroundColor: var(--ak-c-dialog--BackgroundColor);
|
--pf-c-about-modal-box--BackgroundColor: var(--ak-c-dialog--BackgroundColor);
|
||||||
width: unset;
|
width: unset;
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export class AdminInterface extends WithCapabilitiesConfig(
|
|||||||
|
|
||||||
WebsocketClient.connect();
|
WebsocketClient.connect();
|
||||||
|
|
||||||
this.#sidebarMatcher = window.matchMedia("(width >= 1200px)");
|
this.#sidebarMatcher = window.matchMedia("(width > 1210px)");
|
||||||
this.sidebarOpen = this.#sidebarMatcher.matches;
|
this.sidebarOpen = this.#sidebarMatcher.matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -322,30 +322,6 @@ export class ApplicationViewPage extends WithLicenseSummary(AKElement) {
|
|||||||
id="page-app-entitlements"
|
id="page-app-entitlements"
|
||||||
aria-label="${msg("Application entitlements")}"
|
aria-label="${msg("Application entitlements")}"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
slot="header"
|
|
||||||
class="pf-c-banner pf-m-info"
|
|
||||||
role="status"
|
|
||||||
aria-live="polite"
|
|
||||||
>
|
|
||||||
<div class="pf-l-flex pf-m-space-items-sm">
|
|
||||||
<div class="pf-l-flex__item">
|
|
||||||
<i class="fas fa-info-circle" aria-hidden="true"></i>
|
|
||||||
</div>
|
|
||||||
<div class="pf-l-flex__item">
|
|
||||||
${msg("Application entitlements are in preview.", {
|
|
||||||
id: "application.entitlements.preview.info",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<div class="pf-l-flex__item">
|
|
||||||
<a href="mailto:hello+feature/app-ent@goauthentik.io"
|
|
||||||
>${msg("Send us feedback!", {
|
|
||||||
id: "preview.send-us-feedback",
|
|
||||||
})}</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pf-c-page__main-section pf-m-no-padding-mobile">
|
<div class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||||
<div class="pf-c-card">
|
<div class="pf-c-card">
|
||||||
<div class="pf-c-card__title">
|
<div class="pf-c-card__title">
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ export class ApplicationWizardEditBindingStep extends ApplicationWizardStep<Poli
|
|||||||
<ak-switch-input
|
<ak-switch-input
|
||||||
name="negate"
|
name="negate"
|
||||||
?checked=${instance?.negate ?? false}
|
?checked=${instance?.negate ?? false}
|
||||||
label=${msg("Negate result")}
|
label=${msg("Negate Result")}
|
||||||
help=${msg("Negates the outcome of the binding. Messages are unaffected.")}
|
help=${msg("Negates the outcome of the binding. Messages are unaffected.")}
|
||||||
></ak-switch-input>
|
></ak-switch-input>
|
||||||
<ak-number-input
|
<ak-number-input
|
||||||
@@ -218,8 +218,9 @@ export class ApplicationWizardEditBindingStep extends ApplicationWizardStep<Poli
|
|||||||
></ak-number-input>
|
></ak-number-input>
|
||||||
<ak-radio-input
|
<ak-radio-input
|
||||||
name="failureResult"
|
name="failureResult"
|
||||||
label=${msg("Failure result")}
|
label=${msg("Failure Result")}
|
||||||
.options=${createPassFailOptions}
|
.options=${createPassFailOptions}
|
||||||
|
required
|
||||||
></ak-radio-input>
|
></ak-radio-input>
|
||||||
</form>`;
|
</form>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -381,7 +381,7 @@ export class ApplicationWizardSubmitStep extends CustomEmitterElement(Applicatio
|
|||||||
return html`<h2 class="pf-c-wizard__main-title">
|
return html`<h2 class="pf-c-wizard__main-title">
|
||||||
${msg("Review the Application and Provider")}
|
${msg("Review the Application and Provider")}
|
||||||
</h2>
|
</h2>
|
||||||
<fieldset>
|
<fieldset class="ak-c-fieldset" name="application-details">
|
||||||
<legend>${msg("Application Details")}</legend>
|
<legend>${msg("Application Details")}</legend>
|
||||||
<dl class="pf-c-description-list">
|
<dl class="pf-c-description-list">
|
||||||
<div class="pf-c-description-list__group">
|
<div class="pf-c-description-list__group">
|
||||||
@@ -419,7 +419,7 @@ export class ApplicationWizardSubmitStep extends CustomEmitterElement(Applicatio
|
|||||||
|
|
||||||
${
|
${
|
||||||
renderer
|
renderer
|
||||||
? html`<fieldset>
|
? html`<fieldset class="ak-c-fieldset" name="provider-details">
|
||||||
<legend>${msg("Provider Details")}</legend>
|
<legend>${msg("Provider Details")}</legend>
|
||||||
${renderer(provider)}
|
${renderer(provider)}
|
||||||
</fieldset>`
|
</fieldset>`
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export class BrandListPage extends TablePage<Brand> {
|
|||||||
return [
|
return [
|
||||||
item.domain,
|
item.domain,
|
||||||
item.brandingTitle || msg("-"),
|
item.brandingTitle || msg("-"),
|
||||||
html`<ak-status-label ?good=${item._default}></ak-status-label>`,
|
html`<ak-status-label ?good=${item._default} type="neutral"></ak-status-label>`,
|
||||||
html`<div class="ak-c-table__actions">
|
html`<div class="ak-c-table__actions">
|
||||||
${IconEditButton(BrandForm, item.brandUuid, item.brandingTitle)}
|
${IconEditButton(BrandForm, item.brandUuid, item.brandingTitle)}
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export class ConfigModal extends ModalButton {
|
|||||||
></ak-codemirror>
|
></ak-codemirror>
|
||||||
</ak-expand>
|
</ak-expand>
|
||||||
</div>
|
</div>
|
||||||
<fieldset class="pf-c-modal-box__footer">
|
<fieldset class="ak-c-fieldset pf-c-modal-box__footer">
|
||||||
<legend class="sr-only">${msg("Form actions")}</legend>
|
<legend class="sr-only">${msg("Form actions")}</legend>
|
||||||
<button
|
<button
|
||||||
class="pf-c-button pf-m-plain"
|
class="pf-c-button pf-m-plain"
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export class DeviceAddHowTo extends ModalButton {
|
|||||||
})}
|
})}
|
||||||
</ak-tabs>`}
|
</ak-tabs>`}
|
||||||
</div>
|
</div>
|
||||||
<fieldset class="pf-c-modal-box__footer">
|
<fieldset class="ak-c-fieldset pf-c-modal-box__footer">
|
||||||
<legend class="sr-only">${msg("Form actions")}</legend>
|
<legend class="sr-only">${msg("Form actions")}</legend>
|
||||||
<button
|
<button
|
||||||
class="pf-c-button pf-m-primary"
|
class="pf-c-button pf-m-primary"
|
||||||
|
|||||||
@@ -175,6 +175,21 @@ export class BoundPoliciesList<T extends PolicyBinding = PolicyBinding> extends
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected renderNewPolicyButton(): SlottedTemplateResult {
|
protected renderNewPolicyButton(): SlottedTemplateResult {
|
||||||
|
if (!this.allowedTypes.includes(PolicyBindingCheckTarget.Policy)) {
|
||||||
|
return html`<button
|
||||||
|
type="button"
|
||||||
|
class="pf-c-button pf-m-primary"
|
||||||
|
${modalInvoker(() => {
|
||||||
|
return StrictUnsafe<PolicyBindingForm>(this.bindingEditForm, {
|
||||||
|
allowedTypes: this.allowedTypes,
|
||||||
|
typeNotices: this.typeNotices,
|
||||||
|
targetPk: this.target || "",
|
||||||
|
});
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
${msg("Bind existing group/user")}
|
||||||
|
</button>`;
|
||||||
|
}
|
||||||
return html`<button
|
return html`<button
|
||||||
class="pf-c-button pf-m-primary"
|
class="pf-c-button pf-m-primary"
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ export class PolicyBindingForm<T extends PolicyBinding = PolicyBinding> extends
|
|||||||
</ak-switch-input>
|
</ak-switch-input>
|
||||||
<ak-switch-input
|
<ak-switch-input
|
||||||
name="negate"
|
name="negate"
|
||||||
label=${msg("Negate result")}
|
label=${msg("Negate Result")}
|
||||||
?checked=${this.instance?.negate ?? false}
|
?checked=${this.instance?.negate ?? false}
|
||||||
help=${msg("Negates the outcome of the binding. Messages are unaffected.")}
|
help=${msg("Negates the outcome of the binding. Messages are unaffected.")}
|
||||||
>
|
>
|
||||||
@@ -293,7 +293,11 @@ export class PolicyBindingForm<T extends PolicyBinding = PolicyBinding> extends
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal name="failureResult" label=${msg("Failure result")}>
|
<ak-form-element-horizontal
|
||||||
|
name="failureResult"
|
||||||
|
label=${msg("Failure Result")}
|
||||||
|
required
|
||||||
|
>
|
||||||
<ak-radio .options=${createPassFailOptions} .value=${this.instance?.failureResult}>
|
<ak-radio .options=${createPassFailOptions} .value=${this.instance?.failureResult}>
|
||||||
</ak-radio>
|
</ak-radio>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export class PolicyWizard extends CreateWizard {
|
|||||||
@property()
|
@property()
|
||||||
public bindingTarget: string | null = null;
|
public bindingTarget: string | null = null;
|
||||||
|
|
||||||
public override groupLabel = msg("Bind New Policy");
|
public override groupLabel = msg("Choose Policy Type");
|
||||||
public override groupDescription = msg("Select the type of policy you want to create.");
|
public override groupDescription = msg("Select the type of policy you want to create.");
|
||||||
|
|
||||||
public override initialSteps = this.showBindingPage
|
public override initialSteps = this.showBindingPage
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
|
|||||||
></ak-crypto-certificate-search>
|
></ak-crypto-certificate-search>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
${msg(
|
${msg(
|
||||||
"When connecting to an LDAP Server with TLS, certificates are not checked by default. Specify a keypair to validate the remote certificate.",
|
"Leave empty to skip certificate validation, or select a certificate/keypair containing the LDAP server CA chain to validate the remote certificate.",
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import { DEFAULT_CONFIG } from "#common/api/config";
|
|||||||
|
|
||||||
import { DataProvision, DualSelectPair } from "#elements/ak-dual-select/types";
|
import { DataProvision, DualSelectPair } from "#elements/ak-dual-select/types";
|
||||||
|
|
||||||
|
import { AKLabel } from "#components/ak-label";
|
||||||
|
|
||||||
import { deviceTypeRestrictionPair } from "#admin/stages/authenticator_webauthn/utils";
|
import { deviceTypeRestrictionPair } from "#admin/stages/authenticator_webauthn/utils";
|
||||||
import { BaseStageForm } from "#admin/stages/BaseStageForm";
|
import { BaseStageForm } from "#admin/stages/BaseStageForm";
|
||||||
|
|
||||||
@@ -114,11 +116,20 @@ export class AuthenticatorValidateStageForm extends BaseStageForm<AuthenticatorV
|
|||||||
></ak-text-input>
|
></ak-text-input>
|
||||||
<ak-form-group open label="${msg("Stage-specific settings")}">
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<div class="pf-c-form">
|
<div class="pf-c-form">
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal required name="deviceClasses">
|
||||||
label=${msg("Device classes")}
|
${AKLabel(
|
||||||
required
|
{
|
||||||
name="deviceClasses"
|
slot: "label",
|
||||||
>
|
className: "pf-c-form__group-label",
|
||||||
|
htmlFor: "deviceClasses",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
msg("Device Classes"),
|
||||||
|
)}
|
||||||
|
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg("Device classes which can be used to authenticate.")}
|
||||||
|
</p>
|
||||||
<ak-checkbox-group
|
<ak-checkbox-group
|
||||||
name="users"
|
name="users"
|
||||||
class="user-field-select"
|
class="user-field-select"
|
||||||
@@ -129,9 +140,6 @@ export class AuthenticatorValidateStageForm extends BaseStageForm<AuthenticatorV
|
|||||||
this.isDeviceClassSelected(name as DeviceClassesEnum),
|
this.isDeviceClassSelected(name as DeviceClassesEnum),
|
||||||
)}
|
)}
|
||||||
></ak-checkbox-group>
|
></ak-checkbox-group>
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg("Device classes which can be used to authenticate.")}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Last validation threshold")}
|
label=${msg("Last validation threshold")}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { sourcesProvider, sourcesSelector } from "./IdentificationStageFormHelpe
|
|||||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||||
import { groupBy } from "#common/utils";
|
import { groupBy } from "#common/utils";
|
||||||
|
|
||||||
|
import { AKLabel } from "#components/ak-label";
|
||||||
|
|
||||||
import { BaseStageForm } from "#admin/stages/BaseStageForm";
|
import { BaseStageForm } from "#admin/stages/BaseStageForm";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -87,7 +89,22 @@ export class IdentificationStageForm extends BaseStageForm<IdentificationStage>
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group open label="${msg("Stage-specific settings")}">
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<div class="pf-c-form">
|
<div class="pf-c-form">
|
||||||
<ak-form-element-horizontal label=${msg("User fields")} name="userFields">
|
<ak-form-element-horizontal name="userFields">
|
||||||
|
${AKLabel(
|
||||||
|
{
|
||||||
|
slot: "label",
|
||||||
|
className: "pf-c-form__group-label",
|
||||||
|
htmlFor: "userFields",
|
||||||
|
},
|
||||||
|
msg("User Fields"),
|
||||||
|
)}
|
||||||
|
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
|
||||||
<ak-checkbox-group
|
<ak-checkbox-group
|
||||||
class="user-field-select"
|
class="user-field-select"
|
||||||
.options=${userSelectFields}
|
.options=${userSelectFields}
|
||||||
@@ -95,11 +112,6 @@ export class IdentificationStageForm extends BaseStageForm<IdentificationStage>
|
|||||||
.map(({ name }) => name)
|
.map(({ name }) => name)
|
||||||
.filter((name) => this.isUserFieldSelected(name))}
|
.filter((name) => this.isUserFieldSelected(name))}
|
||||||
></ak-checkbox-group>
|
></ak-checkbox-group>
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg(
|
|
||||||
"Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal label=${msg("Password stage")} name="passwordStage">
|
<ak-form-element-horizontal label=${msg("Password stage")} name="passwordStage">
|
||||||
<ak-search-select
|
<ak-search-select
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import "#elements/forms/SearchSelect/index";
|
|||||||
|
|
||||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||||
|
|
||||||
|
import { AKLabel } from "#components/ak-label";
|
||||||
|
|
||||||
import { RenderFlowOption } from "#admin/flows/utils";
|
import { RenderFlowOption } from "#admin/flows/utils";
|
||||||
import { BaseStageForm } from "#admin/stages/BaseStageForm";
|
import { BaseStageForm } from "#admin/stages/BaseStageForm";
|
||||||
|
|
||||||
@@ -87,7 +89,20 @@ export class PasswordStageForm extends BaseStageForm<PasswordStage> {
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group open label="${msg("Stage-specific settings")}">
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<div class="pf-c-form">
|
<div class="pf-c-form">
|
||||||
<ak-form-element-horizontal label=${msg("Backends")} required name="backends">
|
<ak-form-element-horizontal required name="backends">
|
||||||
|
${AKLabel(
|
||||||
|
{
|
||||||
|
slot: "label",
|
||||||
|
className: "pf-c-form__group-label",
|
||||||
|
htmlFor: "backends",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
msg("Backends"),
|
||||||
|
)}
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg("Selection of backends to test the password against.")}
|
||||||
|
</p>
|
||||||
|
|
||||||
<ak-checkbox-group
|
<ak-checkbox-group
|
||||||
class="user-field-select"
|
class="user-field-select"
|
||||||
.options=${backends}
|
.options=${backends}
|
||||||
@@ -95,9 +110,6 @@ export class PasswordStageForm extends BaseStageForm<PasswordStage> {
|
|||||||
.map(({ name }) => name)
|
.map(({ name }) => name)
|
||||||
.filter((name) => this.isBackendSelected(name))}
|
.filter((name) => this.isBackendSelected(name))}
|
||||||
></ak-checkbox-group>
|
></ak-checkbox-group>
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${msg("Selection of backends to test the password against.")}
|
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Configuration flow")}
|
label=${msg("Configuration flow")}
|
||||||
|
|||||||
@@ -3,10 +3,13 @@ import "#elements/forms/FormGroup";
|
|||||||
import "#elements/forms/HorizontalFormElement";
|
import "#elements/forms/HorizontalFormElement";
|
||||||
import "#elements/forms/Radio";
|
import "#elements/forms/Radio";
|
||||||
import "#components/ak-text-input";
|
import "#components/ak-text-input";
|
||||||
|
import "#components/ak-radio-input";
|
||||||
import "#elements/forms/SearchSelect/index";
|
import "#elements/forms/SearchSelect/index";
|
||||||
|
|
||||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||||
|
|
||||||
|
import { RadioOption } from "#elements/forms/Radio";
|
||||||
|
|
||||||
import { AKLabel } from "#components/ak-label";
|
import { AKLabel } from "#components/ak-label";
|
||||||
|
|
||||||
import { BaseStageForm } from "#admin/stages/BaseStageForm";
|
import { BaseStageForm } from "#admin/stages/BaseStageForm";
|
||||||
@@ -109,39 +112,37 @@ export class UserWriteStageForm extends BaseStageForm<UserWriteStage> {
|
|||||||
?checked=${this.instance?.createUsersAsInactive ?? true}
|
?checked=${this.instance?.createUsersAsInactive ?? true}
|
||||||
help=${msg("Mark newly created users as inactive.")}
|
help=${msg("Mark newly created users as inactive.")}
|
||||||
></ak-switch-input>
|
></ak-switch-input>
|
||||||
<ak-form-element-horizontal label=${msg("User type")} name="userType">
|
<ak-radio-input
|
||||||
<ak-radio
|
label=${msg("User type")}
|
||||||
.options=${[
|
name="userType"
|
||||||
{
|
help=${msg("User type used for newly created users.")}
|
||||||
label: msg("Internal"),
|
.options=${[
|
||||||
value: UserTypeEnum.Internal,
|
{
|
||||||
default: true,
|
label: msg("Internal"),
|
||||||
description: html`${msg(
|
value: UserTypeEnum.Internal,
|
||||||
"Internal users might be users such as company employees, which will get access to the full Enterprise feature set.",
|
default: true,
|
||||||
)}`,
|
description: html`${msg(
|
||||||
},
|
"Internal users might be users such as company employees, which will get access to the full Enterprise feature set.",
|
||||||
{
|
)}`,
|
||||||
label: msg("External"),
|
},
|
||||||
value: UserTypeEnum.External,
|
{
|
||||||
description: html`${msg(
|
label: msg("External"),
|
||||||
"External users might be external consultants or B2C customers. These users don't get access to enterprise features.",
|
value: UserTypeEnum.External,
|
||||||
)}`,
|
description: html`${msg(
|
||||||
},
|
"External users might be external consultants or B2C customers. These users don't get access to enterprise features.",
|
||||||
{
|
)}`,
|
||||||
label: msg("Service account"),
|
},
|
||||||
value: UserTypeEnum.ServiceAccount,
|
{
|
||||||
description: html`${msg(
|
label: msg("Service account"),
|
||||||
"Service accounts should be used for machine-to-machine authentication or other automations.",
|
value: UserTypeEnum.ServiceAccount,
|
||||||
)}`,
|
description: html`${msg(
|
||||||
},
|
"Service accounts should be used for machine-to-machine authentication or other automations.",
|
||||||
]}
|
)}`,
|
||||||
.value=${this.instance?.userType}
|
},
|
||||||
>
|
] satisfies RadioOption<UserTypeEnum>[]}
|
||||||
</ak-radio>
|
.value=${this.instance?.userType}
|
||||||
<p class="pf-c-form__helper-text">
|
>
|
||||||
${msg("User type used for newly created users.")}
|
</ak-radio-input>
|
||||||
</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("User path template")}
|
label=${msg("User path template")}
|
||||||
name="userPathTemplate"
|
name="userPathTemplate"
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ export class UserBulkRevokeSessionsForm extends ModalButton {
|
|||||||
>
|
>
|
||||||
</ak-user-bulk-revoke-sessions-table>
|
</ak-user-bulk-revoke-sessions-table>
|
||||||
</section>
|
</section>
|
||||||
<fieldset class="pf-c-modal-box__footer">
|
<fieldset class="ak-c-fieldset pf-c-modal-box__footer">
|
||||||
<legend class="sr-only">${msg("Form actions")}</legend>
|
<legend class="sr-only">${msg("Form actions")}</legend>
|
||||||
<ak-spinner-button
|
<ak-spinner-button
|
||||||
.callAction=${async () => {
|
.callAction=${async () => {
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 907 KiB After Width: | Height: | Size: 900 KiB |
@@ -48,7 +48,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width < 1200px) {
|
@media (width <= 1210px) {
|
||||||
column-gap: calc(var(--pf-global--spacer--md) / 2);
|
column-gap: calc(var(--pf-global--spacer--md) / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width < 1200px) {
|
@media (width <= 1210px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,7 +164,7 @@
|
|||||||
grid-area: toggle;
|
grid-area: toggle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width >= 1200px) {
|
@media (width > 1210px) {
|
||||||
slot[name="toggle"] {
|
slot[name="toggle"] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ const statusToDetails = new Map<P4Disposition, [string, string]>([
|
|||||||
export class AkStatusLabel extends AKElement {
|
export class AkStatusLabel extends AKElement {
|
||||||
static styles = [PFLabel, Styles];
|
static styles = [PFLabel, Styles];
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean, reflect: true })
|
||||||
public good: boolean | null = null;
|
public good: boolean = false;
|
||||||
|
|
||||||
@property({ type: String, attribute: "good-label" })
|
@property({ type: String, attribute: "good-label" })
|
||||||
public goodLabel = msg("Yes");
|
public goodLabel = msg("Yes");
|
||||||
|
|||||||
113
web/src/elements/ak-checkbox-group/ak-checkbox-group.css
Normal file
113
web/src/elements/ak-checkbox-group/ak-checkbox-group.css
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
.pf-c-form__group-control {
|
||||||
|
padding-block: calc(var(--pf-c-form--m-horizontal__group-label--md--PaddingTop) * 1.3);
|
||||||
|
--pf-c-grid__group-control--m-stack--Gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.pf-c-check {
|
||||||
|
--pf-c-check--checkmark--BorderColor: var(--pf-global--BorderColor--100);
|
||||||
|
--pf-c-check--checkmark--Color: transparent;
|
||||||
|
--pf-c-check--BorderColor: transparent;
|
||||||
|
|
||||||
|
--pf-c-check--checked--BackgroundColor: transparent;
|
||||||
|
--pf-c-check--checked--BorderColor: var(--pf-global--active-color--300);
|
||||||
|
--pf-c-check--checked--Hover--BorderColor: var(--pf-global--active-color--400);
|
||||||
|
|
||||||
|
--pf-c-check--hover--BorderColor: var(--pf-global--active-color--200);
|
||||||
|
|
||||||
|
--pf-c-check--disabled--BackgroundColor: var(--pf-global--BackgroundColor--150);
|
||||||
|
transition:
|
||||||
|
border-color 0.2s,
|
||||||
|
color 0.2s;
|
||||||
|
|
||||||
|
padding-inline: var(--pf-global--spacer--form-element);
|
||||||
|
padding-block: var(--pf-global--spacer--sm);
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: var(--pf-c-check--BackgroundColor, transparent);
|
||||||
|
border-radius: var(--pf-global--BorderRadius--sm);
|
||||||
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
align-content: center;
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas: "input .";
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
column-gap: var(--pf-global--spacer--md);
|
||||||
|
row-gap: 0;
|
||||||
|
|
||||||
|
.pf-c-check__input {
|
||||||
|
grid-area: input;
|
||||||
|
align-self: center;
|
||||||
|
margin-block: 0;
|
||||||
|
appearance: none;
|
||||||
|
content: none;
|
||||||
|
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
padding: 0.25em;
|
||||||
|
border: 0.5px solid;
|
||||||
|
color: var(--pf-c-check--checkmark--BorderColor, transparent);
|
||||||
|
border-radius: 3px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1;
|
||||||
|
background: color-mix(var(--pf-c-check--checkmark--BorderColor), transparent 84%);
|
||||||
|
box-shadow: inset 0 0 1px
|
||||||
|
color-mix(var(--pf-c-check--checkmark--BorderColor), var(--pf-global--BorderColor--200));
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
font-family: "Font Awesome 5 Free";
|
||||||
|
font-weight: 900;
|
||||||
|
|
||||||
|
content: "\f00c";
|
||||||
|
display: block;
|
||||||
|
line-height: 1;
|
||||||
|
color: var(--pf-c-check--checkmark--Color);
|
||||||
|
transition: color 0.2s;
|
||||||
|
|
||||||
|
filter: drop-shadow(color-mix(currentColor, transparent 50%) 0 0 1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:has(.pf-c-check__input:not(:disabled)) .pf-c-check__label {
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-color: var(--pf-c-check--checked--BorderColor);
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has(.pf-c-check__input:checked:not(:disabled)) {
|
||||||
|
--pf-c-check--BackgroundColor: var(--pf-c-check--checked--BackgroundColor);
|
||||||
|
--pf-c-check--BorderColor: var(--pf-c-check--checked--BorderColor);
|
||||||
|
--pf-c-check--checkmark--BorderColor: var(--pf-global--active-color--300);
|
||||||
|
--pf-c-check--checkmark--Color: var(--pf-c-check--checked--BorderColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:has(.pf-c-check__input:checked:not(:disabled)) {
|
||||||
|
--pf-c-check--checkmark--BorderColor: var(--pf-c-check--checked--Hover--BorderColor);
|
||||||
|
--pf-c-check--checkmark--Color: var(--pf-c-check--checked--Hover--BorderColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(:has(.pf-c-check__input:checked)):not(:has(.pf-c-check__input:disabled)) {
|
||||||
|
--pf-c-check--checkmark--BorderColor: var(--pf-c-check--hover--BorderColor);
|
||||||
|
--pf-c-check--checkmark--Color: color-mix(
|
||||||
|
var(--pf-c-check--hover--BorderColor),
|
||||||
|
transparent 75%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has(.pf-c-check__input:disabled) {
|
||||||
|
cursor: not-allowed;
|
||||||
|
|
||||||
|
--pf-c-check--BackgroundColor: var(--pf-c-check--disabled--BackgroundColor) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-c-check__label {
|
||||||
|
align-self: center;
|
||||||
|
font-weight: var(--pf-global--FontWeight--overpass--semi-bold);
|
||||||
|
font-family: var(--pf-global--FontFamily--redhat-updated--heading--sans-serif);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has(.pf-c-check__input:last-child) .pf-c-check__label {
|
||||||
|
grid-row: 1 / -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import Styles from "#elements/ak-checkbox-group/ak-checkbox-group.css";
|
||||||
import { AKControlElement } from "#elements/ControlElement";
|
import { AKControlElement } from "#elements/ControlElement";
|
||||||
|
import { SlottedTemplateResult } from "#elements/types";
|
||||||
import { CustomEmitterElement } from "#elements/utils/eventEmitter";
|
import { CustomEmitterElement } from "#elements/utils/eventEmitter";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { PropertyValues } from "@lit/reactive-element";
|
import { PropertyValues } from "@lit/reactive-element";
|
||||||
import { css, html, TemplateResult } from "lit";
|
import { html, TemplateResult } from "lit";
|
||||||
import { customElement, property, queryAll, state } from "lit/decorators.js";
|
import { customElement, property, queryAll, state } from "lit/decorators.js";
|
||||||
import { map } from "lit/directives/map.js";
|
import { map } from "lit/directives/map.js";
|
||||||
|
|
||||||
@@ -14,11 +16,9 @@ type CheckboxKv = { name: string; label: string | TemplateResult };
|
|||||||
type CheckboxPr = [string, string | TemplateResult];
|
type CheckboxPr = [string, string | TemplateResult];
|
||||||
export type CheckboxPair = CheckboxKv | CheckboxPr;
|
export type CheckboxPair = CheckboxKv | CheckboxPr;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
function* kvToPairs(items: Iterable<CheckboxPair>): Iterable<CheckboxPr> {
|
||||||
const isCheckboxPr = (t: any): t is CheckboxPr => Array.isArray(t);
|
|
||||||
function* kvToPairs(items: CheckboxPair[]): Iterable<CheckboxPr> {
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
yield isCheckboxPr(item) ? item : [item.name, item.label];
|
yield Array.isArray(item) ? item : [item.name, item.label];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,51 +75,40 @@ const AkElementWithCustomEvents = CustomEmitterElement(AKControlElement);
|
|||||||
* protocol.
|
* protocol.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@customElement("ak-checkbox-group")
|
@customElement("ak-checkbox-group")
|
||||||
export class CheckboxGroup extends AkElementWithCustomEvents {
|
export class CheckboxGroup extends AkElementWithCustomEvents {
|
||||||
static styles = [
|
static styles = [PFForm, PFCheck, Styles];
|
||||||
PFForm,
|
|
||||||
PFCheck,
|
|
||||||
css`
|
|
||||||
.pf-c-form__group-control {
|
|
||||||
padding-top: calc(
|
|
||||||
var(--pf-c-form--m-horizontal__group-label--md--PaddingTop) * 1.3
|
|
||||||
);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
|
|
||||||
static get formAssociated() {
|
static get formAssociated() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@property({ type: Array })
|
@property({ type: Array })
|
||||||
options: CheckboxPair[] = [];
|
public options: CheckboxPair[] = [];
|
||||||
|
|
||||||
@property({ type: Array })
|
@property({ type: Array })
|
||||||
value: string[] = [];
|
public value: string[] = [];
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
public name: string | null = null;
|
public name: string | null = null;
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
required = false;
|
public required = false;
|
||||||
|
|
||||||
@queryAll('input[type="checkbox"]')
|
@queryAll('input[type="checkbox"]')
|
||||||
checkboxes!: NodeListOf<HTMLInputElement>;
|
protected checkboxes!: NodeListOf<HTMLInputElement>;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
values: string[] = [];
|
protected values: string[] = [];
|
||||||
|
|
||||||
internals?: ElementInternals;
|
protected internals?: ElementInternals;
|
||||||
doneFirstUpdate = false;
|
protected doneFirstUpdate = false;
|
||||||
|
|
||||||
toJSON() {
|
public toJSON() {
|
||||||
return this.values;
|
return this.values;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get formValue() {
|
protected get formValue() {
|
||||||
if (typeof this.name !== "string") {
|
if (typeof this.name !== "string") {
|
||||||
throw new Error("This cannot be called without having the name set.");
|
throw new Error("This cannot be called without having the name set.");
|
||||||
}
|
}
|
||||||
@@ -129,18 +118,16 @@ export class CheckboxGroup extends AkElementWithCustomEvents {
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
protected clickListener = (ev: Event) => {
|
||||||
super();
|
|
||||||
this.onClick = this.onClick.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
onClick(ev: Event) {
|
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
|
||||||
this.values = Array.from(this.checkboxes)
|
this.values = Array.from(this.checkboxes)
|
||||||
.filter((checkbox) => checkbox.checked)
|
.filter((checkbox) => checkbox.checked)
|
||||||
.map((checkbox) => checkbox.name);
|
.map((checkbox) => checkbox.name);
|
||||||
|
|
||||||
this.dispatchCustomEvent("change", this.values);
|
this.dispatchCustomEvent("change", this.values);
|
||||||
this.dispatchCustomEvent("input", this.values);
|
this.dispatchCustomEvent("input", this.values);
|
||||||
|
|
||||||
if (this.internals) {
|
if (this.internals) {
|
||||||
this.internals.setValidity({});
|
this.internals.setValidity({});
|
||||||
if (this.required && this.values.length === 0) {
|
if (this.required && this.values.length === 0) {
|
||||||
@@ -154,19 +141,20 @@ export class CheckboxGroup extends AkElementWithCustomEvents {
|
|||||||
}
|
}
|
||||||
this.internals.setFormValue(this.formValue);
|
this.internals.setFormValue(this.formValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Doing a write-back so anyone examining the checkbox.value field will get something
|
// Doing a write-back so anyone examining the checkbox.value field will get something
|
||||||
// meaningful. Doesn't do anything for anyone, usually, but it's nice to have.
|
// meaningful. Doesn't do anything for anyone, usually, but it's nice to have.
|
||||||
this.value = this.values;
|
this.value = this.values;
|
||||||
}
|
};
|
||||||
|
|
||||||
willUpdate(changed: PropertyValues<this>) {
|
protected override willUpdate(changed: PropertyValues<this>) {
|
||||||
if (changed.has("value") && !this.doneFirstUpdate) {
|
if (changed.has("value") && !this.doneFirstUpdate) {
|
||||||
this.doneFirstUpdate = true;
|
this.doneFirstUpdate = true;
|
||||||
this.values = this.value;
|
this.values = this.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
public override connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this.dataset.akControl = "true";
|
this.dataset.akControl = "true";
|
||||||
if (this.name && !this.internals) {
|
if (this.name && !this.internals) {
|
||||||
@@ -192,32 +180,35 @@ export class CheckboxGroup extends AkElementWithCustomEvents {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
protected renderCheckbox = ([name, label]: CheckboxPr): SlottedTemplateResult => {
|
||||||
const renderOne = ([name, label]: CheckboxPr) => {
|
const selected = this.values.includes(name);
|
||||||
const selected = this.values.includes(name);
|
const blockFwd = (e: Event) => {
|
||||||
const blockFwd = (e: Event) => {
|
e.stopImmediatePropagation();
|
||||||
e.stopImmediatePropagation();
|
|
||||||
};
|
|
||||||
|
|
||||||
return html` <div part="checkbox" class="pf-c-check" @click=${this.onClick}>
|
|
||||||
<input
|
|
||||||
part="input"
|
|
||||||
@change=${blockFwd}
|
|
||||||
@input=${blockFwd}
|
|
||||||
name="${name}"
|
|
||||||
class="pf-c-check__input"
|
|
||||||
type="checkbox"
|
|
||||||
?checked=${selected}
|
|
||||||
id="ak-check-${name}"
|
|
||||||
/>
|
|
||||||
<label part="label" class="pf-c-check__label" for="ak-check-${name}"
|
|
||||||
>${label}</label
|
|
||||||
>
|
|
||||||
</div>`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return html`<label
|
||||||
|
part="label"
|
||||||
|
for="ak-check-${name}"
|
||||||
|
class="pf-c-check"
|
||||||
|
@click=${this.clickListener}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
part="input"
|
||||||
|
@change=${blockFwd}
|
||||||
|
@input=${blockFwd}
|
||||||
|
name=${name}
|
||||||
|
class="pf-c-check__input"
|
||||||
|
type="checkbox"
|
||||||
|
?checked=${selected}
|
||||||
|
id="ak-check-${name}"
|
||||||
|
/>
|
||||||
|
<div class="pf-c-check__label">${label}</div>
|
||||||
|
</label>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override render(): SlottedTemplateResult {
|
||||||
return html`<div part="checkbox-group" class="pf-c-form__group-control pf-m-stack">
|
return html`<div part="checkbox-group" class="pf-c-form__group-control pf-m-stack">
|
||||||
${map(kvToPairs(this.options), renderOne)}
|
${map(kvToPairs(this.options), this.renderCheckbox)}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,11 +22,12 @@ function resolvePath(...args: string[]): string {
|
|||||||
* - Intercepts local links and scrolls to the target element.
|
* - Intercepts local links and scrolls to the target element.
|
||||||
*/
|
*/
|
||||||
export const MDXAnchor = ({
|
export const MDXAnchor = ({
|
||||||
href,
|
href: initialHref,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.AnchorHTMLAttributes<HTMLAnchorElement>) => {
|
}: React.AnchorHTMLAttributes<HTMLAnchorElement>) => {
|
||||||
const { publicDirectory } = useMDXModule();
|
const { publicDirectory } = useMDXModule();
|
||||||
|
let href = initialHref;
|
||||||
|
|
||||||
if (href?.startsWith(".") && publicDirectory) {
|
if (href?.startsWith(".") && publicDirectory) {
|
||||||
const nextPathname = resolvePath(publicDirectory, href);
|
const nextPathname = resolvePath(publicDirectory, href);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
);
|
);
|
||||||
--ak-c-command-palette__group--Color: var(--pf-global--palette--purple-100);
|
--ak-c-command-palette__group--Color: var(--pf-global--palette--purple-100);
|
||||||
|
|
||||||
--ak-fieldset--BorderColor: transparent;
|
--ak-c-fieldset--BorderColor: transparent;
|
||||||
|
|
||||||
--ak-c-command-palette__item--BackgroundColor: transparent;
|
--ak-c-command-palette__item--BackgroundColor: transparent;
|
||||||
--ak-c-command-palette__item--Color: var(--pf-global--palette--purple-50);
|
--ak-c-command-palette__item--Color: var(--pf-global--palette--purple-50);
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
transform: translate3d(0, 0, 0); /* Fixes rendering artifacts. */
|
transform: translate3d(0, 0, 0); /* Fixes rendering artifacts. */
|
||||||
|
|
||||||
@media (prefers-contrast: more) {
|
@media (prefers-contrast: more) {
|
||||||
--ak-fieldset--BorderColor: var(--pf-global--palette--purple-500);
|
--ak-c-fieldset--BorderColor: var(--pf-global--palette--purple-500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
transition-duration: 0.2s;
|
transition-duration: 0.2s;
|
||||||
|
|
||||||
legend {
|
legend {
|
||||||
--ak-fieldset__legend--PaddingInlineBase: var(--pf-global--spacer--md);
|
--ak-c-fieldset__legend--PaddingInlineBase: var(--pf-global--spacer--md);
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--ak-c-command-palette__group--Color);
|
color: var(--ak-c-command-palette__group--Color);
|
||||||
|
|||||||
@@ -524,7 +524,7 @@ export class AKCommandPaletteModal extends AKModal {
|
|||||||
Object.entries(grouped),
|
Object.entries(grouped),
|
||||||
(_group, groupIdx) => `group-${groupIdx}`,
|
(_group, groupIdx) => `group-${groupIdx}`,
|
||||||
([groupLabel, commands], groupIdx) => html`
|
([groupLabel, commands], groupIdx) => html`
|
||||||
<fieldset part="results-group">
|
<fieldset class="ak-c-fieldset" part="results-group">
|
||||||
<legend
|
<legend
|
||||||
class="${!groupLabel ? "sr-only more-contrast-only" : ""}"
|
class="${!groupLabel ? "sr-only more-contrast-only" : ""}"
|
||||||
data-label=${ifPresent(groupLabel)}
|
data-label=${ifPresent(groupLabel)}
|
||||||
|
|||||||
@@ -115,8 +115,8 @@
|
|||||||
/* #region Footer */
|
/* #region Footer */
|
||||||
|
|
||||||
fieldset.ak-c-dialog__footer {
|
fieldset.ak-c-dialog__footer {
|
||||||
--ak-fieldset__legend--PaddingInlineBase: var(--pf-global--spacer--md);
|
--ak-c-fieldset__legend--PaddingInlineBase: var(--pf-global--spacer--md);
|
||||||
padding-block: calc(var(--ak-fieldset__legend--PaddingInlineBase) / 2);
|
padding-block: calc(var(--ak-c-fieldset__legend--PaddingInlineBase) / 2);
|
||||||
border-inline: none;
|
border-inline: none;
|
||||||
border-block-end: none;
|
border-block-end: none;
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export class ConfirmationForm extends ModalButton {
|
|||||||
<slot class="pf-c-content" name="body"></slot>
|
<slot class="pf-c-content" name="body"></slot>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
<fieldset class="pf-c-modal-box__footer">
|
<fieldset class="ak-c-fieldset pf-c-modal-box__footer">
|
||||||
<legend class="sr-only">${msg("Form actions")}</legend>
|
<legend class="sr-only">${msg("Form actions")}</legend>
|
||||||
<ak-spinner-button
|
<ak-spinner-button
|
||||||
.callAction=${async () => {
|
.callAction=${async () => {
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export class DeleteBulkForm<T> extends ModalButton {
|
|||||||
>
|
>
|
||||||
</ak-used-by-table>
|
</ak-used-by-table>
|
||||||
</section>
|
</section>
|
||||||
<fieldset class="pf-c-modal-box__footer">
|
<fieldset class="ak-c-fieldset pf-c-modal-box__footer">
|
||||||
<legend class="sr-only">${msg("Form actions")}</legend>
|
<legend class="sr-only">${msg("Form actions")}</legend>
|
||||||
<ak-spinner-button
|
<ak-spinner-button
|
||||||
.callAction=${async () => {
|
.callAction=${async () => {
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ export class ModalForm extends ModalButton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected renderActions(): SlottedTemplateResult {
|
protected renderActions(): SlottedTemplateResult {
|
||||||
return html`<fieldset class="pf-c-modal-box__footer">
|
return html`<fieldset class="ak-c-fieldset pf-c-modal-box__footer">
|
||||||
<legend class="sr-only">${msg("Form actions")}</legend>
|
<legend class="sr-only">${msg("Form actions")}</legend>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export class Radio<T extends Jsonifiable = never> extends FormAssociatedElement<
|
|||||||
|
|
||||||
@property()
|
@property()
|
||||||
public set value(nextValue: T) {
|
public set value(nextValue: T) {
|
||||||
if (!nextValue) {
|
if (nextValue === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1054,7 +1054,12 @@ export abstract class Table<T extends object, D = T>
|
|||||||
<thead aria-label=${msg("Column actions")}>
|
<thead aria-label=${msg("Column actions")}>
|
||||||
<tr class="pf-c-table__header-row">
|
<tr class="pf-c-table__header-row">
|
||||||
${this.checkbox ? this.renderAllOnThisPageCheckbox() : nothing}
|
${this.checkbox ? this.renderAllOnThisPageCheckbox() : nothing}
|
||||||
${this.expandable ? html`<td aria-hidden="true"></td>` : nothing}
|
${this.expandable
|
||||||
|
? html`<th
|
||||||
|
class="pf-c-table__toggle pf-m-pressable"
|
||||||
|
aria-hidden="true"
|
||||||
|
></th>`
|
||||||
|
: nothing}
|
||||||
${this.columns.map((column, idx) => {
|
${this.columns.map((column, idx) => {
|
||||||
const [label, orderBy, ariaLabel] = column;
|
const [label, orderBy, ariaLabel] = column;
|
||||||
const columnID = this.#columnIDs.get(column) ?? `column-${idx}`;
|
const columnID = this.#columnIDs.get(column) ?? `column-${idx}`;
|
||||||
|
|||||||
@@ -18,4 +18,5 @@
|
|||||||
.empty-state-primary {
|
.empty-state-primary {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--pf-global--spacer--sm);
|
gap: var(--pf-global--spacer--sm);
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ export class TypeCreateWizardPage extends WithLicenseSummary(WizardPage) {
|
|||||||
html`<form
|
html`<form
|
||||||
${ref(this.formRef)}
|
${ref(this.formRef)}
|
||||||
part="form type-create list"
|
part="form type-create list"
|
||||||
class="pf-c-form pf-m-horizontal ak-m-content-center"
|
class="pf-c-form pf-m-horizontal"
|
||||||
role="radiogroup"
|
role="radiogroup"
|
||||||
aria-label=${ifPresent(this.headline)}
|
aria-label=${ifPresent(this.headline)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ export class FlowInspector extends AKElement {
|
|||||||
|
|
||||||
protected renderNextStage({ currentPlan, isCompleted }: FlowInspection): TemplateResult {
|
protected renderNextStage({ currentPlan, isCompleted }: FlowInspection): TemplateResult {
|
||||||
return html`<div class="pf-c-card">
|
return html`<div class="pf-c-card">
|
||||||
<fieldset>
|
<fieldset class="ak-c-fieldset">
|
||||||
<legend class="pf-c-card__title">${msg("Next stage")}</legend>
|
<legend class="pf-c-card__title">${msg("Next stage")}</legend>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<dl class="pf-c-description-list">
|
<dl class="pf-c-description-list">
|
||||||
@@ -184,7 +184,7 @@ ${stringify(this.getStage(currentPlan?.nextPlannedStage?.stageObj))}</pre
|
|||||||
currentPlan,
|
currentPlan,
|
||||||
}: FlowInspection): TemplateResult {
|
}: FlowInspection): TemplateResult {
|
||||||
return html`<div class="pf-c-card">
|
return html`<div class="pf-c-card">
|
||||||
<fieldset>
|
<fieldset class="ak-c-fieldset">
|
||||||
<legend class="pf-c-card__title">${msg("Plan history")}</legend>
|
<legend class="pf-c-card__title">${msg("Plan history")}</legend>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<ol class="pf-c-progress-stepper pf-m-vertical">
|
<ol class="pf-c-progress-stepper pf-m-vertical">
|
||||||
@@ -248,7 +248,7 @@ ${stringify(this.getStage(currentPlan?.nextPlannedStage?.stageObj))}</pre
|
|||||||
|
|
||||||
protected renderCurrentPlan({ currentPlan }: FlowInspection): TemplateResult {
|
protected renderCurrentPlan({ currentPlan }: FlowInspection): TemplateResult {
|
||||||
return html`<div class="pf-c-card">
|
return html`<div class="pf-c-card">
|
||||||
<fieldset>
|
<fieldset class="ak-c-fieldset">
|
||||||
<legend class="pf-c-card__title">${msg("Current plan context")}</legend>
|
<legend class="pf-c-card__title">${msg("Current plan context")}</legend>
|
||||||
<pre class="pf-c-card__body"><code>${stringify(
|
<pre class="pf-c-card__body"><code>${stringify(
|
||||||
currentPlan?.planContext,
|
currentPlan?.planContext,
|
||||||
@@ -259,7 +259,7 @@ ${stringify(this.getStage(currentPlan?.nextPlannedStage?.stageObj))}</pre
|
|||||||
|
|
||||||
protected renderSession({ currentPlan }: FlowInspection): TemplateResult {
|
protected renderSession({ currentPlan }: FlowInspection): TemplateResult {
|
||||||
return html`<div class="pf-c-card">
|
return html`<div class="pf-c-card">
|
||||||
<fieldset>
|
<fieldset class="ak-c-fieldset">
|
||||||
<legend class="pf-c-card__title">${msg("Session ID")}</legend>
|
<legend class="pf-c-card__title">${msg("Session ID")}</legend>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<code class="break"> ${currentPlan?.sessionId} </code>
|
<code class="break"> ${currentPlan?.sessionId} </code>
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ export class RedirectStage extends BaseStage<RedirectChallenge, FlowChallengeRes
|
|||||||
<p>${msg("You're about to be redirected to the following URL.")}</p>
|
<p>${msg("You're about to be redirected to the following URL.")}</p>
|
||||||
<code>${this.getURL()}</code>
|
<code>${this.getURL()}</code>
|
||||||
</div>
|
</div>
|
||||||
<fieldset class="pf-c-form__group pf-m-action">
|
<fieldset class="ak-c-fieldset pf-c-form__group pf-m-action">
|
||||||
<legend class="sr-only">${msg("Form actions")}</legend>
|
<legend class="sr-only">${msg("Form actions")}</legend>
|
||||||
<a
|
<a
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export class AccessDeniedStage extends BaseStage<
|
|||||||
: nothing}
|
: nothing}
|
||||||
</ak-empty-state>
|
</ak-empty-state>
|
||||||
${this.challenge?.flowInfo?.cancelUrl
|
${this.challenge?.flowInfo?.cancelUrl
|
||||||
? html`<fieldset class="pf-c-form__group pf-m-action">
|
? html`<fieldset class="ak-c-fieldset pf-c-form__group pf-m-action">
|
||||||
<legend class="sr-only">${msg("Form actions")}</legend>
|
<legend class="sr-only">${msg("Form actions")}</legend>
|
||||||
<a
|
<a
|
||||||
class="pf-c-button pf-m-primary pf-m-block"
|
class="pf-c-button pf-m-primary pf-m-block"
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export class AuthenticatorDuoStage extends BaseStage<
|
|||||||
</p>
|
</p>
|
||||||
<a href=${this.challenge.activationCode}>${msg("Duo activation")}</a>
|
<a href=${this.challenge.activationCode}>${msg("Duo activation")}</a>
|
||||||
|
|
||||||
<fieldset class="pf-c-form__group pf-m-action">
|
<fieldset class="ak-c-fieldset pf-c-form__group pf-m-action">
|
||||||
<legend class="sr-only">${msg("Form actions")}</legend>
|
<legend class="sr-only">${msg("Form actions")}</legend>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export class AuthenticatorEmailStage extends BaseStage<
|
|||||||
${AKFormErrors({ errors: this.challenge?.responseErrors?.email })}
|
${AKFormErrors({ errors: this.challenge?.responseErrors?.email })}
|
||||||
</div>
|
</div>
|
||||||
${this.renderNonFieldErrors()}
|
${this.renderNonFieldErrors()}
|
||||||
<fieldset class="pf-c-form__group pf-m-action">
|
<fieldset class="ak-c-fieldset pf-c-form__group pf-m-action">
|
||||||
<legend class="sr-only">${msg("Form actions")}</legend>
|
<legend class="sr-only">${msg("Form actions")}</legend>
|
||||||
<button
|
<button
|
||||||
name="continue"
|
name="continue"
|
||||||
@@ -120,7 +120,7 @@ export class AuthenticatorEmailStage extends BaseStage<
|
|||||||
${AKFormErrors({ errors: this.challenge.responseErrors?.code })}
|
${AKFormErrors({ errors: this.challenge.responseErrors?.code })}
|
||||||
</div>
|
</div>
|
||||||
${this.renderNonFieldErrors()}
|
${this.renderNonFieldErrors()}
|
||||||
<fieldset class="pf-c-form__group pf-m-action">
|
<fieldset class="ak-c-fieldset pf-c-form__group pf-m-action">
|
||||||
<legend class="sr-only">${msg("Form actions")}</legend>
|
<legend class="sr-only">${msg("Form actions")}</legend>
|
||||||
<button
|
<button
|
||||||
name="continue"
|
name="continue"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user