Merge branch 'rust-worker-2' into rust-proxy

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
Marc 'risson' Schmitt
2026-04-23 15:46:53 +02:00
389 changed files with 8729 additions and 4794 deletions

View File

@@ -37,7 +37,7 @@ runs:
sudo rsync -a --delete /tmp/empty/ /usr/local/lib/android/
- name: Install uv
if: ${{ contains(inputs.dependencies, 'python') }}
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v5
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v5
with:
enable-cache: true
- name: Setup python
@@ -64,12 +64,12 @@ runs:
rustflags: ""
- name: Setup rust dependencies
if: ${{ contains(inputs.dependencies, 'rust') }}
uses: taiki-e/install-action@5939f3337e40968c39aa70f5ecb1417a92fb25a0 # v2
uses: taiki-e/install-action@055f5df8c3f65ea01cd41e9dc855becd88953486 # v2
with:
tool: cargo-deny cargo-machete cargo-llvm-cov nextest
- name: Setup node (web)
if: ${{ contains(inputs.dependencies, 'node') }}
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v4
with:
node-version-file: "${{ inputs.working-directory }}web/package.json"
cache: "npm"
@@ -77,7 +77,7 @@ runs:
registry-url: "https://registry.npmjs.org"
- name: Setup node (root)
if: ${{ contains(inputs.dependencies, 'node') }}
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v4
with:
node-version-file: "${{ inputs.working-directory }}package.json"
cache: "npm"

View File

@@ -2,7 +2,7 @@ services:
postgresql:
image: docker.io/library/postgres:${PSQL_TAG:-16}
volumes:
- db-data:/var/lib/postgresql/data
- db-data:/var/lib/postgresql
command: "-c log_statement=all"
environment:
POSTGRES_USER: authentik

View File

@@ -66,6 +66,8 @@ updates:
default-days: 7
semver-major-days: 14
semver-patch-days: 3
exclude:
- rustls-webpki
- package-ecosystem: rust-toolchain
directory: "/"

View File

@@ -90,7 +90,7 @@ jobs:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: int128/docker-manifest-create-action@44422a4b046d55dc036df622039ed3aec43c613c # v2
- uses: int128/docker-manifest-create-action@3de37de96c4e900bc3eef9055d3c50abdb4f769d # v2
id: build
with:
tags: ${{ matrix.tag }}

View File

@@ -33,7 +33,7 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
node-version-file: website/package.json
cache: "npm"
@@ -71,7 +71,7 @@ jobs:
with:
name: api-docs
path: website/api/build
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
node-version-file: website/package.json
cache: "npm"

View File

@@ -24,7 +24,7 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- name: Setup authentik env
uses: ./.github/actions/setup
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
node-version-file: lifecycle/aws/package.json
cache: "npm"

View File

@@ -36,7 +36,7 @@ jobs:
NODE_ENV: production
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
node-version-file: website/package.json
cache: "npm"
@@ -53,7 +53,7 @@ jobs:
NODE_ENV: production
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
node-version-file: website/package.json
cache: "npm"

View File

@@ -127,7 +127,10 @@ jobs:
with:
postgresql_version: ${{ matrix.psql }}
- name: run migrations to stable
run: uv run python -m lifecycle.migrate
run: |
docker ps
docker logs setup-postgresql-1
uv run python -m lifecycle.migrate
- name: checkout current code
run: |
set -x

View File

@@ -145,7 +145,7 @@ jobs:
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: "go.mod"
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
node-version-file: web/package.json
cache: "npm"

View File

@@ -32,7 +32,7 @@ jobs:
project: web
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
node-version-file: ${{ matrix.project }}/package.json
cache: "npm"
@@ -47,7 +47,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
node-version-file: web/package.json
cache: "npm"
@@ -73,7 +73,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
node-version-file: web/package.json
cache: "npm"

View File

@@ -35,13 +35,13 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
fetch-depth: 2
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
node-version-file: ${{ matrix.package }}/package.json
registry-url: "https://registry.npmjs.org"
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # 24d32ffd492484c1d75e0c0b894501ddb9d30d62
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # 24d32ffd492484c1d75e0c0b894501ddb9d30d62
with:
files: |
${{ matrix.package }}/package.json

View File

@@ -87,7 +87,7 @@ jobs:
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: "go.mod"
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
node-version-file: web/package.json
cache: "npm"
@@ -151,7 +151,7 @@ jobs:
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
with:
go-version-file: "go.mod"
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v5
with:
node-version-file: web/package.json
cache: "npm"

91
Cargo.lock generated
View File

@@ -208,7 +208,7 @@ dependencies = [
"time",
"tokio",
"tokio-retry2",
"tokio-tungstenite 0.29.0",
"tokio-tungstenite",
"tracing",
"url",
"uuid",
@@ -307,9 +307,9 @@ dependencies = [
[[package]]
name = "aws-lc-rs"
version = "1.16.2"
version = "1.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc"
checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f"
dependencies = [
"aws-lc-fips-sys",
"aws-lc-sys",
@@ -319,9 +319,9 @@ dependencies = [
[[package]]
name = "aws-lc-sys"
version = "0.39.0"
version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a"
checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7"
dependencies = [
"cc",
"cmake",
@@ -331,9 +331,9 @@ dependencies = [
[[package]]
name = "axum"
version = "0.8.8"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
dependencies = [
"axum-core",
"axum-macros",
@@ -359,7 +359,7 @@ dependencies = [
"sha1",
"sync_wrapper",
"tokio",
"tokio-tungstenite 0.28.0",
"tokio-tungstenite",
"tower",
"tower-layer",
"tower-service",
@@ -387,9 +387,9 @@ dependencies = [
[[package]]
name = "axum-macros"
version = "0.5.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca"
dependencies = [
"proc-macro2",
"quote",
@@ -597,9 +597,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.6.0"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
dependencies = [
"clap_builder",
"clap_derive",
@@ -619,9 +619,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.6.0"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
dependencies = [
"heck",
"proc-macro2",
@@ -2176,7 +2176,7 @@ dependencies = [
"num-integer",
"num-iter",
"num-traits",
"rand 0.8.5",
"rand 0.8.6",
"smallvec",
"zeroize",
]
@@ -2776,9 +2776,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
version = "0.8.5"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
dependencies = [
"libc",
"rand_chacha 0.3.1",
@@ -3046,9 +3046,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.37"
version = "0.23.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21"
dependencies = [
"aws-lc-rs",
"log",
@@ -3111,9 +3111,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-webpki"
version = "0.103.12"
version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [
"aws-lc-rs",
"ring",
@@ -3630,7 +3630,7 @@ dependencies = [
"memchr",
"once_cell",
"percent-encoding",
"rand 0.8.5",
"rand 0.8.6",
"rsa",
"serde",
"sha1",
@@ -3671,7 +3671,7 @@ dependencies = [
"md-5",
"memchr",
"once_cell",
"rand 0.8.5",
"rand 0.8.6",
"serde",
"serde_json",
"sha2",
@@ -3919,9 +3919,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.51.1"
version = "1.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c"
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
dependencies = [
"bytes",
"libc",
@@ -3977,18 +3977,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-tungstenite"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857"
dependencies = [
"futures-util",
"log",
"tokio",
"tungstenite 0.28.0",
]
[[package]]
name = "tokio-tungstenite"
version = "0.29.0"
@@ -4001,7 +3989,7 @@ dependencies = [
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tungstenite 0.29.0",
"tungstenite",
"webpki-roots 0.26.11",
]
@@ -4204,23 +4192,6 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "tungstenite"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442"
dependencies = [
"bytes",
"data-encoding",
"http",
"httparse",
"log",
"rand 0.9.2",
"sha1",
"thiserror 2.0.18",
"utf-8",
]
[[package]]
name = "tungstenite"
version = "0.29.0"
@@ -4347,12 +4318,6 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf8-zero"
version = "0.8.1"
@@ -4373,9 +4338,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.23.0"
version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
dependencies = [
"getrandom 0.4.2",
"js-sys",

View File

@@ -22,9 +22,9 @@ publish = false
arc-swap = "= 1.9.1"
argh = "= 0.1.19"
axum-server = { version = "= 0.8.0", features = ["tls-rustls-no-provider"] }
aws-lc-rs = { version = "= 1.16.2", features = ["fips"] }
axum = { version = "= 0.8.8", features = ["http2", "macros", "ws"] }
clap = { version = "= 4.6.0", features = ["derive", "env"] }
aws-lc-rs = { version = "= 1.16.3", features = ["fips"] }
axum = { version = "= 0.8.9", features = ["http2", "macros", "ws"] }
clap = { version = "= 4.6.1", features = ["derive", "env"] }
client-ip = { version = "0.2.1", features = ["forwarded-header"] }
color-eyre = "= 0.6.5"
colored = "= 3.1.1"
@@ -66,7 +66,7 @@ reqwest-middleware = { version = "= 0.5.1", features = [
"query",
"rustls",
] }
rustls = { version = "= 0.23.37", features = ["fips"] }
rustls = { version = "= 0.23.38", features = ["fips"] }
sentry = { version = "= 0.47.0", default-features = false, features = [
"backtrace",
"contexts",
@@ -97,7 +97,7 @@ sqlx = { version = "= 0.8.6", default-features = false, features = [
tempfile = "= 3.27.0"
thiserror = "= 2.0.18"
time = { version = "= 0.3.47", features = ["macros"] }
tokio = { version = "= 1.51.1", features = ["full", "tracing"] }
tokio = { version = "= 1.52.1", features = ["full", "tracing"] }
tokio-retry2 = "= 0.9.1"
tokio-rustls = "= 0.26.4"
tokio-tungstenite = { version = "= 0.29.0", features = [
@@ -116,7 +116,7 @@ tracing-subscriber = { version = "= 0.3.23", features = [
"tracing-log",
] }
url = "= 2.5.8"
uuid = { version = "= 1.23.0", features = ["serde", "v4"] }
uuid = { version = "= 1.23.1", features = ["serde", "v4"] }
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" }

View File

@@ -1,7 +1,7 @@
from collections.abc import Generator, Iterator
from contextlib import contextmanager
from tempfile import SpooledTemporaryFile
from urllib.parse import urlsplit
from urllib.parse import urlsplit, urlunsplit
import boto3
from botocore.config import Config
@@ -164,16 +164,19 @@ class S3Backend(ManageableBackend):
)
def _file_url(name: str, request: HttpRequest | None) -> str:
client = self.client
params = {
"Bucket": self.bucket_name,
"Key": f"{self.base_path}/{name}",
}
url = self.client.generate_presigned_url(
"get_object",
Params=params,
ExpiresIn=expires_in,
HttpMethod="GET",
operation_name = "GetObject"
operation_model = client.meta.service_model.operation_model(operation_name)
request_dict = client._convert_to_request_dict(
params,
operation_model,
endpoint_url=client.meta.endpoint_url,
context={"is_presign_request": True},
)
# Support custom domain for S3-compatible storage (so not AWS)
@@ -183,9 +186,8 @@ class S3Backend(ManageableBackend):
CONFIG.get(f"storage.{self.name}.custom_domain", None),
)
if custom_domain:
parsed = urlsplit(url)
scheme = "https" if use_https else "http"
path = parsed.path
path = request_dict["url_path"]
# When using path-style addressing, the presigned URL contains the bucket
# name in the path (e.g., /bucket-name/key). Since custom_domain must
@@ -200,9 +202,22 @@ class S3Backend(ManageableBackend):
if not path.startswith("/"):
path = f"/{path}"
url = f"{scheme}://{custom_domain}{path}?{parsed.query}"
custom_base = urlsplit(f"{scheme}://{custom_domain}")
return url
# Sign the final public URL instead of signing the internal S3 endpoint and
# rewriting it afterwards. Presigned SigV4 URLs include the host header in the
# canonical request, so post-sign host changes break strict backends like RustFS.
public_path = f"{custom_base.path.rstrip('/')}{path}" if custom_base.path else path
request_dict["url_path"] = public_path
request_dict["url"] = urlunsplit(
(custom_base.scheme, custom_base.netloc, public_path, "", "")
)
return client._request_signer.generate_presigned_url(
request_dict,
operation_name,
expires_in=expires_in,
)
if use_cache:
return self._cache_get_or_set(name, request, _file_url, expires_in)

View File

@@ -1,4 +1,5 @@
from unittest import skipUnless
from urllib.parse import parse_qs, urlsplit
from botocore.exceptions import UnsupportedSignatureVersionError
from django.test import TestCase
@@ -168,6 +169,44 @@ class TestS3Backend(FileTestS3BackendMixin, TestCase):
f"URL: {url}",
)
@CONFIG.patch("storage.s3.secure_urls", False)
@CONFIG.patch("storage.s3.addressing_style", "path")
def test_file_url_custom_domain_resigns_for_custom_host(self):
"""Test presigned URLs are signed for the custom domain host.
Host-changing custom domains must produce a signature query string for
the public host, not reuse the internal endpoint signature.
"""
bucket_name = self.media_s3_bucket_name
key_name = "application-icons/test.svg"
custom_domain = f"files.example.test:8020/{bucket_name}"
endpoint_signed_url = self.media_s3_backend.client.generate_presigned_url(
"get_object",
Params={
"Bucket": bucket_name,
"Key": f"{self.media_s3_backend.base_path}/{key_name}",
},
ExpiresIn=900,
HttpMethod="GET",
)
with CONFIG.patch("storage.media.s3.custom_domain", custom_domain):
custom_url = self.media_s3_backend.file_url(key_name, use_cache=False)
endpoint_parts = urlsplit(endpoint_signed_url)
custom_parts = urlsplit(custom_url)
self.assertEqual(custom_parts.scheme, "http")
self.assertEqual(custom_parts.netloc, "files.example.test:8020")
self.assertEqual(parse_qs(custom_parts.query)["X-Amz-SignedHeaders"], ["host"])
self.assertNotEqual(
custom_parts.query,
endpoint_parts.query,
"Custom-domain URLs must be signed for the public host, not reuse the endpoint "
"signature query string.",
)
def test_themed_urls_without_theme_variable(self):
"""Test themed_urls returns None when filename has no %(theme)s"""
result = self.media_s3_backend.themed_urls("logo.png")

View File

@@ -4,7 +4,7 @@ from collections.abc import Iterator
from copy import copy
from django.core.cache import cache
from django.db.models import Case, QuerySet
from django.db.models import Case, Q, QuerySet
from django.db.models.expressions import When
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext as _
@@ -36,9 +36,13 @@ from authentik.rbac.filters import ObjectFilter
LOGGER = get_logger()
def user_app_cache_key(user_pk: str, page_number: int | None = None) -> str:
def user_app_cache_key(
user_pk: str, page_number: int | None = None, only_with_launch_url: bool = False
) -> str:
"""Cache key where application list for user is saved"""
key = f"{CACHE_PREFIX}app_access/{user_pk}"
if only_with_launch_url:
key += "/launch"
if page_number:
key += f"/{page_number}"
return key
@@ -274,11 +278,19 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
if superuser_full_list and request.user.is_superuser:
return super().list(request)
only_with_launch_url = str(
request.query_params.get("only_with_launch_url", "false")
).lower()
only_with_launch_url = (
str(request.query_params.get("only_with_launch_url", "false")).lower()
) == "true"
queryset = self._filter_queryset_for_list(self.get_queryset())
if only_with_launch_url:
# Pre-filter at DB level to skip expensive per-app policy evaluation
# for apps that can never appear in the launcher:
# - No meta_launch_url AND no provider: no possible launch URL
# - meta_launch_url="blank://blank": documented convention to hide from launcher
queryset = queryset.exclude(
Q(meta_launch_url="", provider__isnull=True) | Q(meta_launch_url="blank://blank")
)
paginator: Pagination = self.paginator
paginated_apps = paginator.paginate_queryset(queryset, request)
@@ -295,7 +307,6 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
except ValueError as exc:
raise ValidationError from exc
allowed_applications = self._get_allowed_applications(paginated_apps, user=for_user)
allowed_applications = self._expand_applications(allowed_applications)
serializer = self.get_serializer(allowed_applications, many=True)
return self.get_paginated_response(serializer.data)
@@ -305,19 +316,26 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
allowed_applications = self._get_allowed_applications(paginated_apps)
if should_cache:
allowed_applications = cache.get(
user_app_cache_key(self.request.user.pk, paginator.page.number)
user_app_cache_key(
self.request.user.pk, paginator.page.number, only_with_launch_url
)
)
if not allowed_applications:
if allowed_applications:
# Re-fetch cached applications since pickled instances lose prefetched
# relationships, causing N+1 queries during serialization
allowed_applications = self._expand_applications(allowed_applications)
else:
LOGGER.debug("Caching allowed application list", page=paginator.page.number)
allowed_applications = self._get_allowed_applications(paginated_apps)
cache.set(
user_app_cache_key(self.request.user.pk, paginator.page.number),
user_app_cache_key(
self.request.user.pk, paginator.page.number, only_with_launch_url
),
allowed_applications,
timeout=86400,
)
allowed_applications = self._expand_applications(allowed_applications)
if only_with_launch_url == "true":
if only_with_launch_url:
allowed_applications = self._filter_applications_with_launch_url(allowed_applications)
serializer = self.get_serializer(allowed_applications, many=True)

View File

@@ -790,9 +790,13 @@ class Application(SerializerModel, PolicyBindingModel):
def get_provider(self) -> Provider | None:
"""Get casted provider instance. Needs Application queryset with_provider"""
if hasattr(self, "_cached_provider"):
return self._cached_provider
if not self.provider:
self._cached_provider = None
return None
return get_deepest_child(self.provider)
self._cached_provider = get_deepest_child(self.provider)
return self._cached_provider
def backchannel_provider_for[T: Provider](self, provider_type: type[T], **kwargs) -> T | None:
"""Get Backchannel provider for a specific type"""

View File

@@ -138,13 +138,7 @@ class AgentConnectorController(BaseController[AgentConnector]):
"AllowDeviceIdentifiersInAttestation": True,
"AuthenticationMethod": "UserSecureEnclaveKey",
"EnableAuthorization": True,
"EnableCreateUserAtLogin": True,
"FileVaultPolicy": ["RequireAuthentication"],
"LoginPolicy": ["RequireAuthentication"],
"NewUserAuthorizationMode": "Standard",
"UnlockPolicy": ["RequireAuthentication"],
"UseSharedDeviceKeys": True,
"UserAuthorizationMode": "Standard",
},
},
],

View File

@@ -0,0 +1,54 @@
# Generated by Django 5.2.12 on 2026-03-06 14:38
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"authentik_endpoints_connectors_agent",
"0004_agentconnector_challenge_idle_timeout_and_more",
),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="AppleIndependentSecureEnclave",
fields=[
("created", models.DateTimeField(auto_now_add=True)),
("last_updated", models.DateTimeField(auto_now=True)),
(
"name",
models.CharField(
help_text="The human-readable name of this device.", max_length=64
),
),
(
"confirmed",
models.BooleanField(default=True, help_text="Is this device ready for use?"),
),
("last_used", models.DateTimeField(null=True)),
("uuid", models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
("apple_secure_enclave_key", models.TextField()),
("apple_enclave_key_id", models.TextField()),
("device_type", models.TextField()),
(
"user",
models.ForeignKey(
help_text="The user that this device belongs to.",
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "Apple Independent Secure Enclave",
"verbose_name_plural": "Apple Independent Secure Enclaves",
},
),
]

View File

@@ -19,6 +19,7 @@ from authentik.flows.stage import StageView
from authentik.lib.generators import generate_key
from authentik.lib.models import InternallyManagedMixin, SerializerModel
from authentik.lib.utils.time import timedelta_string_validator
from authentik.stages.authenticator.models import Device as Authenticator
if TYPE_CHECKING:
from authentik.endpoints.connectors.agent.controller import AgentConnectorController
@@ -172,3 +173,17 @@ class AppleNonce(InternallyManagedMixin, ExpiringModel):
class Meta(ExpiringModel.Meta):
verbose_name = _("Apple Nonce")
verbose_name_plural = _("Apple Nonces")
class AppleIndependentSecureEnclave(Authenticator):
"""A device-independent secure enclave key, used by Tap-to-login"""
uuid = models.UUIDField(primary_key=True, default=uuid4)
apple_secure_enclave_key = models.TextField()
apple_enclave_key_id = models.TextField()
device_type = models.TextField()
class Meta:
verbose_name = _("Apple Independent Secure Enclave")
verbose_name_plural = _("Apple Independent Secure Enclaves")

View File

@@ -0,0 +1,28 @@
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer
from authentik.endpoints.connectors.agent.models import AppleIndependentSecureEnclave
class AppleIndependentSecureEnclaveSerializer(ModelSerializer):
class Meta:
model = AppleIndependentSecureEnclave
fields = [
"uuid",
"user",
"apple_secure_enclave_key",
"apple_enclave_key_id",
"device_type",
]
class AppleIndependentSecureEnclaveViewSet(UsedByMixin, ModelViewSet):
queryset = AppleIndependentSecureEnclave.objects.all()
serializer_class = AppleIndependentSecureEnclaveSerializer
search_fields = [
"name",
"user__name",
]
ordering = ["uuid"]
filterset_fields = ["user", "apple_enclave_key_id"]

View File

@@ -11,6 +11,7 @@ from authentik.endpoints.connectors.agent.models import (
AgentConnector,
AgentDeviceConnection,
AgentDeviceUserBinding,
AppleIndependentSecureEnclave,
AppleNonce,
DeviceToken,
EnrollmentToken,
@@ -25,7 +26,7 @@ class TestAppleToken(TestCase):
def setUp(self):
self.apple_sign_key = create_test_cert(PrivateKeyAlg.ECDSA)
sign_key_pem = self.apple_sign_key.public_key.public_bytes(
self.sign_key_pem = self.apple_sign_key.public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
).decode()
@@ -50,7 +51,7 @@ class TestAppleToken(TestCase):
device=self.device,
connector=self.connector,
apple_sign_key_id=self.apple_sign_key.kid,
apple_signing_key=sign_key_pem,
apple_signing_key=self.sign_key_pem,
apple_encryption_key=self.enc_pub,
)
self.user = create_test_user()
@@ -59,7 +60,7 @@ class TestAppleToken(TestCase):
user=self.user,
order=0,
apple_enclave_key_id=self.apple_sign_key.kid,
apple_secure_enclave_key=sign_key_pem,
apple_secure_enclave_key=self.sign_key_pem,
)
self.device_token = DeviceToken.objects.create(device=self.connection)
@@ -113,3 +114,62 @@ class TestAppleToken(TestCase):
).first()
self.assertIsNotNone(event)
self.assertEqual(event.context["device"]["name"], self.device.name)
@reconcile_app("authentik_crypto")
def test_token_independent(self):
nonce = generate_id()
AgentDeviceUserBinding.objects.all().delete()
AppleIndependentSecureEnclave.objects.create(
user=self.user,
apple_enclave_key_id=self.apple_sign_key.kid,
apple_secure_enclave_key=self.sign_key_pem,
)
AppleNonce.objects.create(
device_token=self.device_token,
nonce=nonce,
)
embedded = encode(
{"iss": str(self.connector.pk), "aud": str(self.device.pk), "request_nonce": nonce},
self.apple_sign_key.private_key,
headers={
"kid": self.apple_sign_key.kid,
},
algorithm=JWTAlgorithms.from_private_key(self.apple_sign_key.private_key),
)
assertion = encode(
{
"iss": str(self.connector.pk),
"aud": "http://testserver/endpoints/agent/psso/token/",
"request_nonce": nonce,
"assertion": embedded,
"jwe_crypto": {
"apv": (
"AAAABUFwcGxlAAAAQQTFgZOospN6KbkhXhx1lfa-AKYxjEfJhTJrkpdEY_srMmkPzS7VN0Bzt2AtNBEXE"
"aphDONiP2Mq6Oxytv5JKOxHAAAAJDgyOThERkY5LTVFMUUtNEUwMS04OEUwLUI3QkQzOUM4QjA3Qw"
)
},
},
self.apple_sign_key.private_key,
headers={
"kid": self.apple_sign_key.kid,
},
algorithm=JWTAlgorithms.from_private_key(self.apple_sign_key.private_key),
)
res = self.client.post(
reverse("authentik_enterprise_endpoints_connectors_agent:psso-token"),
data={
"assertion": assertion,
"platform_sso_version": "1.0",
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
},
)
self.assertEqual(res.status_code, 200)
event = Event.objects.filter(
action=EventAction.LOGIN,
app="authentik.endpoints.connectors.agent",
).first()
self.assertIsNotNone(event)
self.assertEqual(event.context["device"]["name"], self.device.name)

View File

@@ -1,5 +1,8 @@
from django.urls import path
from authentik.enterprise.endpoints.connectors.agent.api.secure_enclave import (
AppleIndependentSecureEnclaveViewSet,
)
from authentik.enterprise.endpoints.connectors.agent.views.apple_jwks import AppleJWKSView
from authentik.enterprise.endpoints.connectors.agent.views.apple_nonce import NonceView
from authentik.enterprise.endpoints.connectors.agent.views.apple_register import (
@@ -23,6 +26,7 @@ urlpatterns = [
]
api_urlpatterns = [
("endpoints/agents/psso/ise", AppleIndependentSecureEnclaveViewSet),
path(
"endpoints/agents/psso/register/device/",
RegisterDeviceView.as_view(),

View File

@@ -19,6 +19,7 @@ from authentik.endpoints.connectors.agent.models import (
AgentConnector,
AgentDeviceConnection,
AgentDeviceUserBinding,
AppleIndependentSecureEnclave,
AppleNonce,
DeviceAuthenticationToken,
)
@@ -103,7 +104,9 @@ class TokenView(View):
nonce.delete()
return decoded
def validate_embedded_assertion(self, assertion: str) -> tuple[AgentDeviceUserBinding, dict]:
def validate_embedded_assertion(
self, assertion: str
) -> tuple[AgentDeviceUserBinding | AppleIndependentSecureEnclave, dict]:
"""Decode an embedded assertion and validate it by looking up the matching device user"""
decode_unvalidated = get_unverified_header(assertion)
expected_kid = decode_unvalidated["kid"]
@@ -112,8 +115,13 @@ class TokenView(View):
target=self.device_connection.device, apple_enclave_key_id=expected_kid
).first()
if not device_user:
LOGGER.warning("Could not find device user binding for user")
raise ValidationError("Invalid request")
independent_user = AppleIndependentSecureEnclave.objects.filter(
apple_enclave_key_id=expected_kid
).first()
if not independent_user:
LOGGER.warning("Could not find device user binding or independent enclave for user")
raise ValidationError("Invalid request")
device_user = independent_user
decoded: dict[str, Any] = decode(
assertion,
device_user.apple_secure_enclave_key,

View File

@@ -11,6 +11,10 @@ class FlowNonApplicableException(SentryIgnoredException):
policy_result: PolicyResult | None = None
def __init__(self, policy_result: PolicyResult | None = None, *args):
super().__init__(*args)
self.policy_result = policy_result
@property
def messages(self) -> str:
"""Get messages from policy result, fallback to generic reason"""

View File

@@ -42,6 +42,7 @@ class Migration(migrations.Migration):
("require_superuser", "Require Superuser"),
("require_redirect", "Require Redirect"),
("require_outpost", "Require Outpost"),
("require_token", "Require Token"),
],
default="none",
help_text="Required level of authentication and authorization to access a flow.",

View File

@@ -40,6 +40,7 @@ class FlowAuthenticationRequirement(models.TextChoices):
REQUIRE_SUPERUSER = "require_superuser"
REQUIRE_REDIRECT = "require_redirect"
REQUIRE_OUTPOST = "require_outpost"
REQUIRE_TOKEN = "require_token"
class NotConfiguredAction(models.TextChoices):

View File

@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Any
from django.core.cache import cache
from django.http import HttpRequest, HttpResponse
from django.utils.translation import gettext as _
from sentry_sdk import start_span
from sentry_sdk.tracing import Span
from structlog.stdlib import BoundLogger, get_logger
@@ -26,6 +27,7 @@ from authentik.lib.config import CONFIG
from authentik.lib.utils.urls import redirect_with_qs
from authentik.outposts.models import Outpost
from authentik.policies.engine import PolicyEngine
from authentik.policies.types import PolicyResult
from authentik.root.middleware import ClientIPMiddleware
if TYPE_CHECKING:
@@ -226,6 +228,15 @@ class FlowPlanner:
and context.get(PLAN_CONTEXT_IS_REDIRECTED) is None
):
raise FlowNonApplicableException()
if (
self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_TOKEN
and context.get(PLAN_CONTEXT_IS_RESTORED) is None
):
raise FlowNonApplicableException(
PolicyResult(
False, _("This link is invalid or has expired. Please request a new one.")
)
)
outpost_user = ClientIPMiddleware.get_outpost_user(request)
if self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_OUTPOST:
if not outpost_user:
@@ -273,9 +284,7 @@ class FlowPlanner:
engine.build()
result = engine.result
if not result.passing:
exc = FlowNonApplicableException()
exc.policy_result = result
raise exc
raise FlowNonApplicableException(result)
# User is passing so far, check if we have a cached plan
cached_plan_key = cache_key(self.flow, user)
cached_plan = cache.get(cached_plan_key, None)

View File

@@ -1,5 +1,6 @@
"""flow views tests"""
from datetime import timedelta
from unittest.mock import MagicMock, PropertyMock, patch
from urllib.parse import urlencode
@@ -7,6 +8,7 @@ from django.http import HttpRequest, HttpResponse
from django.test import override_settings
from django.test.client import RequestFactory
from django.urls import reverse
from django.utils.timezone import now
from rest_framework.exceptions import ParseError
from authentik.core.models import Group, User
@@ -17,6 +19,7 @@ from authentik.flows.models import (
FlowDeniedAction,
FlowDesignation,
FlowStageBinding,
FlowToken,
InvalidResponseAction,
)
from authentik.flows.planner import FlowPlan, FlowPlanner
@@ -24,6 +27,7 @@ from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, StageVie
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import (
NEXT_ARG_NAME,
QS_KEY_TOKEN,
QS_QUERY,
SESSION_KEY_PLAN,
FlowExecutorView,
@@ -740,3 +744,77 @@ class TestFlowExecutor(FlowTestCase):
"title": flow.title,
},
)
@patch(
"authentik.flows.views.executor.to_stage_response",
TO_STAGE_RESPONSE_MOCK,
)
def test_expired_flow_token(self):
"""Test that an expired flow token shows an appropriate error message"""
flow = create_test_flow(
FlowDesignation.RECOVERY,
authentication=FlowAuthenticationRequirement.REQUIRE_TOKEN,
)
user = create_test_user()
plan = FlowPlan(flow_pk=flow.pk.hex, bindings=[], markers=[])
token = FlowToken.objects.create(
user=user,
identifier=generate_id(),
flow=flow,
_plan=FlowToken.pickle(plan),
expires=now() - timedelta(hours=1),
)
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug})
response = self.client.get(
url + f"?{urlencode({QS_QUERY: urlencode({QS_KEY_TOKEN: token.key})})}"
)
self.assertStageResponse(
response,
flow,
component="ak-stage-access-denied",
error_message="This link is invalid or has expired. Please request a new one.",
)
@patch(
"authentik.flows.views.executor.to_stage_response",
TO_STAGE_RESPONSE_MOCK,
)
def test_invalid_flow_token_require_token(self):
"""Test that an invalid/nonexistent token on a REQUIRE_TOKEN flow shows error"""
flow = create_test_flow(
FlowDesignation.RECOVERY,
authentication=FlowAuthenticationRequirement.REQUIRE_TOKEN,
)
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug})
response = self.client.get(
url + f"?{urlencode({QS_QUERY: urlencode({QS_KEY_TOKEN: 'invalid-token'})})}"
)
self.assertStageResponse(
response,
flow,
component="ak-stage-access-denied",
error_message="This link is invalid or has expired. Please request a new one.",
)
@patch(
"authentik.flows.views.executor.to_stage_response",
TO_STAGE_RESPONSE_MOCK,
)
def test_no_token_require_token(self):
"""Test that accessing a REQUIRE_TOKEN flow without any token shows error"""
flow = create_test_flow(
FlowDesignation.RECOVERY,
authentication=FlowAuthenticationRequirement.REQUIRE_TOKEN,
)
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug})
response = self.client.get(url)
self.assertStageResponse(
response,
flow,
component="ak-stage-access-denied",
error_message="This link is invalid or has expired. Please request a new one.",
)

View File

@@ -26,6 +26,7 @@ from authentik.flows.models import (
)
from authentik.flows.planner import (
PLAN_CONTEXT_IS_REDIRECTED,
PLAN_CONTEXT_IS_RESTORED,
PLAN_CONTEXT_PENDING_USER,
FlowPlanner,
cache_key,
@@ -129,6 +130,22 @@ class TestFlowPlanner(TestCase):
planner.allow_empty_flows = True
planner.plan(request)
def test_authentication_require_token(self):
"""Test flow authentication (require_token)"""
flow = create_test_flow()
flow.authentication = FlowAuthenticationRequirement.REQUIRE_TOKEN
request = self.request_factory.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
planner = FlowPlanner(flow)
planner.allow_empty_flows = True
with self.assertRaises(FlowNonApplicableException):
planner.plan(request)
context = {PLAN_CONTEXT_IS_RESTORED: True}
planner.plan(request, context)
@patch(
"authentik.policies.engine.PolicyEngine.result",
POLICY_RETURN_FALSE,

View File

@@ -62,6 +62,7 @@ from authentik.policies.engine import PolicyEngine
LOGGER = get_logger()
# Argument used to redirect user after login
NEXT_ARG_NAME = "next"
SESSION_KEY_PLAN = "authentik/flows/plan"
SESSION_KEY_GET = "authentik/flows/get"
SESSION_KEY_POST = "authentik/flows/post"

View File

@@ -14,7 +14,16 @@ def chunked_queryset[T: Model](queryset: QuerySet[T], chunk_size: int = 1_000) -
def get_chunks(qs: QuerySet) -> Generator[QuerySet[T]]:
qs = qs.order_by("pk")
pks = qs.values_list("pk", flat=True)
start_pk = pks[0]
# The outer queryset.exists() guard can race with a concurrent
# transaction that deletes the last matching row (or with a
# different isolation-level snapshot), so by the time this
# generator starts iterating the queryset may be empty and
# pks[0] would raise IndexError and crash the caller. Using
# .first() returns None on an empty queryset, which we bail
# out on cleanly. See goauthentik/authentik#21643.
start_pk = pks.first()
if start_pk is None:
return
while True:
try:
end_pk = pks.filter(pk__gte=start_pk)[chunk_size]

View File

@@ -141,26 +141,6 @@ class TestAuthorize(OAuthTestCase):
OAuthAuthorizationParams.from_request(request)
self.assertEqual(cm.exception.cause, "redirect_uri_forbidden_scheme")
def test_invalid_redirect_uri_empty(self):
"""test missing/invalid redirect URI"""
provider = OAuth2Provider.objects.create(
name=generate_id(),
client_id="test",
authorization_flow=create_test_flow(),
redirect_uris=[],
)
request = self.factory.get(
"/",
data={
"response_type": "code",
"client_id": "test",
"redirect_uri": "+",
},
)
OAuthAuthorizationParams.from_request(request)
provider.refresh_from_db()
self.assertEqual(provider.redirect_uris, [RedirectURI(RedirectURIMatchingMode.STRICT, "+")])
def test_invalid_redirect_uri_regex(self):
"""test missing/invalid redirect URI"""
OAuth2Provider.objects.create(

View File

@@ -6,10 +6,11 @@ from urllib.parse import quote
from django.urls import reverse
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_flow
from authentik.lib.generators import generate_id
from authentik.providers.oauth2.models import OAuth2Provider
from authentik.providers.oauth2.models import DeviceToken, OAuth2Provider, ScopeMapping
from authentik.providers.oauth2.tests.utils import OAuthTestCase
@@ -110,3 +111,57 @@ class TesOAuth2DeviceBackchannel(OAuthTestCase):
self.assertEqual(res.status_code, 200)
body = loads(res.content.decode())
self.assertEqual(body["expires_in"], 60)
@apply_blueprint("system/providers-oauth2.yaml")
def test_backchannel_scopes(self):
"""Test backchannel"""
self.provider.property_mappings.set(
ScopeMapping.objects.filter(
managed__in=[
"goauthentik.io/providers/oauth2/scope-openid",
"goauthentik.io/providers/oauth2/scope-email",
"goauthentik.io/providers/oauth2/scope-profile",
]
)
)
creds = b64encode(f"{self.provider.client_id}:".encode()).decode()
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
HTTP_AUTHORIZATION=f"Basic {creds}",
data={"scope": "openid email"},
)
self.assertEqual(res.status_code, 200)
body = loads(res.content.decode())
self.assertEqual(body["expires_in"], 60)
token = DeviceToken.objects.filter(device_code=body["device_code"]).first()
self.assertIsNotNone(token)
self.assertEqual(len(token.scope), 2)
self.assertIn("openid", token.scope)
self.assertIn("email", token.scope)
@apply_blueprint("system/providers-oauth2.yaml")
def test_backchannel_scopes_extra(self):
"""Test backchannel"""
self.provider.property_mappings.set(
ScopeMapping.objects.filter(
managed__in=[
"goauthentik.io/providers/oauth2/scope-openid",
"goauthentik.io/providers/oauth2/scope-email",
"goauthentik.io/providers/oauth2/scope-profile",
]
)
)
creds = b64encode(f"{self.provider.client_id}:".encode()).decode()
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
HTTP_AUTHORIZATION=f"Basic {creds}",
data={"scope": "openid email foo"},
)
self.assertEqual(res.status_code, 200)
body = loads(res.content.decode())
self.assertEqual(body["expires_in"], 60)
token = DeviceToken.objects.filter(device_code=body["device_code"]).first()
self.assertIsNotNone(token)
self.assertEqual(len(token.scope), 2)
self.assertIn("openid", token.scope)
self.assertIn("email", token.scope)

View File

@@ -59,9 +59,7 @@ from authentik.providers.oauth2.models import (
AuthorizationCode,
GrantTypes,
OAuth2Provider,
RedirectURI,
RedirectURIMatchingMode,
RedirectURIType,
ResponseMode,
ResponseTypes,
ScopeMapping,
@@ -197,18 +195,6 @@ class OAuthAuthorizationParams:
LOGGER.warning("Missing redirect uri.")
raise RedirectUriError("", allowed_redirect_urls).with_cause("redirect_uri_missing")
if len(allowed_redirect_urls) < 1:
LOGGER.info("Setting redirect for blank redirect_uris", redirect=self.redirect_uri)
self.provider.redirect_uris = [
RedirectURI(
RedirectURIMatchingMode.STRICT,
self.redirect_uri,
RedirectURIType.AUTHORIZATION,
)
]
self.provider.save()
allowed_redirect_urls = self.provider.authorization_redirect_uris
match_found = False
for allowed in allowed_redirect_urls:
if allowed.matching_mode == RedirectURIMatchingMode.STRICT:

View File

@@ -15,7 +15,7 @@ from authentik.core.models import Application
from authentik.lib.config import CONFIG
from authentik.lib.utils.time import timedelta_from_string
from authentik.providers.oauth2.errors import DeviceCodeError
from authentik.providers.oauth2.models import DeviceToken, OAuth2Provider
from authentik.providers.oauth2.models import DeviceToken, OAuth2Provider, ScopeMapping
from authentik.providers.oauth2.utils import TokenResponse, extract_client_auth
from authentik.providers.oauth2.views.device_init import QS_KEY_CODE
@@ -28,7 +28,7 @@ class DeviceView(View):
client_id: str
provider: OAuth2Provider
scopes: list[str] = []
scopes: set[str] = []
def parse_request(self):
"""Parse incoming request"""
@@ -44,7 +44,21 @@ class DeviceView(View):
raise DeviceCodeError("invalid_client") from None
self.provider = provider
self.client_id = client_id
self.scopes = self.request.POST.get("scope", "").split(" ")
scopes_to_check = set(self.request.POST.get("scope", "").split())
default_scope_names = set(
ScopeMapping.objects.filter(provider__in=[self.provider]).values_list(
"scope_name", flat=True
)
)
self.scopes = scopes_to_check
if not scopes_to_check.issubset(default_scope_names):
LOGGER.info(
"Application requested scopes not configured, setting to overlap",
scope_allowed=default_scope_names,
scope_given=self.scopes,
)
self.scopes = self.scopes.intersection(default_scope_names)
def dispatch(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
throttle = AnonRateThrottle()

View File

@@ -12,7 +12,13 @@ from django.db import connection, models
from django.templatetags.static import static
from django.utils.translation import gettext_lazy as _
from ldap3 import ALL, NONE, RANDOM, Connection, Server, ServerPool, Tls
from ldap3.core.exceptions import LDAPException, LDAPInsufficientAccessRightsResult, LDAPSchemaError
from ldap3.core.exceptions import (
LDAPAdminLimitExceededResult,
LDAPAttributeError,
LDAPException,
LDAPInsufficientAccessRightsResult,
LDAPSchemaError,
)
from rest_framework.serializers import Serializer
from structlog.stdlib import get_logger
@@ -278,10 +284,17 @@ class LDAPSource(IncomingSyncSource):
successful = conn.bind()
if successful:
return conn
except (LDAPSchemaError, LDAPInsufficientAccessRightsResult) as exc:
# Schema error, so try connecting without schema info
except (
LDAPSchemaError,
LDAPInsufficientAccessRightsResult,
LDAPAdminLimitExceededResult,
LDAPAttributeError,
) as exc:
# Schema error or rate limit during schema fetch, retry without schema info
# See https://github.com/goauthentik/authentik/issues/4590
# See also https://github.com/goauthentik/authentik/issues/3399
# LDAPAdminLimitExceededResult: Google Secure LDAP rate-limits schema queries
# LDAPAttributeError: Google Secure LDAP returns unsupported attrs in schema
if server_kwargs.get("get_info", ALL) == NONE:
LOGGER.warning("Failed to connect after schema downgrade", source=self, exc=exc)
raise exc

View File

@@ -17,7 +17,7 @@ from authentik.core.api.sources import SourceSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer
from authentik.lib.utils.http import get_http_session
from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.models import OAuthSource, PKCEMethod
from authentik.sources.oauth.types.registry import SourceType, registry
@@ -83,13 +83,24 @@ class OAuthSourceSerializer(SourceSerializer):
"authorization_url": "authorization_endpoint",
"access_token_url": "token_endpoint",
"profile_url": "userinfo_endpoint",
"pkce": "code_challenge_methods_supported",
}
for ak_key, oidc_key in field_map.items():
# Don't overwrite user-set values
if ak_key in attrs and attrs[ak_key]:
continue
attrs[ak_key] = config.get(oidc_key, "")
# code_challenge_methods_supported is a list per RFC 8414, not a
# single method. Pick one (prefer S256, the RFC-recommended method)
# rather than letting the list round-trip into the pkce TextField
# and later str() into the authorize URL as "['plain', 'S256']".
if not attrs.get("pkce"):
supported_methods = config.get("code_challenge_methods_supported") or []
attrs["pkce"] = PKCEMethod.NONE
if isinstance(supported_methods, list):
if PKCEMethod.S256 in supported_methods:
attrs["pkce"] = PKCEMethod.S256
elif PKCEMethod.PLAIN in supported_methods:
attrs["pkce"] = PKCEMethod.PLAIN
inferred_oidc_jwks_url = config.get("jwks_uri", "")
# Prefer user-entered URL to inferred URL to default URL

View File

@@ -79,6 +79,7 @@ class TestOAuthSource(APITestCase):
"token_endpoint": "http://mock/oauth/token",
"userinfo_endpoint": "http://mock/oauth/userinfo",
"jwks_uri": "http://mock/oauth/discovery/keys",
"code_challenge_methods_supported": ["S256"],
}
jwks_config = {"keys": []}
with Mocker() as mocker:
@@ -109,6 +110,7 @@ class TestOAuthSource(APITestCase):
serializer.validated_data["oidc_jwks_url"], "http://mock/oauth/discovery/keys"
)
self.assertEqual(serializer.validated_data["oidc_jwks"], jwks_config)
self.assertEqual(serializer.validated_data["pkce"], PKCEMethod.S256)
def test_api_validate_openid_connect_invalid(self):
"""Test API validation (with OIDC endpoints)"""

View File

@@ -36,6 +36,14 @@ class UserWriteStageView(StageView):
super().__init__(executor, **kwargs)
self.disallowed_user_attributes = [
"groups",
# Block attribute writes that would otherwise land on the model's
# primary key. An IdP that returns an `id` claim (mocksaml is one
# example) used to crash the enrollment flow with
# ValueError: Field 'id' expected a number but got '<hex>'
# because hasattr(user, "id") is true and setattr(user, "id", ...)
# was taken unchecked. See #21580.
"id",
"pk",
]
@staticmethod

View File

@@ -315,6 +315,34 @@ class TestUserWriteStage(FlowTestCase):
component="ak-stage-access-denied",
)
def test_user_update_ignores_id_from_idp(self):
"""IdP-supplied `id`/`pk` attributes must not land on the model
primary key and crash user save (#21580)."""
existing = User.objects.create(username="unittest", email="test@goauthentik.io")
original_pk = existing.pk
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = existing
plan.context[PLAN_CONTEXT_PROMPT] = {
"username": "idp-user",
# Hex string from a SAML IdP; would previously crash with
# ValueError: Field 'id' expected a number but got '<hex>'.
"id": "1dda9fb491dc01bd24d2423ba2f22ae561f56ddf2376b29a11c80281d21201f9",
"pk": "also-not-an-int",
}
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertEqual(response.status_code, 200)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
user = User.objects.get(username="idp-user")
self.assertEqual(user.pk, original_pk)
def test_write_attribute(self):
"""Test write_attribute"""
user = create_test_admin_user()

View File

@@ -5644,6 +5644,7 @@
"authentik_endpoints_connectors_agent.add_agentconnector",
"authentik_endpoints_connectors_agent.add_agentdeviceconnection",
"authentik_endpoints_connectors_agent.add_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.add_appleindependentsecureenclave",
"authentik_endpoints_connectors_agent.add_applenonce",
"authentik_endpoints_connectors_agent.add_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.add_devicetoken",
@@ -5651,6 +5652,7 @@
"authentik_endpoints_connectors_agent.change_agentconnector",
"authentik_endpoints_connectors_agent.change_agentdeviceconnection",
"authentik_endpoints_connectors_agent.change_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.change_appleindependentsecureenclave",
"authentik_endpoints_connectors_agent.change_applenonce",
"authentik_endpoints_connectors_agent.change_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.change_devicetoken",
@@ -5658,6 +5660,7 @@
"authentik_endpoints_connectors_agent.delete_agentconnector",
"authentik_endpoints_connectors_agent.delete_agentdeviceconnection",
"authentik_endpoints_connectors_agent.delete_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.delete_appleindependentsecureenclave",
"authentik_endpoints_connectors_agent.delete_applenonce",
"authentik_endpoints_connectors_agent.delete_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.delete_devicetoken",
@@ -5665,6 +5668,7 @@
"authentik_endpoints_connectors_agent.view_agentconnector",
"authentik_endpoints_connectors_agent.view_agentdeviceconnection",
"authentik_endpoints_connectors_agent.view_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.view_appleindependentsecureenclave",
"authentik_endpoints_connectors_agent.view_applenonce",
"authentik_endpoints_connectors_agent.view_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.view_devicetoken",
@@ -8426,7 +8430,8 @@
"require_unauthenticated",
"require_superuser",
"require_redirect",
"require_outpost"
"require_outpost",
"require_token"
],
"title": "Authentication",
"description": "Required level of authentication and authorization to access a flow."
@@ -11319,6 +11324,7 @@
"authentik_endpoints_connectors_agent.add_agentconnector",
"authentik_endpoints_connectors_agent.add_agentdeviceconnection",
"authentik_endpoints_connectors_agent.add_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.add_appleindependentsecureenclave",
"authentik_endpoints_connectors_agent.add_applenonce",
"authentik_endpoints_connectors_agent.add_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.add_devicetoken",
@@ -11326,6 +11332,7 @@
"authentik_endpoints_connectors_agent.change_agentconnector",
"authentik_endpoints_connectors_agent.change_agentdeviceconnection",
"authentik_endpoints_connectors_agent.change_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.change_appleindependentsecureenclave",
"authentik_endpoints_connectors_agent.change_applenonce",
"authentik_endpoints_connectors_agent.change_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.change_devicetoken",
@@ -11333,6 +11340,7 @@
"authentik_endpoints_connectors_agent.delete_agentconnector",
"authentik_endpoints_connectors_agent.delete_agentdeviceconnection",
"authentik_endpoints_connectors_agent.delete_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.delete_appleindependentsecureenclave",
"authentik_endpoints_connectors_agent.delete_applenonce",
"authentik_endpoints_connectors_agent.delete_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.delete_devicetoken",
@@ -11340,6 +11348,7 @@
"authentik_endpoints_connectors_agent.view_agentconnector",
"authentik_endpoints_connectors_agent.view_agentdeviceconnection",
"authentik_endpoints_connectors_agent.view_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.view_appleindependentsecureenclave",
"authentik_endpoints_connectors_agent.view_applenonce",
"authentik_endpoints_connectors_agent.view_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.view_devicetoken",

36
go.mod
View File

@@ -7,10 +7,10 @@ require (
beryju.io/radius-eap v0.1.0
github.com/avast/retry-go/v4 v4.7.0
github.com/coreos/go-oidc/v3 v3.18.0
github.com/getsentry/sentry-go v0.44.1
github.com/getsentry/sentry-go v0.45.1
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-ldap/ldap/v3 v3.4.13
github.com/go-openapi/runtime v0.29.3
github.com/go-openapi/runtime v0.29.4
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.2
@@ -19,11 +19,11 @@ require (
github.com/gorilla/sessions v1.4.0
github.com/gorilla/websocket v1.5.3
github.com/grafana/pyroscope-go v1.2.8
github.com/jackc/pgx/v5 v5.9.1
github.com/jackc/pgx/v5 v5.9.2
github.com/jellydator/ttlcache/v3 v3.4.0
github.com/mitchellh/mapstructure v1.5.0
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
github.com/pires/go-proxyproto v0.11.0
github.com/pires/go-proxyproto v0.12.0
github.com/prometheus/client_golang v1.23.2
github.com/sethvargo/go-envconfig v1.3.0
github.com/sirupsen/logrus v1.9.4
@@ -51,21 +51,21 @@ require (
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.24.3 // indirect
github.com/go-openapi/analysis v0.25.0 // indirect
github.com/go-openapi/errors v0.22.7 // indirect
github.com/go-openapi/jsonpointer v0.22.5 // indirect
github.com/go-openapi/jsonreference v0.21.5 // indirect
github.com/go-openapi/loads v0.23.3 // indirect
github.com/go-openapi/spec v0.22.4 // indirect
github.com/go-openapi/strfmt v0.26.0 // indirect
github.com/go-openapi/swag/conv v0.25.5 // indirect
github.com/go-openapi/swag/fileutils v0.25.5 // indirect
github.com/go-openapi/strfmt v0.26.1 // indirect
github.com/go-openapi/swag/conv v0.26.0 // indirect
github.com/go-openapi/swag/fileutils v0.26.0 // indirect
github.com/go-openapi/swag/jsonname v0.25.5 // indirect
github.com/go-openapi/swag/jsonutils v0.25.5 // indirect
github.com/go-openapi/swag/jsonutils v0.26.0 // indirect
github.com/go-openapi/swag/loading v0.25.5 // indirect
github.com/go-openapi/swag/mangling v0.25.5 // indirect
github.com/go-openapi/swag/stringutils v0.25.5 // indirect
github.com/go-openapi/swag/typeutils v0.25.5 // indirect
github.com/go-openapi/swag/stringutils v0.26.0 // indirect
github.com/go-openapi/swag/typeutils v0.26.0 // indirect
github.com/go-openapi/swag/yamlutils v0.25.5 // indirect
github.com/go-openapi/validate v0.25.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
@@ -85,15 +85,15 @@ require (
github.com/prometheus/procfs v0.16.1 // indirect
github.com/spf13/pflag v1.0.9 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.41.0 // indirect
go.opentelemetry.io/otel/metric v1.41.0 // indirect
go.opentelemetry.io/otel/trace v1.41.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

88
go.sum
View File

@@ -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/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/getsentry/sentry-go v0.44.1 h1:/cPtrA5qB7uMRrhgSn9TYtcEF36auGP3Y6+ThvD/yaI=
github.com/getsentry/sentry-go v0.44.1/go.mod h1:XDotiNZbgf5U8bPDUAfvcFmOnMQQceESxyKaObSssW0=
github.com/getsentry/sentry-go v0.45.1 h1:9rfzJtGiJG+MGIaWZXidDGHcH5GU1Z5y0WVJGf9nysw=
github.com/getsentry/sentry-go v0.45.1/go.mod h1:XDotiNZbgf5U8bPDUAfvcFmOnMQQceESxyKaObSssW0=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
@@ -41,8 +41,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/analysis v0.24.3 h1:a1hrvMr8X0Xt69KP5uVTu5jH62DscmDifrLzNglAayk=
github.com/go-openapi/analysis v0.24.3/go.mod h1:Nc+dWJ/FxZbhSow5Yh3ozg5CLJioB+XXT6MdLvJUsUw=
github.com/go-openapi/analysis v0.25.0 h1:EnjAq1yO8wEO9HbPmY8vLPEIkdZuuFhCAKBPvCB7bCs=
github.com/go-openapi/analysis v0.25.0/go.mod h1:5WFTRE43WLkPG9r9OtlMfqkkvUTYLVVCIxLlEpyF8kE=
github.com/go-openapi/errors v0.22.7 h1:JLFBGC0Apwdzw3484MmBqspjPbwa2SHvpDm0u5aGhUA=
github.com/go-openapi/errors v0.22.7/go.mod h1://QW6SD9OsWtH6gHllUCddOXDL0tk0ZGNYHwsw4sW3w=
github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
@@ -51,36 +51,36 @@ github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe
github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw=
github.com/go-openapi/loads v0.23.3 h1:g5Xap1JfwKkUnZdn+S0L3SzBDpcTIYzZ5Qaag0YDkKQ=
github.com/go-openapi/loads v0.23.3/go.mod h1:NOH07zLajXo8y55hom0omlHWDVVvCwBM/S+csCK8LqA=
github.com/go-openapi/runtime v0.29.3 h1:h5twGaEqxtQg40ePiYm9vFFH1q06Czd7Ot6ufdK0w/Y=
github.com/go-openapi/runtime v0.29.3/go.mod h1:8A1W0/L5eyNJvKciqZtvIVQvYO66NlB7INMSZ9bw/oI=
github.com/go-openapi/runtime v0.29.4 h1:k2lDxrGoSAJRdhFG2tONKMpkizY/4X1cciSdtzk4Jjo=
github.com/go-openapi/runtime v0.29.4/go.mod h1:K0k/2raY6oqXJnZAgWJB2i/12QKrhUKpZcH4PfV9P18=
github.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ=
github.com/go-openapi/spec v0.22.4/go.mod h1:WQ6Ai0VPWMZgMT4XySjlRIE6GP1bGQOtEThn3gcWLtQ=
github.com/go-openapi/strfmt v0.26.0 h1:SDdQLyOEqu8W96rO1FRG1fuCtVyzmukky0zcD6gMGLU=
github.com/go-openapi/strfmt v0.26.0/go.mod h1:Zslk5VZPOISLwmWTMBIS7oiVFem1o1EI6zULY8Uer7Y=
github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g=
github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k=
github.com/go-openapi/swag/fileutils v0.25.5 h1:B6JTdOcs2c0dBIs9HnkyTW+5gC+8NIhVBUwERkFhMWk=
github.com/go-openapi/swag/fileutils v0.25.5/go.mod h1:V3cT9UdMQIaH4WiTrUc9EPtVA4txS0TOmRURmhGF4kc=
github.com/go-openapi/strfmt v0.26.1 h1:7zGCHji7zSYDC2tCXIusoxYQz/48jAf2q+sF6wXTG+c=
github.com/go-openapi/strfmt v0.26.1/go.mod h1:Zslk5VZPOISLwmWTMBIS7oiVFem1o1EI6zULY8Uer7Y=
github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I=
github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE=
github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU=
github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc=
github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo=
github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5 h1:SX6sE4FrGb4sEnnxbFL/25yZBb5Hcg1inLeErd86Y1U=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5/go.mod h1:/2KvOTrKWjVA5Xli3DZWdMCZDzz3uV/T7bXwrKWPquo=
github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA=
github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y=
github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU=
github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g=
github.com/go-openapi/swag/mangling v0.25.5 h1:hyrnvbQRS7vKePQPHHDso+k6CGn5ZBs5232UqWZmJZw=
github.com/go-openapi/swag/mangling v0.25.5/go.mod h1:6hadXM/o312N/h98RwByLg088U61TPGiltQn71Iw0NY=
github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M=
github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII=
github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E=
github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc=
github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg=
github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE=
github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4=
github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE=
github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=
github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=
github.com/go-openapi/testify/enable/yaml/v2 v2.4.1 h1:NZOrZmIb6PTv5LTFxr5/mKV/FjbUzGE7E6gLz7vFoOQ=
github.com/go-openapi/testify/enable/yaml/v2 v2.4.1/go.mod h1:r7dwsujEHawapMsxA69i+XMGZrQ5tRauhLAjV/sxg3Q=
github.com/go-openapi/testify/v2 v2.4.1 h1:zB34HDKj4tHwyUQHrUkpV0Q0iXQ6dUCOQtIqn8hE6Iw=
github.com/go-openapi/testify/v2 v2.4.1/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
github.com/go-openapi/testify/enable/yaml/v2 v2.4.2 h1:5zRca5jw7lzVREKCZVNBpysDNBjj74rBh0N2BGQbSR0=
github.com/go-openapi/testify/enable/yaml/v2 v2.4.2/go.mod h1:XVevPw5hUXuV+5AkI1u1PeAm27EQVrhXTTCPAF85LmE=
github.com/go-openapi/testify/v2 v2.4.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLuq0ePx4=
github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
github.com/go-openapi/validate v0.25.2 h1:12NsfLAwGegqbGWr2CnvT65X/Q2USJipmJ9b7xDJZz0=
github.com/go-openapi/validate v0.25.2/go.mod h1:Pgl1LpPPGFnZ+ys4/hTlDiRYQdI1ocKypgE+8Q8BLfY=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
@@ -117,8 +117,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
@@ -159,8 +159,8 @@ github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNs
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -199,14 +199,14 @@ github.com/wwt/guac v1.3.2/go.mod h1:eKm+NrnK7A88l4UBEcYNpZQGMpZRryYKoz4D/0/n1C0
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=
go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE=
go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=
go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps=
go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8=
go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90=
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
@@ -216,8 +216,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab h1:628ME69lBm9C6JY2wXhAph/yjN3jezx1z7BIDLUwxjo=
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -227,8 +227,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -245,8 +245,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -258,8 +258,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

View File

@@ -11,6 +11,7 @@ import (
"os"
"os/signal"
"runtime"
"sync"
"syscall"
"time"
@@ -45,6 +46,7 @@ type APIController struct {
reloadOffset time.Duration
eventConn *websocket.Conn
eventConnMu sync.Mutex
lastWsReconnect time.Time
wsIsReconnecting bool
eventHandlers []EventHandler

View File

@@ -77,7 +77,12 @@ func (ac *APIController) initEvent(outpostUUID string, attempt int) error {
Instruction: EventKindHello,
Args: ac.getEventPingArgs(),
}
// Serialize this write against concurrent SendEventHello callers (health
// ticker, RAC handlers) sharing the same *websocket.Conn. Gorilla's Conn
// does not permit concurrent writes.
ac.eventConnMu.Lock()
err = ws.WriteJSON(msg)
ac.eventConnMu.Unlock()
if err != nil {
ac.logger.WithField("logger", "authentik.outpost.events").WithError(err).Warning("Failed to hello to authentik")
return err
@@ -91,7 +96,9 @@ func (ac *APIController) initEvent(outpostUUID string, attempt int) error {
func (ac *APIController) Shutdown() {
// Cleanly close the connection by sending a close message and then
// waiting (with timeout) for the server to close the connection.
ac.eventConnMu.Lock()
err := ac.eventConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
ac.eventConnMu.Unlock()
if err != nil {
ac.logger.WithError(err).Warning("failed to write close message")
return
@@ -252,6 +259,10 @@ func (a *APIController) SendEventHello(args map[string]any) error {
Instruction: EventKindHello,
Args: allArgs,
}
// Gorilla *websocket.Conn does not permit concurrent writes. This method
// is invoked from the health ticker and from RAC session handlers.
a.eventConnMu.Lock()
err := a.eventConn.WriteJSON(aliveMsg)
a.eventConnMu.Unlock()
return err
}

View File

@@ -9,7 +9,7 @@
"version": "0.0.0",
"license": "MIT",
"devDependencies": {
"aws-cdk": "^2.1118.0",
"aws-cdk": "^2.1118.2",
"cross-env": "^10.1.0"
},
"engines": {
@@ -25,9 +25,9 @@
"license": "MIT"
},
"node_modules/aws-cdk": {
"version": "2.1118.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1118.0.tgz",
"integrity": "sha512-Tfd865GRewDTXIbTVtix/l+v8t3rZENvdHcQQZS2wXYVXfHzljULFXe9JKkgZUNDPB1zo9tSBUu8jjiHRm7nWg==",
"version": "2.1118.2",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1118.2.tgz",
"integrity": "sha512-jHuShSx0JI14enDz2Hk2Qe0LYTDPzLyF2nBhWCvoXyRCpz31sI3XsCh4KO5ZXKfw9ET0bHvDTVnMZQPBpswg8A==",
"dev": true,
"license": "Apache-2.0",
"bin": {

View File

@@ -7,7 +7,7 @@
"aws-cfn": "cross-env CI=false cdk synth --version-reporting=false > template.yaml"
},
"devDependencies": {
"aws-cdk": "^2.1118.0",
"aws-cdk": "^2.1118.2",
"cross-env": "^10.1.0"
},
"engines": {

View File

@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
# Stage: Build webui
FROM --platform=${BUILDPLATFORM} docker.io/library/node:24-trixie-slim@sha256:9707cd4542f400df5078df04f9652a272429112f15202d22b5b8bdd148df494f AS node-builder
FROM --platform=${BUILDPLATFORM} docker.io/library/node:24-trixie-slim@sha256:735dd688da64d22ebd9dd374b3e7e5a874635668fd2a6ec20ca1f99264294086 AS node-builder
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
@@ -29,7 +29,7 @@ RUN npm run build && \
npm run build:sfe
# Stage: Build go proxy
FROM docker.io/library/golang:1.26.2-trixie@sha256:c0074c718b473f3827043f86532c4c0ff537e3fe7a81b8219b0d1ccfcc2c9a09 AS go-builder
FROM docker.io/library/golang:1.26.2-trixie@sha256:982ae929f9a74083a242c6e25d19d7d9ed78c6e97fab639a119e90707ba819e2 AS go-builder
ARG TARGETARCH
ARG TARGETVARIANT

View File

@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
# Stage 1: Build
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.2-trixie@sha256:c0074c718b473f3827043f86532c4c0ff537e3fe7a81b8219b0d1ccfcc2c9a09 AS builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.2-trixie@sha256:982ae929f9a74083a242c6e25d19d7d9ed78c6e97fab639a119e90707ba819e2 AS builder
ARG TARGETOS
ARG TARGETARCH

View File

@@ -21,7 +21,7 @@ COPY web .
RUN npm run build-proxy
# Stage 2: Build
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.2-trixie@sha256:c0074c718b473f3827043f86532c4c0ff537e3fe7a81b8219b0d1ccfcc2c9a09 AS builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.2-trixie@sha256:982ae929f9a74083a242c6e25d19d7d9ed78c6e97fab639a119e90707ba819e2 AS builder
ARG TARGETOS
ARG TARGETARCH

View File

@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
# Stage 1: Build
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.2-trixie@sha256:c0074c718b473f3827043f86532c4c0ff537e3fe7a81b8219b0d1ccfcc2c9a09 AS builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.2-trixie@sha256:982ae929f9a74083a242c6e25d19d7d9ed78c6e97fab639a119e90707ba819e2 AS builder
ARG TARGETOS
ARG TARGETARCH

View File

@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
# Stage 1: Build
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.2-trixie@sha256:c0074c718b473f3827043f86532c4c0ff537e3fe7a81b8219b0d1ccfcc2c9a09 AS builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.2-trixie@sha256:982ae929f9a74083a242c6e25d19d7d9ed78c6e97fab639a119e90707ba819e2 AS builder
ARG TARGETOS
ARG TARGETARCH

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-04-08 00:28+0000\n"
"POT-Creation-Date: 2026-04-23 00:25+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -732,6 +732,14 @@ msgstr ""
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Independent Secure Enclave"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Independent Secure Enclaves"
msgstr ""
#: authentik/endpoints/facts.py
msgid "Operating System name, such as 'Server 2022' or 'Ubuntu'"
msgstr ""
@@ -1571,6 +1579,10 @@ msgstr ""
msgid "Flow Tokens"
msgstr ""
#: authentik/flows/planner.py
msgid "This link is invalid or has expired. Please request a new one."
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr ""

View File

@@ -4,3 +4,4 @@ Yubi
Yubikey
Yubikeys
mycorp
mocksaml

View File

@@ -19,18 +19,19 @@ impl ServerConfig {
.host
.clone()
.ok_or_else(|| eyre!("environment variable `AUTHENTIK_HOST` not set"))?;
let host = if host.ends_with('/') {
host
} else {
format!("{host}/")
};
let host = host.parse()?;
let mut host: Url = host.parse()?;
let token = config::get()
.token
.clone()
.ok_or_else(|| eyre!("environment variable `AUTHENTIK_TOKEN` not set"))?;
let insecure = config::get().insecure.unwrap_or(false);
if !host.path().ends_with('/') {
host.path_segments_mut()
.map_err(|()| eyre!("URL cannot be a base"))?
.push("");
}
Ok(Self {
host,
token,
@@ -43,7 +44,7 @@ impl ServerConfig {
pub fn make_config() -> Result<Configuration> {
let server_config = ServerConfig::new()?;
let base_path = format!("{}api/v3", server_config.host);
let base_path = server_config.host.join("api/v3")?.into();
let client = reqwest::ClientBuilder::new()
.tls_danger_accept_invalid_hostnames(server_config.insecure)
@@ -95,3 +96,135 @@ where
Ok(results)
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::{ServerConfig, make_config};
use crate::config;
#[test]
fn server_config_no_trailing_slash() {
config::init().expect("failed to init config");
config::set(json!({
"host": "http://localhost:9000",
"token": "token",
}))
.expect("failed to set config");
let server_config = ServerConfig::new().expect("failed to create server config");
assert_eq!(server_config.host.as_str(), "http://localhost:9000/");
}
#[test]
fn server_config_with_trailing_slash() {
config::init().expect("failed to init config");
config::set(json!({
"host": "http://localhost:9000/",
"token": "token",
}))
.expect("failed to set config");
let server_config = ServerConfig::new().expect("failed to create server config");
assert_eq!(server_config.host.as_str(), "http://localhost:9000/");
}
#[test]
fn server_config_with_path_no_trailing_slash() {
config::init().expect("failed to init config");
config::set(json!({
"host": "http://localhost:9000/authentik",
"token": "token",
}))
.expect("failed to set config");
let server_config = ServerConfig::new().expect("failed to create server config");
assert_eq!(
server_config.host.as_str(),
"http://localhost:9000/authentik/"
);
}
#[test]
fn server_config_with_path_and_trailing_slash() {
config::init().expect("failed to init config");
config::set(json!({
"host": "http://localhost:9000/authentik/",
"token": "token",
}))
.expect("failed to set config");
let server_config = ServerConfig::new().expect("failed to create server config");
assert_eq!(
server_config.host.as_str(),
"http://localhost:9000/authentik/"
);
}
#[test]
fn make_config_no_trailing_slash() {
config::init().expect("failed to init config");
config::set(json!({
"host": "http://localhost:9000",
"token": "token",
}))
.expect("failed to set config");
let api_config = make_config().expect("failed to make config");
assert_eq!(api_config.base_path, "http://localhost:9000/api/v3");
}
#[test]
fn make_config_with_trailing_slash() {
config::init().expect("failed to init config");
config::set(json!({
"host": "http://localhost:9000/",
"token": "token",
}))
.expect("failed to set config");
let api_config = make_config().expect("failed to make config");
assert_eq!(api_config.base_path, "http://localhost:9000/api/v3");
}
#[test]
fn make_config_with_path_no_trailing_slash() {
config::init().expect("failed to init config");
config::set(json!({
"host": "http://localhost:9000/authentik",
"token": "token",
}))
.expect("failed to set config");
let api_config = make_config().expect("failed to make config");
assert_eq!(
api_config.base_path,
"http://localhost:9000/authentik/api/v3"
);
}
#[test]
fn make_config_with_path_and_trailing_slash() {
config::init().expect("failed to init config");
config::set(json!({
"host": "http://localhost:9000/authentik/",
"token": "token",
}))
.expect("failed to set config");
let api_config = make_config().expect("failed to make config");
assert_eq!(
api_config.base_path,
"http://localhost:9000/authentik/api/v3"
);
}
}

View File

@@ -400,7 +400,7 @@ mod tests {
let arbiter = tasks.arbiter();
let mut events_rx = arbiter.events_subscribe();
super::run(&mut tasks).expect("failed to start watcher");
super::start(&mut tasks).expect("failed to start watcher");
assert_eq!(super::get().secret_key, "my_secret_key");
assert_eq!(super::get().postgresql.password, "my_postgres_pass");

View File

@@ -433,6 +433,11 @@ func (c *APIClient) prepareRequest(
// Walk through any authentication.
// AccessToken Authentication
if auth, ok := ctx.Value(ContextAccessToken).(string); ok {
localVarRequest.Header.Add("Authorization", "Bearer "+auth)
}
}
for header, value := range c.cfg.DefaultHeader {

View File

@@ -29,6 +29,9 @@ func (c contextKey) String() string {
}
var (
// ContextAccessToken takes a string oauth2 access token as authentication for the request.
ContextAccessToken = contextKey("accesstoken")
// ContextServerIndex uses a server configuration from the index.
ContextServerIndex = contextKey("serverIndex")

View File

@@ -21,6 +21,8 @@ import type {
AgentPSSODeviceRegistrationResponse,
AgentPSSOUserRegistrationRequest,
AgentTokenResponse,
AppleIndependentSecureEnclave,
AppleIndependentSecureEnclaveRequest,
Connector,
DeviceAccessGroup,
DeviceAccessGroupRequest,
@@ -41,6 +43,7 @@ import type {
MDMConfigRequest,
MDMConfigResponse,
PaginatedAgentConnectorList,
PaginatedAppleIndependentSecureEnclaveList,
PaginatedConnectorList,
PaginatedDeviceAccessGroupList,
PaginatedDeviceUserBindingList,
@@ -49,6 +52,7 @@ import type {
PaginatedFleetConnectorList,
PaginatedGoogleChromeConnectorList,
PatchedAgentConnectorRequest,
PatchedAppleIndependentSecureEnclaveRequest,
PatchedDeviceAccessGroupRequest,
PatchedDeviceUserBindingRequest,
PatchedEndpointDeviceRequest,
@@ -69,6 +73,8 @@ import {
AgentPSSODeviceRegistrationResponseFromJSON,
AgentPSSOUserRegistrationRequestToJSON,
AgentTokenResponseFromJSON,
AppleIndependentSecureEnclaveFromJSON,
AppleIndependentSecureEnclaveRequestToJSON,
ConnectorFromJSON,
DeviceAccessGroupFromJSON,
DeviceAccessGroupRequestToJSON,
@@ -89,6 +95,7 @@ import {
MDMConfigRequestToJSON,
MDMConfigResponseFromJSON,
PaginatedAgentConnectorListFromJSON,
PaginatedAppleIndependentSecureEnclaveListFromJSON,
PaginatedConnectorListFromJSON,
PaginatedDeviceAccessGroupListFromJSON,
PaginatedDeviceUserBindingListFromJSON,
@@ -97,6 +104,7 @@ import {
PaginatedFleetConnectorListFromJSON,
PaginatedGoogleChromeConnectorListFromJSON,
PatchedAgentConnectorRequestToJSON,
PatchedAppleIndependentSecureEnclaveRequestToJSON,
PatchedDeviceAccessGroupRequestToJSON,
PatchedDeviceUserBindingRequestToJSON,
PatchedEndpointDeviceRequestToJSON,
@@ -201,6 +209,41 @@ export interface EndpointsAgentsEnrollmentTokensViewKeyRetrieveRequest {
tokenUuid: string;
}
export interface EndpointsAgentsPssoIseCreateRequest {
appleIndependentSecureEnclaveRequest: AppleIndependentSecureEnclaveRequest;
}
export interface EndpointsAgentsPssoIseDestroyRequest {
uuid: string;
}
export interface EndpointsAgentsPssoIseListRequest {
appleEnclaveKeyId?: string;
ordering?: string;
page?: number;
pageSize?: number;
search?: string;
user?: number;
}
export interface EndpointsAgentsPssoIsePartialUpdateRequest {
uuid: string;
patchedAppleIndependentSecureEnclaveRequest?: PatchedAppleIndependentSecureEnclaveRequest;
}
export interface EndpointsAgentsPssoIseRetrieveRequest {
uuid: string;
}
export interface EndpointsAgentsPssoIseUpdateRequest {
uuid: string;
appleIndependentSecureEnclaveRequest: AppleIndependentSecureEnclaveRequest;
}
export interface EndpointsAgentsPssoIseUsedByListRequest {
uuid: string;
}
export interface EndpointsAgentsPssoRegisterDeviceCreateRequest {
agentPSSODeviceRegistrationRequest: AgentPSSODeviceRegistrationRequest;
}
@@ -1822,6 +1865,515 @@ export class EndpointsApi extends runtime.BaseAPI {
return await response.value();
}
/**
* Creates request options for endpointsAgentsPssoIseCreate without sending the request
*/
async endpointsAgentsPssoIseCreateRequestOpts(
requestParameters: EndpointsAgentsPssoIseCreateRequest,
): Promise<runtime.RequestOpts> {
if (requestParameters["appleIndependentSecureEnclaveRequest"] == null) {
throw new runtime.RequiredError(
"appleIndependentSecureEnclaveRequest",
'Required parameter "appleIndependentSecureEnclaveRequest" was null or undefined when calling endpointsAgentsPssoIseCreate().',
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters["Content-Type"] = "application/json";
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("authentik", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/endpoints/agents/psso/ise/`;
return {
path: urlPath,
method: "POST",
headers: headerParameters,
query: queryParameters,
body: AppleIndependentSecureEnclaveRequestToJSON(
requestParameters["appleIndependentSecureEnclaveRequest"],
),
};
}
/**
* Mixin to add a used_by endpoint to return a list of all objects using this object
*/
async endpointsAgentsPssoIseCreateRaw(
requestParameters: EndpointsAgentsPssoIseCreateRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<runtime.ApiResponse<AppleIndependentSecureEnclave>> {
const requestOptions =
await this.endpointsAgentsPssoIseCreateRequestOpts(requestParameters);
const response = await this.request(requestOptions, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) =>
AppleIndependentSecureEnclaveFromJSON(jsonValue),
);
}
/**
* Mixin to add a used_by endpoint to return a list of all objects using this object
*/
async endpointsAgentsPssoIseCreate(
requestParameters: EndpointsAgentsPssoIseCreateRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<AppleIndependentSecureEnclave> {
const response = await this.endpointsAgentsPssoIseCreateRaw(
requestParameters,
initOverrides,
);
return await response.value();
}
/**
* Creates request options for endpointsAgentsPssoIseDestroy without sending the request
*/
async endpointsAgentsPssoIseDestroyRequestOpts(
requestParameters: EndpointsAgentsPssoIseDestroyRequest,
): Promise<runtime.RequestOpts> {
if (requestParameters["uuid"] == null) {
throw new runtime.RequiredError(
"uuid",
'Required parameter "uuid" was null or undefined when calling endpointsAgentsPssoIseDestroy().',
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("authentik", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/endpoints/agents/psso/ise/{uuid}/`;
urlPath = urlPath.replace(
`{${"uuid"}}`,
encodeURIComponent(String(requestParameters["uuid"])),
);
return {
path: urlPath,
method: "DELETE",
headers: headerParameters,
query: queryParameters,
};
}
/**
* Mixin to add a used_by endpoint to return a list of all objects using this object
*/
async endpointsAgentsPssoIseDestroyRaw(
requestParameters: EndpointsAgentsPssoIseDestroyRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<runtime.ApiResponse<void>> {
const requestOptions =
await this.endpointsAgentsPssoIseDestroyRequestOpts(requestParameters);
const response = await this.request(requestOptions, initOverrides);
return new runtime.VoidApiResponse(response);
}
/**
* Mixin to add a used_by endpoint to return a list of all objects using this object
*/
async endpointsAgentsPssoIseDestroy(
requestParameters: EndpointsAgentsPssoIseDestroyRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<void> {
await this.endpointsAgentsPssoIseDestroyRaw(requestParameters, initOverrides);
}
/**
* Creates request options for endpointsAgentsPssoIseList without sending the request
*/
async endpointsAgentsPssoIseListRequestOpts(
requestParameters: EndpointsAgentsPssoIseListRequest,
): Promise<runtime.RequestOpts> {
const queryParameters: any = {};
if (requestParameters["appleEnclaveKeyId"] != null) {
queryParameters["apple_enclave_key_id"] = requestParameters["appleEnclaveKeyId"];
}
if (requestParameters["ordering"] != null) {
queryParameters["ordering"] = requestParameters["ordering"];
}
if (requestParameters["page"] != null) {
queryParameters["page"] = requestParameters["page"];
}
if (requestParameters["pageSize"] != null) {
queryParameters["page_size"] = requestParameters["pageSize"];
}
if (requestParameters["search"] != null) {
queryParameters["search"] = requestParameters["search"];
}
if (requestParameters["user"] != null) {
queryParameters["user"] = requestParameters["user"];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("authentik", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/endpoints/agents/psso/ise/`;
return {
path: urlPath,
method: "GET",
headers: headerParameters,
query: queryParameters,
};
}
/**
* Mixin to add a used_by endpoint to return a list of all objects using this object
*/
async endpointsAgentsPssoIseListRaw(
requestParameters: EndpointsAgentsPssoIseListRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<runtime.ApiResponse<PaginatedAppleIndependentSecureEnclaveList>> {
const requestOptions = await this.endpointsAgentsPssoIseListRequestOpts(requestParameters);
const response = await this.request(requestOptions, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) =>
PaginatedAppleIndependentSecureEnclaveListFromJSON(jsonValue),
);
}
/**
* Mixin to add a used_by endpoint to return a list of all objects using this object
*/
async endpointsAgentsPssoIseList(
requestParameters: EndpointsAgentsPssoIseListRequest = {},
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<PaginatedAppleIndependentSecureEnclaveList> {
const response = await this.endpointsAgentsPssoIseListRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* Creates request options for endpointsAgentsPssoIsePartialUpdate without sending the request
*/
async endpointsAgentsPssoIsePartialUpdateRequestOpts(
requestParameters: EndpointsAgentsPssoIsePartialUpdateRequest,
): Promise<runtime.RequestOpts> {
if (requestParameters["uuid"] == null) {
throw new runtime.RequiredError(
"uuid",
'Required parameter "uuid" was null or undefined when calling endpointsAgentsPssoIsePartialUpdate().',
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters["Content-Type"] = "application/json";
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("authentik", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/endpoints/agents/psso/ise/{uuid}/`;
urlPath = urlPath.replace(
`{${"uuid"}}`,
encodeURIComponent(String(requestParameters["uuid"])),
);
return {
path: urlPath,
method: "PATCH",
headers: headerParameters,
query: queryParameters,
body: PatchedAppleIndependentSecureEnclaveRequestToJSON(
requestParameters["patchedAppleIndependentSecureEnclaveRequest"],
),
};
}
/**
* Mixin to add a used_by endpoint to return a list of all objects using this object
*/
async endpointsAgentsPssoIsePartialUpdateRaw(
requestParameters: EndpointsAgentsPssoIsePartialUpdateRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<runtime.ApiResponse<AppleIndependentSecureEnclave>> {
const requestOptions =
await this.endpointsAgentsPssoIsePartialUpdateRequestOpts(requestParameters);
const response = await this.request(requestOptions, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) =>
AppleIndependentSecureEnclaveFromJSON(jsonValue),
);
}
/**
* Mixin to add a used_by endpoint to return a list of all objects using this object
*/
async endpointsAgentsPssoIsePartialUpdate(
requestParameters: EndpointsAgentsPssoIsePartialUpdateRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<AppleIndependentSecureEnclave> {
const response = await this.endpointsAgentsPssoIsePartialUpdateRaw(
requestParameters,
initOverrides,
);
return await response.value();
}
/**
* Creates request options for endpointsAgentsPssoIseRetrieve without sending the request
*/
async endpointsAgentsPssoIseRetrieveRequestOpts(
requestParameters: EndpointsAgentsPssoIseRetrieveRequest,
): Promise<runtime.RequestOpts> {
if (requestParameters["uuid"] == null) {
throw new runtime.RequiredError(
"uuid",
'Required parameter "uuid" was null or undefined when calling endpointsAgentsPssoIseRetrieve().',
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("authentik", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/endpoints/agents/psso/ise/{uuid}/`;
urlPath = urlPath.replace(
`{${"uuid"}}`,
encodeURIComponent(String(requestParameters["uuid"])),
);
return {
path: urlPath,
method: "GET",
headers: headerParameters,
query: queryParameters,
};
}
/**
* Mixin to add a used_by endpoint to return a list of all objects using this object
*/
async endpointsAgentsPssoIseRetrieveRaw(
requestParameters: EndpointsAgentsPssoIseRetrieveRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<runtime.ApiResponse<AppleIndependentSecureEnclave>> {
const requestOptions =
await this.endpointsAgentsPssoIseRetrieveRequestOpts(requestParameters);
const response = await this.request(requestOptions, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) =>
AppleIndependentSecureEnclaveFromJSON(jsonValue),
);
}
/**
* Mixin to add a used_by endpoint to return a list of all objects using this object
*/
async endpointsAgentsPssoIseRetrieve(
requestParameters: EndpointsAgentsPssoIseRetrieveRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<AppleIndependentSecureEnclave> {
const response = await this.endpointsAgentsPssoIseRetrieveRaw(
requestParameters,
initOverrides,
);
return await response.value();
}
/**
* Creates request options for endpointsAgentsPssoIseUpdate without sending the request
*/
async endpointsAgentsPssoIseUpdateRequestOpts(
requestParameters: EndpointsAgentsPssoIseUpdateRequest,
): Promise<runtime.RequestOpts> {
if (requestParameters["uuid"] == null) {
throw new runtime.RequiredError(
"uuid",
'Required parameter "uuid" was null or undefined when calling endpointsAgentsPssoIseUpdate().',
);
}
if (requestParameters["appleIndependentSecureEnclaveRequest"] == null) {
throw new runtime.RequiredError(
"appleIndependentSecureEnclaveRequest",
'Required parameter "appleIndependentSecureEnclaveRequest" was null or undefined when calling endpointsAgentsPssoIseUpdate().',
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters["Content-Type"] = "application/json";
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("authentik", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/endpoints/agents/psso/ise/{uuid}/`;
urlPath = urlPath.replace(
`{${"uuid"}}`,
encodeURIComponent(String(requestParameters["uuid"])),
);
return {
path: urlPath,
method: "PUT",
headers: headerParameters,
query: queryParameters,
body: AppleIndependentSecureEnclaveRequestToJSON(
requestParameters["appleIndependentSecureEnclaveRequest"],
),
};
}
/**
* Mixin to add a used_by endpoint to return a list of all objects using this object
*/
async endpointsAgentsPssoIseUpdateRaw(
requestParameters: EndpointsAgentsPssoIseUpdateRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<runtime.ApiResponse<AppleIndependentSecureEnclave>> {
const requestOptions =
await this.endpointsAgentsPssoIseUpdateRequestOpts(requestParameters);
const response = await this.request(requestOptions, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) =>
AppleIndependentSecureEnclaveFromJSON(jsonValue),
);
}
/**
* Mixin to add a used_by endpoint to return a list of all objects using this object
*/
async endpointsAgentsPssoIseUpdate(
requestParameters: EndpointsAgentsPssoIseUpdateRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<AppleIndependentSecureEnclave> {
const response = await this.endpointsAgentsPssoIseUpdateRaw(
requestParameters,
initOverrides,
);
return await response.value();
}
/**
* Creates request options for endpointsAgentsPssoIseUsedByList without sending the request
*/
async endpointsAgentsPssoIseUsedByListRequestOpts(
requestParameters: EndpointsAgentsPssoIseUsedByListRequest,
): Promise<runtime.RequestOpts> {
if (requestParameters["uuid"] == null) {
throw new runtime.RequiredError(
"uuid",
'Required parameter "uuid" was null or undefined when calling endpointsAgentsPssoIseUsedByList().',
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("authentik", []);
if (tokenString) {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
let urlPath = `/endpoints/agents/psso/ise/{uuid}/used_by/`;
urlPath = urlPath.replace(
`{${"uuid"}}`,
encodeURIComponent(String(requestParameters["uuid"])),
);
return {
path: urlPath,
method: "GET",
headers: headerParameters,
query: queryParameters,
};
}
/**
* Get a list of all objects that use this object
*/
async endpointsAgentsPssoIseUsedByListRaw(
requestParameters: EndpointsAgentsPssoIseUsedByListRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<runtime.ApiResponse<Array<UsedBy>>> {
const requestOptions =
await this.endpointsAgentsPssoIseUsedByListRequestOpts(requestParameters);
const response = await this.request(requestOptions, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(UsedByFromJSON));
}
/**
* Get a list of all objects that use this object
*/
async endpointsAgentsPssoIseUsedByList(
requestParameters: EndpointsAgentsPssoIseUsedByListRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<Array<UsedBy>> {
const response = await this.endpointsAgentsPssoIseUsedByListRaw(
requestParameters,
initOverrides,
);
return await response.value();
}
/**
* Creates request options for endpointsAgentsPssoRegisterDeviceCreate without sending the request
*/

View File

@@ -0,0 +1,106 @@
/* tslint:disable */
/* eslint-disable */
/**
* authentik
* Making authentication simple.
*
* The version of the OpenAPI document: 2026.5.0-rc1
* Contact: hello@goauthentik.io
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
*
* @export
* @interface AppleIndependentSecureEnclave
*/
export interface AppleIndependentSecureEnclave {
/**
*
* @type {string}
* @memberof AppleIndependentSecureEnclave
*/
uuid?: string;
/**
* The user that this device belongs to.
* @type {number}
* @memberof AppleIndependentSecureEnclave
*/
user: number;
/**
*
* @type {string}
* @memberof AppleIndependentSecureEnclave
*/
appleSecureEnclaveKey: string;
/**
*
* @type {string}
* @memberof AppleIndependentSecureEnclave
*/
appleEnclaveKeyId: string;
/**
*
* @type {string}
* @memberof AppleIndependentSecureEnclave
*/
deviceType: string;
}
/**
* Check if a given object implements the AppleIndependentSecureEnclave interface.
*/
export function instanceOfAppleIndependentSecureEnclave(
value: object,
): value is AppleIndependentSecureEnclave {
if (!("user" in value) || value["user"] === undefined) return false;
if (!("appleSecureEnclaveKey" in value) || value["appleSecureEnclaveKey"] === undefined)
return false;
if (!("appleEnclaveKeyId" in value) || value["appleEnclaveKeyId"] === undefined) return false;
if (!("deviceType" in value) || value["deviceType"] === undefined) return false;
return true;
}
export function AppleIndependentSecureEnclaveFromJSON(json: any): AppleIndependentSecureEnclave {
return AppleIndependentSecureEnclaveFromJSONTyped(json, false);
}
export function AppleIndependentSecureEnclaveFromJSONTyped(
json: any,
ignoreDiscriminator: boolean,
): AppleIndependentSecureEnclave {
if (json == null) {
return json;
}
return {
uuid: json["uuid"] == null ? undefined : json["uuid"],
user: json["user"],
appleSecureEnclaveKey: json["apple_secure_enclave_key"],
appleEnclaveKeyId: json["apple_enclave_key_id"],
deviceType: json["device_type"],
};
}
export function AppleIndependentSecureEnclaveToJSON(json: any): AppleIndependentSecureEnclave {
return AppleIndependentSecureEnclaveToJSONTyped(json, false);
}
export function AppleIndependentSecureEnclaveToJSONTyped(
value?: AppleIndependentSecureEnclave | null,
ignoreDiscriminator: boolean = false,
): any {
if (value == null) {
return value;
}
return {
uuid: value["uuid"],
user: value["user"],
apple_secure_enclave_key: value["appleSecureEnclaveKey"],
apple_enclave_key_id: value["appleEnclaveKeyId"],
device_type: value["deviceType"],
};
}

View File

@@ -0,0 +1,110 @@
/* tslint:disable */
/* eslint-disable */
/**
* authentik
* Making authentication simple.
*
* The version of the OpenAPI document: 2026.5.0-rc1
* Contact: hello@goauthentik.io
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
*
* @export
* @interface AppleIndependentSecureEnclaveRequest
*/
export interface AppleIndependentSecureEnclaveRequest {
/**
*
* @type {string}
* @memberof AppleIndependentSecureEnclaveRequest
*/
uuid?: string;
/**
* The user that this device belongs to.
* @type {number}
* @memberof AppleIndependentSecureEnclaveRequest
*/
user: number;
/**
*
* @type {string}
* @memberof AppleIndependentSecureEnclaveRequest
*/
appleSecureEnclaveKey: string;
/**
*
* @type {string}
* @memberof AppleIndependentSecureEnclaveRequest
*/
appleEnclaveKeyId: string;
/**
*
* @type {string}
* @memberof AppleIndependentSecureEnclaveRequest
*/
deviceType: string;
}
/**
* Check if a given object implements the AppleIndependentSecureEnclaveRequest interface.
*/
export function instanceOfAppleIndependentSecureEnclaveRequest(
value: object,
): value is AppleIndependentSecureEnclaveRequest {
if (!("user" in value) || value["user"] === undefined) return false;
if (!("appleSecureEnclaveKey" in value) || value["appleSecureEnclaveKey"] === undefined)
return false;
if (!("appleEnclaveKeyId" in value) || value["appleEnclaveKeyId"] === undefined) return false;
if (!("deviceType" in value) || value["deviceType"] === undefined) return false;
return true;
}
export function AppleIndependentSecureEnclaveRequestFromJSON(
json: any,
): AppleIndependentSecureEnclaveRequest {
return AppleIndependentSecureEnclaveRequestFromJSONTyped(json, false);
}
export function AppleIndependentSecureEnclaveRequestFromJSONTyped(
json: any,
ignoreDiscriminator: boolean,
): AppleIndependentSecureEnclaveRequest {
if (json == null) {
return json;
}
return {
uuid: json["uuid"] == null ? undefined : json["uuid"],
user: json["user"],
appleSecureEnclaveKey: json["apple_secure_enclave_key"],
appleEnclaveKeyId: json["apple_enclave_key_id"],
deviceType: json["device_type"],
};
}
export function AppleIndependentSecureEnclaveRequestToJSON(
json: any,
): AppleIndependentSecureEnclaveRequest {
return AppleIndependentSecureEnclaveRequestToJSONTyped(json, false);
}
export function AppleIndependentSecureEnclaveRequestToJSONTyped(
value?: AppleIndependentSecureEnclaveRequest | null,
ignoreDiscriminator: boolean = false,
): any {
if (value == null) {
return value;
}
return {
uuid: value["uuid"],
user: value["user"],
apple_secure_enclave_key: value["appleSecureEnclaveKey"],
apple_enclave_key_id: value["appleEnclaveKeyId"],
device_type: value["deviceType"],
};
}

View File

@@ -23,6 +23,7 @@ export const AuthenticationEnum = {
RequireSuperuser: "require_superuser",
RequireRedirect: "require_redirect",
RequireOutpost: "require_outpost",
RequireToken: "require_token",
UnknownDefaultOpenApi: "11184809",
} as const;
export type AuthenticationEnum = (typeof AuthenticationEnum)[keyof typeof AuthenticationEnum];

View File

@@ -0,0 +1,100 @@
/* tslint:disable */
/* eslint-disable */
/**
* authentik
* Making authentication simple.
*
* The version of the OpenAPI document: 2026.5.0-rc1
* Contact: hello@goauthentik.io
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import type { AppleIndependentSecureEnclave } from "./AppleIndependentSecureEnclave";
import {
AppleIndependentSecureEnclaveFromJSON,
AppleIndependentSecureEnclaveToJSON,
} from "./AppleIndependentSecureEnclave";
import type { Pagination } from "./Pagination";
import { PaginationFromJSON, PaginationToJSON } from "./Pagination";
/**
*
* @export
* @interface PaginatedAppleIndependentSecureEnclaveList
*/
export interface PaginatedAppleIndependentSecureEnclaveList {
/**
*
* @type {Pagination}
* @memberof PaginatedAppleIndependentSecureEnclaveList
*/
pagination: Pagination;
/**
*
* @type {Array<AppleIndependentSecureEnclave>}
* @memberof PaginatedAppleIndependentSecureEnclaveList
*/
results: Array<AppleIndependentSecureEnclave>;
/**
*
* @type {{ [key: string]: any; }}
* @memberof PaginatedAppleIndependentSecureEnclaveList
*/
autocomplete: { [key: string]: any };
}
/**
* Check if a given object implements the PaginatedAppleIndependentSecureEnclaveList interface.
*/
export function instanceOfPaginatedAppleIndependentSecureEnclaveList(
value: object,
): value is PaginatedAppleIndependentSecureEnclaveList {
if (!("pagination" in value) || value["pagination"] === undefined) return false;
if (!("results" in value) || value["results"] === undefined) return false;
if (!("autocomplete" in value) || value["autocomplete"] === undefined) return false;
return true;
}
export function PaginatedAppleIndependentSecureEnclaveListFromJSON(
json: any,
): PaginatedAppleIndependentSecureEnclaveList {
return PaginatedAppleIndependentSecureEnclaveListFromJSONTyped(json, false);
}
export function PaginatedAppleIndependentSecureEnclaveListFromJSONTyped(
json: any,
ignoreDiscriminator: boolean,
): PaginatedAppleIndependentSecureEnclaveList {
if (json == null) {
return json;
}
return {
pagination: PaginationFromJSON(json["pagination"]),
results: (json["results"] as Array<any>).map(AppleIndependentSecureEnclaveFromJSON),
autocomplete: json["autocomplete"],
};
}
export function PaginatedAppleIndependentSecureEnclaveListToJSON(
json: any,
): PaginatedAppleIndependentSecureEnclaveList {
return PaginatedAppleIndependentSecureEnclaveListToJSONTyped(json, false);
}
export function PaginatedAppleIndependentSecureEnclaveListToJSONTyped(
value?: PaginatedAppleIndependentSecureEnclaveList | null,
ignoreDiscriminator: boolean = false,
): any {
if (value == null) {
return value;
}
return {
pagination: PaginationToJSON(value["pagination"]),
results: (value["results"] as Array<any>).map(AppleIndependentSecureEnclaveToJSON),
autocomplete: value["autocomplete"],
};
}

View File

@@ -0,0 +1,107 @@
/* tslint:disable */
/* eslint-disable */
/**
* authentik
* Making authentication simple.
*
* The version of the OpenAPI document: 2026.5.0-rc1
* Contact: hello@goauthentik.io
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
*
* @export
* @interface PatchedAppleIndependentSecureEnclaveRequest
*/
export interface PatchedAppleIndependentSecureEnclaveRequest {
/**
*
* @type {string}
* @memberof PatchedAppleIndependentSecureEnclaveRequest
*/
uuid?: string;
/**
* The user that this device belongs to.
* @type {number}
* @memberof PatchedAppleIndependentSecureEnclaveRequest
*/
user?: number;
/**
*
* @type {string}
* @memberof PatchedAppleIndependentSecureEnclaveRequest
*/
appleSecureEnclaveKey?: string;
/**
*
* @type {string}
* @memberof PatchedAppleIndependentSecureEnclaveRequest
*/
appleEnclaveKeyId?: string;
/**
*
* @type {string}
* @memberof PatchedAppleIndependentSecureEnclaveRequest
*/
deviceType?: string;
}
/**
* Check if a given object implements the PatchedAppleIndependentSecureEnclaveRequest interface.
*/
export function instanceOfPatchedAppleIndependentSecureEnclaveRequest(
value: object,
): value is PatchedAppleIndependentSecureEnclaveRequest {
return true;
}
export function PatchedAppleIndependentSecureEnclaveRequestFromJSON(
json: any,
): PatchedAppleIndependentSecureEnclaveRequest {
return PatchedAppleIndependentSecureEnclaveRequestFromJSONTyped(json, false);
}
export function PatchedAppleIndependentSecureEnclaveRequestFromJSONTyped(
json: any,
ignoreDiscriminator: boolean,
): PatchedAppleIndependentSecureEnclaveRequest {
if (json == null) {
return json;
}
return {
uuid: json["uuid"] == null ? undefined : json["uuid"],
user: json["user"] == null ? undefined : json["user"],
appleSecureEnclaveKey:
json["apple_secure_enclave_key"] == null ? undefined : json["apple_secure_enclave_key"],
appleEnclaveKeyId:
json["apple_enclave_key_id"] == null ? undefined : json["apple_enclave_key_id"],
deviceType: json["device_type"] == null ? undefined : json["device_type"],
};
}
export function PatchedAppleIndependentSecureEnclaveRequestToJSON(
json: any,
): PatchedAppleIndependentSecureEnclaveRequest {
return PatchedAppleIndependentSecureEnclaveRequestToJSONTyped(json, false);
}
export function PatchedAppleIndependentSecureEnclaveRequestToJSONTyped(
value?: PatchedAppleIndependentSecureEnclaveRequest | null,
ignoreDiscriminator: boolean = false,
): any {
if (value == null) {
return value;
}
return {
uuid: value["uuid"],
user: value["user"],
apple_secure_enclave_key: value["appleSecureEnclaveKey"],
apple_enclave_key_id: value["appleEnclaveKeyId"],
device_type: value["deviceType"],
};
}

View File

@@ -13,6 +13,8 @@ export * from "./AlgEnum";
export * from "./App";
export * from "./AppEnum";
export * from "./AppleChallengeResponseRequest";
export * from "./AppleIndependentSecureEnclave";
export * from "./AppleIndependentSecureEnclaveRequest";
export * from "./AppleLoginChallenge";
export * from "./Application";
export * from "./ApplicationEntitlement";
@@ -350,6 +352,7 @@ export * from "./OutpostRequest";
export * from "./OutpostTypeEnum";
export * from "./PKCEMethodEnum";
export * from "./PaginatedAgentConnectorList";
export * from "./PaginatedAppleIndependentSecureEnclaveList";
export * from "./PaginatedApplicationEntitlementList";
export * from "./PaginatedApplicationList";
export * from "./PaginatedAuthenticatedSessionList";
@@ -515,6 +518,7 @@ export * from "./PasswordPolicyRequest";
export * from "./PasswordStage";
export * from "./PasswordStageRequest";
export * from "./PatchedAgentConnectorRequest";
export * from "./PatchedAppleIndependentSecureEnclaveRequest";
export * from "./PatchedApplicationEntitlementRequest";
export * from "./PatchedApplicationRequest";
export * from "./PatchedAuthenticatorDuoStageRequest";

View File

@@ -33,7 +33,7 @@ dependencies = [
"drf-spectacular==0.29.0",
"dumb-init==1.2.5.post1",
"duo-client==5.6.1",
"fido2==2.1.1",
"fido2==2.2.0",
"geoip2==5.2.0",
"geopy==2.4.1",
"google-api-python-client==2.194.0",
@@ -43,26 +43,26 @@ dependencies = [
"jwcrypto==1.5.7",
"kubernetes==35.0.0",
"ldap3==2.9.1",
"lxml==6.0.4",
"lxml==6.1.0",
"msgraph-sdk==1.55.0",
"opencontainers==0.0.15",
"packaging==26.0",
"packaging==26.1",
"paramiko==4.0.0",
"psycopg[c,pool]==3.3.3",
"pydantic-scim==0.0.8",
"pydantic==2.12.5",
"pydantic==2.13.2",
"pyjwt==2.11.0",
"pyrad==2.5.4",
"python-kadmin-rs==0.7.0",
"pyyaml==6.0.3",
"requests-oauthlib==2.0.0",
"scim2-filter-parser==0.7.0",
"sentry-sdk==2.57.0",
"sentry-sdk==2.58.0",
"service-identity==24.2.0",
"setproctitle==1.3.7",
"structlog==25.5.0",
"swagger-spec-validator==3.0.4",
"twilio==9.10.4",
"twilio==9.10.5",
"ua-parser==1.0.2",
"unidecode==1.4.0",
"urllib3<3",
@@ -76,7 +76,7 @@ dependencies = [
[dependency-groups]
dev = [
"aws-cdk-lib==2.248.0",
"aws-cdk-lib==2.250.0",
"bandit==1.9.4",
"black==26.3.1",
"bpython==0.26",
@@ -85,7 +85,7 @@ dev = [
"coverage[toml]==7.13.5",
"daphne==4.2.1",
"debugpy==1.8.20",
"django-stubs[compatible-mypy]==6.0.2",
"django-stubs[compatible-mypy]==6.0.3",
"djangorestframework-stubs[compatible-mypy]==3.16.9",
"drf-jsonschema-serializer==3.0.0",
"freezegun==1.5.5",
@@ -101,8 +101,8 @@ dev = [
"pytest-timeout==2.4.0",
"pytest==9.0.3",
"requests-mock==1.12.1",
"ruff==0.15.10",
"selenium==4.41.0",
"ruff==0.15.11",
"selenium==4.43.0",
"types-channels==4.3.0.20260408",
"types-docker==7.1.0.20260409",
"types-jwcrypto==1.5.7.20260409",

View File

@@ -1,3 +1,3 @@
[toolchain]
channel = "1.94.1"
channel = "1.95.0"
components = ["clippy", "rust-analyzer", "llvm-tools-preview"]

View File

@@ -5649,6 +5649,209 @@ paths:
$ref: '#/components/responses/ValidationErrorResponse'
'403':
$ref: '#/components/responses/GenericErrorResponse'
/endpoints/agents/psso/ise/:
get:
operationId: endpoints_agents_psso_ise_list
description: Mixin to add a used_by endpoint to return a list of all objects
using this object
parameters:
- in: query
name: apple_enclave_key_id
schema:
type: string
- $ref: '#/components/parameters/QueryPaginationOrdering'
- $ref: '#/components/parameters/QueryPaginationPage'
- $ref: '#/components/parameters/QueryPaginationPageSize'
- $ref: '#/components/parameters/QuerySearch'
- in: query
name: user
schema:
type: integer
tags:
- endpoints
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedAppleIndependentSecureEnclaveList'
description: ''
'400':
$ref: '#/components/responses/ValidationErrorResponse'
'403':
$ref: '#/components/responses/GenericErrorResponse'
post:
operationId: endpoints_agents_psso_ise_create
description: Mixin to add a used_by endpoint to return a list of all objects
using this object
tags:
- endpoints
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AppleIndependentSecureEnclaveRequest'
required: true
security:
- authentik: []
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/AppleIndependentSecureEnclave'
description: ''
'400':
$ref: '#/components/responses/ValidationErrorResponse'
'403':
$ref: '#/components/responses/GenericErrorResponse'
/endpoints/agents/psso/ise/{uuid}/:
get:
operationId: endpoints_agents_psso_ise_retrieve
description: Mixin to add a used_by endpoint to return a list of all objects
using this object
parameters:
- in: path
name: uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Apple Independent Secure Enclave.
required: true
tags:
- endpoints
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/AppleIndependentSecureEnclave'
description: ''
'400':
$ref: '#/components/responses/ValidationErrorResponse'
'403':
$ref: '#/components/responses/GenericErrorResponse'
put:
operationId: endpoints_agents_psso_ise_update
description: Mixin to add a used_by endpoint to return a list of all objects
using this object
parameters:
- in: path
name: uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Apple Independent Secure Enclave.
required: true
tags:
- endpoints
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AppleIndependentSecureEnclaveRequest'
required: true
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/AppleIndependentSecureEnclave'
description: ''
'400':
$ref: '#/components/responses/ValidationErrorResponse'
'403':
$ref: '#/components/responses/GenericErrorResponse'
patch:
operationId: endpoints_agents_psso_ise_partial_update
description: Mixin to add a used_by endpoint to return a list of all objects
using this object
parameters:
- in: path
name: uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Apple Independent Secure Enclave.
required: true
tags:
- endpoints
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedAppleIndependentSecureEnclaveRequest'
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/AppleIndependentSecureEnclave'
description: ''
'400':
$ref: '#/components/responses/ValidationErrorResponse'
'403':
$ref: '#/components/responses/GenericErrorResponse'
delete:
operationId: endpoints_agents_psso_ise_destroy
description: Mixin to add a used_by endpoint to return a list of all objects
using this object
parameters:
- in: path
name: uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Apple Independent Secure Enclave.
required: true
tags:
- endpoints
security:
- authentik: []
responses:
'204':
description: No response body
'400':
$ref: '#/components/responses/ValidationErrorResponse'
'403':
$ref: '#/components/responses/GenericErrorResponse'
/endpoints/agents/psso/ise/{uuid}/used_by/:
get:
operationId: endpoints_agents_psso_ise_used_by_list
description: Get a list of all objects that use this object
parameters:
- in: path
name: uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Apple Independent Secure Enclave.
required: true
tags:
- endpoints
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/UsedBy'
description: ''
'400':
$ref: '#/components/responses/ValidationErrorResponse'
'403':
$ref: '#/components/responses/GenericErrorResponse'
/endpoints/agents/psso/register/device/:
post:
operationId: endpoints_agents_psso_register_device_create
@@ -33772,6 +33975,49 @@ components:
type: string
minLength: 1
default: ak-source-oauth-apple
AppleIndependentSecureEnclave:
type: object
properties:
uuid:
type: string
format: uuid
user:
type: integer
description: The user that this device belongs to.
apple_secure_enclave_key:
type: string
apple_enclave_key_id:
type: string
device_type:
type: string
required:
- apple_enclave_key_id
- apple_secure_enclave_key
- device_type
- user
AppleIndependentSecureEnclaveRequest:
type: object
properties:
uuid:
type: string
format: uuid
user:
type: integer
description: The user that this device belongs to.
apple_secure_enclave_key:
type: string
minLength: 1
apple_enclave_key_id:
type: string
minLength: 1
device_type:
type: string
minLength: 1
required:
- apple_enclave_key_id
- apple_secure_enclave_key
- device_type
- user
AppleLoginChallenge:
type: object
description: Special challenge for apple-native authentication flow, which happens
@@ -34109,6 +34355,7 @@ components:
- require_superuser
- require_redirect
- require_outpost
- require_token
type: string
AuthenticatorAttachmentEnum:
enum:
@@ -44295,6 +44542,21 @@ components:
- autocomplete
- pagination
- results
PaginatedAppleIndependentSecureEnclaveList:
type: object
properties:
pagination:
$ref: '#/components/schemas/Pagination'
results:
type: array
items:
$ref: '#/components/schemas/AppleIndependentSecureEnclave'
autocomplete:
$ref: '#/components/schemas/Autocomplete'
required:
- autocomplete
- pagination
- results
PaginatedApplicationEntitlementList:
type: object
properties:
@@ -47080,6 +47342,24 @@ components:
type: array
items:
type: integer
PatchedAppleIndependentSecureEnclaveRequest:
type: object
properties:
uuid:
type: string
format: uuid
user:
type: integer
description: The user that this device belongs to.
apple_secure_enclave_key:
type: string
minLength: 1
apple_enclave_key_id:
type: string
minLength: 1
device_type:
type: string
minLength: 1
PatchedApplicationEntitlementRequest:
type: object
description: ApplicationEntitlement Serializer

View File

@@ -74,7 +74,7 @@ pub(crate) fn start(tasks: &mut Tasks) -> Result<Arc<Metrics>> {
tasks
.build_task()
.name(&format!("{}::run_upkeep", module_path!(),))
.name(&format!("{}::run_upkeep", module_path!()))
.spawn(run_upkeep(arbiter, Arc::clone(&metrics)))?;
for addr in config::get().listen.metrics.iter().copied() {

View File

@@ -70,7 +70,7 @@ impl Worker {
cmd.arg(&socket_path);
let client = Client::builder(TokioExecutor::new())
.pool_idle_timeout(Duration::from_secs(60))
.pool_idle_timeout(Duration::from_mins(1))
.set_host(false)
.build(UnixSocketConnector::new(socket_path.clone()));

250
uv.lock generated
View File

@@ -342,7 +342,7 @@ requires-dist = [
{ name = "drf-spectacular", specifier = "==0.29.0" },
{ name = "dumb-init", specifier = "==1.2.5.post1" },
{ name = "duo-client", specifier = "==5.6.1" },
{ name = "fido2", specifier = "==2.1.1" },
{ name = "fido2", specifier = "==2.2.0" },
{ name = "geoip2", specifier = "==5.2.0" },
{ name = "geopy", specifier = "==2.4.1" },
{ name = "google-api-python-client", specifier = "==2.194.0" },
@@ -352,13 +352,13 @@ requires-dist = [
{ name = "jwcrypto", specifier = "==1.5.7" },
{ name = "kubernetes", specifier = "==35.0.0" },
{ name = "ldap3", specifier = "==2.9.1" },
{ name = "lxml", specifier = "==6.0.4" },
{ name = "lxml", specifier = "==6.1.0" },
{ name = "msgraph-sdk", specifier = "==1.55.0" },
{ name = "opencontainers", git = "https://github.com/vsoch/oci-python?rev=ceb4fcc090851717a3069d78e85ceb1e86c2740c" },
{ name = "packaging", specifier = "==26.0" },
{ name = "packaging", specifier = "==26.1" },
{ name = "paramiko", specifier = "==4.0.0" },
{ name = "psycopg", extras = ["c", "pool"], specifier = "==3.3.3" },
{ name = "pydantic", specifier = "==2.12.5" },
{ name = "pydantic", specifier = "==2.13.2" },
{ name = "pydantic-scim", specifier = "==0.0.8" },
{ name = "pyjwt", specifier = "==2.11.0" },
{ name = "pyrad", specifier = "==2.5.4" },
@@ -366,12 +366,12 @@ requires-dist = [
{ name = "pyyaml", specifier = "==6.0.3" },
{ name = "requests-oauthlib", specifier = "==2.0.0" },
{ name = "scim2-filter-parser", specifier = "==0.7.0" },
{ name = "sentry-sdk", specifier = "==2.57.0" },
{ name = "sentry-sdk", specifier = "==2.58.0" },
{ name = "service-identity", specifier = "==24.2.0" },
{ name = "setproctitle", specifier = "==1.3.7" },
{ name = "structlog", specifier = "==25.5.0" },
{ name = "swagger-spec-validator", specifier = "==3.0.4" },
{ name = "twilio", specifier = "==9.10.4" },
{ name = "twilio", specifier = "==9.10.5" },
{ name = "ua-parser", specifier = "==1.0.2" },
{ name = "unidecode", specifier = "==1.4.0" },
{ name = "urllib3", specifier = "<3" },
@@ -385,7 +385,7 @@ requires-dist = [
[package.metadata.requires-dev]
dev = [
{ name = "aws-cdk-lib", specifier = "==2.248.0" },
{ name = "aws-cdk-lib", specifier = "==2.250.0" },
{ name = "bandit", specifier = "==1.9.4" },
{ name = "black", specifier = "==26.3.1" },
{ name = "bpython", specifier = "==0.26" },
@@ -394,7 +394,7 @@ dev = [
{ name = "coverage", extras = ["toml"], specifier = "==7.13.5" },
{ name = "daphne", specifier = "==4.2.1" },
{ name = "debugpy", specifier = "==1.8.20" },
{ name = "django-stubs", extras = ["compatible-mypy"], specifier = "==6.0.2" },
{ name = "django-stubs", extras = ["compatible-mypy"], specifier = "==6.0.3" },
{ name = "djangorestframework-stubs", extras = ["compatible-mypy"], specifier = "==3.16.9" },
{ name = "drf-jsonschema-serializer", specifier = "==3.0.0" },
{ name = "freezegun", specifier = "==1.5.5" },
@@ -410,8 +410,8 @@ dev = [
{ name = "pytest-randomly", specifier = "==4.0.1" },
{ name = "pytest-timeout", specifier = "==2.4.0" },
{ name = "requests-mock", specifier = "==1.12.1" },
{ name = "ruff", specifier = "==0.15.10" },
{ name = "selenium", specifier = "==4.41.0" },
{ name = "ruff", specifier = "==0.15.11" },
{ name = "selenium", specifier = "==4.43.0" },
{ name = "types-channels", specifier = "==4.3.0.20260408" },
{ name = "types-docker", specifier = "==7.1.0.20260409" },
{ name = "types-jwcrypto", specifier = "==1.5.7.20260409" },
@@ -495,7 +495,7 @@ wheels = [
[[package]]
name = "aws-cdk-lib"
version = "2.248.0"
version = "2.250.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aws-cdk-asset-awscli-v1" },
@@ -506,9 +506,9 @@ dependencies = [
{ name = "publication" },
{ name = "typeguard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/70/42/a8250f31763adb21acb90088b99f7f37c3b8d09c3fd94dc631c0e69d9463/aws_cdk_lib-2.248.0.tar.gz", hash = "sha256:a00c73bfa6865ef62d700d6f97dd4124979ccfe69dc1ef70f2526aa49c52bbc8", size = 48734274, upload-time = "2026-04-03T07:35:05.438Z" }
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" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/0b/41b0979581668c709b01a7d26f9c2716031f2b0c53ad5a01341160c631b0/aws_cdk_lib-2.248.0-py3-none-any.whl", hash = "sha256:e89db288365371e6b42aaae8b45ad2064615dfbe87e95741a2f2bb7ec1f33dad", size = 49395398, upload-time = "2026-04-03T07:34:25.671Z" },
{ 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" },
]
[[package]]
@@ -1253,7 +1253,7 @@ s3 = [
[[package]]
name = "django-stubs"
version = "6.0.2"
version = "6.0.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
@@ -1261,9 +1261,9 @@ dependencies = [
{ name = "types-pyyaml" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/03/b2/f0214d86180f937c8e3358ff831b20f0634d95bd77436b18861c647e15bc/django_stubs-6.0.2.tar.gz", hash = "sha256:56d43b5e3741563af0063e5b6283f908c625b0439aa06314268673699d1bdccd", size = 274742, upload-time = "2026-04-01T08:27:35.092Z" }
sdist = { url = "https://files.pythonhosted.org/packages/86/0c/8d0d875af79bf774c1c3997c84aa118dba3a77be12086b9c14e130e8ec72/django_stubs-6.0.3.tar.gz", hash = "sha256:ee895f403c373608eeb50822f0733f9d9ec5ab12731d4ab58956053bb95fdd9e", size = 278214, upload-time = "2026-04-18T15:11:22.327Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/49/e7/8f2aaa22eac7fa18db3aca0e7b651ccf5ac79a2021bf67e75a16934a7076/django_stubs-6.0.2-py3-none-any.whl", hash = "sha256:c3bc84d80421758f3b2ad9e1358e001d719388a8eb106e67c873e606216108d4", size = 538234, upload-time = "2026-04-01T08:27:33.411Z" },
{ url = "https://files.pythonhosted.org/packages/80/a3/6751b7684d20fc4f228bdd3dd8341d382ab3faaf65d3d050c0d59ab0a1b0/django_stubs-6.0.3-py3-none-any.whl", hash = "sha256:5fee22bcbbad59a78c727a820b6f4e68ff442ca76a922b7002e57c25dd7cb390", size = 541570, upload-time = "2026-04-18T15:11:20.711Z" },
]
[package.optional-dependencies]
@@ -1468,14 +1468,14 @@ wheels = [
[[package]]
name = "fido2"
version = "2.1.1"
version = "2.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cryptography" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e7/3c/c65377e48c144afca6b02c69f10c0fe936db556096a4e2c9798e2aa72db6/fido2-2.1.1.tar.gz", hash = "sha256:f1379f845870cc7fc64c7f07323c3ce41e8c96c37054e79e0acd5630b3fec5ac", size = 4455940, upload-time = "2026-01-19T11:08:34.683Z" }
sdist = { url = "https://files.pythonhosted.org/packages/09/34/4837e2f5640baf61d8abd6125ccb6cc60b4b2933088528356ad6e781496f/fido2-2.2.0.tar.gz", hash = "sha256:0d8122e690096ad82afde42ac9d6433a4eeffda64084f36341ea02546b181dd1", size = 294167, upload-time = "2026-04-15T06:42:50.264Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e2/ab/d0fa89cc4b982800dd88daa799612f11642bf9393851715d9eaeba3cfcac/fido2-2.1.1-py3-none-any.whl", hash = "sha256:f85c16c8084abf6530b6c6ec3a0cf8575943321842e06916686943a8b784182c", size = 226945, upload-time = "2026-01-19T11:08:29.675Z" },
{ url = "https://files.pythonhosted.org/packages/01/82/f3c5dd87b0977f5547cc132b7969e6f5075a8c2f5881cf4b6df6378505f9/fido2-2.2.0-py3-none-any.whl", hash = "sha256:3587ccf0af7b71b5dd73f17e1dbec9f0fd157292f9163f02e7778f46d0d25fe5", size = 234025, upload-time = "2026-04-15T06:42:51.813Z" },
]
[[package]]
@@ -2060,46 +2060,46 @@ wheels = [
[[package]]
name = "lxml"
version = "6.0.4"
version = "6.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ce/08/1217ca4043f55c3c92993b283a7dbfa456a2058d8b57bbb416cc96b6efff/lxml-6.0.4.tar.gz", hash = "sha256:4137516be2a90775f99d8ef80ec0283f8d78b5d8bd4630ff20163b72e7e9abf2", size = 4237780, upload-time = "2026-04-12T16:28:24.182Z" }
sdist = { url = "https://files.pythonhosted.org/packages/28/30/9abc9e34c657c33834eaf6cd02124c61bdf5944d802aa48e69be8da3585d/lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13", size = 4197006, upload-time = "2026-04-18T04:32:51.613Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/10/18/36e28a809c509a67496202771f545219ac5a2f1cd61aae325991fcf5ab91/lxml-6.0.4-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:569d3b18340863f603582d2124e742a68e85755eff5e47c26a55e298521e3a01", size = 8575045, upload-time = "2026-04-12T16:25:33.57Z" },
{ url = "https://files.pythonhosted.org/packages/11/38/a168c820e3b08d3b4fa0f4e6b53b3930086b36cc11e428106d38c36778cd/lxml-6.0.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3b6245ee5241342d45e1a54a4a8bc52ef322333ada74f24aa335c4ab36f20161", size = 4622963, upload-time = "2026-04-12T16:25:36.818Z" },
{ url = "https://files.pythonhosted.org/packages/53/e0/2c9d6abdd82358cea3c0d8d6ca272a6af0f38156abce7827efb6d5b62d17/lxml-6.0.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:79a1173ba3213a3693889a435417d4e9f3c07d96e30dc7cc3a712ed7361015fe", size = 4948832, upload-time = "2026-04-12T16:25:39.104Z" },
{ url = "https://files.pythonhosted.org/packages/96/d7/f2202852e91d7baf3a317f4523a9c14834145301e5b0f2e80c01c4bfbd49/lxml-6.0.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc18bb975666b443ba23aedd2fcf57e9d0d97546b52a1de97a447c4061ba4110", size = 5085865, upload-time = "2026-04-12T16:25:41.226Z" },
{ url = "https://files.pythonhosted.org/packages/09/57/abee549324496e92708f71391c6060a164d3c95369656a1a15e9f20d8162/lxml-6.0.4-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2079f5dc83291ac190a52f8354b78648f221ecac19fb2972a2d056b555824de7", size = 5030001, upload-time = "2026-04-12T16:25:43.695Z" },
{ url = "https://files.pythonhosted.org/packages/c2/f8/432da7178c5917a16468af6c5da68fef7cf3357d4bd0e6f50272ec9a59b5/lxml-6.0.4-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3eda02da4ca16e9ca22bbe5654470c17fa1abcd967a52e4c2e50ff278221e351", size = 5646303, upload-time = "2026-04-12T16:25:46.577Z" },
{ url = "https://files.pythonhosted.org/packages/82/f9/e1c04ef667a6bf9c9dbd3bf04c50fa51d7ee25b258485bb748b27eb9a1c7/lxml-6.0.4-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3787cdc3832b70e21ac2efafea2a82a8ccb5e85bec110dc68b26023e9d3caae", size = 5237940, upload-time = "2026-04-12T16:25:49.157Z" },
{ url = "https://files.pythonhosted.org/packages/d0/f0/cdea60d92df731725fc3c4f33e387b100f210acd45c92969e42d2ba993fa/lxml-6.0.4-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:3f276d49c23103565d39440b9b3f4fc08fa22f5a96395ea4b4d4fea4458b1505", size = 5350050, upload-time = "2026-04-12T16:25:52.027Z" },
{ url = "https://files.pythonhosted.org/packages/2e/15/bf52c7a70b6081bb9e00d37cc90fcf60aa84468d9d173ad2fade38ec34c5/lxml-6.0.4-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:fdfdad73736402375b11b3a137e48cd09634177516baf5fc0bd80d1ca85f3cda", size = 4696409, upload-time = "2026-04-12T16:25:55.141Z" },
{ url = "https://files.pythonhosted.org/packages/c5/69/9bade267332cc06f9a9aa773b5a11bdfb249af485df9e142993009ea1fc4/lxml-6.0.4-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:75912421456946931daba0ec3cedfa824c756585d05bde97813a17992bfbd013", size = 5249072, upload-time = "2026-04-12T16:25:57.362Z" },
{ url = "https://files.pythonhosted.org/packages/14/ca/043bcacb096d6ed291cbbc58724e9625a453069d6edeb840b0bf18038d05/lxml-6.0.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:48cd5a88da67233fd82f2920db344503c2818255217cd6ea462c9bb8254ba7cb", size = 5083779, upload-time = "2026-04-12T16:26:00.018Z" },
{ url = "https://files.pythonhosted.org/packages/04/89/f5fb18d76985969e84af13682e489acabee399bb54738a363925ea6e7390/lxml-6.0.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:87af86a8fa55b9ff1e6ee4233d762296f2ce641ba948af783fb995c5a8a3371b", size = 4736953, upload-time = "2026-04-12T16:26:02.289Z" },
{ url = "https://files.pythonhosted.org/packages/84/ba/d1d7284bb4ba951f188c3fc0455943c1fcbd1c33d1324d6d57b7d4a45be6/lxml-6.0.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a743714cd656ba7ccb29d199783906064c7b5ba3c0e2a79f0244ea0badc6a98c", size = 5669605, upload-time = "2026-04-12T16:26:04.694Z" },
{ url = "https://files.pythonhosted.org/packages/72/05/1463e55f2de27bb60feddc894dd7c0833bd501f8861392ed416291b38db5/lxml-6.0.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e31c76bd066fb4f81d9a32e5843bffdf939ab27afb1ffc1c924e749bfbdb00e3", size = 5236886, upload-time = "2026-04-12T16:26:07.659Z" },
{ url = "https://files.pythonhosted.org/packages/fe/fb/0b6ee9194ce3ac49db4cadaa8a9158f04779fc768b6c27c4e2945d71a99d/lxml-6.0.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f185fd6e7d550e9917d7103dccf51be589aba953e15994fb04646c1730019685", size = 5263382, upload-time = "2026-04-12T16:26:10.067Z" },
{ url = "https://files.pythonhosted.org/packages/9a/93/ec18a08e98dd82cac39f1d2511ee2bed5affb94d228356d8ef165a4ec3b9/lxml-6.0.4-cp314-cp314-win32.whl", hash = "sha256:774660028f8722a598400430d2746fb0075949f84a9a5cd9767d9152e3baaac5", size = 3656164, upload-time = "2026-04-12T16:26:59.568Z" },
{ url = "https://files.pythonhosted.org/packages/15/86/52507316abfc7150bf6bb191e39a12e301ee80334610a493884ae2f9d20d/lxml-6.0.4-cp314-cp314-win_amd64.whl", hash = "sha256:fbd7d14349413f5609c0b537b1a48117d6ccef1af37986af6b03766ad05bf43e", size = 4062512, upload-time = "2026-04-12T16:27:02.212Z" },
{ url = "https://files.pythonhosted.org/packages/f1/d5/09c593a2ef2234b8cd6cf059e2dc212e0654bf05c503f0ef2daf05adb680/lxml-6.0.4-cp314-cp314-win_arm64.whl", hash = "sha256:a61a01ec3fbfd5b73a69a7bf513271051fd6c5795d82fc5daa0255934cd8db3d", size = 3740745, upload-time = "2026-04-12T16:27:04.444Z" },
{ url = "https://files.pythonhosted.org/packages/4a/3c/42a98bf6693938bf7b285ec7f70ba2ae9d785d0e5b2cdb85d2ee29e287eb/lxml-6.0.4-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:504edb62df33cea502ea6e73847c647ba228623ca3f80a228be5723a70984dd5", size = 8826437, upload-time = "2026-04-12T16:26:12.911Z" },
{ url = "https://files.pythonhosted.org/packages/c2/c2/ad13f39b2db8709788aa2dcb6e90b81da76db3b5b2e7d35e0946cf984960/lxml-6.0.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f01b7b0316d4c0926d49a7f003b2d30539f392b140a3374bb788bad180bc8478", size = 4734892, upload-time = "2026-04-12T16:26:15.871Z" },
{ url = "https://files.pythonhosted.org/packages/2c/6d/c559d7b5922c5b0380fc2cb5ac134b6a3f9d79d368347a624ee5d68b0816/lxml-6.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ab999933e662501efe4b16e6cfb7c9f9deca7d072cd1788b99c8defde78c0dfb", size = 4969173, upload-time = "2026-04-12T16:26:18.335Z" },
{ url = "https://files.pythonhosted.org/packages/c7/78/ca521e36157f38e3e1a29276855cdf48d213138fc0c8365693ff5c876ca7/lxml-6.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67c3f084389fe75932c39b6869a377f6c8e21e818f31ae8a30c71dd2e59360e2", size = 5103134, upload-time = "2026-04-12T16:26:20.612Z" },
{ url = "https://files.pythonhosted.org/packages/28/a7/7d62d023bacaa0aaf60af8c0a77c6c05f84327396d755f3aa64b788678a9/lxml-6.0.4-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:377ea1d654f76ed6205c87d14920f829c9f4d31df83374d3cbcbdaae804d37b2", size = 5027205, upload-time = "2026-04-12T16:26:22.981Z" },
{ url = "https://files.pythonhosted.org/packages/34/be/51b194b81684f2e85e5d992771c45d70cb22ac6f7291ac6bc7b255830afe/lxml-6.0.4-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e60cd0bcacbfd1a96d63516b622183fb2e3f202300df9eb5533391a8a939dbfa", size = 5594461, upload-time = "2026-04-12T16:26:25.316Z" },
{ url = "https://files.pythonhosted.org/packages/39/24/8850f38fbf89dd072ff31ba22f9e40347aeada7cadf710ecb04b8d9f32d4/lxml-6.0.4-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e9e30fd63d41dd0bbdb020af5cdfffd5d9b554d907cb210f18e8fcdc8eac013", size = 5223378, upload-time = "2026-04-12T16:26:28.68Z" },
{ url = "https://files.pythonhosted.org/packages/2a/9b/595239ba8c719b0fdc7bc9ebdb7564459c9a6b24b8b363df4a02674aeece/lxml-6.0.4-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:1fb4a1606bb68c533002e7ed50d7e55e58f0ef1696330670281cb79d5ab2050d", size = 5311415, upload-time = "2026-04-12T16:26:31.513Z" },
{ url = "https://files.pythonhosted.org/packages/be/cb/aa27ac8d041acf34691577838494ad08df78e83fdfdb66948d2903e9291e/lxml-6.0.4-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:695c7708438e449d57f404db8cc1b769e77ad5b50655f32f8175686ba752f293", size = 4637953, upload-time = "2026-04-12T16:26:33.806Z" },
{ url = "https://files.pythonhosted.org/packages/f6/f2/f19114fd86825c2d1ce41cd99daad218d30cfdd2093d4de9273986fb4d68/lxml-6.0.4-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d49c35ae1e35ee9b569892cf8f8f88db9524f28d66e9daee547a5ef9f3c5f468", size = 5231532, upload-time = "2026-04-12T16:26:36.518Z" },
{ url = "https://files.pythonhosted.org/packages/9a/0e/c3fa354039ec0b6b09f40fbe1129efc572ac6239faa4906de42d5ce87c0a/lxml-6.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5801072f8967625e6249d162065d0d6011ef8ce3d0efb8754496b5246b81a74b", size = 5083767, upload-time = "2026-04-12T16:26:39.332Z" },
{ url = "https://files.pythonhosted.org/packages/b3/4b/1a0dbb6d6ffae16e54a8a3796ded0ad2f9c3bc1ff3728bde33456f4e1d63/lxml-6.0.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cbf768541526eba5ef1a49f991122e41b39781eafd0445a5a110fc09947a20b5", size = 4758079, upload-time = "2026-04-12T16:26:42.138Z" },
{ url = "https://files.pythonhosted.org/packages/a9/01/a246cf5f80f96766051de4b305d6552f80bdaefb37f04e019e42af0aba69/lxml-6.0.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:eecce87cc09233786fc31c230268183bf6375126cfec1c8b3673fcdc8767b560", size = 5618686, upload-time = "2026-04-12T16:26:44.507Z" },
{ url = "https://files.pythonhosted.org/packages/eb/1f/b072a92369039ebef11b0a654be5134fcf3ed04c0f437faf9435ac9ba845/lxml-6.0.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:07dce892881179e11053066faca2da17b0eeb0bb7298f11bcf842a86db207dbd", size = 5227259, upload-time = "2026-04-12T16:26:47.083Z" },
{ url = "https://files.pythonhosted.org/packages/d5/a0/dc97034f9d4c0c4d30875147d81fd2c0c7f3d261b109db36ed746bf8ab1d/lxml-6.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e4f97aee337b947e6699e5574c90d087d3e2ce517016241c07e7e98a28dca885", size = 5246190, upload-time = "2026-04-12T16:26:49.468Z" },
{ url = "https://files.pythonhosted.org/packages/f2/ef/85cb69835113583c2516fee07d0ffb4d824b557424b06ba5872c20ba6078/lxml-6.0.4-cp314-cp314t-win32.whl", hash = "sha256:064477c0d4c695aa1ea4b9c1c4ee9043ab740d12135b74c458cc658350adcd86", size = 3896005, upload-time = "2026-04-12T16:26:52.163Z" },
{ url = "https://files.pythonhosted.org/packages/3d/5e/2231f34cc54b8422b793593138d86d3fa4588fb2297d4ea0472390f25627/lxml-6.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:25bad2d8438f4ef5a7ad4a8d8bcaadde20c0daced8bdb56d46236b0a7d1cbdd0", size = 4391037, upload-time = "2026-04-12T16:26:54.398Z" },
{ url = "https://files.pythonhosted.org/packages/39/53/8ba3cd5984f8363635450c93f63e541a0721b362bb32ae0d8237d9674aee/lxml-6.0.4-cp314-cp314t-win_arm64.whl", hash = "sha256:1dcd9e6cb9b7df808ea33daebd1801f37a8f50e8c075013ed2a2343246727838", size = 3816184, upload-time = "2026-04-12T16:26:57.011Z" },
{ url = "https://files.pythonhosted.org/packages/eb/45/cee4cf203ef0bab5c52afc118da61d6b460c928f2893d40023cfa27e0b80/lxml-6.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8", size = 8576713, upload-time = "2026-04-18T04:32:06.831Z" },
{ url = "https://files.pythonhosted.org/packages/8a/a7/eda05babeb7e046839204eaf254cd4d7c9130ce2bbf0d9e90ea41af5654d/lxml-6.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9", size = 4623874, upload-time = "2026-04-18T04:32:10.755Z" },
{ url = "https://files.pythonhosted.org/packages/e7/e9/db5846de9b436b91890a62f29d80cd849ea17948a49bf532d5278ee69a9e/lxml-6.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03", size = 4949535, upload-time = "2026-04-18T04:34:06.657Z" },
{ url = "https://files.pythonhosted.org/packages/5a/ba/0d3593373dcae1d68f40dc3c41a5a92f2544e68115eb2f62319a4c2a6500/lxml-6.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb", size = 5086881, upload-time = "2026-04-18T04:34:09.556Z" },
{ url = "https://files.pythonhosted.org/packages/43/76/759a7484539ad1af0d125a9afe9c3fb5f82a8779fd1f5f56319d9e4ea2fd/lxml-6.1.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c", size = 5031305, upload-time = "2026-04-18T04:34:12.336Z" },
{ url = "https://files.pythonhosted.org/packages/dc/b9/c1f0daf981a11e47636126901fd4ab82429e18c57aeb0fc3ad2940b42d8b/lxml-6.1.0-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28", size = 5647522, upload-time = "2026-04-18T04:34:14.89Z" },
{ url = "https://files.pythonhosted.org/packages/31/e6/1f533dcd205275363d9ba3511bcec52fa2df86abf8abe6a5f2c599f0dc31/lxml-6.1.0-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086", size = 5239310, upload-time = "2026-04-18T04:34:17.652Z" },
{ url = "https://files.pythonhosted.org/packages/c3/8c/4175fb709c78a6e315ed814ed33be3defd8b8721067e70419a6cf6f971da/lxml-6.1.0-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f", size = 5350799, upload-time = "2026-04-18T04:34:20.529Z" },
{ url = "https://files.pythonhosted.org/packages/fd/77/6ffdebc5994975f0dde4acb59761902bd9d9bb84422b9a0bd239a7da9ca8/lxml-6.1.0-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292", size = 4697693, upload-time = "2026-04-18T04:34:23.541Z" },
{ url = "https://files.pythonhosted.org/packages/f8/f1/565f36bd5c73294602d48e04d23f81ff4c8736be6ba5e1d1ec670ac9be80/lxml-6.1.0-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb", size = 5250708, upload-time = "2026-04-18T04:34:26.001Z" },
{ url = "https://files.pythonhosted.org/packages/5a/11/a68ab9dd18c5c499404deb4005f4bc4e0e88e5b72cd755ad96efec81d18d/lxml-6.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad", size = 5084737, upload-time = "2026-04-18T04:34:28.32Z" },
{ url = "https://files.pythonhosted.org/packages/ab/78/e8f41e2c74f4af564e6a0348aea69fb6daaefa64bc071ef469823d22cc18/lxml-6.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb", size = 4737817, upload-time = "2026-04-18T04:34:30.784Z" },
{ url = "https://files.pythonhosted.org/packages/06/2d/aa4e117aa2ce2f3b35d9ff246be74a2f8e853baba5d2a92c64744474603a/lxml-6.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f", size = 5670753, upload-time = "2026-04-18T04:34:33.675Z" },
{ url = "https://files.pythonhosted.org/packages/08/f5/dd745d50c0409031dbfcc4881740542a01e54d6f0110bd420fa7782110b8/lxml-6.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43", size = 5238071, upload-time = "2026-04-18T04:34:36.12Z" },
{ url = "https://files.pythonhosted.org/packages/3e/74/ad424f36d0340a904665867dab310a3f1f4c96ff4039698de83b77f44c1f/lxml-6.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585", size = 5264319, upload-time = "2026-04-18T04:34:39.035Z" },
{ url = "https://files.pythonhosted.org/packages/53/36/a15d8b3514ec889bfd6aa3609107fcb6c9189f8dc347f1c0b81eded8d87c/lxml-6.1.0-cp314-cp314-win32.whl", hash = "sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f", size = 3657139, upload-time = "2026-04-18T04:32:20.006Z" },
{ url = "https://files.pythonhosted.org/packages/1a/a4/263ebb0710851a3c6c937180a9a86df1206fdfe53cc43005aa2237fd7736/lxml-6.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120", size = 4064195, upload-time = "2026-04-18T04:32:23.876Z" },
{ url = "https://files.pythonhosted.org/packages/80/68/2000f29d323b6c286de077ad20b429fc52272e44eae6d295467043e56012/lxml-6.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946", size = 3741870, upload-time = "2026-04-18T04:32:27.922Z" },
{ url = "https://files.pythonhosted.org/packages/30/e9/21383c7c8d43799f0da90224c0d7c921870d476ec9b3e01e1b2c0b8237c5/lxml-6.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c", size = 8827548, upload-time = "2026-04-18T04:32:15.094Z" },
{ url = "https://files.pythonhosted.org/packages/a5/01/c6bc11cd587030dd4f719f65c5657960649fe3e19196c844c75bf32cd0d6/lxml-6.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d", size = 4735866, upload-time = "2026-04-18T04:32:18.924Z" },
{ url = "https://files.pythonhosted.org/packages/f3/01/757132fff5f4acf25463b5298f1a46099f3a94480b806547b29ce5e385de/lxml-6.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9", size = 4969476, upload-time = "2026-04-18T04:34:41.889Z" },
{ url = "https://files.pythonhosted.org/packages/fd/fb/1bc8b9d27ed64be7c8903db6c89e74dc8c2cd9ec630a7462e4654316dc5b/lxml-6.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9", size = 5103719, upload-time = "2026-04-18T04:34:44.797Z" },
{ url = "https://files.pythonhosted.org/packages/d5/e7/5bf82fa28133536a54601aae633b14988e89ed61d4c1eb6b899b023233aa/lxml-6.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7", size = 5027890, upload-time = "2026-04-18T04:34:47.634Z" },
{ url = "https://files.pythonhosted.org/packages/2d/20/e048db5d4b4ea0366648aa595f26bb764b2670903fc585b87436d0a5032c/lxml-6.1.0-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86", size = 5596008, upload-time = "2026-04-18T04:34:51.503Z" },
{ url = "https://files.pythonhosted.org/packages/9a/c2/d10807bc8da4824b39e5bd01b5d05c077b6fd01bd91584167edf6b269d22/lxml-6.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb", size = 5224451, upload-time = "2026-04-18T04:34:54.263Z" },
{ url = "https://files.pythonhosted.org/packages/3c/15/2ebea45bea427e7f0057e9ce7b2d62c5aba20c6b001cca89ed0aadb3ad41/lxml-6.1.0-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c", size = 5312135, upload-time = "2026-04-18T04:34:56.818Z" },
{ url = "https://files.pythonhosted.org/packages/31/e2/87eeae151b0be2a308d49a7ec444ff3eb192b14251e62addb29d0bf3778f/lxml-6.1.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f", size = 4639126, upload-time = "2026-04-18T04:34:59.704Z" },
{ url = "https://files.pythonhosted.org/packages/a3/51/8a3f6a20902ad604dd746ec7b4000311b240d389dac5e9d95adefd349e0c/lxml-6.1.0-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773", size = 5232579, upload-time = "2026-04-18T04:35:02.658Z" },
{ url = "https://files.pythonhosted.org/packages/6d/d2/650d619bdbe048d2c3f2c31edb00e35670a5e2d65b4fe3b61bce37b19121/lxml-6.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b", size = 5084206, upload-time = "2026-04-18T04:35:05.175Z" },
{ url = "https://files.pythonhosted.org/packages/dd/8a/672ca1a3cbeabd1f511ca275a916c0514b747f4b85bdaae103b8fa92f307/lxml-6.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405", size = 4758906, upload-time = "2026-04-18T04:35:08.098Z" },
{ url = "https://files.pythonhosted.org/packages/be/f1/ef4b691da85c916cb2feb1eec7414f678162798ac85e042fa164419ac05c/lxml-6.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690", size = 5620553, upload-time = "2026-04-18T04:35:11.23Z" },
{ url = "https://files.pythonhosted.org/packages/59/17/94e81def74107809755ac2782fdad4404420f1c92ca83433d117a6d5acf0/lxml-6.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd", size = 5229458, upload-time = "2026-04-18T04:35:14.254Z" },
{ url = "https://files.pythonhosted.org/packages/21/55/c4be91b0f830a871fc1b0d730943d56013b683d4671d5198260e2eae722b/lxml-6.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180", size = 5247861, upload-time = "2026-04-18T04:35:17.006Z" },
{ url = "https://files.pythonhosted.org/packages/c2/ca/77123e4d77df3cb1e968ade7b1f808f5d3a5c1c96b18a33895397de292c1/lxml-6.1.0-cp314-cp314t-win32.whl", hash = "sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2", size = 3897377, upload-time = "2026-04-18T04:32:07.656Z" },
{ url = "https://files.pythonhosted.org/packages/64/ce/3554833989d074267c063209bae8b09815e5656456a2d332b947806b05ff/lxml-6.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5", size = 4392701, upload-time = "2026-04-18T04:32:12.113Z" },
{ url = "https://files.pythonhosted.org/packages/2b/a0/9b916c68c0e57752c07f8f64b30138d9d4059dbeb27b90274dedbea128ff/lxml-6.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac", size = 3817120, upload-time = "2026-04-18T04:32:15.803Z" },
]
[[package]]
@@ -2563,11 +2563,11 @@ wheels = [
[[package]]
name = "packaging"
version = "26.0"
version = "26.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
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" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
{ 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" },
]
[[package]]
@@ -2796,7 +2796,7 @@ wheels = [
[[package]]
name = "pydantic"
version = "2.12.5"
version = "2.13.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
@@ -2804,9 +2804,9 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
sdist = { url = "https://files.pythonhosted.org/packages/09/e5/06d23afac9973109d1e3c8ad38e1547a12e860610e327c05ee686827dc37/pydantic-2.13.2.tar.gz", hash = "sha256:b418196607e61081c3226dcd4f0672f2a194828abb9109e9cfb84026564df2d1", size = 843836, upload-time = "2026-04-17T09:31:59.636Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
{ url = "https://files.pythonhosted.org/packages/77/ca/b45c378e6e8d0b90577288b533e04e95b7afd61bb1d51b6c263176435489/pydantic-2.13.2-py3-none-any.whl", hash = "sha256:a525087f4c03d7e7456a3de89b64cd693d2229933bb1068b9af6befd5563694e", size = 471947, upload-time = "2026-04-17T09:31:57.541Z" },
]
[package.optional-dependencies]
@@ -2816,41 +2816,43 @@ email = [
[[package]]
name = "pydantic-core"
version = "2.41.5"
version = "2.46.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
sdist = { url = "https://files.pythonhosted.org/packages/43/bb/4742f05b739b2478459bb16fa8470549518c802e06ddcf3f106c5081315e/pydantic_core-2.46.2.tar.gz", hash = "sha256:37bb079f9ee3f1a519392b73fda2a96379b31f2013c6b467fe693e7f2987f596", size = 471269, upload-time = "2026-04-17T09:10:07.017Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" },
{ url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" },
{ url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" },
{ url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" },
{ url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" },
{ url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" },
{ url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" },
{ url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" },
{ url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" },
{ url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" },
{ url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" },
{ url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" },
{ url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" },
{ url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" },
{ url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" },
{ url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" },
{ url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" },
{ url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" },
{ url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" },
{ url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" },
{ url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" },
{ url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" },
{ url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" },
{ url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" },
{ url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" },
{ url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" },
{ url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" },
{ url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
{ url = "https://files.pythonhosted.org/packages/d0/96/a50ccb6b539ae780f73cea74905468777680e30c6c3bdf714b9d4c116ea0/pydantic_core-2.46.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:4f59b45f3ef8650c0c736a57f59031d47ed9df4c0a64e83796849d7d14863a2d", size = 2097111, upload-time = "2026-04-17T09:10:49.617Z" },
{ url = "https://files.pythonhosted.org/packages/34/5f/fdead7b3afa822ab6e5a18ee0ecffd54937de1877c01ed13a342e0fb3f07/pydantic_core-2.46.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3a075a29ebef752784a91532a1a85be6b234ccffec0a9d7978a92696387c3da6", size = 1951904, upload-time = "2026-04-17T09:12:32.062Z" },
{ url = "https://files.pythonhosted.org/packages/95/e0/1c5d547e550cdab1bec737492aa08865337af6fe7fc9b96f7f45f17d9519/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d12d786e30c04a9d307c5d7080bf720d9bac7f1668191d8e37633a9562749e2", size = 1978667, upload-time = "2026-04-17T09:11:35.589Z" },
{ url = "https://files.pythonhosted.org/packages/0e/cb/665ce629e218c8228302cb94beff4f6531082a2c87d3ecc3d5e63a26f392/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0d5e6d6343b0b5dcacb3503b5de90022968da8ed0ab9ab39d3eda71c20cbf84e", size = 2046721, upload-time = "2026-04-17T09:11:47.725Z" },
{ url = "https://files.pythonhosted.org/packages/77/e9/6cb2cf60f54c1472bbdfce19d957553b43dbba79d1d7b2930a195c594785/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:233eebac0999b6b9ba76eb56f3ec8fce13164aa16b6d2225a36a79e0f95b5973", size = 2228483, upload-time = "2026-04-17T09:12:08.837Z" },
{ url = "https://files.pythonhosted.org/packages/0d/2a/93e018dd5571f781ebaeda8c0cf65398489d5bee9b1f484df0b6149b43b9/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cc0eee720dd2f14f3b7c349469402b99ad81a174ab49d3533974529e9d93992", size = 2294663, upload-time = "2026-04-17T09:12:52.053Z" },
{ url = "https://files.pythonhosted.org/packages/5e/4f/49e57ca55c770c93d9bb046666a54949b42e3c9099a0c5fe94557873fe30/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83ee76bf2c9910513dbc19e7d82367131fa7508dedd6186a462393071cc11059", size = 2098742, upload-time = "2026-04-17T09:13:45.472Z" },
{ url = "https://files.pythonhosted.org/packages/c6/b0/6e46b5cd3332af665f794b8cdeea206618a8630bd9e7bcc36864518fce81/pydantic_core-2.46.2-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:d61db38eb4ee5192f0c261b7f2d38e420b554df8912245e3546aee5c45e2fd78", size = 2125922, upload-time = "2026-04-17T09:12:54.304Z" },
{ url = "https://files.pythonhosted.org/packages/06/d1/40850c81585be443a2abfdf7f795f8fae831baf8e2f9b2133c8246ac671c/pydantic_core-2.46.2-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8f09a713d17bcd55da8ab02ebd9110c5246a49c44182af213b5212800af8bc83", size = 2183000, upload-time = "2026-04-17T09:10:59.027Z" },
{ url = "https://files.pythonhosted.org/packages/04/af/8493d7dfa03ebb7866909e577c6aa65ea0de7377b86023cc51d0c8e11db3/pydantic_core-2.46.2-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:30cacc5fb696e64b8ef6fd31d9549d394dd7d52760db072eecb98e37e3af1677", size = 2180335, upload-time = "2026-04-17T09:12:57.01Z" },
{ url = "https://files.pythonhosted.org/packages/72/5b/1f6a344c4ffdf284da41c6067b82d5ebcbd11ce1b515ae4b662d4adb6f61/pydantic_core-2.46.2-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:7ccfb105fcfe91a22bbb5563ad3dc124bc1aa75bfd2e53a780ab05f78cdf6108", size = 2330002, upload-time = "2026-04-17T09:12:02.958Z" },
{ url = "https://files.pythonhosted.org/packages/25/ff/9a694126c12d6d2f48a0cafa6f8eef88ef0d8825600e18d03ff2e896c3b2/pydantic_core-2.46.2-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:13ffef637dc8370c249e5b26bd18e9a80a4fca3d809618c44e18ec834a7ca7a8", size = 2359920, upload-time = "2026-04-17T09:10:27.764Z" },
{ url = "https://files.pythonhosted.org/packages/51/c8/3a35c763d68a9cb2675eb10ef242cf66c5d4701b28ae12e688d67d2c180e/pydantic_core-2.46.2-cp314-cp314-win32.whl", hash = "sha256:1b0ab6d756ca2704a938e6c31b53f290c2f9c10d3914235410302a149de1a83e", size = 1953701, upload-time = "2026-04-17T09:13:30.021Z" },
{ url = "https://files.pythonhosted.org/packages/1a/6a/f2726a780365f7dfd89d62036f984f7acb99978c60c5e1fa7c0cb898ed11/pydantic_core-2.46.2-cp314-cp314-win_amd64.whl", hash = "sha256:99ebade8c9ada4df975372d8dd25883daa0e379a05f1cd0c99aa0c04368d01a6", size = 2071867, upload-time = "2026-04-17T09:10:39.205Z" },
{ url = "https://files.pythonhosted.org/packages/e1/79/76baacb9feba3d7c399b245ca1a29c74ea0db04ea693811374827eec2290/pydantic_core-2.46.2-cp314-cp314-win_arm64.whl", hash = "sha256:de87422197cf7f83db91d89c86a21660d749b3cd76cd8a45d115b8e675670f02", size = 2017252, upload-time = "2026-04-17T09:10:26.175Z" },
{ url = "https://files.pythonhosted.org/packages/f1/3b/77c26938f817668d9ad9bab1a905cb23f11d9a3d4bf724d429b3e55a8eaf/pydantic_core-2.46.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:236f22b4a206b5b61db955396b7cf9e2e1ff77f372efe9570128ccfcd6a525eb", size = 2094545, upload-time = "2026-04-17T09:12:19.339Z" },
{ url = "https://files.pythonhosted.org/packages/fe/de/42c13f590e3c260966aa49bcdb1674774f975467c49abd51191e502bea28/pydantic_core-2.46.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c2012f64d2cd7cca50f49f22445aa5a88691ac2b4498ee0a9a977f8ca4f7289f", size = 1933953, upload-time = "2026-04-17T09:09:55.889Z" },
{ url = "https://files.pythonhosted.org/packages/4e/84/ebe3ebb3e2d8db656937cfa6f97f544cb7132f2307a4a7dfdcd0ea102a12/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d07d6c63106d3a9c9a333e2636f9c82c703b1a9e3b079299e58747964e4fdb72", size = 1974435, upload-time = "2026-04-17T09:10:12.371Z" },
{ url = "https://files.pythonhosted.org/packages/b9/15/0bf51ca6709477cd4ef86148b6d7844f3308f029eac361dd0383f1e17b1a/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c326a2b4b85e959d9a1fc3a11f32f84611b6ec07c053e1828a860edf8d068208", size = 2031113, upload-time = "2026-04-17T09:10:00.752Z" },
{ url = "https://files.pythonhosted.org/packages/02/ae/b7b5af9b79db036d9e61a44c481c17a213dc8fc4b8b71fe6875a72fc778b/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac8a65e798f2462552c00d2e013d532c94d646729dda98458beaf51f9ec7b120", size = 2236325, upload-time = "2026-04-17T09:10:33.227Z" },
{ url = "https://files.pythonhosted.org/packages/a6/ae/ecef7477b5a03d4a499708f7e75d2836452ebb70b776c2d64612b334f57a/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a3c2bc1cc8164bedbc160b7bb1e8cc1e8b9c27f69ae4f9ae2b976cdae02b2dd", size = 2278135, upload-time = "2026-04-17T09:10:23.287Z" },
{ url = "https://files.pythonhosted.org/packages/db/e4/2f9d82faa47af6c39fc3f120145fd915971e1e0cb6b55b494fad9fdf8275/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69aa5e10b7e8b1bb4a6888650fd12fcbf11d396ca11d4a44de1450875702830", size = 2109071, upload-time = "2026-04-17T09:11:06.149Z" },
{ url = "https://files.pythonhosted.org/packages/f1/9c/677cf10873fbd0b116575ab7b97c90482b21564f8a8040beb18edef7a577/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4e6df5c3301e65fb42bc5338bf9a1027a02b0a31dc7f54c33775229af474daf0", size = 2106028, upload-time = "2026-04-17T09:10:51.525Z" },
{ url = "https://files.pythonhosted.org/packages/d6/53/6a06183544daba51c059123a2064a99039df25f115a06bdb26f2ea177038/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2c2f6e32548ac8d559b47944effcf8ae4d81c161f6b6c885edc53bc08b8f192d", size = 2164816, upload-time = "2026-04-17T09:11:56.187Z" },
{ url = "https://files.pythonhosted.org/packages/57/6f/10fcdd9e3eca66fc828eef0f6f5850f2dd3bca2c59e6e041fb8bc3da39be/pydantic_core-2.46.2-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:b089a81c58e6ea0485562bbbbbca4f65c0549521606d5ef27fba217aac9b665a", size = 2166130, upload-time = "2026-04-17T09:10:03.804Z" },
{ url = "https://files.pythonhosted.org/packages/29/83/92d3fd0e0156cad2e3cb5c26de73794af78ac9fa0c22ab666e566dd67061/pydantic_core-2.46.2-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:7f700a6d6f64112ae9193709b84303bbab84424ad4b47d0253301aabce9dfc70", size = 2316605, upload-time = "2026-04-17T09:12:45.249Z" },
{ url = "https://files.pythonhosted.org/packages/97/f1/facffdb970981068219582e499b8d0871ed163ffcc6b347de5c412669e4c/pydantic_core-2.46.2-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:67db6814beaa5fefe91101ec7eb9efda613795767be96f7cf58b1ca8c9ca9972", size = 2358385, upload-time = "2026-04-17T09:09:54.657Z" },
{ url = "https://files.pythonhosted.org/packages/8b/a1/b8160b2f22b2199467bc68581a4ed380643c16b348a27d6165c6c242d694/pydantic_core-2.46.2-cp314-cp314t-win32.whl", hash = "sha256:32fbc7447be8e3be99bf7869f7066308f16be55b61f9882c2cefc7931f5c7664", size = 1942373, upload-time = "2026-04-17T09:12:59.594Z" },
{ url = "https://files.pythonhosted.org/packages/0d/90/db89acabe5b150e11d1b59fe3d947dda2ef6abbfef5c82f056ff63802f5d/pydantic_core-2.46.2-cp314-cp314t-win_amd64.whl", hash = "sha256:b317a2b97019c0b95ce99f4f901ae383f40132da6706cdf1731066a73394c25c", size = 2052078, upload-time = "2026-04-17T09:10:19.96Z" },
{ url = "https://files.pythonhosted.org/packages/97/32/e19b83ceb07a3f1bb21798407790bbc9a31740158fd132b94139cb84e16c/pydantic_core-2.46.2-cp314-cp314t-win_arm64.whl", hash = "sha256:7dcb9d40930dfad7ab6b20bcc6ca9d2b030b0f347a0cd9909b54bd53ead521b1", size = 2016941, upload-time = "2026-04-17T09:12:34.447Z" },
]
[[package]]
@@ -3055,11 +3057,11 @@ wheels = [
[[package]]
name = "python-dotenv"
version = "1.2.1"
version = "1.2.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" }
sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
{ url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" },
]
[[package]]
@@ -3259,27 +3261,27 @@ wheels = [
[[package]]
name = "ruff"
version = "0.15.10"
version = "0.15.11"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e7/d9/aa3f7d59a10ef6b14fe3431706f854dbf03c5976be614a9796d36326810c/ruff-0.15.10.tar.gz", hash = "sha256:d1f86e67ebfdef88e00faefa1552b5e510e1d35f3be7d423dc7e84e63788c94e", size = 4631728, upload-time = "2026-04-09T14:06:09.884Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e4/8d/192f3d7103816158dfd5ea50d098ef2aec19194e6cbccd4b3485bdb2eb2d/ruff-0.15.11.tar.gz", hash = "sha256:f092b21708bf0e7437ce9ada249dfe688ff9a0954fc94abab05dcea7dcd29c33", size = 4637264, upload-time = "2026-04-16T18:46:26.58Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/eb/00/a1c2fdc9939b2c03691edbda290afcd297f1f389196172826b03d6b6a595/ruff-0.15.10-py3-none-linux_armv6l.whl", hash = "sha256:0744e31482f8f7d0d10a11fcbf897af272fefdfcb10f5af907b18c2813ff4d5f", size = 10563362, upload-time = "2026-04-09T14:06:21.189Z" },
{ url = "https://files.pythonhosted.org/packages/5c/15/006990029aea0bebe9d33c73c3e28c80c391ebdba408d1b08496f00d422d/ruff-0.15.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1e7c16ea0ff5a53b7c2df52d947e685973049be1cdfe2b59a9c43601897b22e", size = 10951122, upload-time = "2026-04-09T14:06:02.236Z" },
{ url = "https://files.pythonhosted.org/packages/f2/c0/4ac978fe874d0618c7da647862afe697b281c2806f13ce904ad652fa87e4/ruff-0.15.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93cc06a19e5155b4441dd72808fdf84290d84ad8a39ca3b0f994363ade4cebb1", size = 10314005, upload-time = "2026-04-09T14:06:00.026Z" },
{ url = "https://files.pythonhosted.org/packages/da/73/c209138a5c98c0d321266372fc4e33ad43d506d7e5dd817dd89b60a8548f/ruff-0.15.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e1dd04312997c99ea6965df66a14fb4f03ba978564574ffc68b0d61fd3989e", size = 10643450, upload-time = "2026-04-09T14:05:42.137Z" },
{ url = "https://files.pythonhosted.org/packages/ec/76/0deec355d8ec10709653635b1f90856735302cb8e149acfdf6f82a5feb70/ruff-0.15.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8154d43684e4333360fedd11aaa40b1b08a4e37d8ffa9d95fee6fa5b37b6fab1", size = 10379597, upload-time = "2026-04-09T14:05:49.984Z" },
{ url = "https://files.pythonhosted.org/packages/dc/be/86bba8fc8798c081e28a4b3bb6d143ccad3fd5f6f024f02002b8f08a9fa3/ruff-0.15.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab88715f3a6deb6bde6c227f3a123410bec7b855c3ae331b4c006189e895cef", size = 11146645, upload-time = "2026-04-09T14:06:12.246Z" },
{ url = "https://files.pythonhosted.org/packages/a8/89/140025e65911b281c57be1d385ba1d932c2366ca88ae6663685aed8d4881/ruff-0.15.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a768ff5969b4f44c349d48edf4ab4f91eddb27fd9d77799598e130fb628aa158", size = 12030289, upload-time = "2026-04-09T14:06:04.776Z" },
{ url = "https://files.pythonhosted.org/packages/88/de/ddacca9545a5e01332567db01d44bd8cf725f2db3b3d61a80550b48308ea/ruff-0.15.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ee3ef42dab7078bda5ff6a1bcba8539e9857deb447132ad5566a038674540d0", size = 11496266, upload-time = "2026-04-09T14:05:55.485Z" },
{ url = "https://files.pythonhosted.org/packages/bc/bb/7ddb00a83760ff4a83c4e2fc231fd63937cc7317c10c82f583302e0f6586/ruff-0.15.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51cb8cc943e891ba99989dd92d61e29b1d231e14811db9be6440ecf25d5c1609", size = 11256418, upload-time = "2026-04-09T14:05:57.69Z" },
{ url = "https://files.pythonhosted.org/packages/dc/8d/55de0d35aacf6cd50b6ee91ee0f291672080021896543776f4170fc5c454/ruff-0.15.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e59c9bdc056a320fb9ea1700a8d591718b8faf78af065484e801258d3a76bc3f", size = 11288416, upload-time = "2026-04-09T14:05:44.695Z" },
{ url = "https://files.pythonhosted.org/packages/68/cf/9438b1a27426ec46a80e0a718093c7f958ef72f43eb3111862949ead3cc1/ruff-0.15.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:136c00ca2f47b0018b073f28cb5c1506642a830ea941a60354b0e8bc8076b151", size = 10621053, upload-time = "2026-04-09T14:05:52.782Z" },
{ url = "https://files.pythonhosted.org/packages/4c/50/e29be6e2c135e9cd4cb15fbade49d6a2717e009dff3766dd080fcb82e251/ruff-0.15.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b80a2f3c9c8a950d6237f2ca12b206bccff626139be9fa005f14feb881a1ae8", size = 10378302, upload-time = "2026-04-09T14:06:14.361Z" },
{ url = "https://files.pythonhosted.org/packages/18/2f/e0b36a6f99c51bb89f3a30239bc7bf97e87a37ae80aa2d6542d6e5150364/ruff-0.15.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e3e53c588164dc025b671c9df2462429d60357ea91af7e92e9d56c565a9f1b07", size = 10850074, upload-time = "2026-04-09T14:06:16.581Z" },
{ url = "https://files.pythonhosted.org/packages/11/08/874da392558ce087a0f9b709dc6ec0d60cbc694c1c772dab8d5f31efe8cb/ruff-0.15.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b0c52744cf9f143a393e284125d2576140b68264a93c6716464e129a3e9adb48", size = 11358051, upload-time = "2026-04-09T14:06:18.948Z" },
{ url = "https://files.pythonhosted.org/packages/e4/46/602938f030adfa043e67112b73821024dc79f3ab4df5474c25fa4c1d2d14/ruff-0.15.10-py3-none-win32.whl", hash = "sha256:d4272e87e801e9a27a2e8df7b21011c909d9ddd82f4f3281d269b6ba19789ca5", size = 10588964, upload-time = "2026-04-09T14:06:07.14Z" },
{ url = "https://files.pythonhosted.org/packages/25/b6/261225b875d7a13b33a6d02508c39c28450b2041bb01d0f7f1a83d569512/ruff-0.15.10-py3-none-win_amd64.whl", hash = "sha256:28cb32d53203242d403d819fd6983152489b12e4a3ae44993543d6fe62ab42ed", size = 11745044, upload-time = "2026-04-09T14:05:39.473Z" },
{ url = "https://files.pythonhosted.org/packages/58/ed/dea90a65b7d9e69888890fb14c90d7f51bf0c1e82ad800aeb0160e4bacfd/ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188", size = 11035607, upload-time = "2026-04-09T14:05:47.593Z" },
{ url = "https://files.pythonhosted.org/packages/02/1e/6aca3427f751295ab011828e15e9bf452200ac74484f1db4be0197b8170b/ruff-0.15.11-py3-none-linux_armv6l.whl", hash = "sha256:e927cfff503135c558eb581a0c9792264aae9507904eb27809cdcff2f2c847b7", size = 10607943, upload-time = "2026-04-16T18:46:05.967Z" },
{ url = "https://files.pythonhosted.org/packages/e7/26/1341c262e74f36d4e84f3d6f4df0ac68cd53331a66bfc5080daa17c84c0b/ruff-0.15.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7a1b5b2938d8f890b76084d4fa843604d787a912541eae85fd7e233398bbb73e", size = 10988592, upload-time = "2026-04-16T18:46:00.742Z" },
{ url = "https://files.pythonhosted.org/packages/03/71/850b1d6ffa9564fbb6740429bad53df1094082fe515c8c1e74b6d8d05f18/ruff-0.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d4176f3d194afbdaee6e41b9ccb1a2c287dba8700047df474abfbe773825d1cb", size = 10338501, upload-time = "2026-04-16T18:46:03.723Z" },
{ url = "https://files.pythonhosted.org/packages/f2/11/cc1284d3e298c45a817a6aadb6c3e1d70b45c9b36d8d9cce3387b495a03a/ruff-0.15.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b17c886fb88203ced3afe7f14e8d5ae96e9d2f4ccc0ee66aa19f2c2675a27e4", size = 10670693, upload-time = "2026-04-16T18:46:41.941Z" },
{ url = "https://files.pythonhosted.org/packages/ce/9e/f8288b034ab72b371513c13f9a41d9ba3effac54e24bfb467b007daee2ca/ruff-0.15.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49fafa220220afe7758a487b048de4c8f9f767f37dfefad46b9dd06759d003eb", size = 10416177, upload-time = "2026-04-16T18:46:21.717Z" },
{ url = "https://files.pythonhosted.org/packages/85/71/504d79abfd3d92532ba6bbe3d1c19fada03e494332a59e37c7c2dabae427/ruff-0.15.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2ab8427e74a00d93b8bda1307b1e60970d40f304af38bccb218e056c220120d", size = 11221886, upload-time = "2026-04-16T18:46:15.086Z" },
{ url = "https://files.pythonhosted.org/packages/43/5a/947e6ab7a5ad603d65b474be15a4cbc6d29832db5d762cd142e4e3a74164/ruff-0.15.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:195072c0c8e1fc8f940652073df082e37a5d9cb43b4ab1e4d0566ab8977a13b7", size = 12075183, upload-time = "2026-04-16T18:46:07.944Z" },
{ url = "https://files.pythonhosted.org/packages/9f/a1/0b7bb6268775fdd3a0818aee8efd8f5b4e231d24dd4d528ced2534023182/ruff-0.15.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a0996d486af3920dec930a2e7daed4847dfc12649b537a9335585ada163e9e", size = 11516575, upload-time = "2026-04-16T18:46:31.687Z" },
{ url = "https://files.pythonhosted.org/packages/30/c3/bb5168fc4d233cc06e95f482770d0f3c87945a0cd9f614b90ea8dc2f2833/ruff-0.15.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bef2cb556d509259f1fe440bb9cd33c756222cf0a7afe90d15edf0866702431", size = 11306537, upload-time = "2026-04-16T18:46:36.988Z" },
{ url = "https://files.pythonhosted.org/packages/e4/92/4cfae6441f3967317946f3b788136eecf093729b94d6561f963ed810c82e/ruff-0.15.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:030d921a836d7d4a12cf6e8d984a88b66094ccb0e0f17ddd55067c331191bf19", size = 11296813, upload-time = "2026-04-16T18:46:24.182Z" },
{ url = "https://files.pythonhosted.org/packages/43/26/972784c5dde8313acde8ac71ba8ac65475b85db4a2352a76c9934361f9bc/ruff-0.15.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e783b599b4577788dbbb66b9addcef87e9a8832f4ce0c19e34bf55543a2f890", size = 10633136, upload-time = "2026-04-16T18:46:39.802Z" },
{ url = "https://files.pythonhosted.org/packages/5b/53/3985a4f185020c2f367f2e08a103032e12564829742a1b417980ce1514a0/ruff-0.15.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ae90592246625ba4a34349d68ec28d4400d75182b71baa196ddb9f82db025ef5", size = 10424701, upload-time = "2026-04-16T18:46:10.381Z" },
{ url = "https://files.pythonhosted.org/packages/d3/57/bf0dfb32241b56c83bb663a826133da4bf17f682ba8c096973065f6e6a68/ruff-0.15.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1f111d62e3c983ed20e0ca2e800f8d77433a5b1161947df99a5c2a3fb60514f0", size = 10873887, upload-time = "2026-04-16T18:46:29.157Z" },
{ url = "https://files.pythonhosted.org/packages/02/05/e48076b2a57dc33ee8c7a957296f97c744ca891a8ffb4ffb1aaa3b3f517d/ruff-0.15.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:06f483d6646f59eaffba9ae30956370d3a886625f511a3108994000480621d1c", size = 11404316, upload-time = "2026-04-16T18:46:19.462Z" },
{ url = "https://files.pythonhosted.org/packages/88/27/0195d15fe7a897cbcba0904792c4b7c9fdd958456c3a17d2ea6093716a9a/ruff-0.15.11-py3-none-win32.whl", hash = "sha256:476a2aa56b7da0b73a3ee80b6b2f0e19cce544245479adde7baa65466664d5f3", size = 10655535, upload-time = "2026-04-16T18:46:12.47Z" },
{ url = "https://files.pythonhosted.org/packages/3a/5e/c927b325bd4c1d3620211a4b96f47864633199feed60fa936025ab27e090/ruff-0.15.11-py3-none-win_amd64.whl", hash = "sha256:8b6756d88d7e234fb0c98c91511aae3cd519d5e3ed271cae31b20f39cb2a12a3", size = 11779692, upload-time = "2026-04-16T18:46:17.268Z" },
{ url = "https://files.pythonhosted.org/packages/63/b6/aeadee5443e49baa2facd51131159fd6301cc4ccfc1541e4df7b021c37dd/ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4", size = 11032614, upload-time = "2026-04-16T18:46:34.487Z" },
]
[[package]]
@@ -3308,7 +3310,7 @@ wheels = [
[[package]]
name = "selenium"
version = "4.41.0"
version = "4.43.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
@@ -3318,22 +3320,22 @@ dependencies = [
{ name = "urllib3", extra = ["socks"] },
{ name = "websocket-client" },
]
sdist = { url = "https://files.pythonhosted.org/packages/04/7c/133d00d6d013a17d3f39199f27f1a780ec2e95d7b9aa997dc1b8ac2e62a7/selenium-4.41.0.tar.gz", hash = "sha256:003e971f805231ad63e671783a2b91a299355d10cefb9de964c36ff3819115aa", size = 937872, upload-time = "2026-02-20T03:42:06.216Z" }
sdist = { url = "https://files.pythonhosted.org/packages/09/6a/fe950b498a3c570ab538ad1c2b60f18863eecf077a865eea4459f3fa78a9/selenium-4.43.0.tar.gz", hash = "sha256:bada5c08a989f812728a4b5bea884d8e91894e939a441cc3a025201ce718581e", size = 967747, upload-time = "2026-04-10T06:47:03.149Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a8/d6/e4160989ef6b272779af6f3e5c43c3ba9be6687bdc21c68c3fb220e555b3/selenium-4.41.0-py3-none-any.whl", hash = "sha256:b8ccde8d2e7642221ca64af184a92c19eee6accf2e27f20f30472f5efae18eb1", size = 9532858, upload-time = "2026-02-20T03:42:03.218Z" },
{ url = "https://files.pythonhosted.org/packages/82/c7/0c55fbb0275fc368676ea50514ce7d7839d799a8b3ff8425f380186c7626/selenium-4.43.0-py3-none-any.whl", hash = "sha256:4f97639055dcfa9eadf8ccf549ba7b0e49c655d4e2bde19b9a44e916b754e769", size = 9573091, upload-time = "2026-04-10T06:47:01.134Z" },
]
[[package]]
name = "sentry-sdk"
version = "2.57.0"
version = "2.58.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/4f/87/46c0406d8b5ddd026f73adaf5ab75ce144219c41a4830b52df4b9ab55f7f/sentry_sdk-2.57.0.tar.gz", hash = "sha256:4be8d1e71c32fb27f79c577a337ac8912137bba4bcbc64a4ec1da4d6d8dc5199", size = 435288, upload-time = "2026-03-31T09:39:29.264Z" }
sdist = { url = "https://files.pythonhosted.org/packages/26/b3/fb8291170d0e844173164709fc0fa0c221ed75a5da740c8746f2a83b4eb1/sentry_sdk-2.58.0.tar.gz", hash = "sha256:c1144d947352d54e5b7daa63596d9f848adf684989c06c4f5a659f0c85a18f6f", size = 438764, upload-time = "2026-04-13T17:23:26.265Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c9/64/982e07b93219cb52e1cca5d272cb579e2f3eb001956c9e7a9a6d106c9473/sentry_sdk-2.57.0-py2.py3-none-any.whl", hash = "sha256:812c8bf5ff3d2f0e89c82f5ce80ab3a6423e102729c4706af7413fd1eb480585", size = 456489, upload-time = "2026-03-31T09:39:27.524Z" },
{ url = "https://files.pythonhosted.org/packages/fa/eb/d875669993b762556ae8b2efd86219943b4c0864d22204d622a9aee3052b/sentry_sdk-2.58.0-py2.py3-none-any.whl", hash = "sha256:688d1c704ddecf382ea3326f21a67453d4caa95592d722b7c780a36a9d23109e", size = 460919, upload-time = "2026-04-13T17:23:24.675Z" },
]
[[package]]
@@ -3517,7 +3519,7 @@ wheels = [
[[package]]
name = "twilio"
version = "9.10.4"
version = "9.10.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@@ -3525,9 +3527,9 @@ dependencies = [
{ name = "pyjwt" },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d7/28/9ffb8f01fc014bf634f0731bd562fed6209bc54d4249c69acfe68cf70302/twilio-9.10.4.tar.gz", hash = "sha256:2125e0027288903e7e8954ae6d5b1b593a205c28b3d72fa3fdd0b5f25825de04", size = 1637481, upload-time = "2026-03-24T13:04:11.822Z" }
sdist = { url = "https://files.pythonhosted.org/packages/b5/97/c439bc2c058f8a24edd732f5cc82adedd8794bcc2da0836c2eff1e2dbe91/twilio-9.10.5.tar.gz", hash = "sha256:d9f93b9280349ee7b52e7f17a0600fd7bfd0f7ff88eb00c40270164bc058743f", size = 1641690, upload-time = "2026-04-14T09:52:09.392Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bc/3c/3296ee9f141b83623ff2c6c4d22093f7d2a8ea7455f98f705ee5f969bcd7/twilio-9.10.4-py2.py3-none-any.whl", hash = "sha256:5e8414a88c009f40b2ab2a1f00232f5f459f724c7b4bdf25a935c7d7b297a8a3", size = 2279546, upload-time = "2026-03-24T13:04:10.383Z" },
{ url = "https://files.pythonhosted.org/packages/4a/97/4fdde5e54fcbb789aa1a70c371f2f33d7eb1e58e8a6131fdbd8a98490976/twilio-9.10.5-py2.py3-none-any.whl", hash = "sha256:7972db54496fbf501b238f34d1f717f80ff22720313dc706632787aad5934997", size = 2284944, upload-time = "2026-04-14T09:52:07.333Z" },
]
[[package]]

441
web/package-lock.json generated
View File

@@ -21,7 +21,7 @@
"@codemirror/theme-one-dark": "^6.1.3",
"@eslint/js": "^9.39.3",
"@floating-ui/dom": "^1.7.6",
"@formatjs/intl-listformat": "^8.3.1",
"@formatjs/intl-listformat": "^8.3.2",
"@fortawesome/fontawesome-free": "^7.2.0",
"@goauthentik/api": "0.0.0",
"@goauthentik/core": "^1.0.0",
@@ -43,7 +43,7 @@
"@patternfly/elements": "^4.3.1",
"@patternfly/patternfly": "^4.224.2",
"@playwright/test": "^1.59.1",
"@sentry/browser": "^10.47.0",
"@sentry/browser": "^10.48.0",
"@storybook/addon-docs": "^10.3.5",
"@storybook/addon-links": "^10.3.5",
"@storybook/web-components": "^10.3.5",
@@ -51,7 +51,7 @@
"@types/codemirror": "^5.60.17",
"@types/grecaptcha": "^3.0.9",
"@types/guacamole-common-js": "^1.5.5",
"@types/node": "^25.5.2",
"@types/node": "^25.6.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.57.2",
@@ -75,10 +75,10 @@
"eslint-plugin-lit": "^2.2.1",
"eslint-plugin-wc": "^3.1.0",
"fuse.js": "^7.3.0",
"globals": "^17.4.0",
"globals": "^17.5.0",
"guacamole-common-js": "^1.5.0",
"hastscript": "^9.0.1",
"knip": "^6.3.1",
"knip": "^6.4.1",
"lex": "^2025.11.0",
"lit": "^3.3.2",
"lit-analyzer": "^2.0.3",
@@ -91,7 +91,7 @@
"pino": "^10.3.1",
"pino-pretty": "^13.1.2",
"playwright": "^1.58.2",
"prettier": "^3.8.2",
"prettier": "^3.8.3",
"prettier-plugin-packagejson": "^3.0.2",
"pseudolocale": "^2.2.0",
"rapidoc": "^9.3.8",
@@ -111,7 +111,7 @@
"ts-pattern": "^5.9.0",
"turnstile-types": "^1.2.3",
"type-fest": "^5.5.0",
"typescript": "^6.0.2",
"typescript": "^6.0.3",
"typescript-eslint": "^8.57.2",
"unist-util-visit": "^5.1.0",
"vite": "^8.0.8",
@@ -131,7 +131,7 @@
"@rollup/rollup-darwin-arm64": "^4.57.1",
"@rollup/rollup-linux-arm64-gnu": "^4.57.1",
"@rollup/rollup-linux-x64-gnu": "^4.57.1",
"chromedriver": "^147.0.2"
"chromedriver": "^147.0.4"
}
},
"node_modules/@adobe/css-tools": {
@@ -1304,46 +1304,28 @@
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
"license": "MIT"
},
"node_modules/@formatjs/bigdecimal": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@formatjs/bigdecimal/-/bigdecimal-0.2.0.tgz",
"integrity": "sha512-GeaxHZbUoYvHL9tC5eltHLs+1zU70aPw0s7LwqgktIzF5oMhNY4o4deEtusJMsq7WFJF3Ye2zQEzdG8beVk73w==",
"license": "MIT"
},
"node_modules/@formatjs/ecma402-abstract": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-3.2.0.tgz",
"integrity": "sha512-dHnqHgBo6GXYGRsepaE1wmsC2etaivOWd5VaJstZd+HI2zR3DCUjbDVZRtoPGkkXZmyHvBwrdEUuqfvzhF/DtQ==",
"license": "MIT",
"dependencies": {
"@formatjs/bigdecimal": "0.2.0",
"@formatjs/fast-memoize": "3.1.1",
"@formatjs/intl-localematcher": "0.8.2"
}
},
"node_modules/@formatjs/fast-memoize": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.1.tgz",
"integrity": "sha512-CbNbf+tlJn1baRnPkNePnBqTLxGliG6DDgNa/UtV66abwIjwsliPMOt0172tzxABYzSuxZBZfcp//qI8AvBWPg==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.2.tgz",
"integrity": "sha512-vPnriihkfK0lzoQGaXq+qXH23VsYyansRTkTgo2aTG0k1NjLFyZimFVdfj4C9JkSE5dm7CEngcQ5TTc1yAyBfQ==",
"license": "MIT"
},
"node_modules/@formatjs/intl-listformat": {
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-8.3.1.tgz",
"integrity": "sha512-82pPPyPOzkFMBbUmuseMwckFtpT+meZSxZSaxyD/3wRo5DZEq08hBUe1uhzNmukI8hwJFH5mIMBwSIAuse0RDQ==",
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-8.3.2.tgz",
"integrity": "sha512-PH8V1YVSm+AcrQ1D9Th5wxWo74vDs1V1rRGz9PCFm1HTBtZLVMN3P/+tDcpizQBvvdGNwUqHBOdxN5ZZtlq2bQ==",
"license": "MIT",
"dependencies": {
"@formatjs/ecma402-abstract": "3.2.0",
"@formatjs/intl-localematcher": "0.8.2"
"@formatjs/intl-localematcher": "0.8.3"
}
},
"node_modules/@formatjs/intl-localematcher": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.2.tgz",
"integrity": "sha512-q05KMYGJLyqFNFtIb8NhWLF5X3aK/k0wYt7dnRFuy6aLQL+vUwQ1cg5cO4qawEiINybeCPXAWlprY2mSBjSXAQ==",
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.3.tgz",
"integrity": "sha512-pHUjWb9NuhnMs8+PxQdzBtZRFJHlGhrURGAbm6Ltwl82BFajeuiIR3jblSa7ia3r62rXe/0YtVpUG3xWr5bFCA==",
"license": "MIT",
"dependencies": {
"@formatjs/fast-memoize": "3.1.1"
"@formatjs/fast-memoize": "3.1.2"
}
},
"node_modules/@fortawesome/fontawesome-free": {
@@ -3609,75 +3591,75 @@
"license": "MIT"
},
"node_modules/@sentry-internal/browser-utils": {
"version": "10.47.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.47.0.tgz",
"integrity": "sha512-bVFRAeJWMBcBCvJKIFCMJ1/yQToL4vPGqfmlnDZeypcxkqUDKQ/Y3ziLHXoDL2sx0lagcgU2vH1QhCQ67Aujjw==",
"version": "10.48.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.48.0.tgz",
"integrity": "sha512-SCiTLBXzugFKxev6NoKYBIhQoDk0gUh0AVVVepCBqfCJiWBG01Zvv0R5tCVohr4cWRllkQ8mlBdNQd/I7s9tdA==",
"license": "MIT",
"dependencies": {
"@sentry/core": "10.47.0"
"@sentry/core": "10.48.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/feedback": {
"version": "10.47.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.47.0.tgz",
"integrity": "sha512-pdvMmi4dQpX5S/vAAzrhHPIw3T3HjUgDNgUiCBrlp7N9/6zGO2gNPhUnNekP+CjgI/z0rvf49RLqlDenpNrMOg==",
"version": "10.48.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.48.0.tgz",
"integrity": "sha512-tGkEyOM1HDS9qebDphUMEnyk3qq/50AnuTBiFmMJyjNzowylVGmRRk0sr3xkmbVHCDXQCiYnDmSVlJ2x4SDMrQ==",
"license": "MIT",
"dependencies": {
"@sentry/core": "10.47.0"
"@sentry/core": "10.48.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/replay": {
"version": "10.47.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.47.0.tgz",
"integrity": "sha512-ScdovxP7hJxgMt70+7hFvwT02GIaIUAxdEM/YPsayZBeCoAukPW8WiwztJfoKtsfPyKJ5A6f0H3PIxTPcA9Row==",
"version": "10.48.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.48.0.tgz",
"integrity": "sha512-sevRTePfuk4PNuz9KAKpmTZEomAU0aLXyIhOwA0OnUDdxPhkY8kq5lwDbuxTHv6DQUjUX3YgFbY45VH1JEqHKA==",
"license": "MIT",
"dependencies": {
"@sentry-internal/browser-utils": "10.47.0",
"@sentry/core": "10.47.0"
"@sentry-internal/browser-utils": "10.48.0",
"@sentry/core": "10.48.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/replay-canvas": {
"version": "10.47.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.47.0.tgz",
"integrity": "sha512-A5OY8friSe6g8WAK4L8IeOPiEd9D3Ps40DzRH5j2f6SUja0t90mKMvHRcRf8zq0d4BkdB+JM7tjOkwxpuv8heA==",
"version": "10.48.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.48.0.tgz",
"integrity": "sha512-9nWuN2z4O+iwbTfuYV5ZmngBgJU/ZxfOo47A5RJP3Nu/kl59aJ1lUhILYOKyeNOIC/JyeERmpIcTxnlPXQzZ3Q==",
"license": "MIT",
"dependencies": {
"@sentry-internal/replay": "10.47.0",
"@sentry/core": "10.47.0"
"@sentry-internal/replay": "10.48.0",
"@sentry/core": "10.48.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/browser": {
"version": "10.47.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.47.0.tgz",
"integrity": "sha512-rC0agZdxKA5XWfL4VwPOr/rJMogXDqZgnVzr93YWpFn9DMZT/7LzxSJVPIJwRUjx3bFEby3PcTa3YaX7pxm1AA==",
"version": "10.48.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.48.0.tgz",
"integrity": "sha512-4jt2zX2ExgFcNe2x+W+/k81fmDUsOrquGtt028CiGuDuma6kEsWBI4JbooT1jhj2T+eeUxe3YGbM23Zhh7Ghhw==",
"license": "MIT",
"dependencies": {
"@sentry-internal/browser-utils": "10.47.0",
"@sentry-internal/feedback": "10.47.0",
"@sentry-internal/replay": "10.47.0",
"@sentry-internal/replay-canvas": "10.47.0",
"@sentry/core": "10.47.0"
"@sentry-internal/browser-utils": "10.48.0",
"@sentry-internal/feedback": "10.48.0",
"@sentry-internal/replay": "10.48.0",
"@sentry-internal/replay-canvas": "10.48.0",
"@sentry/core": "10.48.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/core": {
"version": "10.47.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.47.0.tgz",
"integrity": "sha512-nsYRAx3EWezDut+Zl+UwwP07thh9uY7CfSAi2whTdcJl5hu1nSp2z8bba7Vq/MGbNLnazkd3A+GITBEML924JA==",
"version": "10.48.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.48.0.tgz",
"integrity": "sha512-h8F+fXVwYC9ro5ZaO8V+v3vqc0awlXHGblEAuVxSGgh4IV/oFX+QVzXeDTTrFOFS6v/Vn5vAyu240eJrJAS6/g==",
"license": "MIT",
"engines": {
"node": ">=18"
@@ -4572,9 +4554,9 @@
}
},
"node_modules/@swc/core": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.24.tgz",
"integrity": "sha512-5Hj8aNasue7yusUt8LGCUe/AjM7RMAce8ZoyDyiFwx7Al+GbYKL+yE7g4sJk8vEr1dKIkTRARkNIJENc4CjkBQ==",
"version": "1.15.30",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.30.tgz",
"integrity": "sha512-R8VQbQY1BZcbIF2p3gjlTCwAQzx1A194ugWfwld5y+WgVVWqVKm7eURGGOVbQVubgKWzidP2agomBbg96rZilQ==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -4589,18 +4571,18 @@
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
"@swc/core-darwin-arm64": "1.15.24",
"@swc/core-darwin-x64": "1.15.24",
"@swc/core-linux-arm-gnueabihf": "1.15.24",
"@swc/core-linux-arm64-gnu": "1.15.24",
"@swc/core-linux-arm64-musl": "1.15.24",
"@swc/core-linux-ppc64-gnu": "1.15.24",
"@swc/core-linux-s390x-gnu": "1.15.24",
"@swc/core-linux-x64-gnu": "1.15.24",
"@swc/core-linux-x64-musl": "1.15.24",
"@swc/core-win32-arm64-msvc": "1.15.24",
"@swc/core-win32-ia32-msvc": "1.15.24",
"@swc/core-win32-x64-msvc": "1.15.24"
"@swc/core-darwin-arm64": "1.15.30",
"@swc/core-darwin-x64": "1.15.30",
"@swc/core-linux-arm-gnueabihf": "1.15.30",
"@swc/core-linux-arm64-gnu": "1.15.30",
"@swc/core-linux-arm64-musl": "1.15.30",
"@swc/core-linux-ppc64-gnu": "1.15.30",
"@swc/core-linux-s390x-gnu": "1.15.30",
"@swc/core-linux-x64-gnu": "1.15.30",
"@swc/core-linux-x64-musl": "1.15.30",
"@swc/core-win32-arm64-msvc": "1.15.30",
"@swc/core-win32-ia32-msvc": "1.15.30",
"@swc/core-win32-x64-msvc": "1.15.30"
},
"peerDependencies": {
"@swc/helpers": ">=0.5.17"
@@ -4612,9 +4594,9 @@
}
},
"node_modules/@swc/core-darwin-arm64": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.24.tgz",
"integrity": "sha512-uM5ZGfFXjtvtJ+fe448PVBEbn/CSxS3UAyLj3O9xOqKIWy3S6hPTXSPbszxkSsGDYKi+YFhzAsR4r/eXLxEQ0g==",
"version": "1.15.30",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.30.tgz",
"integrity": "sha512-VvpP+vq08HmGYewMWvrdsxh9s2lthz/808zXm8Yu5kaqeR8Yia2b0eYXleHQ3VAjoStUDk6LzTheBW9KXYQdMA==",
"cpu": [
"arm64"
],
@@ -4628,9 +4610,9 @@
}
},
"node_modules/@swc/core-darwin-x64": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.24.tgz",
"integrity": "sha512-fMIb/Zfn929pw25VMBhV7Ji2Dl+lCWtUPNdYJQYOke+00E5fcQ9ynxtP8+qhUo/HZc+mYQb1gJxwHM9vty+lXg==",
"version": "1.15.30",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.30.tgz",
"integrity": "sha512-WiJA0hiZI3nwQAO6mu5RqigtWGDtth4Hiq6rbZxAaQyhIcqKIg5IoMRc1Y071lrNJn29eEDMC86Rq58xgUxlDg==",
"cpu": [
"x64"
],
@@ -4644,9 +4626,9 @@
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.24.tgz",
"integrity": "sha512-vOkjsyjjxnoYx3hMEWcGxQrMgnNrRm6WAegBXrN8foHtDAR+zpdhpGF5a4lj1bNPgXAvmysjui8cM1ov/Clkaw==",
"version": "1.15.30",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.30.tgz",
"integrity": "sha512-YANuFUo48kIT6plJgCD0keae9HFXfjxsbvsgevqc0hr/07X/p7sAWTFOGYEc2SXcASaK7UvuQqzlbW8pr7R79g==",
"cpu": [
"arm"
],
@@ -4660,9 +4642,9 @@
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.24.tgz",
"integrity": "sha512-h/oNu+upkXJ6Cicnq7YGVj9PkdfarLCdQa8l/FlHYvfv8CEiMaeeTnpLU7gSBH/rGxosM6Qkfa/J9mThGF9CLA==",
"version": "1.15.30",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.30.tgz",
"integrity": "sha512-VndG8jaR4ugY6u+iVOT0Q+d2fZd7sLgjPgN8W/Le+3EbZKl+cRfFxV7Eoz4gfLqhmneZPdcIzf9T3LkgkmqNLg==",
"cpu": [
"arm64"
],
@@ -4676,9 +4658,9 @@
}
},
"node_modules/@swc/core-linux-arm64-musl": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.24.tgz",
"integrity": "sha512-ZpF/pRe1guk6sKzQI9D1jAORtjTdNlyeXn9GDz8ophof/w2WhojRblvSDJaGe7rJjcPN8AaOkhwdRUh7q8oYIg==",
"version": "1.15.30",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.30.tgz",
"integrity": "sha512-1SYGs2l0Yyyi0pR/P/NKz/x0kqxkoiw+BXeJjLUdecSk/KasncWlJrc6hOvFSgKHOBrzgM5jwuluKtlT8dnrcA==",
"cpu": [
"arm64"
],
@@ -4692,9 +4674,9 @@
}
},
"node_modules/@swc/core-linux-ppc64-gnu": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.24.tgz",
"integrity": "sha512-QZEsZfisHTSJlmyChgDFNmKPb3W6Lhbfo/O76HhIngfEdnQNmukS38/VSe1feho+xkV5A5hETyCbx3sALBZKAQ==",
"version": "1.15.30",
"resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.30.tgz",
"integrity": "sha512-TXREtiXeRhbfDFbmhnkIsXpKfzbfT73YkV2ZF6w0sfxgjC5zI2ZAbaCOq25qxvegofj2K93DtOpm9RLaBgqR2g==",
"cpu": [
"ppc64"
],
@@ -4708,9 +4690,9 @@
}
},
"node_modules/@swc/core-linux-s390x-gnu": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.24.tgz",
"integrity": "sha512-DLdJKVsJgglqQrJBuoUYNmzm3leI7kUZhLbZGHv42onfKsGf6JDS3+bzCUQfte/XOqDjh/tmmn1DR/CF/tCJFw==",
"version": "1.15.30",
"resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.30.tgz",
"integrity": "sha512-DCR2YYeyd6DQE4OuDhImouuNcjXEiEdnn1Y0DyGteugPEDvVuvYk8Xddi+4o2SgWH6jiW8/I+3emZvbep1NC+g==",
"cpu": [
"s390x"
],
@@ -4724,9 +4706,9 @@
}
},
"node_modules/@swc/core-linux-x64-gnu": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.24.tgz",
"integrity": "sha512-IpLYfposPA/XLxYOKpRfeccl1p5dDa3+okZDHHTchBkXEaVCnq5MADPmIWwIYj1tudt7hORsEHccG5no6IUQRw==",
"version": "1.15.30",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.30.tgz",
"integrity": "sha512-5Pizw3NgfOJ5BJOBK8TIRa59xFW2avESTOBDPTAYwZYa1JNDs+KMF9lUfjJiJLM5HiMs/wPheA9eiT0q9m2AoA==",
"cpu": [
"x64"
],
@@ -4740,9 +4722,9 @@
}
},
"node_modules/@swc/core-linux-x64-musl": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.24.tgz",
"integrity": "sha512-JHy3fMSc0t/EPWgo74+OK5TGr51aElnzqfUPaiRf2qJ/BfX5CUCfMiWVBuhI7qmVMBnk1jTRnL/xZnOSHDPLYg==",
"version": "1.15.30",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.30.tgz",
"integrity": "sha512-qyqydP/wyH8alcIP4a2hnGSjHLJjm9H7yDFup+CPy9oTahFgLLwnNcv5UHXqO2Qs3AIND+cls5f/Bb6hqpxdgA==",
"cpu": [
"x64"
],
@@ -4756,9 +4738,9 @@
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.24.tgz",
"integrity": "sha512-Txj+qUH1z2bUd1P3JvwByfjKFti3cptlAxhWgmunBUUxy/IW3CXLZ6l6Gk4liANadKkU71nIU1X30Z5vpMT3BA==",
"version": "1.15.30",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.30.tgz",
"integrity": "sha512-CaQENgDHVGOg1mSF5sQVgvfFHG9kjMor2rkLMLeLOkfZYNj13ppnJ9+lfaBZLZUMMbnlGQnavCJb8PVBUOso7Q==",
"cpu": [
"arm64"
],
@@ -4772,9 +4754,9 @@
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.24.tgz",
"integrity": "sha512-15D/nl3XwrhFpMv+MADFOiVwv3FvH9j8c6Rf8EXBT3Q5LoMh8YnDnSgPYqw1JzPnksvsBX6QPXLiPqmcR/Z4qQ==",
"version": "1.15.30",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.30.tgz",
"integrity": "sha512-30VdLeGk6fugiUs/kUdJ/pAg7z/zpvVbR11RH60jZ0Z42WIeIniYx0rLEWN7h/pKJ3CopqsQ3RsogCAkRKiA2g==",
"cpu": [
"ia32"
],
@@ -4788,9 +4770,9 @@
}
},
"node_modules/@swc/core-win32-x64-msvc": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.24.tgz",
"integrity": "sha512-PR0PlTlPra2JbaDphrOAzm6s0v9rA0F17YzB+XbWD95B4g2cWcZY9LAeTa4xll70VLw9Jr7xBrlohqlQmelMFQ==",
"version": "1.15.30",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.30.tgz",
"integrity": "sha512-4iObHPR+Q4oDY110EF5SF5eIaaVJNpMdG9C0q3Q92BsJ5y467uHz7sYQhP60WYlLFsLQ1el2YrIPUItUAQGOKg==",
"cpu": [
"x64"
],
@@ -4906,13 +4888,6 @@
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
"license": "MIT"
},
"node_modules/@tootallnate/quickjs-emscripten": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
"integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
"license": "MIT",
"optional": true
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
@@ -5299,12 +5274,12 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "25.5.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz",
"integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==",
"version": "25.6.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.18.0"
"undici-types": "~7.19.0"
}
},
"node_modules/@types/ramda": {
@@ -6176,9 +6151,9 @@
}
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.12",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.12.tgz",
"integrity": "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==",
"version": "0.8.13",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz",
"integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
@@ -6206,13 +6181,13 @@
}
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-9.0.0.tgz",
"integrity": "sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 14"
"node": ">= 20"
}
},
"node_modules/ajv": {
@@ -6640,9 +6615,9 @@
}
},
"node_modules/basic-ftp": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.2.tgz",
"integrity": "sha512-1tDrzKsdCg70WGvbFss/ulVAxupNauGnOlgpyjKzeQxzyllBLS0CGLV7tjIXTK3ZQA9/FBEm9qyFFN1bciA6pw==",
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.0.tgz",
"integrity": "sha512-5K9eNNn7ywHPsYnFwjKgYH8Hf8B5emh7JKcPaVjjrMJFQQwGpwowEnZNEtHs7DfR7hCZsmaK3VA4HUK0YarT+w==",
"license": "MIT",
"optional": true,
"engines": {
@@ -7258,18 +7233,18 @@
}
},
"node_modules/chromedriver": {
"version": "147.0.2",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-147.0.2.tgz",
"integrity": "sha512-CgbtfPbdE3kse2gJdVVjvpRZtPV6pooaIVTJKdCj8LaDIMJvco57t2lXJcQObjfg2bhGN7RiGOk0iKpY2vLgLQ==",
"version": "147.0.4",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-147.0.4.tgz",
"integrity": "sha512-eRNbfkoTvAsSfFODM4QVhs3cXB/B4/nFHeI6+ycuKan5e3bJrq8njuLTBHHOLbL0dggxEpMHLiczJUQa+Gw3JA==",
"hasInstallScript": true,
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"@testim/chrome-version": "^1.1.4",
"axios": "^1.13.5",
"axios": "^1.15.0",
"compare-versions": "^6.1.0",
"extract-zip": "^2.0.1",
"proxy-agent": "^6.5.0",
"proxy-agent": "^8.0.1",
"proxy-from-env": "^2.0.0",
"tcp-port-used": "^1.0.2"
},
@@ -8014,13 +7989,13 @@
}
},
"node_modules/data-uri-to-buffer": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
"integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-8.0.0.tgz",
"integrity": "sha512-6UHfyCux51b8PTGDgveqtz1tvphBku5DrMKKJbFAZAJOI2zsjDpDoYE1+QGj7FOMS4BdTFNJsJiR3zEB0xH0yQ==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 14"
"node": ">= 20"
}
},
"node_modules/data-view-buffer": {
@@ -8273,9 +8248,9 @@
}
},
"node_modules/degenerator": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
"integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/degenerator/-/degenerator-7.0.1.tgz",
"integrity": "sha512-ABErK0IefDSyHjlPH7WUEenIAX2rPPnrDcDM+TS3z3+zu9TfyKKi07BQM+8rmxpdE2y1v5fjjdoAS/x4D2U60w==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -8284,7 +8259,10 @@
"esprima": "^4.0.1"
},
"engines": {
"node": ">= 14"
"node": ">= 20"
},
"peerDependencies": {
"quickjs-wasi": "^2.2.0"
}
},
"node_modules/delaunator": {
@@ -10032,18 +10010,18 @@
}
},
"node_modules/get-uri": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz",
"integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==",
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/get-uri/-/get-uri-8.0.0.tgz",
"integrity": "sha512-CqtZlMKvfJeY0Zxv8wazDwXmSKmnMnsmNy8j8+wudi8EyG/pMUB1NqHc+Tv1QaNtpYsK9nOYjb7r7Ufu32RPSw==",
"license": "MIT",
"optional": true,
"dependencies": {
"basic-ftp": "^5.0.2",
"data-uri-to-buffer": "^6.0.2",
"basic-ftp": "^5.2.0",
"data-uri-to-buffer": "8.0.0",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
"node": ">= 20"
}
},
"node_modules/git-hooks-list": {
@@ -10068,9 +10046,9 @@
}
},
"node_modules/globals": {
"version": "17.4.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz",
"integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==",
"version": "17.5.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz",
"integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==",
"license": "MIT",
"engines": {
"node": ">=18"
@@ -10560,17 +10538,17 @@
"license": "BSD-2-Clause"
},
"node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-9.0.0.tgz",
"integrity": "sha512-FcF8VhXYLQcxWCnt/cCpT2apKsRDUGeVEeMqGu4HSTu29U8Yw0TLOjdYIlDsYk3IkUh+taX4IDWpPcCqKDhCjA==",
"license": "MIT",
"optional": true,
"dependencies": {
"agent-base": "^7.1.0",
"agent-base": "9.0.0",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
"node": ">= 20"
}
},
"node_modules/http2-wrapper": {
@@ -10599,17 +10577,17 @@
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-9.0.0.tgz",
"integrity": "sha512-/MVmHp58WkOypgFhCLk4fzpPcFQvTJ/e6LBI7irpIO2HfxUbpmYoHF+KzipzJpxxzJu7aJNWQ0xojJ/dzV2G5g==",
"license": "MIT",
"optional": true,
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
"agent-base": "9.0.0",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
"node": ">= 20"
}
},
"node_modules/human-signals": {
@@ -11583,9 +11561,9 @@
}
},
"node_modules/knip": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/knip/-/knip-6.3.1.tgz",
"integrity": "sha512-22kLJloVcOVOAudCxlFOC0ICAMme7dKsS7pVTEnrmyKGpswb8ieznvAiSKUeFVDJhb01ect6dkDc1Ha1g1sPpg==",
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/knip/-/knip-6.4.1.tgz",
"integrity": "sha512-Ry+ywmDFSZvKp/jx7LxMgsZWRTs931alV84e60lh0Stf6kSRYqSIUTkviyyDFRcSO3yY1Kpbi83OirN+4lA2Xw==",
"funding": [
{
"type": "github",
@@ -13674,9 +13652,9 @@
}
},
"node_modules/netmask": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
"integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz",
"integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==",
"license": "MIT",
"optional": true,
"engines": {
@@ -14416,37 +14394,40 @@
}
},
"node_modules/pac-proxy-agent": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
"integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==",
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-9.0.1.tgz",
"integrity": "sha512-3ZOSpLboOlpW4yp8Cuv21KlTULRqyJ5Uuad3wXpSKFrxdNgcHEyoa22GRaZ2UlgCVuR6z+5BiavtYVvbajL/Yw==",
"license": "MIT",
"optional": true,
"dependencies": {
"@tootallnate/quickjs-emscripten": "^0.23.0",
"agent-base": "^7.1.2",
"agent-base": "9.0.0",
"debug": "^4.3.4",
"get-uri": "^6.0.1",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.6",
"pac-resolver": "^7.0.1",
"socks-proxy-agent": "^8.0.5"
"get-uri": "8.0.0",
"http-proxy-agent": "9.0.0",
"https-proxy-agent": "9.0.0",
"pac-resolver": "9.0.1",
"quickjs-wasi": "^2.2.0",
"socks-proxy-agent": "10.0.0"
},
"engines": {
"node": ">= 14"
"node": ">= 20"
}
},
"node_modules/pac-resolver": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
"integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-9.0.1.tgz",
"integrity": "sha512-lJbS008tmkj08VhoM8Hzuv/VE5tK9MS0OIQ/7+s0lIF+BYhiQWFYzkSpML7lXs9iBu2jfmzBTLzhe9n6BX+dYw==",
"license": "MIT",
"optional": true,
"dependencies": {
"degenerator": "^5.0.0",
"degenerator": "7.0.1",
"netmask": "^2.0.2"
},
"engines": {
"node": ">= 14"
"node": ">= 20"
},
"peerDependencies": {
"quickjs-wasi": "^2.2.0"
}
},
"node_modules/package-manager-detector": {
@@ -14866,9 +14847,9 @@
}
},
"node_modules/prettier": {
"version": "3.8.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.2.tgz",
"integrity": "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==",
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz",
"integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
@@ -15005,29 +14986,29 @@
}
},
"node_modules/protocol-buffers-schema": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
"integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==",
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz",
"integrity": "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==",
"license": "MIT"
},
"node_modules/proxy-agent": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
"integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==",
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-8.0.1.tgz",
"integrity": "sha512-kccqGBqHZXR8onQhY/ganJjoO8QIKKRiFBhPOzbTZK16attzSZ/0XSmp9H7jrRxPKHjhGyx1q32lMPrJ3uLFgA==",
"license": "MIT",
"optional": true,
"dependencies": {
"agent-base": "^7.1.2",
"agent-base": "9.0.0",
"debug": "^4.3.4",
"http-proxy-agent": "^7.0.1",
"https-proxy-agent": "^7.0.6",
"http-proxy-agent": "9.0.0",
"https-proxy-agent": "9.0.0",
"lru-cache": "^7.14.1",
"pac-proxy-agent": "^7.1.0",
"proxy-from-env": "^1.1.0",
"socks-proxy-agent": "^8.0.5"
"pac-proxy-agent": "9.0.1",
"proxy-from-env": "^2.0.0",
"socks-proxy-agent": "10.0.0"
},
"engines": {
"node": ">= 14"
"node": ">= 20"
}
},
"node_modules/proxy-agent/node_modules/lru-cache": {
@@ -15040,13 +15021,6 @@
"node": ">=12"
}
},
"node_modules/proxy-agent/node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT",
"optional": true
},
"node_modules/proxy-from-env": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
@@ -15143,6 +15117,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/quickjs-wasi": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/quickjs-wasi/-/quickjs-wasi-2.2.0.tgz",
"integrity": "sha512-zQxXmQMrEoD3S+jQdYsloq4qAuaxKFHZj6hHqOYGwB2iQZH+q9e/lf5zQPXCKOk0WJuAjzRFbO4KwHIp2D05Iw==",
"license": "MIT",
"optional": true
},
"node_modules/quickselect": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
@@ -16357,18 +16338,18 @@
}
},
"node_modules/socks-proxy-agent": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
"integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-10.0.0.tgz",
"integrity": "sha512-pyp2YR3mNxAMu0mGLtzs4g7O3uT4/9sQOLAKcViAkaS9fJWkud7nmaf6ZREFqQEi24IPkBcjfHjXhPTUWjo3uA==",
"license": "MIT",
"optional": true,
"dependencies": {
"agent-base": "^7.1.2",
"agent-base": "9.0.0",
"debug": "^4.3.4",
"socks": "^2.8.3"
},
"engines": {
"node": ">= 14"
"node": ">= 20"
}
},
"node_modules/sonic-boom": {
@@ -17956,9 +17937,9 @@
}
},
"node_modules/typescript": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz",
"integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==",
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -18071,9 +18052,9 @@
}
},
"node_modules/undici-types": {
"version": "7.18.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"version": "7.19.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
"license": "MIT"
},
"node_modules/unicorn-magic": {
@@ -19199,10 +19180,10 @@
"license": "MIT",
"dependencies": {
"@goauthentik/tsconfig": "^1.0.9",
"@types/node": "^25.5.2",
"@types/node": "^25.6.0",
"@types/semver": "^7.7.1",
"semver": "^7.7.4",
"typescript": "^6.0.2"
"typescript": "^6.0.3"
},
"engines": {
"node": ">=24",
@@ -19244,7 +19225,7 @@
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-swc": "^0.4.0",
"@swc/cli": "^0.8.1",
"@swc/core": "^1.15.24",
"@swc/core": "^1.15.30",
"base64-js": "^1.5.1",
"core-js": "^3.49.0",
"formdata-polyfill": "^2025.11.0",

View File

@@ -97,7 +97,7 @@
"@codemirror/theme-one-dark": "^6.1.3",
"@eslint/js": "^9.39.3",
"@floating-ui/dom": "^1.7.6",
"@formatjs/intl-listformat": "^8.3.1",
"@formatjs/intl-listformat": "^8.3.2",
"@fortawesome/fontawesome-free": "^7.2.0",
"@goauthentik/api": "0.0.0",
"@goauthentik/core": "^1.0.0",
@@ -119,7 +119,7 @@
"@patternfly/elements": "^4.3.1",
"@patternfly/patternfly": "^4.224.2",
"@playwright/test": "^1.59.1",
"@sentry/browser": "^10.47.0",
"@sentry/browser": "^10.48.0",
"@storybook/addon-docs": "^10.3.5",
"@storybook/addon-links": "^10.3.5",
"@storybook/web-components": "^10.3.5",
@@ -127,7 +127,7 @@
"@types/codemirror": "^5.60.17",
"@types/grecaptcha": "^3.0.9",
"@types/guacamole-common-js": "^1.5.5",
"@types/node": "^25.5.2",
"@types/node": "^25.6.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.57.2",
@@ -151,10 +151,10 @@
"eslint-plugin-lit": "^2.2.1",
"eslint-plugin-wc": "^3.1.0",
"fuse.js": "^7.3.0",
"globals": "^17.4.0",
"globals": "^17.5.0",
"guacamole-common-js": "^1.5.0",
"hastscript": "^9.0.1",
"knip": "^6.3.1",
"knip": "^6.4.1",
"lex": "^2025.11.0",
"lit": "^3.3.2",
"lit-analyzer": "^2.0.3",
@@ -167,7 +167,7 @@
"pino": "^10.3.1",
"pino-pretty": "^13.1.2",
"playwright": "^1.58.2",
"prettier": "^3.8.2",
"prettier": "^3.8.3",
"prettier-plugin-packagejson": "^3.0.2",
"pseudolocale": "^2.2.0",
"rapidoc": "^9.3.8",
@@ -187,7 +187,7 @@
"ts-pattern": "^5.9.0",
"turnstile-types": "^1.2.3",
"type-fest": "^5.5.0",
"typescript": "^6.0.2",
"typescript": "^6.0.3",
"typescript-eslint": "^8.57.2",
"unist-util-visit": "^5.1.0",
"vite": "^8.0.8",
@@ -203,7 +203,7 @@
"@rollup/rollup-darwin-arm64": "^4.57.1",
"@rollup/rollup-linux-arm64-gnu": "^4.57.1",
"@rollup/rollup-linux-x64-gnu": "^4.57.1",
"chromedriver": "^147.0.2"
"chromedriver": "^147.0.4"
},
"workspaces": [
"./packages/*"

View File

@@ -45,10 +45,10 @@
},
"dependencies": {
"@goauthentik/tsconfig": "^1.0.9",
"@types/node": "^25.5.2",
"@types/node": "^25.6.0",
"@types/semver": "^7.7.1",
"semver": "^7.7.4",
"typescript": "^6.0.2"
"typescript": "^6.0.3"
},
"engines": {
"node": ">=24",

View File

@@ -20,7 +20,7 @@
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-swc": "^0.4.0",
"@swc/cli": "^0.8.1",
"@swc/core": "^1.15.24",
"@swc/core": "^1.15.30",
"base64-js": "^1.5.1",
"core-js": "^3.49.0",
"formdata-polyfill": "^2025.11.0",

View File

@@ -53,7 +53,7 @@ export class FooterLinkInput extends AKControlElement<FooterLink> {
}
get valid() {
const href = this.json()?.href ?? "";
const href = this.toJSON()?.href ?? "";
return hasLegalScheme(href) && URL.canParse(href);
}

View File

@@ -34,7 +34,7 @@ const metadata: Meta<FooterLinkInput> = {
return;
}
const target = event.target as FooterLinkInput;
messages!.innerText = `${JSON.stringify(target.json(), null, 2)}\n\nValid: ${target.valid ? "Yes" : "No"}`;
messages!.innerText = `${JSON.stringify(target.toJSON(), null, 2)}\n\nValid: ${target.valid ? "Yes" : "No"}`;
});
}, 250);

View File

@@ -40,7 +40,7 @@ const metadata: Meta<IArrayInput<unknown>> = {
return;
}
const target = event.target as FooterLinkInput;
messages!.innerText = `${JSON.stringify(target.json(), null, 2)}\n\nValid: ${target.valid ? "Yes" : "No"}`;
messages!.innerText = `${JSON.stringify(target.toJSON(), null, 2)}\n\nValid: ${target.valid ? "Yes" : "No"}`;
});
}, 250);

View File

@@ -8,7 +8,7 @@ import { AKModal } from "#elements/dialogs/ak-modal";
import { WithBrandConfig } from "#elements/mixins/branding";
import { WithLicenseSummary } from "#elements/mixins/license";
import { SlottedTemplateResult } from "#elements/types";
import { ThemedImage } from "#elements/utils/images";
import { DefaultFlowBackground, ThemedImage } from "#elements/utils/images";
import {
AdminApi,
@@ -27,8 +27,6 @@ import { customElement, state } from "lit/decorators.js";
import PFAbout from "@patternfly/patternfly/components/AboutModalBox/about-modal-box.css";
const DEFAULT_BRAND_IMAGE = "/static/dist/assets/images/flow_background.jpg";
type AboutEntry = [label: string, content?: SlottedTemplateResult];
function renderEntry([label, content = null]: AboutEntry): SlottedTemplateResult {
@@ -191,7 +189,7 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(AKModal)) {
${ref(this.scrollContainerRef)}
class="pf-c-about-modal-box"
style=${styleMap({
"--pf-c-about-modal-box__hero--sm--BackgroundImage": `url(${DEFAULT_BRAND_IMAGE})`,
"--pf-c-about-modal-box__hero--sm--BackgroundImage": `url(${DefaultFlowBackground})`,
})}
part="box"
>

View File

@@ -4,8 +4,12 @@ import "#elements/forms/HorizontalFormElement";
import "#elements/forms/SearchSelect/index";
import { DEFAULT_CONFIG } from "#common/api/config";
import { PFSize } from "#common/enums";
import { APIMessage, MessageLevel } from "#common/messages";
import { Form } from "#elements/forms/Form";
import { SlottedTemplateResult } from "#elements/types";
import { ifPresent } from "#elements/utils/attributes";
import {
Application,
@@ -16,13 +20,24 @@ import {
} from "@goauthentik/api";
import { msg } from "@lit/localize";
import { CSSResult, html, nothing, TemplateResult } from "lit";
import { CSSResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
@customElement("ak-application-check-access-form")
export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
public static override verboseName = msg("Access");
public static override submitVerb = msg("Check");
public static override createLabel = msg("Check");
public static override submittingVerb = msg("Checking");
static styles: CSSResult[] = [...super.styles, PFDescriptionList];
#api = new CoreApi(DEFAULT_CONFIG);
public override size = PFSize.XLarge;
@property({ attribute: false })
public application!: Application;
@@ -30,19 +45,27 @@ export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
public result: PolicyTestResult | null = null;
@property({ attribute: false })
public request?: number;
public request: number | null = null;
getSuccessMessage(): string {
return msg("Successfully sent test-request.");
public override formatAPISuccessMessage(): APIMessage {
return {
level: MessageLevel.success,
message: msg("Successfully sent test-request."),
};
}
async send(data: { forUser: number }): Promise<PolicyTestResult> {
protected override send(data: { forUser: number }): Promise<PolicyTestResult> {
this.request = data.forUser;
const result = await new CoreApi(DEFAULT_CONFIG).coreApplicationsCheckAccessRetrieve({
slug: this.application?.slug,
forUser: data.forUser,
});
return (this.result = result);
return this.#api
.coreApplicationsCheckAccessRetrieve({
slug: this.application?.slug,
forUser: data.forUser,
})
.then((result) => {
this.result = result;
return result;
});
}
public override reset(): void {
@@ -50,15 +73,14 @@ export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
this.result = null;
}
static styles: CSSResult[] = [...super.styles, PFDescriptionList];
protected renderResult(): SlottedTemplateResult {
const { passing, messages = [], logMessages = [] } = this.result || {};
renderResult(): TemplateResult {
return html`
<ak-form-element-horizontal label=${msg("Passing")}>
return html`<ak-form-element-horizontal label=${msg("Passing")}>
<div class="pf-c-form__group-label">
<div class="c-form__horizontal-group">
<span class="pf-c-form__label-text">
<ak-status-label ?good=${this.result?.passing}></ak-status-label>
<ak-status-label ?good=${ifPresent(passing)}></ak-status-label>
</span>
</div>
</div>
@@ -67,54 +89,56 @@ export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
<div class="pf-c-form__group-label">
<div class="c-form__horizontal-group">
<ul>
${(this.result?.messages || []).length > 0
? this.result?.messages?.map((m) => {
return html`<li>
<span class="pf-c-form__label-text">${m}</span>
</li>`;
})
: html`<li>
<span class="pf-c-form__label-text">-</span>
</li>`}
${messages.map((m) => {
return html`<li>
<span class="pf-c-form__label-text">${m}</span>
</li>`;
})}
</ul>
</div>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Log messages")}>
<ak-log-viewer .items=${this.result?.logMessages}></ak-log-viewer>
</ak-form-element-horizontal>
`;
<ak-log-viewer .items=${logMessages}></ak-log-viewer>
</ak-form-element-horizontal>`;
}
protected override renderForm(): TemplateResult {
protected override renderForm(): SlottedTemplateResult {
return html`<ak-form-element-horizontal label=${msg("User")} required name="forUser">
<ak-search-select
placeholder=${msg("Select a user...")}
.fetchObjects=${async (query?: string): Promise<User[]> => {
const args: CoreUsersListRequest = {
ordering: "username",
};
if (query !== undefined) {
if (query) {
args.search = query;
}
const users = await new CoreApi(DEFAULT_CONFIG).coreUsersList(args);
const users = await this.#api.coreUsersList(args);
return users.results;
}}
.renderElement=${(user: User): string => {
return user.username;
}}
.renderDescription=${(user: User): TemplateResult => {
.renderDescription=${(user: User): SlottedTemplateResult => {
return html`${user.name}`;
}}
.value=${(user: User | undefined): number | undefined => {
return user?.pk;
}}
.selected=${(user: User): boolean => {
return user.pk.toString() === this.request?.toString();
return (
typeof this.request === "number" &&
user.pk.toString() === this.request.toString()
);
}}
>
</ak-search-select>
</ak-form-element-horizontal>
${this.result ? this.renderResult() : nothing}`;
${this.renderResult()}`;
}
}

View File

@@ -1,8 +1,3 @@
/* Fix alignment issues with images in tables */
.pf-c-table tbody > tr > * {
vertical-align: middle;
}
tr td:first-child {
width: auto;
min-width: 0px;

View File

@@ -7,7 +7,6 @@ import "#elements/forms/DeleteBulkForm";
import "#elements/forms/ModalForm";
import "#elements/dialogs/ak-modal";
import "#admin/applications/ApplicationForm";
import "#admin/applications/ApplicationWizardHint";
import { DEFAULT_CONFIG } from "#common/api/config";
@@ -127,6 +126,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
return [
html`<ak-app-icon
aria-label=${msg(str`Application icon for "${item.name}"`)}
role="img"
name=${item.name}
icon=${ifPresent(item.metaIconUrl)}
.iconThemedUrls=${item.metaIconThemedUrls}
@@ -201,7 +201,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
"Opens the new application wizard, which will guide you through creating a new application with an existing provider.",
)}
>
${msg("With New Provider...")}
${msg("with New Provider...")}
</button>
</li>
<li role="presentation">
@@ -214,7 +214,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
"Opens the new application form, which will guide you through creating a new application with an existing provider.",
)}
>
${msg("With Existing Provider...")}
${msg("with Existing Provider...")}
</button>
</li>
</menu>

View File

@@ -16,11 +16,15 @@ import { DEFAULT_CONFIG } from "#common/api/config";
import { APIError, parseAPIResponseError, pluckErrorDetail } from "#common/errors/network";
import { AKElement } from "#elements/Base";
import { modalInvoker } from "#elements/dialogs";
import { WithLicenseSummary } from "#elements/mixins/license";
import { setPageDetails } from "#components/ak-page-navbar";
import renderDescriptionList from "#components/DescriptionList";
import { ApplicationCheckAccessForm } from "#admin/applications/ApplicationCheckAccessForm";
import { ApplicationForm } from "#admin/applications/ApplicationForm";
import {
Application,
ContentTypeEnum,
@@ -181,36 +185,28 @@ export class ApplicationViewPage extends WithLicenseSummary(AKElement) {
],
[
msg("Related actions"),
html`<ak-forms-modal>
<span slot="submit">${msg("Save Changes")}</span>
<span slot="header"> ${msg("Update Application")} </span>
<ak-application-form
slot="form"
.instancePk=${this.application.slug}
>
</ak-application-form>
<button
slot="trigger"
class="pf-c-button pf-m-secondary pf-m-block"
>
${msg("Edit")}
</button>
</ak-forms-modal>
<ak-forms-modal .closeAfterSuccessfulSubmit=${false}>
<span slot="submit">${msg("Check")}</span>
<span slot="header"> ${msg("Check Application access")} </span>
<ak-application-check-access-form
slot="form"
.application=${this.application}
>
</ak-application-check-access-form>
<button
slot="trigger"
class="pf-c-button pf-m-secondary pf-m-block"
>
${msg("Check access")}
</button>
</ak-forms-modal>
html`<button
class="pf-c-button pf-m-secondary pf-m-block"
${modalInvoker(ApplicationForm, {
instancePk: this.application.slug,
})}
>
${msg("Edit")}
</button>
<button
class="pf-c-button pf-m-secondary pf-m-block"
${modalInvoker(
ApplicationCheckAccessForm,
{
application: this.application,
},
{
closedBy: "closerequest",
},
)}
>
${msg("Check access")}
</button>
${this.application.launchUrl
? html`<a
target="_blank"
@@ -220,7 +216,7 @@ export class ApplicationViewPage extends WithLicenseSummary(AKElement) {
>
${msg("Launch")}
</a>`
: nothing}`,
: null}`,
],
])}
</div>

View File

@@ -1,134 +0,0 @@
import "#admin/applications/wizard/ak-application-wizard";
import "#components/ak-hint/ak-hint";
import "#components/ak-hint/ak-hint-body";
import "#elements/Label";
import "#elements/buttons/ActionButton/ak-action-button";
import { AKElement } from "#elements/Base";
import { getURLParam } from "#elements/router/RouteMatch";
import { ShowHintController, ShowHintControllerHost } from "#components/ak-hint/ShowHintController";
import { msg } from "@lit/localize";
import { css, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { styleMap } from "lit/directives/style-map.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFLabel from "@patternfly/patternfly/components/Label/label.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
const closeButtonIcon = html`<svg
fill="currentColor"
height="1em"
width="1em"
viewBox="0 0 352 512"
aria-hidden="true"
role="img"
style="vertical-align: -0.125em;"
>
<path
d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"
></path>
</svg>`;
@customElement("ak-application-wizard-hint")
export class AkApplicationWizardHint extends AKElement implements ShowHintControllerHost {
static styles = [
PFButton,
PFPage,
PFLabel,
css`
.pf-c-page__main-section {
padding-bottom: 0;
}
.ak-hint-text {
padding-bottom: var(--pf-global--spacer--md);
}
`,
];
@property({ type: Boolean, attribute: "show-hint" })
forceHint: boolean = false;
@state()
showHint: boolean = true;
showHintController: ShowHintController;
constructor() {
super();
this.showHintController = new ShowHintController(
this,
"202310-application-wizard-announcement",
);
}
renderReminder() {
const sectionStyles = {
paddingBottom: "0",
marginBottom: "-0.5rem",
marginRight: "0.0625rem",
textAlign: "right",
};
const textStyle = { maxWidth: "60ch" };
return html`<section
class="pf-c-page__main-section pf-m-no-padding-mobile"
style="${styleMap(sectionStyles)}"
>
<span class="pf-c-label">
<a class="pf-c-label__content" @click=${this.showHintController.show}>
<span class="pf-c-label__text" style="${styleMap(textStyle)}">
${msg("One hint, 'New Application Wizard', is currently hidden")}
</span>
<button
aria-disabled="false"
aria-label=${msg("Restore Application Wizard Hint")}
class="pf-c-button pf-m-plain"
type="button"
data-ouia-safe="true"
>
${closeButtonIcon}
</button>
</a>
</span>
</section>`;
}
renderHint() {
return html` <section class="pf-c-page__main-section pf-m-no-padding-mobile">
<ak-hint>
<ak-hint-body>
<p class="ak-hint-text">
You can now configure both an application and its authentication provider at
the same time with our new Application Wizard.
<!-- <a href="(link to docs)">Learn more about the wizard here.</a> -->
</p>
<ak-application-wizard .open=${getURLParam("createWizard", false)}>
<button
slot="trigger"
class="pf-c-button pf-m-primary"
data-ouia-component-id="start-application-wizard"
>
${msg("Create with wizard")}
</button>
</ak-application-wizard>
</ak-hint-body>
${this.showHintController.render()}
</ak-hint>
</section>`;
}
render() {
return this.showHint || this.forceHint ? this.renderHint() : this.renderReminder();
}
}
export default AkApplicationWizardHint;
declare global {
interface HTMLElementTagNameMap {
"ak-application-wizard-hint": AkApplicationWizardHint;
}
}

View File

@@ -8,7 +8,6 @@ import { SlottedTemplateResult } from "#elements/types";
import { Provider, ProvidersApi } from "@goauthentik/api";
import { msg } from "@lit/localize";
import { html } from "lit";
import { customElement, property } from "lit/decorators.js";
@customElement("ak-provider-table")
@@ -23,29 +22,24 @@ export class ProviderTable extends Table<Provider> {
public override order = "name";
protected async apiEndpoint(): Promise<PaginatedResponse<Provider>> {
protected override async apiEndpoint(): Promise<PaginatedResponse<Provider>> {
return new ProvidersApi(DEFAULT_CONFIG).providersAllList({
...(await this.defaultEndpointConfig()),
backchannel: this.backchannel,
});
}
protected columns: TableColumn[] = [
protected override columns: TableColumn[] = [
// ---
[msg("Name"), "username"],
[msg("Type")],
];
protected row(item: Provider): SlottedTemplateResult[] {
return [
html`<div>
<div>${item.name}</div>
</div>`,
html`${item.verboseName}`,
];
protected override row(item: Provider): SlottedTemplateResult[] {
return [item.name, item.verboseName];
}
protected renderSelectedChip(item: Provider): SlottedTemplateResult {
protected override renderSelectedChip(item: Provider): SlottedTemplateResult {
return item.name;
}
}

View File

@@ -73,7 +73,6 @@ export class AkBackchannelProvidersInput extends AKElement {
}}
>
${this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing}
<ak-provider-table backchannel></ak-provider-table>
</ak-form>
`);
@@ -100,7 +99,11 @@ export class AkBackchannelProvidersInput extends AKElement {
<i class="fas fa-plus" aria-hidden="true"></i>
</button>
<div class="pf-c-form-control">
<ak-chip-group>${map(this.providers, renderOneChip)}</ak-chip-group>
<ak-chip-group
@click=${this.openSelectBackchannelProvidersModal}
placeholder=${msg("Select one or more backchannel providers...")}
>${map(this.providers, renderOneChip)}</ak-chip-group
>
</div>
</div>
${this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing}

View File

@@ -96,7 +96,7 @@ export class ApplicationWizardBindingsStep extends ApplicationWizardStep {
protected onDeleteBindings() {
const toDelete = this.selectTable
.json()
.toJSON()
.map((i) => (typeof i === "string" ? parseInt(i, 10) : i));
const bindings = this.wizard.bindings.filter((binding, index) => !toDelete.includes(index));

View File

@@ -14,6 +14,7 @@ import { DEFAULT_CONFIG } from "#common/api/config";
import { DefaultBrand } from "#common/ui/config";
import { ModelForm } from "#elements/forms/ModelForm";
import { DefaultFlowBackground } from "#elements/utils/images";
import { AKLabel } from "#components/ak-label";
@@ -65,7 +66,17 @@ export class BrandForm extends ModelForm<Brand, string> {
}
protected override renderForm(): TemplateResult {
return html` <ak-text-input
const {
brandingTitle = "",
brandingLogo = "",
brandingFavicon = "",
brandingCustomCss = "",
} = this.instance ?? DefaultBrand;
const defaultFlowBackground =
this.instance?.brandingDefaultFlowBackground ?? DefaultFlowBackground;
return html`<ak-text-input
required
name="domain"
input-hint="code"
@@ -75,6 +86,7 @@ export class BrandForm extends ModelForm<Brand, string> {
help=${msg(
"Matching is done based on domain suffix, so if you enter domain.tld, foo.domain.tld will still match.",
)}
?autofocus=${!this.instance}
></ak-text-input>
<ak-switch-input
@@ -91,7 +103,7 @@ export class BrandForm extends ModelForm<Brand, string> {
required
name="brandingTitle"
placeholder="authentik"
value="${this.instance?.brandingTitle ?? DefaultBrand.brandingTitle}"
value=${brandingTitle}
label=${msg("Title")}
autocomplete="off"
spellcheck="false"
@@ -102,7 +114,7 @@ export class BrandForm extends ModelForm<Brand, string> {
required
name="brandingLogo"
label=${msg("Logo")}
value="${this.instance?.brandingLogo ?? DefaultBrand.brandingLogo}"
value=${brandingLogo}
.usage=${UsageEnum.Media}
help=${msg("Logo shown in sidebar/header and flow executor.")}
></ak-file-search-input>
@@ -111,7 +123,7 @@ export class BrandForm extends ModelForm<Brand, string> {
required
name="brandingFavicon"
label=${msg("Favicon")}
value="${this.instance?.brandingFavicon ?? DefaultBrand.brandingFavicon}"
value=${brandingFavicon}
.usage=${UsageEnum.Media}
help=${msg("Icon shown in the browser tab.")}
></ak-file-search-input>
@@ -120,8 +132,7 @@ export class BrandForm extends ModelForm<Brand, string> {
required
name="brandingDefaultFlowBackground"
label=${msg("Default flow background")}
value="${this.instance?.brandingDefaultFlowBackground ??
"/static/dist/assets/images/flow_background.jpg"}"
value=${defaultFlowBackground}
.usage=${UsageEnum.Media}
help=${msg(
"Default background used during flow execution. Can be overridden per flow.",
@@ -141,8 +152,7 @@ export class BrandForm extends ModelForm<Brand, string> {
<ak-codemirror
id="branding-custom-css"
mode="css"
value="${this.instance?.brandingCustomCss ??
DefaultBrand.brandingCustomCss}"
value=${brandingCustomCss}
>
</ak-codemirror>
<p class="pf-c-form__helper-text">

View File

@@ -10,6 +10,7 @@ import { DEFAULT_CONFIG } from "#common/api/config";
import { SlottedTemplateResult } from "#elements/types";
import { CreateWizard } from "#elements/wizard/CreateWizard";
import { TypeCreateWizardPageLayouts } from "#elements/wizard/TypeCreateWizardPage";
import { EndpointsApi, TypeCreate } from "@goauthentik/api";
@@ -23,6 +24,8 @@ export class AKEndpointConnectorWizard extends CreateWizard {
public static override verboseName = msg("Endpoint Connector");
public static override verboseNamePlural = msg("Endpoint Connectors");
public override layout = TypeCreateWizardPageLayouts.grid;
protected apiEndpoint = (requestInit?: RequestInit): Promise<TypeCreate[]> => {
return this.#api.endpointsConnectorsTypesList(requestInit);
};

View File

@@ -20,6 +20,8 @@ import { ifDefined } from "lit/directives/if-defined.js";
@customElement("ak-endpoints-device-form")
export class EndpointDeviceForm extends ModelForm<EndpointDevice, string> {
public static override verboseName = msg("Device");
public static override verboseNamePlural = msg("Devices");
loadInstance(pk: string): Promise<EndpointDevice> {
return new EndpointsApi(DEFAULT_CONFIG).endpointsDevicesRetrieve({
deviceUuid: pk,

View File

@@ -1,15 +1,16 @@
import "#elements/cards/AggregateCard";
import "#elements/forms/DeleteBulkForm";
import "#admin/endpoints/devices/DeviceForm";
import "#admin/endpoints/devices/DeviceAddHowTo";
import "#elements/forms/ModalForm";
import { DEFAULT_CONFIG } from "#common/api/config";
import { modalInvoker } from "#elements/dialogs";
import { PaginatedResponse, TableColumn, Timestamp } from "#elements/table/Table";
import { TablePage } from "#elements/table/TablePage";
import { SlottedTemplateResult } from "#elements/types";
import { EndpointDeviceForm } from "#admin/endpoints/devices/DeviceForm";
import { DeviceSummary, EndpointDevice, EndpointsApi } from "@goauthentik/api";
import { msg } from "@lit/localize";
@@ -132,17 +133,14 @@ export class DeviceListPage extends TablePage<EndpointDevice> {
html`${item.facts.data.os?.name} ${item.facts.data.os?.version}`,
html`${item.accessGroupObj?.name || "-"}`,
item.facts.created ? Timestamp(item.facts.created) : html`-`,
html`<ak-forms-modal>
<span slot="submit">${msg("Save Changes")}</span>
<span slot="header">${msg("Update Device")}</span>
<ak-endpoints-device-form slot="form" .instancePk=${item.deviceUuid}>
</ak-endpoints-device-form>
<button slot="trigger" class="pf-c-button pf-m-plain">
<pf-tooltip position="top" content=${msg("Edit")}>
<i class="fas fa-edit" aria-hidden="true"></i>
</pf-tooltip>
</button>
</ak-forms-modal>`,
html`<button
class="pf-c-button pf-m-plain"
${modalInvoker(EndpointDeviceForm, { instancePk: item.deviceUuid })}
>
<pf-tooltip position="top" content=${msg("Edit")}>
<i class="fas fa-edit" aria-hidden="true"></i>
</pf-tooltip>
</button>`,
];
}

View File

@@ -5,20 +5,20 @@ import "#admin/endpoints/devices/facts/DeviceProcessTable";
import "#admin/endpoints/devices/facts/DeviceUserTable";
import "#admin/endpoints/devices/facts/DeviceSoftwareTable";
import "#admin/endpoints/devices/facts/DeviceGroupTable";
import "#admin/endpoints/devices/DeviceForm";
import "#admin/endpoints/devices/DeviceEvents";
import "#elements/forms/ModalForm";
import "#elements/Tabs";
import { DEFAULT_CONFIG } from "#common/api/config";
import { APIError, parseAPIResponseError } from "#common/errors/network";
import { AKElement } from "#elements/Base";
import { modalInvoker } from "#elements/dialogs";
import { Timestamp } from "#elements/table/shared";
import { setPageDetails } from "#components/ak-page-navbar";
import renderDescriptionList, { DescriptionPair } from "#components/DescriptionList";
import { EndpointDeviceForm } from "#admin/endpoints/devices/DeviceForm";
import { getSize, osFamilyToLabel, trySortNumerical } from "#admin/endpoints/devices/utils";
import { DeviceConnection, Disk, EndpointDeviceDetails, EndpointsApi } from "@goauthentik/api";
@@ -124,18 +124,14 @@ export class DeviceViewPage extends AKElement {
[msg("Device access group"), this.device?.accessGroupObj?.name ?? "-"],
[
msg("Actions"),
html`<ak-forms-modal>
<span slot="submit">${msg("Save Changes")}</span>
<span slot="header">${msg("Update Device")}</span>
<ak-endpoints-device-form
slot="form"
.instancePk=${this.device?.deviceUuid}
>
</ak-endpoints-device-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${msg("Edit")}
</button>
</ak-forms-modal>`,
html`<button
class="pf-c-button pf-m-primary"
${modalInvoker(EndpointDeviceForm, {
instancePk: this.device?.deviceUuid,
})}
>
${msg("Edit")}
</button>`,
],
],
{ horizontal: true },

View File

@@ -20,22 +20,24 @@ import { AKStageWizard } from "#admin/stages/ak-stage-wizard";
import { FlowsApi, FlowStageBinding, ModelEnum } from "@goauthentik/api";
import { msg } from "@lit/localize";
import { html, TemplateResult } from "lit";
import { html } from "lit";
import { customElement, property } from "lit/decorators.js";
@customElement("ak-bound-stages-list")
export class BoundStagesList extends Table<FlowStageBinding> {
expandable = true;
checkbox = true;
clearOnRefresh = true;
protected flowsAPI = new FlowsApi(DEFAULT_CONFIG);
order = "order";
public override expandable = true;
public override checkbox = true;
public override clearOnRefresh = true;
@property()
target?: string;
public override order = "order";
async apiEndpoint(): Promise<PaginatedResponse<FlowStageBinding>> {
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsList({
@property({ type: String, useDefault: true })
public target: string | null = null;
protected override async apiEndpoint(): Promise<PaginatedResponse<FlowStageBinding>> {
return this.flowsAPI.flowsBindingsList({
...(await this.defaultEndpointConfig()),
target: this.target || "",
});
@@ -52,7 +54,7 @@ export class BoundStagesList extends Table<FlowStageBinding> {
[msg("Actions"), null, msg("Row Actions")],
];
renderToolbarSelected(): TemplateResult {
renderToolbarSelected(): SlottedTemplateResult {
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk
object-label=${msg("Stage binding(s)")}
@@ -64,12 +66,12 @@ export class BoundStagesList extends Table<FlowStageBinding> {
];
}}
.usedBy=${(item: FlowStageBinding) => {
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsUsedByList({
return this.flowsAPI.flowsBindingsUsedByList({
fsbUuid: item.pk,
});
}}
.delete=${(item: FlowStageBinding) => {
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsDestroy({
return this.flowsAPI.flowsBindingsDestroy({
fsbUuid: item.pk,
});
}}
@@ -80,7 +82,7 @@ export class BoundStagesList extends Table<FlowStageBinding> {
</ak-forms-delete-bulk>`;
}
row(item: FlowStageBinding): SlottedTemplateResult[] {
protected override row(item: FlowStageBinding): SlottedTemplateResult[] {
return [
html`<pre>${item.order}</pre>`,
item.stageObj?.name,
@@ -115,30 +117,27 @@ export class BoundStagesList extends Table<FlowStageBinding> {
protected renderActions(): SlottedTemplateResult {
return html`<button
class="pf-c-button pf-m-primary"
${modalInvoker(AKStageWizard, {
showBindingPage: true,
bindingTarget: this.target,
})}
>
${msg("New Stage")}
</button>
<button
slot="trigger"
class="pf-c-button pf-m-primary"
${modalInvoker(StageBindingForm, { targetPk: this.target })}
>
${msg("Bind Existing Stage")}
</button>`;
class="pf-c-button pf-m-primary"
${modalInvoker(AKStageWizard, {
showBindingPage: true,
bindingTarget: this.target,
})}
>
${msg("Create or bind...")}
</button>`;
}
protected override renderExpanded(item: FlowStageBinding): TemplateResult {
protected override renderExpanded(item: FlowStageBinding): SlottedTemplateResult {
return html`<div class="pf-c-content">
<p>${msg("These bindings control if this stage will be applied to the flow.")}</p>
<ak-bound-policies-list
.target=${item.policybindingmodelPtrId}
.policyEngineMode=${item.policyEngineMode}
>
<span slot="description"
>${msg(
"These bindings control if this stage will be applied to the flow.",
)}</span
>
</ak-bound-policies-list>
</div>`;
}

View File

@@ -218,6 +218,15 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
>
${msg("Require Outpost (flow can only be executed from an outpost)")}
</option>
<option
value=${AuthenticationEnum.RequireToken}
?selected=${this.instance?.authentication ===
AuthenticationEnum.RequireToken}
>
${msg(
"Require Flow token (flow can only be executed from a generated recovery link)",
)}
</option>
</select>
<p class="pf-c-form__helper-text">
${msg("Required authentication level for this flow.")}

View File

@@ -1,6 +1,5 @@
import "#admin/flows/BoundStagesList";
import "#admin/flows/FlowDiagram";
import "#admin/flows/FlowForm";
import "#admin/policies/BoundPoliciesList";
import "#admin/rbac/ak-rbac-object-permission-page";
import "#admin/events/ObjectChangelog";
@@ -11,11 +10,13 @@ import { AndNext, DEFAULT_CONFIG } from "#common/api/config";
import { isResponseErrorLike } from "#common/errors/network";
import { AKElement } from "#elements/Base";
import { modalInvoker } from "#elements/dialogs";
import { SlottedTemplateResult } from "#elements/types";
import { setPageDetails } from "#components/ak-page-navbar";
import renderDescriptionList from "#components/DescriptionList";
import { FlowForm } from "#admin/flows/FlowForm";
import { DesignationToLabel } from "#admin/flows/utils";
import { Flow, FlowsApi, ModelEnum } from "@goauthentik/api";
@@ -97,21 +98,14 @@ export class FlowViewPage extends AKElement {
],
[
msg("Related actions"),
html`<ak-forms-modal>
<span slot="submit">${msg("Save Changes")}</span>
<span slot="header"> ${msg("Update Flow")} </span>
<ak-flow-form
slot="form"
.instancePk=${this.flow.slug}
>
</ak-flow-form>
<button
slot="trigger"
class="pf-c-button pf-m-block pf-m-secondary"
>
${msg("Edit")}
</button>
</ak-forms-modal>
html`<button
class="pf-c-button pf-m-block pf-m-secondary"
${modalInvoker(FlowForm, {
instancePk: this.flow.slug,
})}
>
${msg("Edit")}
</button>
<a
class="pf-c-button pf-m-block pf-m-secondary"
href=${this.flow.exportUrl}

View File

@@ -1,4 +1,3 @@
import "#admin/groups/ak-group-form";
import "#admin/groups/RelatedUserList";
import "#admin/rbac/ak-rbac-object-permission-page";
import "#admin/roles/ak-related-role-table";
@@ -10,19 +9,21 @@ import "#elements/CodeMirror";
import "#elements/Tabs";
import "#elements/buttons/ActionButton/index";
import "#elements/buttons/SpinnerButton/index";
import "#elements/forms/ModalForm";
import "#elements/ak-mdx/ak-mdx";
import { DEFAULT_CONFIG } from "#common/api/config";
import { EVENT_REFRESH } from "#common/constants";
import { AKElement } from "#elements/Base";
import { modalInvoker } from "#elements/dialogs";
import { WithLicenseSummary } from "#elements/mixins/license";
import { SlottedTemplateResult } from "#elements/types";
import { setPageDetails } from "#components/ak-page-navbar";
import renderDescriptionList from "#components/DescriptionList";
import { GroupForm } from "#admin/groups/ak-group-form";
import { ContentTypeEnum, CoreApi, Group, ModelEnum } from "@goauthentik/api";
import { msg, str } from "@lit/localize";
@@ -150,18 +151,14 @@ export class GroupViewPage extends WithLicenseSummary(AKElement) {
],
[
msg("Related actions"),
html`<ak-forms-modal>
<span slot="submit">${msg("Save Changes")}</span>
<span slot="header">${msg("Update Group")}</span>
<ak-group-form slot="form" .instancePk=${this.group.pk}>
</ak-group-form>
<button
slot="trigger"
class="pf-m-primary pf-c-button pf-m-block"
>
${msg("Edit")}
</button>
</ak-forms-modal>`,
html`<button
class="pf-c-button pf-m-primary pf-m-block"
${modalInvoker(GroupForm, {
instancePk: this.group.pk,
})}
>
${msg("Edit")}
</button>`,
],
])}
</div>

View File

@@ -1,19 +1,19 @@
import "#admin/groups/ak-group-form";
import "#admin/users/ak-user-group-table";
import "#components/ak-status-label";
import "#elements/buttons/SpinnerButton/index";
import "#elements/forms/DeleteBulkForm";
import "#elements/forms/HorizontalFormElement";
import "#elements/forms/ModalForm";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
import { DEFAULT_CONFIG } from "#common/api/config";
import { renderModal } from "#elements/dialogs";
import { modalInvoker, renderModal } from "#elements/dialogs";
import { AKFormSubmitEvent, Form } from "#elements/forms/Form";
import { PaginatedResponse, Table, TableColumn } from "#elements/table/Table";
import { SlottedTemplateResult } from "#elements/types";
import { GroupForm } from "#admin/groups/ak-group-form";
import { CoreApi, Group, User } from "@goauthentik/api";
import { msg, str } from "@lit/localize";
@@ -23,6 +23,10 @@ import { ifDefined } from "lit/directives/if-defined.js";
@customElement("ak-group-related-add")
export class RelatedGroupAdd extends Form<{ groups: string[] }> {
public static override verboseName = msg("Group");
public static override submitVerb = msg("Add");
public static override createLabel = msg("Add");
@property({ attribute: false })
public user?: User;
@@ -74,7 +78,10 @@ export class RelatedGroupAdd extends Form<{ groups: string[] }> {
</pf-tooltip>
</button>
<div class="pf-c-form-control">
<ak-chip-group>
<ak-chip-group
@click=${this.openUserGroupSelectModal}
placeholder=${msg("Select one or more groups...")}
>
${this.groupsToAdd.map((group) => {
return html`<ak-chip
removable
@@ -151,40 +158,30 @@ export class RelatedGroupList extends Table<Group> {
return [
html`<a href="#/identity/groups/${item.pk}">${item.name}</a>`,
html`<ak-status-label type="neutral" ?good=${item.isSuperuser}></ak-status-label>`,
html` <ak-forms-modal>
<span slot="submit">${msg("Save Changes")}</span>
<span slot="header">${msg("Update Group")}</span>
<ak-group-form slot="form" .instancePk=${item.pk}> </ak-group-form>
<button slot="trigger" class="pf-c-button pf-m-plain">
<pf-tooltip position="top" content=${msg("Edit")}>
<i class="fas fa-edit" aria-hidden="true"></i>
</pf-tooltip>
</button>
</ak-forms-modal>`,
html`<button
class="pf-c-button pf-m-plain"
${modalInvoker(GroupForm, { instancePk: item.pk })}
>
<pf-tooltip position="top" content=${msg("Edit")}>
<i class="fas fa-edit" aria-hidden="true"></i>
</pf-tooltip>
</button>`,
];
}
renderToolbar(): TemplateResult {
return html`
${this.targetUser
? html`<ak-forms-modal>
<span slot="submit">${msg("Add")}</span>
<span slot="header">${msg("Add Group")}</span>
<ak-group-related-add .user=${this.targetUser} slot="form">
</ak-group-related-add>
<button slot="trigger" class="pf-c-button pf-m-primary">
${msg("Add to existing group")}
</button>
</ak-forms-modal>`
? html`<button
class="pf-c-button pf-m-primary"
${modalInvoker(RelatedGroupAdd, { user: this.targetUser })}
>
${msg("Add to existing group")}
</button>`
: nothing}
<ak-forms-modal>
<span slot="submit">${msg("Create")}</span>
<span slot="header">${msg("Create Group")}</span>
<ak-group-form slot="form"> </ak-group-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${msg("Add new group")}
</button>
</ak-forms-modal>
<button class="pf-c-button pf-m-secondary" ${modalInvoker(GroupForm)}>
${msg("Add new group")}
</button>
${super.renderToolbar()}
`;
}

View File

@@ -14,6 +14,7 @@ import "#elements/forms/ModalForm";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
import { DEFAULT_CONFIG } from "#common/api/config";
import { formatDisambiguatedUserDisplayName } from "#common/users";
import { IconEditButton, renderModal } from "#elements/dialogs";
import { AKFormSubmitEvent, Form } from "#elements/forms/Form";
@@ -22,11 +23,11 @@ import { WithCapabilitiesConfig } from "#elements/mixins/capabilities";
import { getURLParam, updateURLParams } from "#elements/router/RouteMatch";
import { PaginatedResponse, Table, TableColumn, Timestamp } from "#elements/table/Table";
import { SlottedTemplateResult } from "#elements/types";
import { UserOption } from "#elements/user/utils";
import { AKLabel } from "#components/ak-label";
import { RecoveryButtons } from "#admin/users/recovery";
import { ToggleUserActivationButton } from "#admin/users/UserActiveForm";
import { UserForm } from "#admin/users/UserForm";
import { UserImpersonateForm } from "#admin/users/UserImpersonateForm";
@@ -153,7 +154,7 @@ export class AddRelatedUserForm extends Form<{ users: number[] }> {
this.requestUpdate();
}}
>
${UserOption(user)}
${formatDisambiguatedUserDisplayName(user)}
</ak-chip>`;
})}</ak-chip-group
>
@@ -317,22 +318,7 @@ export class RelatedUserList extends WithBrandConfig(WithCapabilitiesConfig(Tabl
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ak-user-active-form
.obj=${item}
object-label=${msg("User")}
.delete=${() => {
return new CoreApi(DEFAULT_CONFIG).coreUsersPartialUpdate({
id: item.pk || 0,
patchedUserRequest: {
isActive: !item.isActive,
},
});
}}
>
<button slot="trigger" class="pf-c-button pf-m-warning">
${item.isActive ? msg("Deactivate") : msg("Activate")}
</button>
</ak-user-active-form>
${ToggleUserActivationButton(item)}
</div>
</dd>
</div>

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