Merge branch 'main' into dev

* main: (65 commits)
  website/integrations: FortiAnalyzer (#22610)
  website: fix British spellings flagged by cspell (#22818)
  website/integrations: fix Node-RED OIDC username docs (#22868)
  website/integrations: 1Password: cleanup (#22699)
  website/docs: improve service account docs (#22145)
  website/docs: update binding wizard labels (#22869)
  core: bump library/golang from 1.26.3-trixie to 1.26.4-trixie in /lifecycle/container (#22837)
  website/docs: add launch URL in Komodo docs (#22866)
  website/docs: add GitLab social login source guide (#22764)
  web: bump the eslint group across 1 directory with 3 updates (#22841)
  website/integrations: add email verified info to Mailcow (#22783)
  website/docs: mark cves CVE-2026-49443 and CVE-2026-49448 (#22808)
  website/integrations: add Icinga Web 2 (#22767)
  core, web: update translations (#22852)
  translate: Updates for project authentik and language hu_HU (#22813)
  website/docs: Add improved akql docs (#22693)
  website/integrations: add Nexterm (#22768)
  website/docs: clarify Google Workspace signed response setting (#22812)
  website/integrations: WordPress: cleanup (#22702)
  website/integrations: add Productive integration (#22769)
  ...
This commit is contained in:
Ken Sternberg
2026-06-05 06:37:31 -07:00
156 changed files with 9280 additions and 1238 deletions
+27 -14
View File
@@ -20,12 +20,22 @@ runs:
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.token }}
# Untrusted/event-derived values are passed via the environment (never
# interpolated into the script body) to avoid template injection.
EVENT_NAME: ${{ github.event_name }}
REPOSITORY: ${{ github.repository }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
LABEL_NAME_CTX: ${{ github.event.label.name }}
PR_NUMBER_CTX: ${{ github.event.pull_request.number }}
MERGE_COMMIT_SHA_CTX: ${{ github.event.pull_request.merge_commit_sha }}
EVENT_ACTION: ${{ github.event.action }}
PR_MERGED_CTX: ${{ github.event.pull_request.merged }}
run: |
set -e -o pipefail
# For issues events, check if it's actually a PR
if [ "${{ github.event_name }}" = "issues" ]; then
if [ "$EVENT_NAME" = "issues" ]; then
# Check if this issue is actually a PR
PR_DATA=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }} 2>/dev/null || echo "null")
PR_DATA=$(gh api "repos/${REPOSITORY}/pulls/${ISSUE_NUMBER}" 2>/dev/null || echo "null")
if [ "$PR_DATA" = "null" ]; then
echo "should_run=false" >> $GITHUB_OUTPUT
echo "reason=not_a_pr" >> $GITHUB_OUTPUT
@@ -35,11 +45,11 @@ runs:
# Get PR data
PR_MERGED=$(echo "$PR_DATA" | jq -r '.merged')
PR_NUMBER="${{ github.event.issue.number }}"
PR_NUMBER="$ISSUE_NUMBER"
MERGE_COMMIT_SHA=$(echo "$PR_DATA" | jq -r '.merge_commit_sha')
# Check if it's a backport label
LABEL_NAME="${{ github.event.label.name }}"
LABEL_NAME="$LABEL_NAME_CTX"
if [[ "$LABEL_NAME" =~ ^backport/(.+)$ ]]; then
if [ "$PR_MERGED" = "true" ]; then
echo "should_run=true" >> $GITHUB_OUTPUT
@@ -61,11 +71,11 @@ runs:
fi
# For pull_request and pull_request_target events
PR_NUMBER="${{ github.event.pull_request.number }}"
MERGE_COMMIT_SHA="${{ github.event.pull_request.merge_commit_sha }}"
PR_NUMBER="$PR_NUMBER_CTX"
MERGE_COMMIT_SHA="$MERGE_COMMIT_SHA_CTX"
# Case 1: PR was just merged (closed + merged = true)
if [ "${{ github.event.action }}" = "closed" ] && [ "${{ github.event.pull_request.merged }}" = "true" ]; then
if [ "$EVENT_ACTION" = "closed" ] && [ "$PR_MERGED_CTX" = "true" ]; then
echo "should_run=true" >> $GITHUB_OUTPUT
echo "reason=pr_merged" >> $GITHUB_OUTPUT
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
@@ -74,12 +84,12 @@ runs:
fi
# Case 2: Label was added
if [ "${{ github.event.action }}" = "labeled" ]; then
LABEL_NAME="${{ github.event.label.name }}"
if [ "$EVENT_ACTION" = "labeled" ]; then
LABEL_NAME="$LABEL_NAME_CTX"
# Check if it's a backport label
if [[ "$LABEL_NAME" =~ ^backport/(.+)$ ]]; then
# Check if PR is already merged
if [ "${{ github.event.pull_request.merged }}" = "true" ]; then
if [ "$PR_MERGED_CTX" = "true" ]; then
echo "should_run=true" >> $GITHUB_OUTPUT
echo "reason=label_added_to_merged_pr" >> $GITHUB_OUTPUT
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
@@ -117,16 +127,18 @@ runs:
GITHUB_TOKEN: ${{ inputs.token }}
PR_NUMBER: ${{ steps.should_run.outputs.pr_number }}
REASON: ${{ steps.should_run.outputs.reason }}
EVENT_NAME: ${{ github.event_name }}
LABEL_NAME_CTX: ${{ github.event.label.name }}
run: |
set -e -o pipefail
# Determine which labels to process
if [ "${REASON}" = "label_added_to_merged_pr" ]; then
# Only process the specific label that was just added
if [ "${{ github.event_name }}" = "issues" ]; then
LABEL_NAME="${{ github.event.label.name }}"
if [ "$EVENT_NAME" = "issues" ]; then
LABEL_NAME="$LABEL_NAME_CTX"
else
LABEL_NAME="${{ github.event.label.name }}"
LABEL_NAME="$LABEL_NAME_CTX"
fi
if [[ "$LABEL_NAME" =~ ^backport/(.+)$ ]]; then
@@ -150,10 +162,11 @@ runs:
PR_TITLE: ${{ github.event.pull_request.title }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
LABELS: '${{ steps.pr_details.outputs.labels }}'
REASON: '${{ steps.should_run.outputs.reason }}'
run: |
set -e -o pipefail
echo "Processing PR #$PR_NUMBER (reason: ${{ steps.should_run.outputs.reason }})"
echo "Processing PR #$PR_NUMBER (reason: ${REASON})"
echo "Found backport labels: $LABELS"
# Process each backport label
+1 -1
View File
@@ -64,7 +64,7 @@ runs:
rustflags: ""
- name: Setup rust dependencies
if: ${{ contains(inputs.dependencies, 'rust') }}
uses: taiki-e/install-action@50b4a718b59c718df4ef27a3b445f86cd57b9f00 # v2
uses: taiki-e/install-action@e49978b799e49ff429d162b7a30601a569ab6538 # v2
with:
tool: cargo-deny cargo-machete cargo-llvm-cov nextest
- name: Setup node (root, web)
+13 -25
View File
@@ -1,38 +1,26 @@
<!--
👋 Hi there! Welcome.
👋 Hi there! Welcome. Please check the contributing guidelines: https://docs.goauthentik.io/docs/developer-docs/#how-can-i-contribute
Please check the Contributing guidelines: https://docs.goauthentik.io/docs/developer-docs/#how-can-i-contribute
⚠️ IMPORTANT: Make sure you are opening this PR from a FEATURE BRANCH, not from your main branch!
If you opened this PR from your main branch, please close it and create a new feature branch instead.
For more information, see: https://docs.goauthentik.io/developer-docs/contributing/#always-use-feature-branches
⚠️ Open this PR from a feature branch, not from main: https://docs.goauthentik.io/developer-docs/contributing/#always-use-feature-branches
-->
## Details
<!--
Explain what this PR changes, what the rationale behind the change is, if any new requirements are introduced or any breaking changes caused by this PR.
### What does this PR change?
Ideally also link an Issue for context that this PR will close using `closes #`
### Why is this change needed?
### How was this tested?
### Linked issues
<!--
Use `closes #N` to auto-close an issue on merge. Use `refs #N` for related issues that this PR does not close.
-->
REPLACE ME
---
## Checklist
- [ ] Local tests pass (`ak test authentik/`)
- [ ] The code has been formatted (`make lint-fix`)
If an API change has been made
- [ ] The API schema and clients have been updated (`make gen`)
If changes to the frontend have been made
- [ ] The code has been formatted (`make web`)
If applicable
- [ ] The documentation has been updated
- [ ] The documentation has been formatted (`make docs`)
- [ ] The project has been linted, built, and tested (`make all`)
- [ ] The documentation has been updated and formatted (`make docs`)
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
with:
image_name: ${{ inputs.image_name }}
image_arch: arm64
runs-on: ubuntu-22.04-arm
runs-on: ubuntu-24.04-arm
registry_dockerhub: ${{ inputs.registry_dockerhub }}
registry_ghcr: ${{ inputs.registry_ghcr }}
release: ${{ inputs.release }}
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
- id: generate_token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
client-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIV_KEY }}
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
@@ -18,7 +18,7 @@ jobs:
- id: generate_token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
client-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIV_KEY }}
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v2
if: ${{ env.GH_APP_ID != '' }}
with:
app-id: ${{ secrets.GH_APP_ID }}
client-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIV_KEY }}
env:
GH_APP_ID: ${{ secrets.GH_APP_ID }}
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIV_KEY }}
- name: Delete 'dev' containers older than a week
uses: snok/container-retention-policy@3b0972b2276b171b212f8c4efbca59ebba26eceb # v3.0.1
uses: snok/container-retention-policy@d3bdcf5ce9b05f685154e4a16c39233b245e3d53 # v3.1.0
with:
image-names: dev-server,dev-ldap,dev-proxy
image-tags: "!gh-next,!gh-main"
+2 -2
View File
@@ -31,7 +31,7 @@ jobs:
name: Generate app token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
client-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIV_KEY }}
- name: Checkout main
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
@@ -59,7 +59,7 @@ jobs:
- id: generate_token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
client-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIV_KEY }}
- name: Checkout main
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
+1 -1
View File
@@ -183,7 +183,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: aws-actions/configure-aws-credentials@99214aa6889fcddfa57764031d71add364327e59 # v6.1.3
- uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0
with:
role-to-assume: "arn:aws:iam::016170277896:role/github_goauthentik_authentik"
aws-region: ${{ env.AWS_REGION }}
+3 -3
View File
@@ -69,7 +69,7 @@ jobs:
name: Generate app token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
client-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIV_KEY }}
- id: get-user-id
name: Get GitHub app user ID
@@ -121,7 +121,7 @@ jobs:
name: Generate app token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
client-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIV_KEY }}
repositories: helm
- id: get-user-id
@@ -163,7 +163,7 @@ jobs:
name: Generate app token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
client-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIV_KEY }}
repositories: version
- id: get-user-id
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
- id: generate_token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
client-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIV_KEY }}
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10
with:
@@ -23,7 +23,7 @@ jobs:
if: ${{ github.event_name != 'pull_request' }}
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
client-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIV_KEY }}
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
if: ${{ github.event_name != 'pull_request' }}
+4
View File
@@ -20,6 +20,10 @@ corepack.tgz
.cspellcache
cspell-report.*
# Release generation artifacts
/changelog.md
/diff.md
# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/
# in your Git repository. Update and uncomment the following line accordingly.
# <django-project-name>/staticfiles/
Generated
+2 -2
View File
@@ -4346,9 +4346,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.23.1"
version = "1.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7"
dependencies = [
"getrandom 0.4.2",
"js-sys",
+1 -1
View File
@@ -112,7 +112,7 @@ tracing-subscriber = { version = "= 0.3.23", features = [
"tracing-log",
] }
url = "= 2.5.8"
uuid = { version = "= 1.23.1", features = ["serde", "v4"] }
uuid = { version = "= 1.23.2", features = ["serde", "v4"] }
which = "= 8.0.2"
ak-axum = { package = "authentik-axum", version = "2026.8.0-rc1", path = "./packages/ak-axum" }
+1 -1
View File
@@ -186,7 +186,7 @@ gen-changelog: ## (Release) generate the changelog based from the commits since
git log --pretty=format:"- %s" $(shell git merge-base ${last_version} ${current_commit})...${current_commit} > merged_to_current
git log --pretty=format:"- %s" $(shell git merge-base ${last_version} ${current_commit})...${last_version} > merged_to_last
grep -Eo 'cherry-pick (#\d+)' merged_to_last | cut -d ' ' -f 2 | sed 's/.*/(&)$$/' > cherry_picked_to_last
grep -vf cherry_picked_to_last merged_to_current | sort > changelog.md
grep -vf cherry_picked_to_last merged_to_current | grep -vE '^- (ci:|website)' | sort > changelog.md
rm merged_to_current
rm merged_to_last
rm cherry_picked_to_last
@@ -68,13 +68,16 @@ class SCIMOAuthAuth:
return conn
token = self.retrieve_token(conn)
access_token = token["access_token"]
refresh_token = token.get("refresh_token")
if not refresh_token and conn:
refresh_token = conn.refresh_token
expires_in = int(token.get("expires_in", 0))
token, _ = UserOAuthSourceConnection.objects.update_or_create(
source=self.provider.auth_oauth,
user=self.user,
defaults={
"access_token": access_token,
"refresh_token": token.get("refresh_token"),
"refresh_token": refresh_token,
"expires": now() + timedelta(seconds=expires_in),
# When using `update_or_create`, `last_updated` is not updated
"last_updated": now(),
@@ -104,6 +104,7 @@ class TestSCIMOAuthToken(APITestCase):
source=self.source,
user=self.provider.auth_oauth_user,
).first()
self.assertEqual(conn.refresh_token, refresh_token)
self.assertIsNotNone(conn)
self.assertTrue(conn.is_valid)
auth = (
+1 -1
View File
@@ -67,7 +67,7 @@ class OpenIDConnectOAuth2Callback(OAuthCallback):
client_class = OpenIDConnectClient
def get_user_id(self, info: dict[str, str]) -> str:
return info.get("sub", None)
return str(info.get("sub") or info.get("id"))
@registry.register()
+2
View File
@@ -269,6 +269,8 @@
".docusaurus/**", // Cache
"./{docs,website}/build", // Topic docs build output
"./{docs,website}/**/build", // Workspaces output
"_redirects", // Redirects file
"_headers", // Headers file
//#endregion
//#region Golang
"go.mod", // Go module file
+1 -1
View File
@@ -10,7 +10,7 @@ require (
github.com/getsentry/sentry-go v0.46.2
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.31.0
github.com/go-openapi/runtime v0.32.2
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.2
+6 -6
View File
@@ -51,8 +51,8 @@ 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.31.0 h1:vhmlo1LMjGXYTlYB0eFm0tTVuAidDHtmrL1nAABzUCg=
github.com/go-openapi/runtime v0.31.0/go.mod h1:fZnoje1YWt7IrH/fHBOS1h9+VzeS1d0cHj8TTkZOaRc=
github.com/go-openapi/runtime v0.32.2 h1:X9mZz716lFwYZ6bFV1BBnthNdHTy46zKM5Em4D1UISI=
github.com/go-openapi/runtime v0.32.2/go.mod h1:IfM3cpgencPuwBp5Uo16i2IQaE74odL7Q4DCGovIQac=
github.com/go-openapi/runtime/server-middleware v0.30.0 h1:8rPoJ/xv7JL8BsovaqboKETlpWBArVh8n+0L/GyePog=
github.com/go-openapi/runtime/server-middleware v0.30.0/go.mod h1:OYNT/TxNvB/VK5oe4htM2jDTwlEXuejVJmu0DVZfAMs=
github.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ=
@@ -79,10 +79,10 @@ github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFu
github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE=
github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ=
github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU=
github.com/go-openapi/testify/enable/yaml/v2 v2.5.0 h1:3hZD1fwydvCx/cc1R2uYNQirHqf2s6lqpKV3FcNTURA=
github.com/go-openapi/testify/enable/yaml/v2 v2.5.0/go.mod h1:TvDZKBH7ZbMaF3EqH2AwTvNQCmzyZq8K1agRjf1B+Nk=
github.com/go-openapi/testify/v2 v2.5.0 h1:UOCr63aAsMIDydZbZGqo5Ev01D4eydItRbekDuZMJLw=
github.com/go-openapi/testify/v2 v2.5.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
github.com/go-openapi/testify/enable/yaml/v2 v2.5.1 h1:q9NtHwK4qHF7yZziBPvZyv7zWAIk8ok88Gh2mR6Jpc8=
github.com/go-openapi/testify/enable/yaml/v2 v2.5.1/go.mod h1:JW0MXIotCYps/XsgJnG3a8Q7rE5xAiBwoOD5OfaIQBk=
github.com/go-openapi/testify/v2 v2.5.1 h1:TMdhCaw8fUNraVSf3Omoob1dO/AzBfhtFAPW0an6sBo=
github.com/go-openapi/testify/v2 v2.5.1/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=
+4 -4
View File
@@ -9,7 +9,7 @@
"version": "0.0.0",
"license": "MIT",
"devDependencies": {
"aws-cdk": "^2.1124.1",
"aws-cdk": "^2.1125.0",
"cross-env": "^10.1.0"
},
"engines": {
@@ -25,9 +25,9 @@
"license": "MIT"
},
"node_modules/aws-cdk": {
"version": "2.1124.1",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1124.1.tgz",
"integrity": "sha512-sRYdPMdkX+02EHaT946AFV0w0CMfbHKWpLZPv525xTCkaVu1eYu6DzHFuTdimxdSN0uGQ2D4LHrD1sr94tRhow==",
"version": "2.1125.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1125.0.tgz",
"integrity": "sha512-QAvsE2XQMcyNOjMMqAS7eDADR9t6vcFcMQvhOmtLfDqgfJXSyTkHvzM5zgwZCdJ4FNqWr5Y/zXvL1Cv5ECKXwQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
+1 -1
View File
@@ -7,7 +7,7 @@
"aws-cfn": "cross-env CI=false cdk synth --version-reporting=false > template.yaml"
},
"devDependencies": {
"aws-cdk": "^2.1124.1",
"aws-cdk": "^2.1125.0",
"cross-env": "^10.1.0"
},
"engines": {
+4 -4
View File
@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
# Stage: Build webui
FROM --platform=${BUILDPLATFORM} docker.io/library/node:26-trixie-slim@sha256:1e738cb88890a15c71880323fbc35a739b7bbc703d72e8bfd1613128f8182f78 AS node-builder
FROM --platform=${BUILDPLATFORM} docker.io/library/node:26-trixie-slim@sha256:aa27a5fbf5acb298116a38133794f080406c6f8dfe52e2e2836bb55dc7cae8f0 AS node-builder
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
@@ -41,7 +41,7 @@ RUN npm run build && \
npm run build:sfe
# Stage: Build go proxy
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.3-trixie@sha256:0f6b034c99663ea8957e7dae99124e37374cbe7fcb5b5646f19b185f8f976279 AS go-builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.4-trixie@sha256:0dcba0d95dbfb072e9917a106b9e07d7cc298097dc83e9307056ef1889de654d AS go-builder
ARG TARGETOS
ARG TARGETARCH
@@ -116,9 +116,9 @@ RUN --mount=type=bind,target=rust-toolchain.toml,src=rust-toolchain.toml \
RUN cat /root/.rustup/settings.toml
# Stage: Download uv
FROM ghcr.io/astral-sh/uv:0.11.17@sha256:03bdc89bb9798628846e60c3a9ad19006c8c3c724ccd2985a33145c039a0577b AS uv
FROM ghcr.io/astral-sh/uv:0.11.19@sha256:b46b03ddfcfbf8f547af7e9eaefdf8a39c8cebcba7c98858d3162bd28cf536f6 AS uv
# Stage: Base python image
FROM ghcr.io/goauthentik/fips-python:3.14.5-slim-trixie-fips@sha256:33d1ed94f2766b893018c038482873aca6e678fb0d4bc053483a4008c574e3c2 AS python-base
FROM ghcr.io/goauthentik/fips-python:3.14.5-slim-trixie-fips@sha256:b332680f098882472bc13d5452b7b348bf8e7ef4400588d85aca41acde77c1f4 AS python-base
ENV VENV_PATH="/ak-root/.venv" \
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \
+1 -1
View File
@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
# Stage 1: Build
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.3-trixie@sha256:0f6b034c99663ea8957e7dae99124e37374cbe7fcb5b5646f19b185f8f976279 AS builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.4-trixie@sha256:0dcba0d95dbfb072e9917a106b9e07d7cc298097dc83e9307056ef1889de654d AS builder
ARG TARGETOS
ARG TARGETARCH
+1 -1
View File
@@ -32,7 +32,7 @@ COPY web .
RUN npm run build-proxy
# Stage 2: Build
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.3-trixie@sha256:0f6b034c99663ea8957e7dae99124e37374cbe7fcb5b5646f19b185f8f976279 AS builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.4-trixie@sha256:0dcba0d95dbfb072e9917a106b9e07d7cc298097dc83e9307056ef1889de654d AS builder
ARG TARGETOS
ARG TARGETARCH
+1 -1
View File
@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
# Stage 1: Build
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.3-trixie@sha256:0f6b034c99663ea8957e7dae99124e37374cbe7fcb5b5646f19b185f8f976279 AS builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.4-trixie@sha256:0dcba0d95dbfb072e9917a106b9e07d7cc298097dc83e9307056ef1889de654d AS builder
ARG TARGETOS
ARG TARGETARCH
+1 -1
View File
@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
# Stage 1: Build
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.3-trixie@sha256:0f6b034c99663ea8957e7dae99124e37374cbe7fcb5b5646f19b185f8f976279 AS builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.26.4-trixie@sha256:0dcba0d95dbfb072e9917a106b9e07d7cc298097dc83e9307056ef1889de654d AS builder
ARG TARGETOS
ARG TARGETARCH
+3
View File
@@ -1,4 +1,5 @@
# Integrations
ADOM
appflowy
Ascensio
Authy
@@ -25,6 +26,7 @@ Gravitee
grommunio
HACS
Homarr
Icinga
Informatique
Jellyseerr
Kimai
@@ -36,6 +38,7 @@ Kubeconfig
Mautic
Mobilizon
myabsorb
Nexterm
Observium
Ofair
Ollama
+26 -7
View File
@@ -15,7 +15,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-06 00:27+0000\n"
"POT-Creation-Date: 2026-05-22 00:36+0000\n"
"PO-Revision-Date: 2025-12-01 19:09+0000\n"
"Last-Translator: Sp P, 2026\n"
"Language-Team: French (France) (https://app.transifex.com/authentik/teams/119923/fr_FR/)\n"
@@ -118,11 +118,11 @@ msgstr "Le fichier de plan n'existe pas"
#: authentik/blueprints/api.py
msgid "Context must be valid JSON"
msgstr ""
msgstr "Le contexte doit être un JSON valide"
#: authentik/blueprints/api.py
msgid "Context must be a JSON object"
msgstr ""
msgstr "Le contexte doit être un objet JSON"
#: authentik/blueprints/api.py
msgid "Failed to validate blueprint"
@@ -261,6 +261,11 @@ msgstr ""
"Le slug '{slug}' est réservé et ne peut pas être utilisé pour les "
"applications."
#: authentik/core/api/groups.py
msgid "User does not have permission to add members to this group."
msgstr ""
"L'utilisateur n'a pas la permission d'ajouter des membres à ce groupe."
#: authentik/core/api/providers.py
msgid ""
"When not set all providers are returned. When set to true, only backchannel "
@@ -303,6 +308,16 @@ msgid "Setting a user to internal service account is not allowed."
msgstr ""
"Définir un utilisateur comme compte de service interne n'est pas autorisé."
#: authentik/core/api/users.py
msgid "User does not have permission to add members to a superuser group."
msgstr ""
"L'utilisateur n'a pas la permission d'ajouter des membres à un groupe de "
"super-utilisateurs."
#: authentik/core/api/users.py
msgid "User does not have permission to assign roles."
msgstr "L'utilisateur n'a pas la permission d'assigner des rôles."
#: authentik/core/api/users.py
msgid "Can't modify internal service account users"
msgstr "Impossible de modifier les utilisateurs du compte de service interne"
@@ -1549,11 +1564,11 @@ msgstr ""
#: authentik/events/models.py
msgid ""
"When set, the selected ceritifcate is used to validate the certificate of "
"When set, the selected certificate is used to validate the certificate of "
"the webhook server."
msgstr ""
"Quand défini, le certificat sélectionné est utilisé pour valider le "
"certificat du serveur de Webhook."
"certificat du serveur de Webhook"
#: authentik/events/models.py
msgid ""
@@ -3123,8 +3138,12 @@ msgid "SAML Sessions"
msgstr "Sessions SAML"
#: authentik/providers/scim/models.py
msgid "OAuth"
msgstr "OAuth"
msgid "OAuth (Silent)"
msgstr "OAuth (silencieux)"
#: authentik/providers/scim/models.py
msgid "OAuth (interactive)"
msgstr "OAuth (en interactif)"
#: authentik/providers/scim/models.py
msgid "Default"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,7 +1,7 @@
---
services:
gen:
image: docker.io/openapitools/openapi-generator-cli:v7.20.0
image: docker.io/openapitools/openapi-generator-cli:v7.22.0
restart: never
network_mode: none
volumes:
+3 -3
View File
@@ -22,7 +22,7 @@ dependencies = [
"django-pgtrigger==4.17.0",
"django-postgres-cache",
"django-postgres-extra==2.0.9",
"django-prometheus==2.4.1",
"django-prometheus==2.5.0",
"django-storages[s3]==1.14.6",
"django-tenants==3.10.1",
"django==5.2.14",
@@ -41,7 +41,7 @@ dependencies = [
"gunicorn==26.0.0",
"jsonpatch==1.33",
"jwcrypto==1.5.7",
"kubernetes==35.0.0",
"kubernetes==36.0.0",
"ldap3==2.9.1",
"lxml==6.1.1",
"msgraph-sdk==1.58.0",
@@ -92,7 +92,7 @@ dev = [
"importlib-metadata==9.0.0",
"k5test==0.10.4",
"lxml-stubs==0.5.1",
"mypy==2.0.0",
"mypy==2.1.0",
"pdoc==16.0.0",
"pytest-django==4.12.0",
"pytest-flakefinder==1.1.0",
+2
View File
@@ -274,5 +274,7 @@ run()
} else {
logger.info("✅ Lockfile is in sync.");
}
process.exit(0);
})
.catch((error) => reportAndExit(error, logger));
+1
View File
@@ -110,5 +110,6 @@ async function main() {
main()
.then(() => {
logger.info("✅ Node.js and npm versions are in sync.");
process.exit(0);
})
.catch((error) => reportAndExit(error, logger));
+7 -4
View File
@@ -102,9 +102,12 @@ async function main() {
subcommand = "use";
}
await $`corepack ${subcommand} ${packageManager}`({ cwd });
logger.info("Corepack installed npm successfully");
return $`corepack ${subcommand} ${packageManager}`({ cwd });
}
main().catch((error) => reportAndExit(error, logger));
main()
.then(() => {
logger.info("Corepack setup completed successfully");
process.exit(0);
})
.catch((error) => reportAndExit(error, logger));
+2 -2
View File
@@ -1,6 +1,6 @@
services:
chromium:
image: ghcr.io/goauthentik/selenium:148.0-ak-0.43.1
image: ghcr.io/goauthentik/selenium:148.0-ak-0.43.2
shm_size: 2g
network_mode: host
restart: always
@@ -13,7 +13,7 @@ services:
environment:
VIDEO_READY_PORT: 9912
mailpit:
image: docker.io/axllent/mailpit:v1.30.0
image: docker.io/axllent/mailpit:v1.30.1
ports:
- 1025:1025
- 8025:8025
Generated
+70 -62
View File
@@ -22,7 +22,7 @@ wheels = [
[[package]]
name = "aiohttp"
version = "3.13.5"
version = "3.14.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohappyeyeballs" },
@@ -33,42 +33,49 @@ dependencies = [
{ name = "propcache" },
{ name = "yarl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/77/9a/152096d4808df8e4268befa55fba462f440f14beab85e8ad9bf990516918/aiohttp-3.13.5.tar.gz", hash = "sha256:9d98cc980ecc96be6eb4c1994ce35d28d8b1f5e5208a23b421187d1209dbb7d1", size = 7858271, upload-time = "2026-03-31T22:01:03.343Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/ab/93ce242f899b68c51b0578c027aafa791ab3614cb9345fa5d37b5f5c8e3e/aiohttp-3.14.0.tar.gz", hash = "sha256:2882de819734c715fd1b9c11c97e09fa020d14438203d1d354d8ed1702791c9b", size = 7940674, upload-time = "2026-06-01T19:41:02.763Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5d/ce/46572759afc859e867a5bc8ec3487315869013f59281ce61764f76d879de/aiohttp-3.13.5-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:eb4639f32fd4a9904ab8fb45bf3383ba71137f3d9d4ba25b3b3f3109977c5b8c", size = 745721, upload-time = "2026-03-31T21:58:50.229Z" },
{ url = "https://files.pythonhosted.org/packages/13/fe/8a2efd7626dbe6049b2ef8ace18ffda8a4dfcbe1bcff3ac30c0c7575c20b/aiohttp-3.13.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:7e5dc4311bd5ac493886c63cbf76ab579dbe4641268e7c74e48e774c74b6f2be", size = 497663, upload-time = "2026-03-31T21:58:52.232Z" },
{ url = "https://files.pythonhosted.org/packages/9b/91/cc8cc78a111826c54743d88651e1687008133c37e5ee615fee9b57990fac/aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:756c3c304d394977519824449600adaf2be0ccee76d206ee339c5e76b70ded25", size = 499094, upload-time = "2026-03-31T21:58:54.566Z" },
{ url = "https://files.pythonhosted.org/packages/0a/33/a8362cb15cf16a3af7e86ed11962d5cd7d59b449202dc576cdc731310bde/aiohttp-3.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecc26751323224cf8186efcf7fbcbc30f4e1d8c7970659daf25ad995e4032a56", size = 1726701, upload-time = "2026-03-31T21:58:56.864Z" },
{ url = "https://files.pythonhosted.org/packages/45/0c/c091ac5c3a17114bd76cbf85d674650969ddf93387876cf67f754204bd77/aiohttp-3.13.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10a75acfcf794edf9d8db50e5a7ec5fc818b2a8d3f591ce93bc7b1210df016d2", size = 1683360, upload-time = "2026-03-31T21:58:59.072Z" },
{ url = "https://files.pythonhosted.org/packages/23/73/bcee1c2b79bc275e964d1446c55c54441a461938e70267c86afaae6fba27/aiohttp-3.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f7a18f258d124cd678c5fe072fe4432a4d5232b0657fca7c1847f599233c83a", size = 1773023, upload-time = "2026-03-31T21:59:01.776Z" },
{ url = "https://files.pythonhosted.org/packages/c7/ef/720e639df03004fee2d869f771799d8c23046dec47d5b81e396c7cda583a/aiohttp-3.13.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df6104c009713d3a89621096f3e3e88cc323fd269dbd7c20afe18535094320be", size = 1853795, upload-time = "2026-03-31T21:59:04.568Z" },
{ url = "https://files.pythonhosted.org/packages/bd/c9/989f4034fb46841208de7aeeac2c6d8300745ab4f28c42f629ba77c2d916/aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:241a94f7de7c0c3b616627aaad530fe2cb620084a8b144d3be7b6ecfe95bae3b", size = 1730405, upload-time = "2026-03-31T21:59:07.221Z" },
{ url = "https://files.pythonhosted.org/packages/ce/75/ee1fd286ca7dc599d824b5651dad7b3be7ff8d9a7e7b3fe9820d9180f7db/aiohttp-3.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c974fb66180e58709b6fc402846f13791240d180b74de81d23913abe48e96d94", size = 1558082, upload-time = "2026-03-31T21:59:09.484Z" },
{ url = "https://files.pythonhosted.org/packages/c3/20/1e9e6650dfc436340116b7aa89ff8cb2bbdf0abc11dfaceaad8f74273a10/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6e27ea05d184afac78aabbac667450c75e54e35f62238d44463131bd3f96753d", size = 1692346, upload-time = "2026-03-31T21:59:12.068Z" },
{ url = "https://files.pythonhosted.org/packages/d8/40/8ebc6658d48ea630ac7903912fe0dd4e262f0e16825aa4c833c56c9f1f56/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a79a6d399cef33a11b6f004c67bb07741d91f2be01b8d712d52c75711b1e07c7", size = 1698891, upload-time = "2026-03-31T21:59:14.552Z" },
{ url = "https://files.pythonhosted.org/packages/d8/78/ea0ae5ec8ba7a5c10bdd6e318f1ba5e76fcde17db8275188772afc7917a4/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c632ce9c0b534fbe25b52c974515ed674937c5b99f549a92127c85f771a78772", size = 1742113, upload-time = "2026-03-31T21:59:17.068Z" },
{ url = "https://files.pythonhosted.org/packages/8a/66/9d308ed71e3f2491be1acb8769d96c6f0c47d92099f3bc9119cada27b357/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fceedde51fbd67ee2bcc8c0b33d0126cc8b51ef3bbde2f86662bd6d5a6f10ec5", size = 1553088, upload-time = "2026-03-31T21:59:19.541Z" },
{ url = "https://files.pythonhosted.org/packages/da/a6/6cc25ed8dfc6e00c90f5c6d126a98e2cf28957ad06fa1036bd34b6f24a2c/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f92995dfec9420bb69ae629abf422e516923ba79ba4403bc750d94fb4a6c68c1", size = 1757976, upload-time = "2026-03-31T21:59:22.311Z" },
{ url = "https://files.pythonhosted.org/packages/c1/2b/cce5b0ffe0de99c83e5e36d8f828e4161e415660a9f3e58339d07cce3006/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20ae0ff08b1f2c8788d6fb85afcb798654ae6ba0b747575f8562de738078457b", size = 1712444, upload-time = "2026-03-31T21:59:24.635Z" },
{ url = "https://files.pythonhosted.org/packages/6c/cf/9e1795b4160c58d29421eafd1a69c6ce351e2f7c8d3c6b7e4ca44aea1a5b/aiohttp-3.13.5-cp314-cp314-win32.whl", hash = "sha256:b20df693de16f42b2472a9c485e1c948ee55524786a0a34345511afdd22246f3", size = 438128, upload-time = "2026-03-31T21:59:27.291Z" },
{ url = "https://files.pythonhosted.org/packages/22/4d/eaedff67fc805aeba4ba746aec891b4b24cebb1a7d078084b6300f79d063/aiohttp-3.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:f85c6f327bf0b8c29da7d93b1cabb6363fb5e4e160a32fa241ed2dce21b73162", size = 464029, upload-time = "2026-03-31T21:59:29.429Z" },
{ url = "https://files.pythonhosted.org/packages/79/11/c27d9332ee20d68dd164dc12a6ecdef2e2e35ecc97ed6cf0d2442844624b/aiohttp-3.13.5-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1efb06900858bb618ff5cee184ae2de5828896c448403d51fb633f09e109be0a", size = 778758, upload-time = "2026-03-31T21:59:31.547Z" },
{ url = "https://files.pythonhosted.org/packages/04/fb/377aead2e0a3ba5f09b7624f702a964bdf4f08b5b6728a9799830c80041e/aiohttp-3.13.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fee86b7c4bd29bdaf0d53d14739b08a106fdda809ca5fe032a15f52fae5fe254", size = 512883, upload-time = "2026-03-31T21:59:34.098Z" },
{ url = "https://files.pythonhosted.org/packages/bb/a6/aa109a33671f7a5d3bd78b46da9d852797c5e665bfda7d6b373f56bff2ec/aiohttp-3.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:20058e23909b9e65f9da62b396b77dfa95965cbe840f8def6e572538b1d32e36", size = 516668, upload-time = "2026-03-31T21:59:36.497Z" },
{ url = "https://files.pythonhosted.org/packages/79/b3/ca078f9f2fa9563c36fb8ef89053ea2bb146d6f792c5104574d49d8acb63/aiohttp-3.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cf20a8d6868cb15a73cab329ffc07291ba8c22b1b88176026106ae39aa6df0f", size = 1883461, upload-time = "2026-03-31T21:59:38.723Z" },
{ url = "https://files.pythonhosted.org/packages/b7/e3/a7ad633ca1ca497b852233a3cce6906a56c3225fb6d9217b5e5e60b7419d/aiohttp-3.13.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:330f5da04c987f1d5bdb8ae189137c77139f36bd1cb23779ca1a354a4b027800", size = 1747661, upload-time = "2026-03-31T21:59:41.187Z" },
{ url = "https://files.pythonhosted.org/packages/33/b9/cd6fe579bed34a906d3d783fe60f2fa297ef55b27bb4538438ee49d4dc41/aiohttp-3.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f1cbf0c7926d315c3c26c2da41fd2b5d2fe01ac0e157b78caefc51a782196cf", size = 1863800, upload-time = "2026-03-31T21:59:43.84Z" },
{ url = "https://files.pythonhosted.org/packages/c0/3f/2c1e2f5144cefa889c8afd5cf431994c32f3b29da9961698ff4e3811b79a/aiohttp-3.13.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:53fc049ed6390d05423ba33103ded7281fe897cf97878f369a527070bd95795b", size = 1958382, upload-time = "2026-03-31T21:59:46.187Z" },
{ url = "https://files.pythonhosted.org/packages/66/1d/f31ec3f1013723b3babe3609e7f119c2c2fb6ef33da90061a705ef3e1bc8/aiohttp-3.13.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:898703aa2667e3c5ca4c54ca36cd73f58b7a38ef87a5606414799ebce4d3fd3a", size = 1803724, upload-time = "2026-03-31T21:59:48.656Z" },
{ url = "https://files.pythonhosted.org/packages/0e/b4/57712dfc6f1542f067daa81eb61da282fab3e6f1966fca25db06c4fc62d5/aiohttp-3.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0494a01ca9584eea1e5fbd6d748e61ecff218c51b576ee1999c23db7066417d8", size = 1640027, upload-time = "2026-03-31T21:59:51.284Z" },
{ url = "https://files.pythonhosted.org/packages/25/3c/734c878fb43ec083d8e31bf029daae1beafeae582d1b35da234739e82ee7/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6cf81fe010b8c17b09495cbd15c1d35afbc8fb405c0c9cf4738e5ae3af1d65be", size = 1806644, upload-time = "2026-03-31T21:59:53.753Z" },
{ url = "https://files.pythonhosted.org/packages/20/a5/f671e5cbec1c21d044ff3078223f949748f3a7f86b14e34a365d74a5d21f/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c564dd5f09ddc9d8f2c2d0a301cd30a79a2cc1b46dd1a73bef8f0038863d016b", size = 1791630, upload-time = "2026-03-31T21:59:56.239Z" },
{ url = "https://files.pythonhosted.org/packages/0b/63/fb8d0ad63a0b8a99be97deac8c04dacf0785721c158bdf23d679a87aa99e/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2994be9f6e51046c4f864598fd9abeb4fba6e88f0b2152422c9666dcd4aea9c6", size = 1809403, upload-time = "2026-03-31T21:59:59.103Z" },
{ url = "https://files.pythonhosted.org/packages/59/0c/bfed7f30662fcf12206481c2aac57dedee43fe1c49275e85b3a1e1742294/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:157826e2fa245d2ef46c83ea8a5faf77ca19355d278d425c29fda0beb3318037", size = 1634924, upload-time = "2026-03-31T22:00:02.116Z" },
{ url = "https://files.pythonhosted.org/packages/17/d6/fd518d668a09fd5a3319ae5e984d4d80b9a4b3df4e21c52f02251ef5a32e/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a8aca50daa9493e9e13c0f566201a9006f080e7c50e5e90d0b06f53146a54500", size = 1836119, upload-time = "2026-03-31T22:00:04.756Z" },
{ url = "https://files.pythonhosted.org/packages/78/b7/15fb7a9d52e112a25b621c67b69c167805cb1f2ab8f1708a5c490d1b52fe/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3b13560160d07e047a93f23aaa30718606493036253d5430887514715b67c9d9", size = 1772072, upload-time = "2026-03-31T22:00:07.494Z" },
{ url = "https://files.pythonhosted.org/packages/7e/df/57ba7f0c4a553fc2bd8b6321df236870ec6fd64a2a473a8a13d4f733214e/aiohttp-3.13.5-cp314-cp314t-win32.whl", hash = "sha256:9a0f4474b6ea6818b41f82172d799e4b3d29e22c2c520ce4357856fced9af2f8", size = 471819, upload-time = "2026-03-31T22:00:10.277Z" },
{ url = "https://files.pythonhosted.org/packages/62/29/2f8418269e46454a26171bfdd6a055d74febf32234e474930f2f60a17145/aiohttp-3.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:18a2f6c1182c51baa1d28d68fea51513cb2a76612f038853c0ad3c145423d3d9", size = 505441, upload-time = "2026-03-31T22:00:12.791Z" },
{ url = "https://files.pythonhosted.org/packages/28/03/5f36ab196a88ba5e9648ae5643e6531e67a3a8c0e96f9c6510ff41540fec/aiohttp-3.14.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:363ef9e91014e7891679bfb2ac0a7c6ea93435dbbfd10ecf41b9f06fcf506c5f", size = 503330, upload-time = "2026-06-01T19:39:18.195Z" },
{ url = "https://files.pythonhosted.org/packages/2c/ce/8b49ec2f30f68e02f314f4832186cd45e583360a5a386058be36855d23b6/aiohttp-3.14.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:884a4edbdad77be9d0ef36142c8b504351b170df0bf62b51e784fadabf311c42", size = 509822, upload-time = "2026-06-01T19:39:20.396Z" },
{ url = "https://files.pythonhosted.org/packages/1a/fe/6edbf5d39bf29322b6816365b17ed8ede4dace164a3aea1abcd30110eb78/aiohttp-3.14.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:70ea956f6cc4a37620966b56c2e205d88ca3e6d85ec063277e414b1035cddad3", size = 483329, upload-time = "2026-06-01T19:39:22.607Z" },
{ url = "https://files.pythonhosted.org/packages/1b/5a/fae531bdbc6456fb6241f46b7b81e4d8a0dd3fc09118a0055dc7141ac1ec/aiohttp-3.14.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:ea3b9806c89f61da22fddf1f12dd524fb368e5e28f1261fbdafe5c3cd8ce893b", size = 489502, upload-time = "2026-06-01T19:39:24.881Z" },
{ url = "https://files.pythonhosted.org/packages/36/f4/48a7b0414db7fed77a03d5dde34508c026afd83510ab6bca08c313855776/aiohttp-3.14.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a071be341c2bd9b0188e62d173509f024e0a35b1c342c53c50f8daaeda8c3bd8", size = 497357, upload-time = "2026-06-01T19:39:27.197Z" },
{ url = "https://files.pythonhosted.org/packages/75/75/e85a13a370acc007fca5feb1fd1b88ac2d8426e6dadd625479b7cadd55a3/aiohttp-3.14.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:198cfe61bf253b19da1fb3e0fa122249dc4f14c12709493fed8054aa0411cc76", size = 750898, upload-time = "2026-06-01T19:39:29.563Z" },
{ url = "https://files.pythonhosted.org/packages/9e/e4/3d637f800c724eff0e2bed64df72557444482366fd0a35b0cec0e6968f6c/aiohttp-3.14.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9dc203d6ce6b9106d54e2a93f41dfdfebfbca2d99962ba503bfd3e5921a6549e", size = 506986, upload-time = "2026-06-01T19:39:31.872Z" },
{ url = "https://files.pythonhosted.org/packages/1d/df/35161f3598bf7501d2b2a805b41ab4f45a2e34150c421bcb4ef8c0d281a7/aiohttp-3.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9e19d17ab02bf16832a2c8c0d55a486792c5b1645665652ee9531aebcc30cb72", size = 508033, upload-time = "2026-06-01T19:39:34.137Z" },
{ url = "https://files.pythonhosted.org/packages/e5/39/b36e5d3d31e850fb4691dd3e941684ac490a2559249f6fa634b6b0fdf020/aiohttp-3.14.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d925fba0c14d5b498a8028b0107beebdfd16c5d48d702ff54f879cb017aaaca3", size = 1746213, upload-time = "2026-06-01T19:39:36.654Z" },
{ url = "https://files.pythonhosted.org/packages/b1/28/24e1409e605a9aa5d84abe0e2acb365354b70ae56d40948101cabe3341ab/aiohttp-3.14.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d33e61021222ce7f9792bcac870d6f58d8adfceda33ab857b01264f4560f2c5f", size = 1705862, upload-time = "2026-06-01T19:39:38.968Z" },
{ url = "https://files.pythonhosted.org/packages/8c/d0/e5eb3ff1daeaf644c7e36a957517672494122628e067c38b263fa04eda77/aiohttp-3.14.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:44eca38755d0105bb32f47d085f5dd449846a449e1245fc105889e3279dcf8e3", size = 1798909, upload-time = "2026-06-01T19:39:41.334Z" },
{ url = "https://files.pythonhosted.org/packages/d3/ba/8943f906f0570342886ababb9a722a44e360f786a028c5e0b0e29e3f735b/aiohttp-3.14.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f13087e06f68fea4941c21a0c541c00553aa16e4f8fd7bbe2b198df761e964d6", size = 1868892, upload-time = "2026-06-01T19:39:43.807Z" },
{ url = "https://files.pythonhosted.org/packages/3a/05/27df32c844b2156e1675a8d8ec22d963e3c8ba469ed7ceb1863320c7b521/aiohttp-3.14.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ff82be7f1ef73634cb77890a770743239bc3d487b848669be1c599889336dc0a", size = 1751659, upload-time = "2026-06-01T19:39:46.398Z" },
{ url = "https://files.pythonhosted.org/packages/7f/62/da182e5910ab912b2e88aa919b61a16046a37a95714a5795b02eb57b2d18/aiohttp-3.14.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a150c0875ac8fd87f1c398650841308a30d65facf7416b12dbdb9cfdcbe5a48c", size = 1578775, upload-time = "2026-06-01T19:39:48.902Z" },
{ url = "https://files.pythonhosted.org/packages/66/e3/53c67097e8a5ce98625e91e3fa7f43c9c6940de680345d03b3509a72a078/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:edc01ea4e1ec5a1649a28866262bf24195889ff7b27bdd947029a6086741de9b", size = 1710090, upload-time = "2026-06-01T19:39:51.392Z" },
{ url = "https://files.pythonhosted.org/packages/dd/55/0e2732ca598c7a4dfe8a775662376d0ca2977cb1030e48386d4da5d9a456/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:540632bf882ff8fc88f2e1697be0761578e89e0d79fb4a8a6d65dc5da7e729d4", size = 1715016, upload-time = "2026-06-01T19:39:53.807Z" },
{ url = "https://files.pythonhosted.org/packages/5a/96/f0b73730798c9ca525afc30b39f1f81bbe24e245d9654c54d3b39d63212d/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:860a86bc2c80237f5dff52edcf427e10a8d8352271fd84845429a3e60199e02c", size = 1763810, upload-time = "2026-06-01T19:39:56.31Z" },
{ url = "https://files.pythonhosted.org/packages/71/cc/11acb6c4518f448323405a7312b6f255d0f974a34373ad1db7633c4aadc8/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5cbd50e6a50d6b99283a826b18cbdebf65b0797689a7535cb0e9dd37be0f63c3", size = 1573064, upload-time = "2026-06-01T19:39:58.718Z" },
{ url = "https://files.pythonhosted.org/packages/de/2d/28c31dde0a7dc98c0ee7d0da2ddcec3f7688c4fc131e5989e278d0c03c0a/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:20144819e99db593e22bbd2f3f2691a5e149f879142d6b8670254708853ff4fb", size = 1775765, upload-time = "2026-06-01T19:40:01.195Z" },
{ url = "https://files.pythonhosted.org/packages/b8/69/155c4ef3aec96417d47024800472b33b16c5d8a665371dcd044c2afdf25d/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:26b6d79aa54cb4ed50cc7d41ed14e99e0f1fc8e7c2d42f2e05b37aea897b2b52", size = 1733716, upload-time = "2026-06-01T19:40:03.631Z" },
{ url = "https://files.pythonhosted.org/packages/5f/44/6126116fd8a316b712bb615660b855c78466bb67ba1bb1742427eafcf7ac/aiohttp-3.14.0-cp314-cp314-win32.whl", hash = "sha256:106ed074a856f3e21d186b8579e2c8afb6da598e267cdaab01059e13db2fc44d", size = 453684, upload-time = "2026-06-01T19:40:06.277Z" },
{ url = "https://files.pythonhosted.org/packages/a2/d7/eff4c58a88c5cac5e38b55f44fb8a6d3929c3cbd77356e383e094d3220bd/aiohttp-3.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f770846edae8f00ecc57af825bce811f787f87a7dcf0e90d191790efe5b31f7", size = 481758, upload-time = "2026-06-01T19:40:08.653Z" },
{ url = "https://files.pythonhosted.org/packages/d7/ed/17b5bd9fbcb46e688f02e572f517754a9a75831e7b54702f027761dc4fa5/aiohttp-3.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:acf1581c4f21ed4b80a2dded504d87b055a071a84d5737ea966435f768275ac6", size = 450557, upload-time = "2026-06-01T19:40:11.03Z" },
{ url = "https://files.pythonhosted.org/packages/12/34/6180103ce9aabc8ebff3f7bb55a1228ffe60f61042823031d9692cb7b101/aiohttp-3.14.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6aa1a40f9cbb3da9f80714c5966b8946c21e6a2530d809b9498b33161e3c8733", size = 787878, upload-time = "2026-06-01T19:40:13.401Z" },
{ url = "https://files.pythonhosted.org/packages/92/e9/08954a40e8b7baa3d8beadd2b074b186e9b1e9c8ddabc288678a6265de50/aiohttp-3.14.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b62af5a8cc96a194eaa01a9ed7b34a3ffa58d3d8daaa1a0d7a749353ad12d228", size = 524400, upload-time = "2026-06-01T19:40:15.972Z" },
{ url = "https://files.pythonhosted.org/packages/08/6a/b5965a634ac4d5ba99a463314cf4ab214ca073fcdc38a15e0294273701fc/aiohttp-3.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6eb63b1417efaf7d1002a6ad034a40d44376afcc16508a57f8e74b49ad26a095", size = 527904, upload-time = "2026-06-01T19:40:18.28Z" },
{ url = "https://files.pythonhosted.org/packages/06/b4/932bcdd850c354d9bcca30f360e475d7852e30413fbbd44b182782ed5432/aiohttp-3.14.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c20b9ad156a79eb97be5cf9e069eec01d2f0dc8472ffbd75299a8b2d4c2cbbde", size = 1912162, upload-time = "2026-06-01T19:40:20.825Z" },
{ url = "https://files.pythonhosted.org/packages/c6/85/ce79bab0310d2e3fd2d7bc7e44412abeff7c8338f8a21dd0f2f1714989e5/aiohttp-3.14.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:40ae7b0642c25632c7eabc4a04754012691864d2a1b93becf7cddb76027b838a", size = 1778813, upload-time = "2026-06-01T19:40:23.726Z" },
{ url = "https://files.pythonhosted.org/packages/05/54/ba62ac2d1bc87e010aad23751e383b8794e45d931df67677313a2da78823/aiohttp-3.14.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:95f5217e76a046b9f228a101717ef8d42b1eb3d9d196d15202db5bf41df88936", size = 1899969, upload-time = "2026-06-01T19:40:26.406Z" },
{ url = "https://files.pythonhosted.org/packages/dc/82/7cc7907725d83a19f31551334061e1ab8e108b1d7ac52632a2a844a4acb5/aiohttp-3.14.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1a4a9f17e85b80878c176695c1998c790e83731d8271881e5d356488652a1f9e", size = 1991771, upload-time = "2026-06-01T19:40:29.061Z" },
{ url = "https://files.pythonhosted.org/packages/d0/1c/a57de71a4508c93a830b77c28af3d08cd97f606dedfc6b94275347744508/aiohttp-3.14.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:145262119b07d7f95abc1839add35ba2bfc84551d4b4660ca11542c0b215455b", size = 1868606, upload-time = "2026-06-01T19:40:31.843Z" },
{ url = "https://files.pythonhosted.org/packages/9c/ae/3839726cd49150a53ed340cc24ce5ba09d4c2117020ef9d45542bec5eb2f/aiohttp-3.14.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:49a33ded29b0b2fa7a367a02cf0fb89af602bb87542a16177ec8ce1c9c51d12a", size = 1665437, upload-time = "2026-06-01T19:40:35.01Z" },
{ url = "https://files.pythonhosted.org/packages/35/1e/c237923232c7da7f0392ea25d89fc5e60c0e93f685f4ebca8e7bcdd5271c/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2cc736a9c9fc2bc4dd71fd404815741b6573df27c3f985948ec4076989ac57de", size = 1834090, upload-time = "2026-06-01T19:40:37.733Z" },
{ url = "https://files.pythonhosted.org/packages/98/02/a5a7a2524f92d3911761b405a7c067c751891942144adc13e2ad79611e39/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b4141a3e5342ee3053a9cab54d25b64ed28289c1041e4c54b3d99839314d90ce", size = 1816907, upload-time = "2026-06-01T19:40:40.46Z" },
{ url = "https://files.pythonhosted.org/packages/fa/76/a8b9f0d09234d516af9f2d7dd715557f33b5da3b0b56ead41d1170e86e3c/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e30871b2d58996cb81aac52d2b1d15ac05257131ef0f90f18c2115a380fbfe7c", size = 1840382, upload-time = "2026-06-01T19:40:43.48Z" },
{ url = "https://files.pythonhosted.org/packages/c9/8e/140e715a0a4bbc211979ea30ec8396ad2ed5bf90ab87d8058fc4668b1923/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:667b881d083ccae3900ea5a241e17e5007ca78844c53ed389bb63d48f729d9c7", size = 1659497, upload-time = "2026-06-01T19:40:46.265Z" },
{ url = "https://files.pythonhosted.org/packages/10/c7/7ba5de8af9650b9767b063c675427b8685f43fa7ce563673a7bc3af60f08/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:b584dfe615d151e9b8f0a8ecb3aee6147f2927ec5b95ba25fe621f5377510928", size = 1870829, upload-time = "2026-06-01T19:40:49.583Z" },
{ url = "https://files.pythonhosted.org/packages/cc/bc/2aaab2f85cadb26ea59c091fa2b8e370d625154b5c14b478f1b489d07551/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6199707cc40e0e9cd39c36fbc97bec416c704e1d0ddce03412bb3b3e6a90ccd0", size = 1832281, upload-time = "2026-06-01T19:40:52.303Z" },
{ url = "https://files.pythonhosted.org/packages/39/98/31b9ad9fbc01f0075ee7221002df5fd2d10b647f451ca5f30edc802d9dd6/aiohttp-3.14.0-cp314-cp314t-win32.whl", hash = "sha256:a8d93334d4961c9d566b1f046c81dee475b7c21eb730728d38237bfa70d1c8e6", size = 490597, upload-time = "2026-06-01T19:40:54.937Z" },
{ url = "https://files.pythonhosted.org/packages/59/1f/299b21441c8de42ff70fddc7cfe65e92f810abcf740739a09b56f7835364/aiohttp-3.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2d2ffe9b614f50f069068b3b52e73414e4107fc10b7efc939a76acff9251fdd2", size = 525789, upload-time = "2026-06-01T19:40:57.306Z" },
{ url = "https://files.pythonhosted.org/packages/70/11/7f83fcba9ee05d4c54d61b3f8104da0d43a59adac44dd28effc0c9a10422/aiohttp-3.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:7a3fc4358e65826c515350f199c210de747cf669998211b1ee6c2e46de364b24", size = 467399, upload-time = "2026-06-01T19:40:59.993Z" },
]
[[package]]
@@ -372,7 +379,7 @@ requires-dist = [
{ name = "django-pgtrigger", specifier = "==4.17.0" },
{ name = "django-postgres-cache", editable = "packages/django-postgres-cache" },
{ name = "django-postgres-extra", specifier = "==2.0.9" },
{ name = "django-prometheus", specifier = "==2.4.1" },
{ name = "django-prometheus", specifier = "==2.5.0" },
{ name = "django-storages", extras = ["s3"], specifier = "==1.14.6" },
{ name = "django-tenants", specifier = "==3.10.1" },
{ name = "djangoql", specifier = "==0.19.1" },
@@ -390,7 +397,7 @@ requires-dist = [
{ name = "gunicorn", specifier = "==26.0.0" },
{ name = "jsonpatch", specifier = "==1.33" },
{ name = "jwcrypto", specifier = "==1.5.7" },
{ name = "kubernetes", specifier = "==35.0.0" },
{ name = "kubernetes", specifier = "==36.0.0" },
{ name = "ldap3", specifier = "==2.9.1" },
{ name = "lxml", specifier = "==6.1.1" },
{ name = "msgraph-sdk", specifier = "==1.58.0" },
@@ -441,7 +448,7 @@ dev = [
{ name = "importlib-metadata", specifier = "==9.0.0" },
{ name = "k5test", specifier = "==0.10.4" },
{ name = "lxml-stubs", specifier = "==0.5.1" },
{ name = "mypy", specifier = "==2.0.0" },
{ name = "mypy", specifier = "==2.1.0" },
{ name = "pdoc", specifier = "==16.0.0" },
{ name = "pytest", specifier = "==9.0.3" },
{ name = "pytest-django", specifier = "==4.12.0" },
@@ -1279,15 +1286,15 @@ wheels = [
[[package]]
name = "django-prometheus"
version = "2.4.1"
version = "2.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
{ name = "prometheus-client" },
]
sdist = { url = "https://files.pythonhosted.org/packages/98/f4/cb39ddd2a41e07a274c4e162c076e906ae232d63b66bbabdea0300878877/django_prometheus-2.4.1.tar.gz", hash = "sha256:073628243d2a6de6a8a8c20e5b512872dfb85d66e1b60b28bcf1eca0155dad95", size = 24464, upload-time = "2025-06-25T15:45:37.149Z" }
sdist = { url = "https://files.pythonhosted.org/packages/7b/c7/dc39c4c19f7b35e827a486d08376de1fad31c50decb26c56e32668314f13/django_prometheus-2.5.0.tar.gz", hash = "sha256:4837b3c3734d8350880839ab8235aafd250b668c348e159d4aecc3cbefeee53e", size = 26465, upload-time = "2026-05-26T19:04:00.77Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/01/50/9c5e022fa92574e5d20606687f15a2aa255e10512a17d11a8216fa117f72/django_prometheus-2.4.1-py2.py3-none-any.whl", hash = "sha256:7fe5af7f7c9ad9cd8a429fe0f3f1bf651f0e244f77162147869eab7ec09cc5e7", size = 29541, upload-time = "2025-06-25T15:45:35.433Z" },
{ url = "https://files.pythonhosted.org/packages/5d/5d/6ec3083ba69545696c962ae505a0e52e280e7592d4c278c2f3803cabb688/django_prometheus-2.5.0-py2.py3-none-any.whl", hash = "sha256:f15efb526cd53f9cf12da72dc55506322f5566b017a819ff27be1da302303134", size = 31801, upload-time = "2026-05-26T19:03:59.505Z" },
]
[[package]]
@@ -2051,9 +2058,10 @@ wheels = [
[[package]]
name = "kubernetes"
version = "35.0.0"
version = "36.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
{ name = "certifi" },
{ name = "durationpy" },
{ name = "python-dateutil" },
@@ -2064,9 +2072,9 @@ dependencies = [
{ name = "urllib3" },
{ name = "websocket-client" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2c/8f/85bf51ad4150f64e8c665daf0d9dfe9787ae92005efb9a4d1cba592bd79d/kubernetes-35.0.0.tar.gz", hash = "sha256:3d00d344944239821458b9efd484d6df9f011da367ecb155dadf9513f05f09ee", size = 1094642, upload-time = "2026-01-16T01:05:27.76Z" }
sdist = { url = "https://files.pythonhosted.org/packages/bf/59/dc635e4e9afb3884bc5c57f14fe23783e4c04601aa20b835ac75c41d1625/kubernetes-36.0.0.tar.gz", hash = "sha256:027b606bb8032e6c6464a53236bdd9bd9a94c237e1063bc45a303c25b304ced9", size = 2346728, upload-time = "2026-05-20T20:44:24.28Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/70/05b685ea2dffcb2adbf3cdcea5d8865b7bc66f67249084cf845012a0ff13/kubernetes-35.0.0-py2.py3-none-any.whl", hash = "sha256:39e2b33b46e5834ef6c3985ebfe2047ab39135d41de51ce7641a7ca5b372a13d", size = 2017602, upload-time = "2026-01-16T01:05:25.991Z" },
{ url = "https://files.pythonhosted.org/packages/cd/d2/6f99ca9c7eb961dfdd45b9643101399a8ee20922c662c362c91e9cc7e832/kubernetes-36.0.0-py2.py3-none-any.whl", hash = "sha256:a766433357ec9f90db7565cccf52e28e7fca40b0ef366c80a6022adbc0ac0425", size = 4660469, upload-time = "2026-05-20T20:44:20.893Z" },
]
[[package]]
@@ -2484,7 +2492,7 @@ wheels = [
[[package]]
name = "mypy"
version = "2.0.0"
version = "2.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "ast-serialize" },
@@ -2493,23 +2501,23 @@ dependencies = [
{ name = "pathspec" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cf/dc/7e6d49f04fca40b9dd5c752a51a432ffe67fb45200702bc9eee0cb4bbb26/mypy-2.0.0.tar.gz", hash = "sha256:1a9e3900ac5c40f1fe813506c7739da6e6f0eab2729067ebd94bfb0bbba53532", size = 3869036, upload-time = "2026-05-06T19:26:43.22Z" }
sdist = { url = "https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size = 3898359, upload-time = "2026-05-11T18:37:36.237Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ce/2c/6fefe954207860aed6eeb91776795e64a257d3ce0360862288984ce121f5/mypy-2.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c918c64e8ce36557851b0347f84eb12f1965d3a06813c36df253eb0c0afd1d82", size = 14729633, upload-time = "2026-05-06T19:24:53.383Z" },
{ url = "https://files.pythonhosted.org/packages/23/d6/d336f5b820af189eb0390cce21de62d264c0a4e64713dfbe81bfc4fc7739/mypy-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:301f1a8ccc7d79b542ee218b28bb49443a83e194eb3d10da63ff1649e5aa5d34", size = 13559524, upload-time = "2026-05-06T19:22:24.906Z" },
{ url = "https://files.pythonhosted.org/packages/af/a6/d7bb54fde1770f0484e5fbdbdce37a41e95ed0a1cd493ec60ead111e356c/mypy-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fdf4ef489d44ce350bac3fd699907834e551d4c934e9cc862ef201215ab1558d", size = 13936018, upload-time = "2026-05-06T19:25:02.992Z" },
{ url = "https://files.pythonhosted.org/packages/7d/ba/5be51316b91e6a6bf6e3a8adb3de500e7e1fb5bf9491743b8cbc81a34a2c/mypy-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cde2d0989f912fc850890f727d0d76495e7a6c5bdd9912a1efdb64952b4398d", size = 14910712, upload-time = "2026-05-06T19:25:21.83Z" },
{ url = "https://files.pythonhosted.org/packages/b7/37/e2c8c3b373e20ebfb66e6c83a99027fd67df4ec43b08879f74e822d2dc4c/mypy-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdf05693c231a14fe37dbfce192a3a1372c26a833af4a80f550547742952e719", size = 15141499, upload-time = "2026-05-06T19:20:50.924Z" },
{ url = "https://files.pythonhosted.org/packages/12/36/07756f933e00416d912e35878cfcf89a593a3350a885691c0bb85ae0226a/mypy-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:73aee2da33a2237e66cbe84a94780e53599847e86bb3aa7b93e405e8cd9905f2", size = 11240511, upload-time = "2026-05-06T19:21:32.39Z" },
{ url = "https://files.pythonhosted.org/packages/70/05/79ac1f20f2397353f3845f7b8bb5d8006cda7c8ef9092f04f9de3c6135f2/mypy-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:1f6dcd8f39971f41edab2728c877c4ac8b50ad3c387ff2770423b79a05d23910", size = 10149336, upload-time = "2026-05-06T19:22:08.383Z" },
{ url = "https://files.pythonhosted.org/packages/53/e0/0db84e0ebbad6e99e566c68e4b465784f2a2294f7719e8db9d509ef23087/mypy-2.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a04e980b9275c76159da66c6e1723c7798306f9802b31bdaf9358d0c84030ce8", size = 15797362, upload-time = "2026-05-06T19:22:00.835Z" },
{ url = "https://files.pythonhosted.org/packages/0a/a4/14cc0768164dd53bec48aa41a20270b18df9bf72aa5054278bf133608315/mypy-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:33f9cf4825469b2bc73c53ba55f6d9a9b4cdb60f9e6e228745581520f29b8771", size = 14635914, upload-time = "2026-05-06T19:23:43.675Z" },
{ url = "https://files.pythonhosted.org/packages/08/48/d866a3e23b4dc5974c77d9cf65a435bf22de01a84dd4620917950e233960/mypy-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191675c3c7dc2a5c7722a035a6909c277f14046c5e4e02aa5fbf65f8524f08ad", size = 15270866, upload-time = "2026-05-06T19:22:34.756Z" },
{ url = "https://files.pythonhosted.org/packages/71/eb/de9ef94958eb2078a6b908ceb247757dc384d3a238d3bd6ed7d81de5eaf8/mypy-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3d26c4321a3b06fc9f04c741e0733af693f82d823f8e64e47b2e63b7f19fa84", size = 16093131, upload-time = "2026-05-06T19:23:56.541Z" },
{ url = "https://files.pythonhosted.org/packages/ad/07/0ab2c1a9d26e90942612724cbd5788f16b7810c5dd39bfcf79286c6c4524/mypy-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bbcbc4d5917ca6ce12de70e051de7f533e3bf92d548b41a38a2232a6fe356525", size = 16330685, upload-time = "2026-05-06T19:21:42.037Z" },
{ url = "https://files.pythonhosted.org/packages/a6/8f/46f85d1371a5be642dad263828118ae1efd536d91d8bd2000c68acff3920/mypy-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:dbc6ba6d40572ae49268531565793a8f07eac7fc65ad76d482c9b4c8765b6043", size = 12752017, upload-time = "2026-05-06T19:22:44.002Z" },
{ url = "https://files.pythonhosted.org/packages/7a/e6/94ca48800cac19eb28a58188a768aaec0d16cac0f373915f073058ab0855/mypy-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:77926029dfcb7e1a3ecb0acb2ddbb24ca36be03f7d623e1759ad5376be8f6c01", size = 10527097, upload-time = "2026-05-06T19:20:58.973Z" },
{ url = "https://files.pythonhosted.org/packages/5c/14/fd0694aa594d6e9f9fd16ce821be2eff295197a273262ef56ddcc1388d68/mypy-2.0.0-py3-none-any.whl", hash = "sha256:8a92b2be3146b4fa1f062af7eb05574cbf3e6eb8e1f14704af1075423144e4e5", size = 2673434, upload-time = "2026-05-06T19:26:32.856Z" },
{ url = "https://files.pythonhosted.org/packages/b0/ca/b279a672e874aedd5498ae25f722dacc8aa86bbffb939b3f97cbb1cf6686/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", size = 14848422, upload-time = "2026-05-11T18:35:45.984Z" },
{ url = "https://files.pythonhosted.org/packages/27/e6/3efe56c631d959b9b4454e208b0ac4b7f4f58b404c89f8bec7b49efdfc21/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", size = 13677374, upload-time = "2026-05-11T18:36:57.188Z" },
{ url = "https://files.pythonhosted.org/packages/84/7f/8107ea87a44fd1f1b59882442f033c9c3488c127201b1d1d15f1cbd6022e/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", size = 14055743, upload-time = "2026-05-11T18:35:18.361Z" },
{ url = "https://files.pythonhosted.org/packages/51/4d/b6d34db183133b83761b9199a82d31557cdbb70a380d8c3b3438e11882a3/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", size = 15020937, upload-time = "2026-05-11T18:34:59.618Z" },
{ url = "https://files.pythonhosted.org/packages/ff/d7/f08360c691d758acb02f45022c34d98b92892f4ea756644e1000d4b9f3d8/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", size = 15253371, upload-time = "2026-05-11T18:36:41.081Z" },
{ url = "https://files.pythonhosted.org/packages/67/1b/09460a13719530a19bce27bd3bc8449e83569dd2ba7faf51c9c3c30c0b61/mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", size = 11326429, upload-time = "2026-05-11T18:34:13.526Z" },
{ url = "https://files.pythonhosted.org/packages/40/62/75dbf0f82f7b6680340efc614af29dd0b3c17b8a4f1cd09b8bd2fd6bc814/mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", size = 10218799, upload-time = "2026-05-11T18:32:23.491Z" },
{ url = "https://files.pythonhosted.org/packages/b2/66/caca04ed7d972fb6eb6dd1ccd6df1de5c38fae8c5b3dc1c4e8e0d85ee6b9/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", size = 15923458, upload-time = "2026-05-11T18:35:28.64Z" },
{ url = "https://files.pythonhosted.org/packages/ed/52/2d90cbe49d014b13ed7ff337930c30bad35893fe38a1e4641e756bb62191/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780", size = 14757697, upload-time = "2026-05-11T18:36:14.208Z" },
{ url = "https://files.pythonhosted.org/packages/ac/37/d98f4a14e081b238992d0ed96b6d39c7cc0148c9699eb71eaa68629665ea/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", size = 15405638, upload-time = "2026-05-11T18:33:48.249Z" },
{ url = "https://files.pythonhosted.org/packages/a3/c2/15c46613b24a84fad2aea1248bf9619b99c2767ae9071fe224c179a0b7d4/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", size = 16215852, upload-time = "2026-05-11T18:32:50.296Z" },
{ url = "https://files.pythonhosted.org/packages/5c/90/9c16a57f482c76d25f6379762b56bbf65c711d8158cf271fb2802cfb0640/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", size = 16452695, upload-time = "2026-05-11T18:33:38.182Z" },
{ url = "https://files.pythonhosted.org/packages/0f/4c/215a4eeb63cacc5f17f516691ea7285d11e249802b942476bff15922a314/mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", size = 12866622, upload-time = "2026-05-11T18:34:39.945Z" },
{ url = "https://files.pythonhosted.org/packages/4b/50/1043e1db5f455ffe4c9ab22747cd8ca2bc492b1e4f4e21b130a44ee2b217/mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", size = 10610798, upload-time = "2026-05-11T18:36:31.444Z" },
{ url = "https://files.pythonhosted.org/packages/0d/2a/13ca1f292f6db1b98ff495ef3467736b331621c5917cad984b7043e7348d/mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", size = 2693302, upload-time = "2026-05-11T18:31:29.246Z" },
]
[[package]]
+21
View File
@@ -123,3 +123,24 @@ storybook-static/
.wireit
custom-elements.json
### Agents ###
AGENT.local.md
AGENTS.local.md
CLAUDE.local.md
.agents/*.local.json
.agents/scheduled_tasks.*
.agents/worktree
## Claude
.claude/*.local.json
.claude/scheduled_tasks.*
.claude/worktree
## Pi
.pi
.deps-stamp
+148
View File
@@ -0,0 +1,148 @@
## Project Overview
This is the **authentik WebUI** — the default web interface for the authentik identity server. It is a TypeScript monorepo using Lit web components and PatternFly 4 design system.
There are three distinct UI applications, each with its own base URL and router:
- **Flow** (`/if/flow/`) — Form orchestration for login, signup, password reset, etc.
- **User** (`/if/user/`) — End-user portal for applications and profile settings
- **Admin** (`/if/admin/`) — Server administration and configuration
All three share three core context objects:
- **Config** — Server configuration and user permissions
- **CurrentTenant/Brand** — Theme, logos, favicon, default flows
- **SessionUser** — Logged-in user with impersonation support
## Commands
### Development
```bash
npm run watch # Build + watch locales and bundler (main dev workflow)
npm run storybook # Storybook dev server on port 6006
```
### Build
```bash
npm run build # Production build to dist/
npm run build-locales # Compile i18n translations
```
### Testing
```bash
npm test # Vitest: unit tests (Node) + browser tests (Chromium/Playwright)
npm run test:e2e # Playwright E2E tests against a running authentik instance
```
To run a single test file:
```bash
npx vitest run path/to/file.test.ts
```
### Linting & Formatting
```bash
npm run lint # ESLint with --fix
npm run lint-check # ESLint, no fixes (CI mode, max-warnings: 0)
npm run lint:types # TypeScript type checking (tsc --noEmit)
npm run prettier # Format all files
npm run format # Combined prettier + lint
npm run precommit # Full pre-commit check (format, lint, types, etc.)
```
## Architecture
### Directory Structure
```
src/
admin/ # Admin interface application
user/ # User portal application
flow/ # Flow execution interface + FlowExecutor
components/ # UI components that use context (depend on app state)
elements/ # Reusable UI elements without context (portable)
common/ # Non-UI shared libraries (API helpers, global state, utils)
styles/ # Global CSS (PatternFly, authentik tokens, locales)
standalone/ # Third-party apps (loading screen, API browser)
rac/ # Remote Access Components (Guacamole-based)
locales/ # Auto-generated i18n (do not edit manually)
packages/
core/ # Monorepo utilities (paths, environment, version)
sfe/ # Standalone Frontend Engine (Rollup-based)
test/
unit/ # Node.js unit tests (*.test.ts)
browser/ # Playwright browser tests (*.browser.test.ts)
lit/ # Lit test helpers (renderLit, setup.js)
e2e/ # E2E test fixtures, selectors, auth utilities
bundler/ # Custom ESBuild/Vite plugins
scripts/ # Build scripts (esbuild config, localization)
```
### Key Files
- `src/elements/Base.ts``AKElement`: base class for all components
- `src/elements/Interface.ts` — Base interface class with context management
- `src/common/global.ts` — Global authentik config and state
- `src/flow/FlowExecutor.ts` — Flow execution engine
- `scripts/build-web.mjs` — Main ESBuild configuration
### Conventions
- **Custom element prefix**: `ak-` (e.g., `<ak-command-palette>`)
- **Context**: Lit Context API via `ContextControllerRegistry`
- **`components/`** depends on app context; **`elements/`** must not
- **Import aliases**: `#elements/*`, `#components/*`, `#common/*`, `#admin/*`, `#user/*`, `#flow/*`, etc. (mapped in `package.json`)
NEVER call the authentik API in a different way than using the `@goauthentik/api` package.
In no case are you to use Fetch, Axios, or other methods.
## Tech Stack
| Concern | Library |
| ------------------ | ----------------------------------------- |
| UI components | Lit 3.x + Web Components |
| Design system | PatternFly 4 |
| Build | ESBuild + Vite 7 |
| Tests | Vitest 4 + Playwright |
| i18n | Lit Localize (runtime mode, 18 languages) |
| API client | `@goauthentik/api` (generated) |
| Linting | ESLint 9 + `@goauthentik/eslint-config` |
| Task orchestration | Wireit |
## TypeScript Notes
- `tsconfig.json` uses `"useDefineForClassFields": false` — required for Lit decorators and Storybook; do not change.
- `"moduleResolution": "bundler"` — path aliases resolved at build time via `package.json#imports`.
- Decorators are enabled with `"experimentalDecorators": true`.
- Use `unknown` instead of `any` where possible, and prefer more specific types to both. Avoid `as any` casts.
- When importing a module, prefer an import alias as defined in `package.json` (`#flow/…`, `#elements/…`, `#common/…`) over relative paths into `src/`. This ensures the import will work from any location, including tests.
## i18n
Translatable strings use `msg()` from `@lit/localize`. To add new strings, use `msg()` and run:
```bash
npm run extract-locales # Extract new strings to XLIFF files
npm run pseudolocalize # Generate pseudo-locales for layout testing
```
Never edit files in `src/locales/` directly — they are auto-generated.
### Message ID conventions
Always provide an explicit `id` to `msg()`; do not rely on auto-generated hashes. IDs follow `<feature>.<subfeature>.<role>[.<modifier>]` with kebab-case in every segment.
- **Feature-first, not component-first.** Use `captcha.*`, `command-palette.*`, `used-by.*` — not `ak-secret-text-input.*` or other element/class names. IDs must survive component renames.
- **Kebab-case in every segment.** No camelCase (`usedBy`, `ariaLabel`, `emailInAngleBrackets`), no snake_case. `used-by.count.one`, `wizard.aria-label.default`, `user.display.email-in-angle-brackets`.
- **Trailing segment is the semantic role**, not the surface wording: `.label`, `.placeholder`, `.description`, `.tooltip`, `.aria-label`, `.alt-text`, `.error`, `.success`. This lets translators filter by role.
- **CLDR plural suffixes** for counts: `.zero`, `.one`, `.two`, `.few`, `.many`, `.other`.
- **Composable fragments** that get concatenated go under `.prefix.*` / `.suffix.*` (see `command-palette.prefix.*`).
- **Shared strings** go under a top-level namespace like `common.actions.*` or `forms.validation.*` rather than being duplicated per feature.
- **No flat kebab IDs** like `command-palette-placeholder` or `drawer-toggle-button-notifications` for new strings. Use the dotted hierarchy: `command-palette.placeholder`, `drawer.toggle-button.notifications`. Migrate legacy flat IDs opportunistically when touching surrounding code; do not do bulk renames.
+1
View File
@@ -0,0 +1 @@
@AGENTS.md
+1391 -294
View File
File diff suppressed because it is too large Load Diff
+11 -11
View File
@@ -118,23 +118,23 @@
"@openlayers-elements/maps": "^0.4.0",
"@patternfly/elements": "^4.4.0",
"@patternfly/patternfly": "^4.224.2",
"@playwright/test": "^1.59.1",
"@sentry/browser": "^10.53.1",
"@storybook/addon-docs": "^10.3.6",
"@storybook/addon-links": "^10.3.6",
"@storybook/web-components": "^10.3.6",
"@storybook/web-components-vite": "^10.3.6",
"@playwright/test": "^1.60.0",
"@sentry/browser": "^10.54.0",
"@storybook/addon-docs": "^10.4.1",
"@storybook/addon-links": "^10.4.1",
"@storybook/web-components": "^10.4.1",
"@storybook/web-components-vite": "^10.4.1",
"@types/codemirror": "^5.60.17",
"@types/grecaptcha": "^3.0.9",
"@types/guacamole-common-js": "^1.5.5",
"@types/node": "^25.7.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.57.2",
"@typescript-eslint/parser": "^8.57.2",
"@typescript-eslint/utils": "^8.57.2",
"@typescript-eslint/eslint-plugin": "^8.60.0",
"@typescript-eslint/parser": "^8.60.0",
"@typescript-eslint/utils": "^8.60.0",
"@typescript/native-preview": "^7.0.0-dev.20260510.1",
"@vitest/browser": "^4.1.6",
"@vitest/browser": "^4.1.7",
"@vitest/browser-playwright": "^4.1.6",
"@webcomponents/webcomponentsjs": "^2.8.0",
"base64-js": "^1.5.1",
@@ -155,7 +155,7 @@
"globals": "^17.6.0",
"guacamole-common-js": "^1.5.0",
"hastscript": "^9.0.1",
"knip": "^6.12.0",
"knip": "^6.14.1",
"lex": "^2025.11.0",
"lit": "^3.3.3",
"lit-analyzer": "^2.0.3",
@@ -277,7 +277,7 @@ export class AdminSettingsForm extends Form<SettingsRequest> {
<ak-form-group
label=${msg("Flags")}
description=${msg(
"Flags allow you to enable new functionality and behaviour in authentik early.",
"Flags allow you to enable new functionality and behavior in authentik early.",
)}
>
<div class="pf-c-form">
@@ -31,7 +31,7 @@ export class EnterpriseStatusCard extends AKElement {
case LicenseSummaryStatusEnum.ExpirySoon:
return html`<ak-label color=${PFColor.Orange}>${msg("Expiring soon")}</ak-label>`;
case LicenseSummaryStatusEnum.Unlicensed:
return html`<ak-label color=${PFColor.Grey}>${msg("Unlicensed")}</ak-label>`;
return html`<ak-label color=${PFColor.Gray}>${msg("Unlicensed")}</ak-label>`;
case LicenseSummaryStatusEnum.ReadOnly:
return html`<ak-label color=${PFColor.Red}>${msg("Read Only")}</ak-label>`;
case LicenseSummaryStatusEnum.Valid:
+1 -1
View File
@@ -87,7 +87,7 @@ export class DataExportListPage extends TablePage<DataExport> {
Timestamp(item.requestedOn),
html`${item.completed
? html`<ak-label color=${PFColor.Green}>${msg("Finished")}</ak-label>`
: html`<ak-label color=${PFColor.Grey}>${msg("Queued")}</ak-label>`}`,
: html`<ak-label color=${PFColor.Gray}>${msg("Queued")}</ak-label>`}`,
item.completed && item.fileUrl
? html`<div>
<a href="${item.fileUrl}">
+2 -2
View File
@@ -28,7 +28,7 @@ export const LifecycleIterationStatus: LitFC<LifecycleIterationStatusProps> = ({
)
.with(
LifecycleIterationStateEnum.Canceled,
() => html`<ak-label color=${PFColor.Grey}>${msg("Canceled")}</ak-label>`,
() => html`<ak-label color=${PFColor.Gray}>${msg("Canceled")}</ak-label>`,
)
.otherwise(() => html`<ak-label color=${PFColor.Grey}>${msg("Unknown")}</ak-label>`);
.otherwise(() => html`<ak-label color=${PFColor.Gray}>${msg("Unknown")}</ak-label>`);
};
@@ -52,7 +52,7 @@ export class OutpostHealthSimpleElement extends AKElement {
return html`<ak-spinner></ak-spinner>`;
}
if (!this.outpostHealths || this.outpostHealths.length === 0) {
return html`<ak-label color=${PFColor.Grey}>${msg("Not available")}</ak-label>`;
return html`<ak-label color=${PFColor.Gray}>${msg("Not available")}</ak-label>`;
}
const outdatedOutposts = this.outpostHealths.filter((h) => h.versionOutdated);
if (outdatedOutposts.length > 0) {
@@ -85,7 +85,9 @@ export class EventMatcherPolicyForm extends BasePolicyForm<EventMatcherPolicy> {
<a
rel="noopener noreferrer"
target="_blank"
href=${docLink("/sys-mgmt/events/logging-events/#advanced-queries")}
href=${docLink(
"/sys-mgmt/akql/#use-akql-in-an-event-matcher-policy",
)}
>
${msg("See documentation for examples.")}
</a>
+1 -1
View File
@@ -100,7 +100,7 @@ export class SourceListPage extends TablePage<Source> {
return [
html`<div>
<div>${item.name}</div>
<ak-label color=${PFColor.Grey} compact> ${msg("Built-in")}</ak-label>
<ak-label color=${PFColor.Gray} compact> ${msg("Built-in")}</ak-label>
</div>`,
html`${msg("Built-in")}`,
nothing,
+2 -2
View File
@@ -34,8 +34,8 @@ const statusToDetails = new Map<P4Disposition, [string, string]>([
*
* - type="error" (default): A Red
* - type="warning" An orange
* - type="info" A grey
* - type="neutral" A grey
* - type="info" A gray
* - type="neutral" A gray
*
* By default, the messages for "good" and "other" are "Yes" and "No" respectively, but these can be
* customized with the attributes `good-label` and `bad-label`.
+3 -3
View File
@@ -14,7 +14,7 @@ export enum PFColor {
Orange = "pf-m-orange",
Red = "pf-m-red",
Blue = "pf-m-blue",
Grey = "",
Gray = "",
}
export const levelNames = ["warning", "info", "success", "danger"];
@@ -26,7 +26,7 @@ const chromeList: Chrome[] = [
["warning", PFColor.Orange, "pf-m-orange", "fa-exclamation-triangle"],
["success", PFColor.Green, "pf-m-green", "fa-check"],
["running", PFColor.Blue, "pf-m-blue", "fa-clock"],
["info", PFColor.Grey, "pf-m-grey", "fa-info-circle"],
["info", PFColor.Gray, "pf-m-grey", "fa-info-circle"],
];
export interface ILabel {
@@ -38,7 +38,7 @@ export interface ILabel {
@customElement("ak-label")
export class Label extends AKElement implements ILabel {
@property()
color: PFColor = PFColor.Grey;
color: PFColor = PFColor.Gray;
@property()
icon?: string;
+5 -5
View File
@@ -41,8 +41,8 @@ Chart.register(LineController, BarController, DoughnutController);
Chart.register(ArcElement, BarElement, PointElement, LineElement);
Chart.register(TimeScale, TimeSeriesScale, LinearScale, Filler);
export const FONT_COLOUR_DARK_MODE = "#fafafa";
export const FONT_COLOUR_LIGHT_MODE = "#151515";
export const FONT_COLOR_DARK_MODE = "#fafafa";
export const FONT_COLOR_LIGHT_MODE = "#151515";
export abstract class AKChart<T> extends AKElement {
public role = "figure";
@@ -59,7 +59,7 @@ export abstract class AKChart<T> extends AKElement {
@property()
centerText?: string;
fontColour = FONT_COLOUR_LIGHT_MODE;
fontColor = FONT_COLOR_LIGHT_MODE;
static styles: CSSResult[] = [
css`
@@ -91,9 +91,9 @@ export abstract class AKChart<T> extends AKElement {
this.addEventListener(EVENT_REFRESH, this.refreshHandler);
this.addEventListener(ThemeChangeEvent.eventName, ((ev: CustomEvent<UiThemeEnum>) => {
if (ev.detail === UiThemeEnum.Light) {
this.fontColour = FONT_COLOUR_LIGHT_MODE;
this.fontColor = FONT_COLOR_LIGHT_MODE;
} else {
this.fontColour = FONT_COLOUR_DARK_MODE;
this.fontColor = FONT_COLOR_DARK_MODE;
}
this.chart?.update();
}) as EventListener);
+2 -2
View File
@@ -22,7 +22,7 @@ export class TaskStatus extends AKElement {
switch (this.status) {
case TaskAggregatedStatusEnum.Queued:
case LastTaskStatusEnum.Queued:
return html`<ak-label color=${PFColor.Grey}>${msg("Waiting to run")}</ak-label>`;
return html`<ak-label color=${PFColor.Gray}>${msg("Waiting to run")}</ak-label>`;
case TaskAggregatedStatusEnum.Consumed:
case LastTaskStatusEnum.Consumed:
return html`<ak-label color=${PFColor.Blue}>${msg("Consumed")}</ak-label>`;
@@ -49,7 +49,7 @@ export class TaskStatus extends AKElement {
case LastTaskStatusEnum.Error:
return html`<ak-label color=${PFColor.Red}>${msg("Error")}</ak-label>`;
default:
return html`<ak-label color=${PFColor.Grey}>${msg("Unknown")}</ak-label>`;
return html`<ak-label color=${PFColor.Gray}>${msg("Unknown")}</ak-label>`;
}
}
}
+50
View File
@@ -0,0 +1,50 @@
# Test Directory Router
This directory holds three flavors of automated tests for the authentik WebUI. Each has its own conventions doc — **read the relevant one before writing or modifying tests there.**
| Directory | What lives here | Runner / environment | Conventions |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | --------------------------------------------- |
| `test/unit/` | Pure-Node tests for functions, classes, and modules with no DOM dependency. | Vitest, Node environment. | [`test/unit/AGENTS.md`](unit/AGENTS.md) |
| `test/browser/` | End-to-end tests that drive the admin and user UIs in Chromium against a running authentik instance. | Vitest browser provider (Playwright) + `#e2e` fixtures. | [`test/browser/AGENTS.md`](browser/AGENTS.md) |
| `test/lit/` | Shared Lit render helpers (`renderLit`, `LitViteContext`) for component-level browser tests. No tests live here directly. | — | — |
| `test/blueprints/` | YAML blueprints (e.g. `test-admin-user.yaml`) seeded into authentik for browser tests to authenticate against. | — | — |
## Picking the right flavor
Walk this list top-to-bottom and stop at the first match:
1. **Pure function, no DOM, no network?**`test/unit/`. Cheap, fast, branch-heavy coverage. See [`unit/AGENTS.md`](unit/AGENTS.md).
2. **A feature flow the user actually clicks through** (wizard, dialog, navigation, list table, login)? → `test/browser/`. Drive the real UI; do not write a unit test with a `@goauthentik/api` client to fake it. See [`browser/AGENTS.md`](browser/AGENTS.md).
3. **Regression for a specific bug?** Find the feature suite in `test/browser/` it belongs to and add another `test(...)` there. Do **not** create a new file scoped to the bug. If the bug is in a pure function, add an `it(...)` to the matching `test/unit/` file instead.
4. **Lit component behavior in isolation** (a component's lifecycle, slots, events, reactive updates, with no whole-app context)? Colocate as `Component.browser.test.ts` next to the source — the Vitest config picks up `**/*.browser.test.ts`, and `test/lit/setup.js` exposes `page.renderLit(...)` for mounting. No consumers exist yet, so check with the team before adding the first one.
If you're tempted to do something that doesn't fit cleanly into one bucket — a unit test that imports a Lit component, a browser test that calls the REST API to seed data — that's a strong signal you've chosen the wrong bucket. Re-read the conventions doc for the bucket you actually want.
## Cross-cutting rules
These apply everywhere in `test/`:
- **No bespoke API clients.** Never build a `fetch`-based admin client inside a test file. Unit tests don't need one; browser tests must drive the UI; if a real seeding gap exists, extend a fixture or blueprint instead.
- **No hard-coded credentials beyond what's already in fixtures.** Browser tests authenticate via `session.login()` using the bootstrap admin from `test/blueprints/test-admin-user.yaml`. Don't read `process.env.AK_TEST_BOOTSTRAP_TOKEN` from a test.
- **Deterministic naming for entities.** When a browser test creates data, use `IDGenerator.randomID(...)` for uniqueness — see browser conventions. Unit tests should never need this.
- **One file per feature / symbol.** Resist creating one-off files named after a bug, a ticket, or a date.
- **Test names are full sentences.** `"returns null once the input is exhausted"`, `"Create application with existing provider"`. Not `"works"`, not `"#22383"`.
## Running
```bash
npm test # Both projects (unit + browser)
npx vitest run test/unit # Just unit tests
npx vitest run test/browser # Just browser tests
npx vitest run path/to/single.test.ts # One file
npm run test:e2e # Playwright e2e CLI path (same test/browser sources)
```
Browser tests require a running authentik instance reachable at `AK_TEST_RUNNER_PAGE_URL` (defaults to `http://localhost:9000`). The `prerequisites.setup.ts` health check will fail loudly if it isn't up.
## Where things live
- Playwright fixtures (`session`, `navigator`, `form`, `pointer`) and the `#e2e` entry point: `e2e/`.
- Lit render helper for component tests: `test/lit/`.
- Seed blueprints (test admin user, etc.): `test/blueprints/`.
- Generators (`IDGenerator`, `randomName`) used by browser tests: `e2e/utils/generators.ts` and `@goauthentik/core/id`.
+1
View File
@@ -0,0 +1 @@
@AGENTS.md
+150
View File
@@ -0,0 +1,150 @@
# Browser Test Conventions
These are Playwright tests run under Vitest's browser runner (Chromium). They exercise the **admin and user UIs end-to-end** against a running authentik instance. Tests live in `test/browser/*.test.ts`; supporting fixtures and helpers live in `e2e/`.
## Philosophy
**Drive the UI, not the API.** A test for a feature should exercise the same path a user takes — click "New Provider", fill the form, click "Create", verify it appears. We don't seed entities through the REST API and then click one button to verify a single side effect. If the UI flow breaks, the test must break with it; if we shortcut through the API, regressions in the wizards, modals, navigation, and form bindings go undetected.
**Cover features, not bugs.** A test file is named after the feature it exercises (`providers.test.ts`, `applications.test.ts`), not the bug it was written for. Regression tests for specific defects belong inside the feature's existing suite as an additional `test(...)` case — not as a one-off file with bespoke API plumbing.
**No bespoke HTTP clients.** If you find yourself writing a `makeAPIClient` helper inside a test, stop. Either drive the UI to create the prerequisite state, or — if the prerequisite is truly out of scope for the feature under test — extend a fixture so the pattern is reusable.
**No explicit cleanup.** Entity names are seeded with `IDGenerator.randomID(...)` so each run produces unique slugs. Stale entities from prior runs don't collide and are expected to accumulate in dev environments. Don't add `try/finally` cleanup blocks — they obscure the assertion at the end of the test and tend to swallow the real failure when the UI flow breaks.
## Imports
Tests import from the `#e2e` alias, never from `@playwright/test` directly:
```ts
import { expect, test } from "#e2e";
import { randomName } from "#e2e/utils/generators";
import { IDGenerator } from "@goauthentik/core/id";
import { series } from "@goauthentik/core/promises";
```
The `#e2e` entry (`e2e/index.ts`) re-exports `expect` from Playwright and exports a `test` that has been extended with our fixtures.
## Fixtures
Destructure what you need from the test callback. All are constructed per-test:
| Fixture | Purpose |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `session` | `login({ to, username?, password?, rememberMe? })`, `toLoginPage()`, `checkAuthenticated()`. Defaults to `test-admin@goauthentik.io` / `test-runner`. |
| `navigator` | `navigate(to)` and `waitForPathname(to)` — use these over `page.goto` so URL waits are consistent. |
| `form` | `fill(label, value, ctx?)`, `search(query, ctx?)`, `selectSearchValue(label, pattern, ctx?)`, `setInputCheck(label, bool, ctx?)`, `setRadio(group, name, ctx?)`, `setFormGroup(pattern, open, ctx?)`. Knows about `ak-switch-input`, `ak-form-group`, and search-select dropdowns. |
| `pointer` | `click(name, role?, ctx?)` — high-level click by accessible name; defaults to buttons/links. |
| `page` | Raw Playwright `Page` for anything the fixtures don't cover. Shadow DOM is pierced automatically. |
| `baseURL` | The instance URL, from `AK_TEST_RUNNER_PAGE_URL` (defaults to `http://localhost:9000`). |
Most steps in most tests should go through `form` and `pointer`. Reach for `page.locator(...)` only when there isn't a fixture method that fits.
## Shape of a test
```ts
test.describe("Feature name", () => {
const names = new Map<string, string>();
test.beforeEach("Seed names", async ({ page: _page }, { testId }) => {
const seed = IDGenerator.randomID(6);
names.set(testId, `${randomName(seed)} (${seed})`);
});
test("Do the thing", async ({ session, navigator, form, pointer, page }, testInfo) => {
const name = names.get(testInfo.testId)!;
const { fill, search, selectSearchValue } = form;
const { click } = pointer;
await test.step("Authenticate", async () => {
await session.login({ to: "/if/admin/#/core/providers" });
});
const dialog = page.getByRole("dialog", { name: "New Provider Wizard" });
await test.step("Open wizard", async () => {
await expect(dialog, "Wizard is initially closed").toBeHidden();
await click("New Provider");
await expect(dialog, "Wizard opens").toBeVisible();
});
await test.step("Fill form", async () => {
await series(
[click, "OAuth2/OpenID", "option"],
[fill, "Provider Name", name],
[
selectSearchValue,
"Authorization Flow",
/default-provider-authorization-explicit-consent/,
],
[click, "Create"],
);
});
await test.step("Verify created", async () => {
await expect(await search(name), "Provider is visible").toBeVisible();
});
});
});
```
Conventions baked in above:
- **`test.describe` per feature**, plain imperative names per test.
- **`test.step(...)` for every meaningful phase** — these show up in traces and HTML reports and make failures self-locating.
- **Names keyed by `testId`** in a module-scoped `Map`, populated in `beforeEach`.
- **`series([fn, ...args], ...)`** for ordered form-fill sequences. Reads top-to-bottom as a script of user actions.
- **Dialog locator captured once**, then passed as the `ctx?` argument to scope `fill`/`click`/`selectSearchValue` inside it.
- **Every `expect` has a message** as the second argument — it shows up in the failure output. Phrase it as the property being asserted ("Wizard opens", "Provider is visible"), not as a restatement of the matcher.
- **First parameter must be a destructure pattern**, even when you don't reference any fixture — write `async ({ page: _page }, { testId }) => {…}`. A bare identifier (`async (_, { testId }) => {…}`) throws `First argument must use the object destructuring pattern` at runtime because Playwright inspects the parameter pattern to decide which fixtures to inject, and an empty destructure (`async ({}, { testId }) => {…}`) trips ESLint's `no-empty-pattern`. Destructure-and-rename is the only form that satisfies both.
## Locator preferences
In order, prefer:
1. **ARIA role queries**`page.getByRole("button", { name: "Create" })`, `page.getByRole("dialog", { name: /Launch Endpoint/i })`, `page.getByLabel("Username")`. These survive style/markup changes and document intent.
2. **Web component tags**`page.locator("ak-stage-identification")`, `page.locator("ak-form-group", { hasText: /Advanced/ })`. Stable element contracts.
3. **`data-test-id`** — `page.getByTestId("...")`. The Playwright config sets `testIdAttribute: "data-test-id"`. Only add a new test id when role/label queries can't disambiguate.
4. **CSS selectors** — last resort.
Shadow DOM works transparently — don't write `.shadowRoot` traversals; Playwright pierces.
## Assertions
```ts
await expect(dialog, "Dialog is initially closed").toBeHidden();
await expect(dialog, "Dialog opens").toBeVisible();
await expect(row, "Endpoint row appears without manual refresh").toBeVisible({ timeout: 5_000 });
await expect(input, "Input has expected value").toHaveValue("foo");
await expect(checkbox, "Checkbox is checked").toBeChecked();
```
- Always pass a message.
- Use explicit `{ timeout: ... }` only when the default (5s) genuinely isn't enough — generally for the first assertion after an async UI transition like a dialog mount or a navigation.
- Don't add `page.waitForTimeout` — wait for the locator condition you actually care about.
## Anti-patterns (do not do these)
- **Bespoke API clients in test files.** No `makeAPIClient`, no raw `fetch(`${baseURL}/api/v3/...`)` for setup. See [Philosophy](#philosophy).
- **Reading `process.env.AK_TEST_BOOTSTRAP_TOKEN`** from a test. Tests authenticate as a real user via `session.login()`.
- **One-file regression tests for a single bug.** Add a `test(...)` case to the relevant feature suite instead.
- **`try/finally` cleanup blocks.** Names are randomized; let entities accumulate.
- **`page.goto` with no wait.** Use `navigator.navigate(to)` or `session.login({ to })`.
- **Asserting against CSS selectors when a role/label exists.** If you find yourself writing `.locator('button[type="submit"]')`, check whether `getByRole("button", { name: ... })` works first.
- **Skipping `test.step`.** Long flat tests are hard to debug; wrap each phase.
## Adding new coverage
When extending an existing suite, follow the surrounding patterns — same fixture destructure, same `Map<testId, name>` style, same dialog-as-context idiom. When introducing a new suite, model the structure on `applications.test.ts` or `providers.test.ts`; those are the canonical examples.
If you need a helper that doesn't exist yet (a new form input shape, a new common navigation), extend the fixture in `e2e/fixtures/` rather than duplicating logic in tests.
## Running
```bash
npm test # All Vitest (unit + browser)
npx vitest run test/browser/foo.test.ts # Single browser test file
```
The Playwright config (`playwright.config.js`) is also present for the `npm run test:e2e` path and configures Chromium with traces on first retry and a dark color scheme. The browser tests through Vitest use `@vitest/browser-playwright` and target the same `test/browser/` directory.
+1
View File
@@ -0,0 +1 @@
@AGENTS.md
+95
View File
@@ -0,0 +1,95 @@
# Unit Test Conventions
Pure-Node, no-browser tests for individual functions, pure logic, and modules with no DOM dependencies. Runs under Vitest's Node environment — no Playwright, no Lit rendering, no live authentik instance.
## When a unit test is the right tool
- The thing under test is a **plain function or class** with no DOM, network, or component lifecycle.
- You want to cover **branches, edge cases, error paths, and invariants** thoroughly and fast.
- The behavior is deterministic given inputs — no timers, no external services, no `customElements.define`.
If the answer involves rendering a Lit component, clicking something, awaiting network, or asserting against the DOM, it does not belong here. Push it to a colocated Lit component test or to `test/browser/`.
## File layout
- Files live in `test/unit/*.test.ts`.
- One file per module/feature under test — name it after the symbol or module (`lexer.test.ts`, `authenticator-validate-challenge-selection.test.ts`).
- The Vitest config also picks up `**/*.unit.test.ts` anywhere in the workspace, so a tightly-coupled test may be colocated next to its source as `foo.unit.test.ts` when that's clearer than a parallel `test/unit/` file.
## Imports
```ts
import { describe, expect, it, vi } from "vitest";
import { shouldResetSelectedChallenge } from "#flow/stages/authenticator_validate/challenge-selection";
```
- Use `describe` / `it` / `expect` from `vitest`. Do **not** import `test`/`expect` from `#e2e` — that's for browser tests and pulls in Playwright.
- Reach into source via the package `#alias` imports (`#flow/…`, `#elements/…`, `#common/…`) — never relative paths into `src/`.
- Use `vi` for spies, mocks, and timers. Prefer real implementations; only mock at module boundaries that actually pose a problem (network, time, randomness).
## Shape of a test
```ts
describe("shouldResetSelectedChallenge", () => {
it("returns true when the previously selected challenge is no longer allowed", () => {
const selected = makeDeviceChallenge(DeviceClassesEnum.Email, "email-1");
const allowed = [
makeDeviceChallenge(DeviceClassesEnum.Totp, "totp-1"),
makeDeviceChallenge(DeviceClassesEnum.Webauthn, "webauthn-1"),
];
expect(shouldResetSelectedChallenge(selected, allowed)).toBe(true);
});
it("returns false when the previously selected challenge is still allowed", () => { ... });
it("returns false when there was no selected challenge", () => { ... });
});
```
Conventions:
- **`describe(symbolName)`** at the top, optionally nested by method or behavior (`describe("addRule")`, `describe("tokenization")`, `describe("states")` — see `lexer.test.ts`).
- **`it("returns X when Y")`** — full sentences starting with the verb. State both the outcome and the precondition. Bad: `"works"`, `"handles nulls"`. Good: `"returns null once the input is exhausted"`, `"rolls back the lexer index when an action rejects"`.
- **Arrange / act / assert** with a blank line between phases where it improves scanability. Inline factories like `makeDeviceChallenge(...)` for repeated test-data shapes — keep them at the top of the file, not in shared helpers, until two files need the same one.
- **One concept per `it`.** If you reach for "and" in the name, split it.
- **No assertion messages** on `expect()` in unit tests. The test name and matcher already describe intent; Vitest's output is sufficient.
## Assertions
Plain Vitest matchers — `toBe`, `toEqual`, `toBeNull`, `toBeTruthy`, `toThrow(/regex/)`, `toHaveBeenCalledTimes`, etc. Use:
- `toBe` for primitives and reference identity.
- `toEqual` for structural equality.
- `toThrow(/regex/)` for error paths — match a stable fragment of the message, not the whole thing.
- `.mock.calls[i]?.[j]` to assert on spy arguments precisely.
## Mocking and spies
```ts
const defunct = vi.fn((chr: string) => `?${chr}`);
expect(defunct).toHaveBeenCalledTimes(2);
expect(defunct.mock.calls[0]?.[0]).toBe("@");
```
- Prefer constructing test doubles inline with `vi.fn()` over module-level `vi.mock(...)`.
- Reach for `vi.useFakeTimers()` only when the code under test reads the clock — don't preemptively fake time.
- If you need `vi.mock("module")`, hoist it to the top of the file and explain _why_ in a one-line comment if the reason isn't obvious from the import.
## What NOT to do here
- **Do not import from `@playwright/test` or `#e2e`.** Those are for browser tests.
- **Do not call `customElements.define` or import Lit components.** The Node environment has no DOM. Component coverage belongs in `test/browser/` (or a `.browser.test.ts` colocated with the component, once the Lit render helper has a real consumer).
- **Do not hit the network or filesystem.** Pure-function tests; if the unit needs IO, you're testing the wrong layer.
- **Do not silently pass on `try/catch`.** Use `expect(() => …).toThrow(...)` for error paths so a missing throw fails the test.
- **Do not assert against snapshots** unless the output is a stable, intentional artifact (e.g. a token stream). Snapshots rot fast when used as a substitute for thinking about the contract.
## Running
```bash
npx vitest run test/unit # All unit tests
npx vitest run test/unit/lexer.test.ts # One file
npx vitest test/unit/lexer.test.ts -t "tokenization" # Filter by name
```
The `npm test` script runs both the unit and browser projects; for fast iteration on a pure-logic change, run the single file directly.
+1
View File
@@ -0,0 +1 @@
@AGENTS.md
+3 -3
View File
@@ -8889,9 +8889,6 @@ Vazby na skupiny/uživatele jsou kontrolovány vůči uživateli události.</tar
<trans-unit id="s3b4ad8abf4f08c35">
<source>Hide managed roles</source>
</trans-unit>
<trans-unit id="s7dcfe418b8d601f6">
<source>Flags allow you to enable new functionality and behaviour in authentik early.</source>
</trans-unit>
<trans-unit id="s8aa73079812f56b2">
<source>Refresh other flow tabs upon authentication</source>
</trans-unit>
@@ -11237,6 +11234,9 @@ Vazby na skupiny/uživatele jsou kontrolovány vůči uživateli události.</tar
<source><x id="0" equiv-text="${entityLabel}"/> Details</source>
<note from="lit-localize">Label for the step in the creation wizard where the details of the entity being created are filled in. The placeholder {entity} is replaced with the name of the entity type, for example 'User Details' or 'Group Details'.</note>
</trans-unit>
<trans-unit id="sd1b98c71ea0d603b">
<source>Flags allow you to enable new functionality and behavior in authentik early.</source>
</trans-unit>
</body>
</file>
</xliff>
+3 -3
View File
@@ -8921,9 +8921,6 @@ Bindings zu Gruppen/Benutzern werden mit dem Benutzer des Ereignisses abgegliche
<trans-unit id="s3b4ad8abf4f08c35">
<source>Hide managed roles</source>
</trans-unit>
<trans-unit id="s7dcfe418b8d601f6">
<source>Flags allow you to enable new functionality and behaviour in authentik early.</source>
</trans-unit>
<trans-unit id="s8aa73079812f56b2">
<source>Refresh other flow tabs upon authentication</source>
</trans-unit>
@@ -11269,6 +11266,9 @@ Bindings zu Gruppen/Benutzern werden mit dem Benutzer des Ereignisses abgegliche
<source><x id="0" equiv-text="${entityLabel}"/> Details</source>
<note from="lit-localize">Label for the step in the creation wizard where the details of the entity being created are filled in. The placeholder {entity} is replaced with the name of the entity type, for example 'User Details' or 'Group Details'.</note>
</trans-unit>
<trans-unit id="sd1b98c71ea0d603b">
<source>Flags allow you to enable new functionality and behavior in authentik early.</source>
</trans-unit>
</body>
</file>
</xliff>
+3 -3
View File
@@ -6912,9 +6912,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s3b4ad8abf4f08c35">
<source>Hide managed roles</source>
</trans-unit>
<trans-unit id="s7dcfe418b8d601f6">
<source>Flags allow you to enable new functionality and behaviour in authentik early.</source>
</trans-unit>
<trans-unit id="s8aa73079812f56b2">
<source>Refresh other flow tabs upon authentication</source>
</trans-unit>
@@ -9260,6 +9257,9 @@ Bindings to groups/users are checked against the user of the event.</source>
<source><x id="0" equiv-text="${entityLabel}"/> Details</source>
<note from="lit-localize">Label for the step in the creation wizard where the details of the entity being created are filled in. The placeholder {entity} is replaced with the name of the entity type, for example 'User Details' or 'Group Details'.</note>
</trans-unit>
<trans-unit id="sd1b98c71ea0d603b">
<source>Flags allow you to enable new functionality and behavior in authentik early.</source>
</trans-unit>
</body>
</file>
</xliff>
+3 -3
View File
@@ -8848,9 +8848,6 @@ Las vinculaciones a grupos/usuarios se verifican en función del usuario del eve
<trans-unit id="s3b4ad8abf4f08c35">
<source>Hide managed roles</source>
</trans-unit>
<trans-unit id="s7dcfe418b8d601f6">
<source>Flags allow you to enable new functionality and behaviour in authentik early.</source>
</trans-unit>
<trans-unit id="s8aa73079812f56b2">
<source>Refresh other flow tabs upon authentication</source>
</trans-unit>
@@ -11196,6 +11193,9 @@ Las vinculaciones a grupos/usuarios se verifican en función del usuario del eve
<source><x id="0" equiv-text="${entityLabel}"/> Details</source>
<note from="lit-localize">Label for the step in the creation wizard where the details of the entity being created are filled in. The placeholder {entity} is replaced with the name of the entity type, for example 'User Details' or 'Group Details'.</note>
</trans-unit>
<trans-unit id="sd1b98c71ea0d603b">
<source>Flags allow you to enable new functionality and behavior in authentik early.</source>
</trans-unit>
</body>
</file>
</xliff>
+3 -3
View File
@@ -9087,9 +9087,6 @@ Liitokset käyttäjiin/ryhmiin tarkistetaan tapahtuman käyttäjästä.</target>
<trans-unit id="s3b4ad8abf4f08c35">
<source>Hide managed roles</source>
</trans-unit>
<trans-unit id="s7dcfe418b8d601f6">
<source>Flags allow you to enable new functionality and behaviour in authentik early.</source>
</trans-unit>
<trans-unit id="s8aa73079812f56b2">
<source>Refresh other flow tabs upon authentication</source>
</trans-unit>
@@ -11435,6 +11432,9 @@ Liitokset käyttäjiin/ryhmiin tarkistetaan tapahtuman käyttäjästä.</target>
<source><x id="0" equiv-text="${entityLabel}"/> Details</source>
<note from="lit-localize">Label for the step in the creation wizard where the details of the entity being created are filled in. The placeholder {entity} is replaced with the name of the entity type, for example 'User Details' or 'Group Details'.</note>
</trans-unit>
<trans-unit id="sd1b98c71ea0d603b">
<source>Flags allow you to enable new functionality and behavior in authentik early.</source>
</trans-unit>
</body>
</file>
</xliff>
+3 -3
View File
@@ -9076,9 +9076,6 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<trans-unit id="s3b4ad8abf4f08c35">
<source>Hide managed roles</source>
</trans-unit>
<trans-unit id="s7dcfe418b8d601f6">
<source>Flags allow you to enable new functionality and behaviour in authentik early.</source>
</trans-unit>
<trans-unit id="s8aa73079812f56b2">
<source>Refresh other flow tabs upon authentication</source>
</trans-unit>
@@ -11424,6 +11421,9 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<source><x id="0" equiv-text="${entityLabel}"/> Details</source>
<note from="lit-localize">Label for the step in the creation wizard where the details of the entity being created are filled in. The placeholder {entity} is replaced with the name of the entity type, for example 'User Details' or 'Group Details'.</note>
</trans-unit>
<trans-unit id="sd1b98c71ea0d603b">
<source>Flags allow you to enable new functionality and behavior in authentik early.</source>
</trans-unit>
</body>
</file>
</xliff>
+3 -3
View File
@@ -8797,9 +8797,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s3b4ad8abf4f08c35">
<source>Hide managed roles</source>
</trans-unit>
<trans-unit id="s7dcfe418b8d601f6">
<source>Flags allow you to enable new functionality and behaviour in authentik early.</source>
</trans-unit>
<trans-unit id="s8aa73079812f56b2">
<source>Refresh other flow tabs upon authentication</source>
</trans-unit>
@@ -11145,6 +11142,9 @@ Bindings to groups/users are checked against the user of the event.</source>
<source><x id="0" equiv-text="${entityLabel}"/> Details</source>
<note from="lit-localize">Label for the step in the creation wizard where the details of the entity being created are filled in. The placeholder {entity} is replaced with the name of the entity type, for example 'User Details' or 'Group Details'.</note>
</trans-unit>
<trans-unit id="sd1b98c71ea0d603b">
<source>Flags allow you to enable new functionality and behavior in authentik early.</source>
</trans-unit>
</body>
</file>
</xliff>
+3 -3
View File
@@ -9077,9 +9077,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s3b4ad8abf4f08c35">
<source>Hide managed roles</source>
</trans-unit>
<trans-unit id="s7dcfe418b8d601f6">
<source>Flags allow you to enable new functionality and behaviour in authentik early.</source>
</trans-unit>
<trans-unit id="s8aa73079812f56b2">
<source>Refresh other flow tabs upon authentication</source>
</trans-unit>
@@ -11425,6 +11422,9 @@ Bindings to groups/users are checked against the user of the event.</source>
<source><x id="0" equiv-text="${entityLabel}"/> Details</source>
<note from="lit-localize">Label for the step in the creation wizard where the details of the entity being created are filled in. The placeholder {entity} is replaced with the name of the entity type, for example 'User Details' or 'Group Details'.</note>
</trans-unit>
<trans-unit id="sd1b98c71ea0d603b">
<source>Flags allow you to enable new functionality and behavior in authentik early.</source>
</trans-unit>
</body>
</file>
</xliff>
+3 -3
View File
@@ -8449,9 +8449,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s3b4ad8abf4f08c35">
<source>Hide managed roles</source>
</trans-unit>
<trans-unit id="s7dcfe418b8d601f6">
<source>Flags allow you to enable new functionality and behaviour in authentik early.</source>
</trans-unit>
<trans-unit id="s8aa73079812f56b2">
<source>Refresh other flow tabs upon authentication</source>
</trans-unit>
@@ -10797,6 +10794,9 @@ Bindings to groups/users are checked against the user of the event.</source>
<source><x id="0" equiv-text="${entityLabel}"/> Details</source>
<note from="lit-localize">Label for the step in the creation wizard where the details of the entity being created are filled in. The placeholder {entity} is replaced with the name of the entity type, for example 'User Details' or 'Group Details'.</note>
</trans-unit>
<trans-unit id="sd1b98c71ea0d603b">
<source>Flags allow you to enable new functionality and behavior in authentik early.</source>
</trans-unit>
</body>
</file>
</xliff>
+3 -3
View File
@@ -8134,9 +8134,6 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
<trans-unit id="s3b4ad8abf4f08c35">
<source>Hide managed roles</source>
</trans-unit>
<trans-unit id="s7dcfe418b8d601f6">
<source>Flags allow you to enable new functionality and behaviour in authentik early.</source>
</trans-unit>
<trans-unit id="s8aa73079812f56b2">
<source>Refresh other flow tabs upon authentication</source>
</trans-unit>
@@ -10482,6 +10479,9 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
<source><x id="0" equiv-text="${entityLabel}"/> Details</source>
<note from="lit-localize">Label for the step in the creation wizard where the details of the entity being created are filled in. The placeholder {entity} is replaced with the name of the entity type, for example 'User Details' or 'Group Details'.</note>
</trans-unit>
<trans-unit id="sd1b98c71ea0d603b">
<source>Flags allow you to enable new functionality and behavior in authentik early.</source>
</trans-unit>
</body>
</file>
</xliff>
+3 -3
View File
@@ -8473,9 +8473,6 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
<trans-unit id="s3b4ad8abf4f08c35">
<source>Hide managed roles</source>
</trans-unit>
<trans-unit id="s7dcfe418b8d601f6">
<source>Flags allow you to enable new functionality and behaviour in authentik early.</source>
</trans-unit>
<trans-unit id="s8aa73079812f56b2">
<source>Refresh other flow tabs upon authentication</source>
</trans-unit>
@@ -10821,6 +10818,9 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
<source><x id="0" equiv-text="${entityLabel}"/> Details</source>
<note from="lit-localize">Label for the step in the creation wizard where the details of the entity being created are filled in. The placeholder {entity} is replaced with the name of the entity type, for example 'User Details' or 'Group Details'.</note>
</trans-unit>
<trans-unit id="sd1b98c71ea0d603b">
<source>Flags allow you to enable new functionality and behavior in authentik early.</source>
</trans-unit>
</body>
</file>
</xliff>
+3 -3
View File
@@ -9069,9 +9069,6 @@ por exemplo: <x id="0" equiv-text="&lt;code&gt;"/>oci://registry.domain.tld/path
<trans-unit id="s3b4ad8abf4f08c35">
<source>Hide managed roles</source>
</trans-unit>
<trans-unit id="s7dcfe418b8d601f6">
<source>Flags allow you to enable new functionality and behaviour in authentik early.</source>
</trans-unit>
<trans-unit id="s8aa73079812f56b2">
<source>Refresh other flow tabs upon authentication</source>
</trans-unit>
@@ -11417,6 +11414,9 @@ por exemplo: <x id="0" equiv-text="&lt;code&gt;"/>oci://registry.domain.tld/path
<source><x id="0" equiv-text="${entityLabel}"/> Details</source>
<note from="lit-localize">Label for the step in the creation wizard where the details of the entity being created are filled in. The placeholder {entity} is replaced with the name of the entity type, for example 'User Details' or 'Group Details'.</note>
</trans-unit>
<trans-unit id="sd1b98c71ea0d603b">
<source>Flags allow you to enable new functionality and behavior in authentik early.</source>
</trans-unit>
</body>
</file>
</xliff>
+3 -3
View File
@@ -8559,9 +8559,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s3b4ad8abf4f08c35">
<source>Hide managed roles</source>
</trans-unit>
<trans-unit id="s7dcfe418b8d601f6">
<source>Flags allow you to enable new functionality and behaviour in authentik early.</source>
</trans-unit>
<trans-unit id="s8aa73079812f56b2">
<source>Refresh other flow tabs upon authentication</source>
</trans-unit>
@@ -10907,6 +10904,9 @@ Bindings to groups/users are checked against the user of the event.</source>
<source><x id="0" equiv-text="${entityLabel}"/> Details</source>
<note from="lit-localize">Label for the step in the creation wizard where the details of the entity being created are filled in. The placeholder {entity} is replaced with the name of the entity type, for example 'User Details' or 'Group Details'.</note>
</trans-unit>
<trans-unit id="sd1b98c71ea0d603b">
<source>Flags allow you to enable new functionality and behavior in authentik early.</source>
</trans-unit>
</body>
</file>
</xliff>
+3 -3
View File
@@ -8549,9 +8549,6 @@ Gruplara/kullanıcılara yapılan bağlamalar, etkinliğin kullanıcısına kar
<trans-unit id="s3b4ad8abf4f08c35">
<source>Hide managed roles</source>
</trans-unit>
<trans-unit id="s7dcfe418b8d601f6">
<source>Flags allow you to enable new functionality and behaviour in authentik early.</source>
</trans-unit>
<trans-unit id="s8aa73079812f56b2">
<source>Refresh other flow tabs upon authentication</source>
</trans-unit>
@@ -10897,6 +10894,9 @@ Gruplara/kullanıcılara yapılan bağlamalar, etkinliğin kullanıcısına kar
<source><x id="0" equiv-text="${entityLabel}"/> Details</source>
<note from="lit-localize">Label for the step in the creation wizard where the details of the entity being created are filled in. The placeholder {entity} is replaced with the name of the entity type, for example 'User Details' or 'Group Details'.</note>
</trans-unit>
<trans-unit id="sd1b98c71ea0d603b">
<source>Flags allow you to enable new functionality and behavior in authentik early.</source>
</trans-unit>
</body>
</file>
</xliff>
+3 -4
View File
@@ -9229,10 +9229,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<source>Hide managed roles</source>
<target>隐藏管理角色</target>
</trans-unit>
<trans-unit id="s7dcfe418b8d601f6">
<source>Flags allow you to enable new functionality and behaviour in authentik early.</source>
<target>标志允许你提前启用 authentik 的新功能和行为</target>
</trans-unit>
<trans-unit id="s8aa73079812f56b2">
<source>Refresh other flow tabs upon authentication</source>
</trans-unit>
@@ -11716,6 +11712,9 @@ Bindings to groups/users are checked against the user of the event.</source>
<source><x id="0" equiv-text="${entityLabel}"/> Details</source>
<note from="lit-localize">Label for the step in the creation wizard where the details of the entity being created are filled in. The placeholder {entity} is replaced with the name of the entity type, for example 'User Details' or 'Group Details'.</note>
</trans-unit>
<trans-unit id="sd1b98c71ea0d603b">
<source>Flags allow you to enable new functionality and behavior in authentik early.</source>
</trans-unit>
</body>
</file>
</xliff>
+3 -3
View File
@@ -8186,9 +8186,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s3b4ad8abf4f08c35">
<source>Hide managed roles</source>
</trans-unit>
<trans-unit id="s7dcfe418b8d601f6">
<source>Flags allow you to enable new functionality and behaviour in authentik early.</source>
</trans-unit>
<trans-unit id="s8aa73079812f56b2">
<source>Refresh other flow tabs upon authentication</source>
</trans-unit>
@@ -10534,6 +10531,9 @@ Bindings to groups/users are checked against the user of the event.</source>
<source><x id="0" equiv-text="${entityLabel}"/> Details</source>
<note from="lit-localize">Label for the step in the creation wizard where the details of the entity being created are filled in. The placeholder {entity} is replaced with the name of the entity type, for example 'User Details' or 'Group Details'.</note>
</trans-unit>
<trans-unit id="sd1b98c71ea0d603b">
<source>Flags allow you to enable new functionality and behavior in authentik early.</source>
</trans-unit>
</body>
</file>
</xliff>
+1 -1
View File
@@ -1,4 +1,4 @@
FROM --platform=${BUILDPLATFORM} docker.io/library/node:26.2.0-trixie@sha256:980c5420a7a2ddcb44037726977f2a349e5c7b64217516c7488dce4c74d71583 AS docs-builder
FROM --platform=${BUILDPLATFORM} docker.io/library/node:26.3.0-trixie@sha256:e3ffe0cbaeebdcddbfe1ee7bca9b564a92863a8386d5b99a3d72677b3667b61d AS docs-builder
ENV NODE_ENV=production
+2
View File
@@ -11,6 +11,8 @@ However, any flow can be executed via an API from anywhere, in fact that is what
Because the flow executor stores its state in the HTTP Session, so you need to ensure that cookies between flow executor requests are persisted.
:::
When a flow execution starts, authentik creates a [flow plan](/docs/add-secure-apps/flows-stages/flow/planner/) for the current session. The flow executor advances through that plan as each stage completes.
:::info
Note that the HTTP session must be obtained as a cookie before `GET /api/v3/flows/executor/:slug` can be called. If you are using a JWT for authentication, you first have to obtain a session cookie via `GET /api/v3/flows/instances/:slug/execute/` before requesting `GET /api/v3/flows/executor/:slug`.
:::
+9 -9
View File
@@ -48,15 +48,15 @@
"typescript": "^6.0.3"
},
"optionalDependencies": {
"@rspack/binding-darwin-arm64": "2.0.0",
"@rspack/binding-linux-arm64-gnu": "2.0.0",
"@rspack/binding-linux-x64-gnu": "2.0.0",
"@swc/core-darwin-arm64": "1.15.33",
"@swc/core-linux-arm64-gnu": "1.15.33",
"@swc/core-linux-x64-gnu": "1.15.33",
"@swc/html-darwin-arm64": "1.15.33",
"@swc/html-linux-arm64-gnu": "1.15.33",
"@swc/html-linux-x64-gnu": "1.15.33",
"@rspack/binding-darwin-arm64": "2.0.4",
"@rspack/binding-linux-arm64-gnu": "2.0.4",
"@rspack/binding-linux-x64-gnu": "2.0.4",
"@swc/core-darwin-arm64": "1.15.40",
"@swc/core-linux-arm64-gnu": "1.15.40",
"@swc/core-linux-x64-gnu": "1.15.40",
"@swc/html-darwin-arm64": "1.15.40",
"@swc/html-linux-arm64-gnu": "1.15.40",
"@swc/html-linux-x64-gnu": "1.15.40",
"lightningcss-darwin-arm64": "1.32.0",
"lightningcss-linux-arm64-gnu": "1.32.0",
"lightningcss-linux-x64-gnu": "1.32.0"
@@ -49,7 +49,7 @@ The most common ways to control access to an application by using bindings are:
### Policy-driven authorization
To use a [policy](../../customize/policies/index.md) to control which users or groups can access an application, click on an application in the applications list, click the **Policy/Group/User Bindings** tab, and then select **Policy** from the **Policy/Group/User Bindings** options.
To use a [policy](../../customize/policies/index.md) to control which users or groups can access an application, click an application in the applications list, open the **Policy / Group / User Bindings** tab, and click **Create or bind...**. You can then create a new policy and bind it to the application, or select **Bind an existing policy** under **Bind Existing...**.
### Bind a user or group to an application
@@ -57,11 +57,11 @@ You can bind a user or group to an application either when you create a new appl
#### When creating an application and provider
Follow the instructions for [creating a new application and provider](#create-an-application-and-provider-pair). On the **Policy/Group/User Bindings** tab at the top of the page, you can select **Group** or \*User\*\* to bind a specific group or userto the application.
Follow the instructions for [creating a new application and provider](#create-an-application-and-provider-pair). On the **Configure Bindings** step, click **Bind existing policy/group/user**. Select **Group** or **User**, choose the group or user to bind to the application, configure any additional binding settings, and then click **Save Binding**.
#### Add binding to an existing application
To bind a user or group to an existing application, click on an application in the applications list, select **Group** or **User** from the **Policy/Group/User Bindings** options, and then select the group or user that you want to bind to the application.
To bind a user or group to an existing application, click the application in the applications list, open the **Policy / Group / User Bindings** tab, and click **Create or bind...**. Under **Bind Existing...**, select **Bind a user** or **Bind a group**, choose the user or group to bind to the application, configure any additional binding settings, and then click **Create**.
## Application Entitlements
@@ -105,8 +105,8 @@ return {
2. Click the name of the application for which you want to create an entitlement.
3. Click the **Application entitlements** tab at the top of the page, and then click **New Entitlement**. Provide a name for the entitlement, enter any optional **Attributes**, and then click **Create**.
4. In the list locate the entitlement to which you want to bind a user or group, and then **click the caret (>) to expand the entitlement details.**
5. In the expanded area, click **Bind existing Group/User**.
6. In the **New Binding** box, select either the tab for **Group** or **User**, and then in the drop-down list, select the group or user.
5. In the expanded area, click **Bind existing group/user**.
6. In the binding modal, select **Group** or **User**, and then select the group or user.
7. Optionally, configure additional settings for the binding, and then click **Create** to create the binding and close the box.
## Hide applications
@@ -132,23 +132,18 @@ If a target has no applicable bindings, authentik treats the result as passing b
A flow-stage binding attaches a stage to a flow and defines the order in which that stage runs.
Flow-stage bindings are also called stage bindings. authentik uses them while building the flow plan that determines which stages a user will see and in what order.
Flow-stage bindings are also called stage bindings. authentik uses them while building the [flow plan](../flows-stages/flow/planner.md) that determines which stages a user will see and in what order.
This matters because stages are reusable objects. The same stage can appear in multiple flows, but each flow-stage binding can have its own policies, users, groups, order, and evaluation settings. When you bind a policy to a stage in a specific flow, you are binding it to that flow-stage binding, not to the reusable stage definition itself.
### When authentik evaluates stage-binding policies
Flow-stage bindings have two evaluation settings:
Flow-stage bindings have two policy evaluation options:
- **Evaluate when flow is planned**: authentik evaluates the binding while it is building the flow plan. If the binding does not pass at planning time, the stage is not added to the plan.
- **Evaluate when the stage is run**: authentik adds the stage to the flow plan, then evaluates the binding again immediately before the stage is shown. If the binding no longer passes, authentik removes that stage from the flow plan.
- **Evaluate when flow is planned**: authentik evaluates policies while building the flow plan.
- **Evaluate when stage is run**: authentik evaluates policies immediately before presenting the stage.
The second option is useful when the decision depends on context that is only available later in the flow. For example, after an identification stage completes, a subsequent stage binding can assess the identified user and then trigger a CAPTCHA or Deny stage as needed.
In other words:
- use **Evaluate when flow is planned** when the decision can already be made before the user reaches the stage
- use **Evaluate when the stage is run** when the decision depends on flow context that is created by an earlier stage
At least one of these options must be enabled, and both can be enabled at the same time. For the full behavior and guidance on choosing the right setting, see [Flow Planner](../flows-stages/flow/planner.md#planning-and-stage-policies).
## What to remember
@@ -18,9 +18,9 @@ When these stages are successfully completed, authentik logs in the user.
![](./simple_stages.png)
By default, policies are evaluated dynamically, right before the stage (to which a policy is bound) is presented to the user. This flexibility allows the login process to continue, change, or stop, based on the success or failure of each policy.
By default, policies bound to stage bindings are evaluated dynamically, right before the stage is presented to the user. This flexibility allows the login process to continue, change, or stop, based on the success or failure of each policy.
This default behavior can be altered by enabling the **Evaluate when flow is planned** option on the stage binding. With this setting a _flow plan_ containing all stages is generated upon flow execution. This means that all attached policies are evaluated upon execution. For more information about flow plans, read our [flow context documentation](./context/index.mdx).
You can change this behavior by enabling the **Evaluate when flow is planned** option on the stage binding. When this option is enabled, authentik uses the [Flow Planner](./planner.md) to evaluate the stage binding's policies when the flow starts, and includes the stage in the flow plan only if those policies pass.
## Policies and permissions
@@ -2,7 +2,7 @@
title: Flow Inspector
---
The Flow Inspector allows administrators to visually determine how custom flows work, inspect the current [flow context](./context/index.mdx) by stepping through the flow process and observing the Inspector with each step, and investigate issues.
The Flow Inspector allows administrators to visually determine how custom flows work, inspect the current [flow context](./context/index.mdx) by stepping through the flow process and observing the Inspector with each step, and investigate issues. It shows details from the active [flow plan](./planner.md).
As shown in the screenshot below, the Flow Inspector displays to the right, beside the selected flow (in this case, "Change Password"), with [information](#flow-inspector-details) about that specific flow and flow context.
@@ -45,13 +45,13 @@ The following information is shown in the Inspector:
#### Next stage
This is the currently planned next stage. If you have stage bindings configured to `Evaluate when flow is planned`, then you see the result here. If, however, you have them configured to re-evaluate (`Evaluate when stage is run`), then this does not show up, because the results vary based on your input.
This is the currently planned next stage. Stages that are evaluated when they run can still be skipped before they are shown. For more information, see [Planning and stage policies](./planner.md#planning-and-stage-policies).
The name and kind of the stage, as well as the unique ID, are shown.
#### Plan history
Here you can see an overview of which stages have run, which is currently active, and which is planned to come next. Same caveats as above apply.
Here you can see an overview of which stages have run, which is currently active, and which is planned to come next.
#### Current plan context
@@ -0,0 +1,42 @@
---
title: Flow Planner
---
The Flow Planner is the component of authentik that takes a configured [flow](./index.md) and, for each incoming request for that flow, determines the specific stages to run and the order in which they will run.
A flow describes a sequence of stages and policies. A flow plan is the per-session execution plan derived from that flow after authentik evaluates the request, the user, and the policies bound to the flow and its stage bindings.
## How the planner works
When a flow is executed, authentik creates a flow plan and does the following:
1. Verifies that the flow can be used in the current authentication context.
2. Evaluates policies bound directly to the flow.
3. Loads the flow's stage bindings in order.
4. Evaluates stage binding policies when **Evaluate when flow is planned** is enabled.
5. Stores the resulting ordered stage list and [flow context](./context/index.mdx) in the user's session.
The [flow executor](./executors/if-flow.md) then presents the first stage of the flow plan. When the stage completes successfully, authentik removes it from the flow plan and continues with the next stage. When no stages remain, the flow plan has completed.
## Planning and stage policies
Stage binding policies can be evaluated at two different times:
- **Evaluate when flow is planned**: The policy is evaluated when the flow plan is created. If the policy does not pass, that stage is not included in the plan.
- **Evaluate when stage is run** (_default_): The policy is evaluated immediately before the stage is presented. If the policy does not pass at that point, authentik skips that stage and continues with the next planned stage.
Both options can be enabled for the same stage binding. Use planning-time evaluation when a policy can be evaluated before any stages run. Use run-time evaluation when a policy depends on data that might be added to the flow context by earlier stages.
For example, a password stage usually depends on the user identified by an earlier identification stage. If a policy for a later stage depends on `pending_user`, evaluate that policy when the stage is run, or ensure `pending_user` is already present when the plan is created.
## Inspecting a plan
Use the [Flow Inspector](./inspector.md) to execute a flow and, while it executes, view the current stage, the next planned stage, the plan history, and the current plan context.
The Flow Inspector is accessed via the Flow Overview page and is particularly useful for troubleshooting flows and determining why stage binding policies fail to pass. It's also useful to evaluate values for use in policies.
## Caching and session state
authentik can cache flow plans so repeated executions of the same flow for the same user do not need to rebuild the same stage list every time. The active plan for a running flow is stored in the HTTP session, so browser-based and API-based flow executors must keep using the same session while the flow is running.
If flow behavior changes after editing stage bindings or policies, start a new flow execution before troubleshooting. Existing sessions may already have a plan in progress.
@@ -19,7 +19,7 @@ Bind this stage where a flow should stop after a policy or earlier stage determi
## Notes
:::caution
To use this stage effectively, make sure **Evaluate when flow is planned** is disabled on the stage binding.
To use this stage effectively, make sure **Evaluate when flow is planned** is disabled on the stage binding. See [Planning and stage policies](../../flow/planner.md#planning-and-stage-policies).
:::
If the binding is evaluated during flow planning, the denial can happen earlier than intended and skip the checks that were meant to decide whether the user should be denied.
@@ -54,7 +54,7 @@ You can use a binding to determine which exact [stages](../stages/index.md) (all
For an overview about all the different types of bindings in authentik and how they are used, refer to [About authentik bindings](../../bindings-overview/index.md).
:::info
Be aware that some stages and flows do not allow user or group bindings, because in certain scenarios (authentication or enrollment), the flow plan doesn't yet know who the user or group is.
Be aware that some stages and flows do not allow user or group bindings, because in certain scenarios (authentication or enrollment), the [flow plan](../flow/planner.md) doesn't yet know who the user or group is.
:::
### Bind a stage to a flow
@@ -53,7 +53,7 @@ To configure this setup:
3. Configure the expression so that it returns `True` only when the Password stage should run. Use one of the expressions below, depending on the authenticator type.
4. Navigate to **Flows and Stages** > **Flows** and open your authentication flow.
5. Open the **Stage Bindings** tab, expand the Password stage binding, and bind the Expression Policy there. Do not bind it to the flow itself or directly to the stage object. For more background, see [Bind a policy to a stage binding](../../../../customize/policies/working_with_policies.md#bind-a-policy-to-a-stage-binding).
6. On the Password stage binding, enable **Evaluate when stage is run**. Disable **Evaluate when flow is planned** unless the user is already known before the flow starts.
6. On the Password stage binding, enable **Evaluate when stage is run**. Disable **Evaluate when flow is planned** unless the user is already known before the flow starts. See [Planning and stage policies](../../flow/planner.md#planning-and-stage-policies).
#### WebAuthn
@@ -1,29 +1,29 @@
---
title: OAuth 2.0 provider
title: OAuth2/OpenID provider
---
In authentik, you can [create](./create-oauth2-provider.md) an [OAuth 2.0](https://oauth.net/2/) provider that authentik uses to authenticate the user to the associated application. This provider supports both generic OAuth2 as well as OpenID Connect (OIDC).
In authentik, you can [create](./create-oauth2-provider.md) an [OAuth 2.0](https://oauth.net/2/) provider that authenticates users to an associated application. The provider supports generic OAuth 2.0 clients and OpenID Connect (OIDC) relying parties.
## authentik and OAuth 2.0
It's important to understand how authentik works with and supports the OAuth 2.0 protocol, so before taking a [closer look at OAuth 2.0 protocol](#about-oauth-20-and-oidc) itself, let's cover a bit about authentik.
Before looking at the [OAuth 2.0 protocol](#about-oauth-20-and-oidc), it helps to understand the roles that authentik can play.
authentik can act either as the OP, (OpenID Provider, with authentik as the IdP), or as the RP (Relying Party, or the application that uses OAuth 2.0 to authenticate). If you want to configure authentik as an OP, then you create a provider, then use the OAuth 2.0 provider. If you want authentik to serve as the RP, then configure a [source](../../../users-sources/sources/index.md). Of course, authentik can serve as both the RP and OP, if you want to use the authentik OAuth provider and also use sources.
authentik can act as the OpenID Provider (OP), where authentik is the identity provider, or as the Relying Party (RP), where authentik uses an external OAuth 2.0 or OIDC provider for authentication. To use authentik as the OP, create an OAuth 2.0 provider and attach it to an application. To use authentik as the RP, configure an OAuth [source](../../../users-sources/sources/index.md). The same authentik instance can act as both an OP and an RP.
All standard OAuth 2.0 flows (authorization code, client_credentials, implicit, hybrid, device code) and grant types are supported in authentik, and we follow the [OIDC spec](https://openid.net/specs/openid-connect-core-1_0.html). OAuth 2.0 in authentik supports OAuth, PKCE, [GitHub compatibility](./github-compatibility.md), and the RP receives data from our scope mapping system.
authentik supports standard OAuth 2.0 flows and grant types, including authorization code, client credentials, implicit, hybrid, refresh token, and device code. authentik also follows the [OIDC spec](https://openid.net/specs/openid-connect-core-1_0.html), supports PKCE and [GitHub compatibility](./github-compatibility.md), and uses [scope mappings](../property-mappings/index.md#scope-mappings-with-oauth2) to control the claims returned to the RP.
The authentik OAuth 2.0 provider comes with all the standard functionality and features of OAuth 2.0, including the OAuth 2.0 security principles such as no cleartext storage of credentials, configurable encryption, configurable short expiration times, and the configuration of automatic rotation of refresh tokens. In short, our OAuth 2.0 protocol support provides full coverage.
The OAuth 2.0 provider includes security-focused configuration options such as no cleartext storage of credentials, configurable signing and encryption keys, short token expiration times, and automatic refresh token rotation.
## About OAuth 2.0 and OIDC
OAuth 2.0 is an authorization protocol that allows an application (the RP) to delegate authorization to an OP. OIDC is an authentication protocol built on top of OAuth2, which provides identity credentials and other data on top of OAuth2.
OAuth 2.0 is an authorization framework that lets an application delegate authorization to an authorization server. OIDC builds authentication on top of OAuth 2.0, adding identity tokens and standardized user claims.
**OAuth 2.0** typically requires two requests (unlike the previous "three-legged" OAuth 1.0). The two "legs", or requests, for OAuth 2.0 are:
A typical OAuth 2.0 authorization code flow has two main requests:
1. An authorization request is prepared by the RP and contains parameters for its implementation of OAuth and which data it requires, and then the User's browser is redirected to that URL.
2. The RP sends a request to authentik in the background to exchange the access code for an access token (and optionally a refresh token).
1. The RP prepares an authorization request that describes the requested access and redirects the user's browser to the OP.
2. After the OP authenticates the user, the RP exchanges the authorization code for an access token and, optionally, a refresh token.
In detail, with OAuth2 when a user accesses the application (the RP) via their browser, the RP then prepares a URL with parameters for the OpenID Provider (OP), which the user's browser is redirected to. The OP authenticates the user and generates an authorization code. The OP then redirects the client (the user's browser) back to the RP, along with that authorization code. In the background, the RP then sends that same authorization code in a request authenticated by the `client_id` and `client_secret` to the OP. Finally, the OP responds by sending an Access Token saying this user has been authorized (the RP is recommended to validate this token using cryptography) and optionally a Refresh Token.
When a user opens the application, the RP builds an authorization URL and redirects the user's browser to the OP. The OP authenticates the user and redirects the browser back to the RP with an authorization code. The RP then sends the code to the OP in a back-channel request, authenticating with its `client_id` and, for confidential clients, its `client_secret`. The OP returns an access token and, when configured, a refresh token. The RP should validate received tokens before trusting their contents.
The image below shows a typical authorization code flow.
@@ -48,7 +48,7 @@ sequenceDiagram
rp->>user: User is logged in
```
## OAuth2 endpoints and bindings
## OAuth 2.0 endpoints and bindings
| Endpoint | URL |
| -------------------- | -------------------------------------------------------------------- |
@@ -63,22 +63,22 @@ sequenceDiagram
| OpenID Configuration | `/application/o/<application slug>/.well-known/openid-configuration` |
:::caution Reserved application slugs
Due to how the OAuth2 provider endpoints are structured, you cannot create applications that use the slugs `authorize`, `token`, `device`, `userinfo`, `introspect`, or `revoke` as these would conflict with the global OAuth2 endpoints.
Due to how the OAuth2/OpenID provider endpoints are structured, you cannot create applications that use the slugs `authorize`, `token`, `device`, `userinfo`, `introspect`, or `revoke` as these would conflict with the global OAuth 2.0 endpoints.
:::
### Cross-provider token introspection and revocation
The token introspection and revocation endpoints are global OAuth2 endpoints, but access to tokens is still scoped by provider. A client can introspect or revoke tokens issued by the same OAuth2 provider that authenticated the request.
The token introspection and revocation endpoints are global OAuth 2.0 endpoints, but access to tokens is still scoped by provider. A client can introspect or revoke tokens issued by the same OAuth2 provider that authenticated the request.
For cross-provider introspection or revocation, authenticate the request with a confidential provider. Then, on the provider that issues the token, select the authenticating provider under **Federated OAuth2/OpenID Providers**. This allows the authenticating provider to introspect and revoke tokens issued by the federated provider.
### Additional configuration options with Redirect URIs
When using an OAuth 2.0 provider in authentik, the OP must validate the provided redirect URI by the RP. An authentik admin can configure a list in the **Redirect URI** field on the Provider.
When using an OAuth 2.0 provider in authentik, the OP must validate the redirect URI provided by the RP. An authentik administrator can configure allowed redirect URIs in the provider's **Redirect URI** field.
When you create a new OAuth 2.0 provider and app in authentik and you leave the **Redirect URI** field empty, then the first time a user opens that app, authentik uses that URL as the saved redirect URL.
If you leave the **Redirect URI** field empty when creating a provider and application, authentik saves the first redirect URI used when a user opens the application.
For advanced use cases, an authentik admin can use regular expressions (regex) instead of a redirect URL. For example, if you want to list ten different applications, instead of listing them all individually, you can create an expression with wildcards. When using regex, be aware that authentik uses a dot as a separator in the URL, but in regex a dot means "one of any character", a wildcard. You should therefore escape the dot with `\.` to prevent its interpretation as a wildcard.
For advanced use cases, an authentik administrator can use regular expressions instead of static redirect URLs. For example, you can use a regular expression to match a group of related redirect URLs instead of listing each URL individually. When matching literal periods in hostnames, escape them as `\.` because, in regular expressions, an unescaped period matches any character.
### OAuth2/OpenID Connect back-channel logout
@@ -86,15 +86,34 @@ Using back-channel logout (a server-to-server notification mechanism) allows an
For more information, see our [OAuth2/OpenID Connect front-channel and back-channel logout](./frontchannel_and_backchannel_logout.mdx) documentation.
## Issuer mode
The **Issuer mode** setting (under **Advanced protocol settings**) controls the value that authentik uses for the `iss` (issuer) claim in the tokens it signs, and for the `issuer` field in the provider's [OpenID Connect discovery document](#oauth-20-endpoints-and-bindings).
| Mode (UI label) | `iss` value |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| **Each provider has a different issuer, based on the application slug** (default) | `https://authentik.company/application/o/<application_slug>/` |
| **Same identifier is used for all providers** | `https://authentik.company/` |
By default, authentik uses per-provider mode: every provider has a unique issuer derived from its application slug. This is the recommended setting, and matches how the discovery and JWKS endpoints are structured, since both are served under the per-application `/application/o/<application_slug>/` path.
### Global issuer mode
Setting the issuer mode to **Same identifier is used for all providers** (referred to internally as **global** mode) makes every OAuth 2.0 provider configured with this mode share the same issuer: the root URL of the instance (`https://authentik.company/`).
:::info Well-known location
Global issuer mode still serves the discovery document at `https://authentik.company/application/o/<application_slug>/.well-known/openid-configuration`, not at the root issuer URL.
:::
## OAuth 2.0 flows and grant types
There are three general flows of OAuth 2.0:
1. Web-based application authorization (Authorization code, Implicit, Refresh token)
2. Client credentials (Machine-to-machine)
1. Web-based application authorization (authorization code, implicit, and hybrid)
2. Client credentials (machine-to-machine)
3. Device code
Additionally, the [Refresh token](#refresh-token-grant) (grant type) is optionally used with any of the above flows, as well as the client credentials and device code flows.
The [refresh token](#refresh-token-grant) grant type can be used with several flows to obtain a new access token without another interactive login.
You can define which grant types are available for your OAuth2 provider when you [create and configure the provider](./create-oauth2-provider.md). By default, all types are selected.
@@ -108,27 +127,27 @@ The flows and grant types used in this case are those used for a typical authori
#### Authorization code
The authorization code is for environments with both a Client and an application server, where the back and forth happens between the client and an app server (the logic lives on app server). The RP needs to authorize itself to the OP. Client ID (public, identifies which app is talking to it) and client secret (the password) that the RP uses to authenticate.
The authorization code grant is intended for applications with a backend server. The browser receives an authorization code, and the backend exchanges that code for tokens. During the exchange, confidential clients authenticate to the OP with their client ID and client secret.
If you configure authentik to use "Offline access" then during the initial auth the OP sends two tokens, an access token (short-lived, hours, can be customized) and a refresh token (typically longer validity, days or infinite). The RP (the app) saves both tokens. When the access token is about to expire, the RP sends the saved refresh token back to the OP, and requests a new access token. When the refresh token itself is about to expire, the RP can also ask for a new refresh token. This can all happen without user interaction if you configured the offline access.
If you configure authentik to allow offline access, the OP can return both a short-lived access token and a longer-lived refresh token during the initial authorization. The RP stores the tokens and uses the refresh token to request a new access token before the current access token expires. Depending on the provider settings, the RP can also receive a rotated refresh token without requiring another user interaction.
:::info
Starting with authentik 2024.2, applications only receive an access token. To receive a refresh token, both applications and authentik must be configured to request the `offline_access` scope. In authentik this can be done by selecting the `offline_access` Scope mapping in the provider settings.
Starting with authentik 2024.2, applications only receive an access token by default. To receive a refresh token, the application must request the `offline_access` scope and the authentik provider must include the `offline_access` scope mapping.
:::
The authorization code grant type is used to convert an authorization code to an access token (and optionally a refresh token). The authorization code is retrieved through the authentik [Authorization flow](../../flows-stages/flow/index.md), can only be used once, and expires quickly.
The authorization code grant converts an authorization code into an access token and, optionally, a refresh token. The authorization code is issued by the authentik [authorization flow](../../flows-stages/flow/index.md), can only be used once, and expires quickly.
#### Implicit
:::info
The OAuth 2.0 [Security Best Current Practice document](https://tools.ietf.org/html/draft-ietf-oauth-security-topics) recommends against using the Implicit flow entirely, and OAuth 2.0 for Browser-Based Apps describes the technique of using the authorization code flow with PKCE instead. ([source](https://oauth.net/2/grant-types/implicit/))
The OAuth 2.0 [Security Best Current Practice document](https://tools.ietf.org/html/draft-ietf-oauth-security-topics) recommends against using the implicit flow. OAuth 2.0 for browser-based apps recommends using the authorization code flow with PKCE instead. ([source](https://oauth.net/2/grant-types/implicit/))
:::
This flow is for more modern single page-applications, or ones you download, that are all client-side (all JS, no backend logic, etc) and have no server to make tokens. Because the secret cannot be stored on the client machine, the implicit flow is required in these architectures. With the implicit flow, the flow skips the second part of the two requests seen in the authorization flow; after the initial author request, the implicit flow receives a token, and then with cryptocracy and with PKCE, it can validate that it is the correct client, and that is safe to send a token. The RP (still called that with this implicit flow) can use cryptography to validate the token.
The implicit flow was designed for browser-only or installed applications that cannot safely store a client secret. In this flow, the OP returns tokens directly from the authorization request instead of requiring a separate back-channel token exchange. For new browser-based applications, use the authorization code flow with PKCE instead.
#### Hybrid
The Hybrid Flow is an OpenID Connect flow that incorporates traits of both the Implicit flow and the Authorization Code flow. It provides an application instant access to an ID token while ensuring secure and safe retrieval of access tokens and refresh tokens. This can be useful in situations where the application needs to quickly access information about the user, while in the background doing further processing to get additional tokens before gaining access to additional resources.
The hybrid flow is an OpenID Connect flow that combines parts of the implicit flow and the authorization code flow. It can return an ID token immediately while the application completes a back-channel exchange to retrieve access tokens and refresh tokens.
### 2. Client credentials
@@ -138,13 +157,13 @@ For more information, see [Machine-to-machine authentication](./machine_to_machi
### 3. Device code
The device code flow is used in situations where there is no browser and limited options for text or data input from a client ("input-constrained devices"). For example, using a subscription TV program on a television, where you use a website on your mobile device to input a code displayed on the TV, authenticate, and then you are logged in to the TV.
The device code flow is used when a device has no browser or has limited text input. For example, a TV application can display a code that the user enters on another device to authenticate the session.
For more information, see [Device code flow](./device_code.md).
#### Refresh token grant
Refresh tokens can be used as long-lived tokens to access user data, and further renew the refresh token down the road.
Refresh tokens let an application obtain new access tokens without requiring another interactive login. Depending on the provider settings, refresh tokens can also be rotated when they are used.
:::info
Starting with authentik 2024.2, the refresh token grant type requires the `offline_access` scope.
@@ -159,7 +178,7 @@ Scopes can be configured using scope mappings, a type of [property mapping](../p
By default, every user that has access to an application can request any of the configured scopes. Starting with authentik 2022.4, you can do additional checks for the scope in an expression policy (bound to the application):
```python
# There are additional fields set in the context, use `ak_logger.debug(request.context)` to see them.
# There are additional fields set in the context; use `ak_logger.debug(request.context)` to see them.
if "my-admin-scope" in request.context["oauth_scopes"]:
return ak_is_group_member(request.user, name="my-admin-group")
@@ -168,7 +187,7 @@ return True
## Default & special scopes
When a client does not request any scopes, authentik will treat the request as if all configured scopes were requested. Depending on the configured authorization flow, consent still needs to be given, and all scopes are listed there.
When a client does not request any scopes, authentik treats the request as if all configured scopes were requested. Depending on the configured authorization flow, consent still needs to be given, and all scopes are listed there.
This does _not_ apply to special scopes, as those are not configurable in the provider.
@@ -182,13 +201,13 @@ This does _not_ apply to special scopes, as those are not configurable in the pr
### authentik
- `goauthentik.io/api`: This scope grants the refresh token access to the authentik API on behalf of the user
- `goauthentik.io/api`: This scope grants the refresh token access to the authentik API on behalf of the user.
### GitHub compatibility
- `user`: No-op, is accepted for compatibility but does not give access to any resources
- `read:user`: Same as above
- `user:email`: Allows read-only access to `/user`, including email address
- `user`: No-op, is accepted for compatibility but does not give access to any resources.
- `read:user`: Same as above.
- `user:email`: Allows read-only access to `/user`, including email address.
- `read:org`: Allows read-only access to `/user/teams`, listing all the user's groups as teams.
### Email scope verification
@@ -223,4 +242,4 @@ When **Signing Key** is not selected, authentik signs JWTs symmetrically with th
### Encryption
authentik can also encrypt JWTs (turning them into JWEs) it issues by selecting an **Encryption Key** in the provider. When selected, all JWTs will be encrypted symmetrically using the selected certificate. authentik uses the `RSA-OAEP-256` algorithm with the `A256CBC-HS512` encryption method.
authentik can also encrypt JWTs (turning them into JWEs) that it issues by selecting an **Encryption Key** in the provider. When selected, all JWTs will be encrypted using the selected certificate. authentik uses the `RSA-OAEP-256` key management algorithm with the `A256CBC-HS512` content encryption method.
@@ -55,7 +55,7 @@ You can optionally add other prompt fields such as `domain` (e.g. `connection_se
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Flows and Stages** > **Flows**.
3. Click the name of the newly created authorization flow.
4. Click on **Stage bindings**, click **New Stage**, and enter the following required settings:
4. Click **Stage Bindings**, click **Create or bind...**, select **New Stage**, and enter the following required settings:
- **Select Type**: Select `Prompt stage` as the prompt type.
- **Create Prompt Stage**:
- **Name**: Enter a name for the prompt stage.
+9 -9
View File
@@ -29,15 +29,15 @@ You can also bind a **user** or **group** directly in the same places where you
Use the built-in policy types when they already match what you need. Reach for an expression policy when the built-in types are too limited.
| Policy type | Use it when | Notes |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| [Event Matcher](./types/event-matcher.md) | You want to react to specific authentik events, usually for notifications or automations. | Matches event action, app, model, and client IP. See [Notifications](../../sys-mgmt/events/notifications.md). |
| [Expression](./types/expression/index.mdx) | You need custom logic that is not covered by a more specialized policy type. | Most flexible option. Runs Python and can inspect flow context, prompt data, user data, request metadata, and more. |
| [GeoIP](./types/geoip.md) | You want to allow or deny requests based on country, ASN, or travel patterns. | Can also check recent login distance and impossible-travel scenarios. |
| [Password](./types/password.md) | You want to validate password complexity, HIBP exposure, or zxcvbn strength. | Commonly attached to a prompt stage's **Validation Policies**. |
| [Password Expiry](./types/password-expiry.md) | You want to expire passwords after a fixed number of days. | Can either deny login or mark the password unusable so the user must update it. |
| [Password Uniqueness](./types/password-uniqueness.md) | You want to prevent password reuse. | Enterprise feature. |
| [Reputation](./types/reputation.md) | You want to react to failed logins or suspicious sign-in activity. | Useful for showing CAPTCHA or another challenge only to low-reputation requests. |
| Policy type | Use it when | Notes |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| [Event Matcher](./types/event-matcher.md) | You want to react to specific authentik events, usually for notifications or automations. | Matches event action, app, model, client IP, and [AKQL queries](../../sys-mgmt/akql.mdx#use-akql-in-an-event-matcher-policy). |
| [Expression](./types/expression/index.mdx) | You need custom logic that is not covered by a more specialized policy type. | Most flexible option. Runs Python and can inspect flow context, prompt data, user data, request metadata, and more. |
| [GeoIP](./types/geoip.md) | You want to allow or deny requests based on country, ASN, or travel patterns. | Can also check recent login distance and impossible-travel scenarios. |
| [Password](./types/password.md) | You want to validate password complexity, HIBP exposure, or zxcvbn strength. | Commonly attached to a prompt stage's **Validation Policies**. |
| [Password Expiry](./types/password-expiry.md) | You want to expire passwords after a fixed number of days. | Can either deny login or mark the password unusable so the user must update it. |
| [Password Uniqueness](./types/password-uniqueness.md) | You want to prevent password reuse. | Enterprise feature. |
| [Reputation](./types/reputation.md) | You want to react to failed logins or suspicious sign-in activity. | Useful for showing CAPTCHA or another challenge only to low-reputation requests. |
## Deprecated policy types
@@ -6,7 +6,7 @@ tags:
- notifications
---
Use an Event Matcher policy when you want to match authentik events against a small set of built-in fields instead of writing a custom expression.
Use an Event Matcher policy when you want to match authentik events with built-in fields or an [AKQL query](../../../sys-mgmt/akql.mdx#use-akql-in-an-event-matcher-policy).
This policy is most commonly used with [Notification Rules](../../../sys-mgmt/events/notifications.md).
@@ -19,17 +19,19 @@ Use an Event Matcher policy when you want to match against events such as:
- activity from a specific authentik app
- activity from a specific client IP
For more complex matching, such as network ranges or logic across multiple event fields, use an [Expression policy](./expression/index.mdx) instead.
For complex Python logic or network range matching, use an [Expression policy](./expression/index.mdx) instead.
## What it matches
An Event Matcher policy can match on:
An Event Matcher policy can match on these built-in fields:
- action
- app
- model
- exact client IP
It can also match on the **Query** field, which uses AKQL. For the available event fields, operators, and examples, see the [AKQL reference](../../../sys-mgmt/akql.mdx).
Any field you leave empty is treated as a wildcard. Any field you configure must match for the policy to pass.
:::info Event Context
@@ -6,7 +6,7 @@ tags:
- flows
---
[Flow context](../../../../add-secure-apps/flows-stages/flow/context/index.mdx) can be read and updated from an [Expression policy](./index.mdx) through `context["flow_plan"].context`.
[Flow context](../../../../add-secure-apps/flows-stages/flow/context/index.mdx) can be read and updated from an [Expression policy](./index.mdx) through `context["flow_plan"].context`. For more information about the active plan, see [Flow Planner](../../../../add-secure-apps/flows-stages/flow/planner.md).
This is useful when you want to influence later stages in the same flow, such as changing a redirect target or passing data to another stage.
@@ -39,7 +39,7 @@ The threshold defaults to a low score, so the policy is naturally suited to "tri
## Use it on stage bindings
When you use a Reputation policy on a flow stage binding, configure the stage binding to **Evaluate when stage is run** so authentik can use the latest request context.
When you use a Reputation policy on a flow stage binding, configure the stage binding to **Evaluate when stage is run** so authentik can use the latest request context. For more information, see [Planning and stage policies](../../../add-secure-apps/flows-stages/flow/planner.md#planning-and-stage-policies).
This is especially important when the policy should react to the current login attempt rather than only to the initial planned flow state.
@@ -49,8 +49,8 @@ These bindings control which users can access a flow.
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Flows and Stages** > **Flows**.
3. In the list of flows, click on the name of the flow to which you want to bind a policy.
4. Click on the **Policy/Group/User Bindings** tab at the top of the page.
5. Either create a new policy and bind it immediately with **Create and bind Policy**, or attach an existing policy, group, or user with **Bind existing policy/group/user**.
4. Click on the **Policy / Group / User Bindings** tab at the top of the page.
5. Click **Create or bind...**. You can then create a new policy and bind it to the flow, or select **Bind an existing policy** under **Bind Existing...**.
### Bind a policy to a stage binding
@@ -67,7 +67,7 @@ When you bind a policy to a stage binding, this task is done per flow, and does
5. Click the arrow (**>**) beside the name of the stage to which you want to bind a policy. The details for that stage are displayed.
6. Either create and bind a new policy, or bind an existing policy, group, or user.
If the policy depends on request data that is only known after the user has interacted with the flow, configure the stage binding to **Evaluate when stage is run** instead of only at planning time.
If the policy depends on request data that is only known after the user has interacted with the flow, configure the stage binding to **Evaluate when stage is run**. For more information, see [Planning and stage policies](../../add-secure-apps/flows-stages/flow/planner.md#planning-and-stage-policies).
### Bind a policy to an application
@@ -76,8 +76,8 @@ These bindings control which users or groups can access an application.
1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications**.
3. In the list of applications, click on the name of the application to which you want to bind a policy.
4. Click on the **Policy/Group/User Bindings** tab at the top of the page.
5. Either create and bind a new policy, or bind an existing policy, group, or user.
4. Click on the **Policy / Group / User Bindings** tab at the top of the page.
5. Click **Create or bind...**. You can then create a new policy and bind it to the application, or select **Bind an existing policy** under **Bind Existing...**.
### Bind a policy to a source
@@ -86,7 +86,7 @@ The tone of the authentik documentation should be friendly but professional. It
### Language
The documentation uses **American English** spelling conventions (e.g., "customize" instead of "customise").
The documentation uses **American English** spelling conventions (e.g., "...ize" instead of "...ise").
### Voice
+1 -1
View File
@@ -101,7 +101,7 @@ A huge shoutout to all the people that contributed, helped test and also transla
- web/elements: use dedicated button for search clear instead of webkit exclusive one
- web/flows: adjust message for email stage
- web/user: don't show managed tokens in user interface
- web/user: initial optimisation for smaller screens
- web/user: initial optimization for smaller screens
- web/user: load interface settings from user settings
## Fixed in 2021.10.1-rc2
+5 -5
View File
@@ -56,7 +56,7 @@ This release does not have any headline features, and mostly fixes bugs.
- web/elements: add new API to store attributes in URL, use for table and tabs
- web/elements: allow app.model names for ak-object-changelog
- web/elements: allow multiple tabs with different state
- web/flows: fix spinner during webauthn not centred
- web/flows: fix spinner during webauthn not centered
- web/flows: update default background
- web/user: fix filtering for applications based on launchURL
- web/user: fix height issues on user interface
@@ -98,7 +98,7 @@ This release does not have any headline features, and mostly fixes bugs.
- stages/prompt: use policyenginemode all
- tests/e2e: add post binding test
- web: fix duplicate classes, make generic icon clickable
- web: fix text colour for bad request on light mode
- web: fix text color for bad request on light mode
- web/admin: show outpost warning on application page too
- web/elements: close dropdown when refresh event is dispatched
- web/user: allow custom font-awesome icons for applications
@@ -158,7 +158,7 @@ This release does not have any headline features, and mostly fixes bugs.
- internal: cleanup duplicate and redundant code, properly set sentry SDK scope settings
- lifecycle: add -Ofair to celery
- web/admin: add sidebar to applications
- web/admin: fix notification unread colours not matching on user and admin interface
- web/admin: fix notification unread colors not matching on user and admin interface
- web/admin: fix stage related flows not being shown in a list
- web/elements: add Markdown component to improve rendering
- web/elements: add support for sidebar on table page
@@ -174,7 +174,7 @@ This release does not have any headline features, and mostly fixes bugs.
- providers/oauth2: don't rely on expiry task for access codes and refresh tokens
- sources/oauth: allow writing to user in SourceConnection
- web: ignore instantSearchSDKJSBridgeClearHighlight error on edge on iOS
- web/admin: fix background colour for application sidebar
- web/admin: fix background color for application sidebar
- web/elements: fix border between search buttons
## Fixed in 2021.12.3
@@ -238,7 +238,7 @@ This release does not have any headline features, and mostly fixes bugs.
- stages/identification: add field for passwordless flow
- tenants: forbid creation of multiple default tenants
- web: add tr to locale
- web: remove page header colour, match user navbar to admin sidebar
- web: remove page header color, match user navbar to admin sidebar
- web/admin: add Admin in titlebar for admin interface
- web/admin: fix alignment in outpost list when expanding rows
- web/admin: fix display when groups/users don't fit on a single row
+2 -2
View File
@@ -50,7 +50,7 @@ slug: "/releases/2021.3"
- providers/oauth2: allow protected_resource_view when method is OPTIONS
- stages/authenticator_static: fix error when disable static tokens
- stages/authenticator_webauthn: add missing migration
- web: fix Colours for user settings in dark mode
- web: fix Colors for user settings in dark mode
- web: fix Flow executor not showing spinner when redirecting
- web: fix Source icons not being displayed on firefox
- web: fix styling for static token list
@@ -77,7 +77,7 @@ slug: "/releases/2021.3"
- web: show related edit button for bound stages and policies
- web: use chunking for vendor and api
- web: use loadingState for autosubmitStage
- web: use sections in sidebar, adjust colouring
- web: use sections in sidebar, adjust coloring
## Upgrading

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