Compare commits

..

49 Commits

Author SHA1 Message Date
b34665fabd release: 2025.4.3 2025-06-27 15:34:22 +02:00
0e07414e97 security: fix CVE-2025-52553 (#15289)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
# Conflicts:
#	website/sidebars/docs.mjs
2025-06-27 15:28:27 +02:00
dcbf5f323c website/docs: fixes misplaced sentence (cherry-pick #14998) (#15181)
website/docs: fixes misplaced sentence (#14998)

fixes misplaced sentence

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Tana M Berry <tana@goauthentik.io>
2025-06-22 00:58:42 +02:00
c3f1d6587d website/docs: fix egregious maintenance fail (cherry-pick #15176) (#15179)
website/docs: fix egregious maintenance fail (#15176)

fix egregious maintenance fail

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Tana M Berry <tana@goauthentik.io>
2025-06-22 00:58:28 +02:00
7254c11cb9 website/docs: remove commented out config options (cherry-pick #15064) (#15066)
website/docs: remove commented out config options (#15064)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-06-16 16:56:22 +02:00
ca4e6a10f5 website/docs: also hide the postgres pool_options setting (cherry-pick #15023) (#15033)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-06-13 15:40:52 +02:00
bda30c5ad5 release: 2025.4.2 2025-06-04 15:27:50 +02:00
588a7ff2e1 website/docs: release notes for 2025.4.2 (cherry-pick #14868) (#14873)
website/docs: release notes for `2025.4.2` (#14868)

Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
2025-06-04 15:27:08 +02:00
599d0f701f website/docs: release notes for 2025.4.1 (cherry-pick #14526) (#14872)
website/docs: release notes for 2025.4.1 (#14526)

* website/docs: release notes for 2025.4.1



* format



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-06-04 15:22:52 +02:00
967e4cce9d website/docs: fix leftover placeholder in release notes (cherry-pick #14377) (#14871)
website/docs: fix leftover placeholder in release notes (#14377)

Update v2025.4.md

changed download URL to match version 2025.4. Otherwise it will give a 404

Signed-off-by: finkerle <145992792+finkerle@users.noreply.github.com>
Co-authored-by: finkerle <145992792+finkerle@users.noreply.github.com>
2025-06-04 15:22:39 +02:00
f1c5f43419 website/docs: fix dry-run release highlight (cherry-pick #14337) (#14870)
website/docs: fix dry-run release highlight (#14337)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-06-04 15:22:00 +02:00
b5b68fc829 website/docs: clarify 2025.4 breaking Reputation changes (cherry-pick #14284) (#14869)
website/docs: clarify `2025.4` breaking Reputation changes (#14284)

* website/docs: clarify `2025.4` breaking Reputation changes

* Update website/docs/releases/2025/v2025.4.md



* change to bump build checks

* another tweak to bounce after rebase

---------

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Tana M Berry <tana@goauthentik.io>
2025-06-04 15:21:28 +02:00
1d7be5e770 core: Migrate permissions before deleteing OldAuthenticatedSession (cherry-pick #14788) (#14791)
core: Migrate permissions before deleteing OldAuthenticatedSession (#14788)

* add migrate_permissions_before_delete to authentik_core 0047 migration

* fix linting

* new approach

* fixup! new approach

---------

Co-authored-by: Marcelo Elizeche Landó <marcelo@goauthentik.io>
Co-authored-by: Simonyi Gergő <gergo@goauthentik.io>
2025-05-30 18:05:52 +02:00
489ef7a0a1 ci: fix broken cache (cherry-pick #14725) (#14792)
ci: fix broken cache (#14725)

* ci: fix broken cache



* fix commit hash



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-05-30 17:28:51 +02:00
668f35cd5b sources/scim: fix all users being added to group when no members are given (cherry-pick #14645) (#14666)
sources/scim: fix all users being added to group when no members are given (#14645)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-05-23 17:24:27 +02:00
42f0528a1d lifecycle: fix arguments not being passed to worker command (cherry-pick #14574) (#14620)
Co-authored-by: Jens L. <jens@goauthentik.io>
fix arguments not being passed to worker command (#14574)
2025-05-22 13:51:08 +02:00
ae47624761 release: 2025.4.1 2025-05-15 19:10:52 +02:00
14a6430e21 core: fix unable to create group if no enable_group_superuser permission is given (cherry-pick #14510) (#14521)
core: fix unable to create group if no enable_group_superuser permission is given (#14510)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-05-15 15:05:04 +02:00
ed0a9d6a0a core: remove OldAuthenticatedSession content type (cherry-pick #14507) (#14509)
core: remove `OldAuthenticatedSession` content type (#14507)

* core: remove `OldAuthenticatedSession` content type

This was left out from https://github.com/goauthentik/authentik/pull/9736

* remove stale content types in `repair_permissions`



* run `remove_stale_contenttypes` for each tenant

---------

Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-05-14 15:33:14 +02:00
53143e0c40 enterprise: fix expired license's users being counted (cherry-pick #14451) (#14496)
enterprise: fix expired license's users being counted (#14451)

* enterprise: fix expired license's users being counted



* tests to the rescue



* hmm



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-05-13 16:07:08 +02:00
178e010ed4 root: backport SFE Build fix (#14495)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-05-13 14:32:13 +02:00
49b666fbde root: temporarily deactivate database pool option (cherry-pick #14443) (#14479)
root: temporarily deactivate database pool option (#14443)

* root: temporarily deactivate database pool option



* format



* deactivate tests



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-05-12 19:13:09 +02:00
c343e3a7f4 core: fix session migration when old session can't be loaded (cherry-pick #14466) (#14480)
core: fix session migration when old session can't be loaded (#14466)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-05-12 16:09:05 +02:00
5febf3ce5b core: bump h11 from 0.14.0 to v0.16.0 (cherry-pick #14352) (#14472)
core: bump h11 from 0.14.0 to v0.16.0 (#14352)

Co-authored-by: Marcelo Elizeche Landó <marcelo@goauthentik.io>
2025-05-11 15:09:22 +02:00
b8c5bd678b web/flows/sfe: fix global background image not being loaded (cherry-pick #14442) (#14450)
web/flows/sfe: fix global background image not being loaded (#14442)

* web/flows/sfe: add initial loading spinner



* fix brand-level default flow background not working with SFE and loading original image with full flow interface

https://github.com/goauthentik/authentik/pull/13079#issuecomment-2853357407


---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-05-09 18:14:21 +02:00
4dd5eccbaa lifecycle: fix ak dump_config (cherry-pick #14445) (#14448)
lifecycle: fix ak dump_config (#14445)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-05-09 18:14:06 +02:00
2410884006 outposts: fix tmpdir in containers not being set (cherry-pick #14444) (#14449)
outposts: fix tmpdir in containers not being set (#14444)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-05-09 18:13:41 +02:00
3cb921b0f9 rbac: fix RoleObjectPermissionTable not showing add_user_to_group (cherry-pick #14312) (#14334)
rbac: fix RoleObjectPermissionTable not showing `add_user_to_group` (#14312)

fix RoleObjectPermissionTable not showing `add_user_to_group`

Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
2025-05-02 18:35:13 +02:00
535f92981f website/docs: docs about initial perms (cherry-pick #14263) (#14282)
website/docs: docs about initial perms (#14263)

* basic procedural steps

* more questions, more typos

* more typos

* tweaks

* more content, new links

* fixed link

* tweak

* fix things

* more fixes

* yet more fixes

* Apply suggestions from code review




* Update website/docs/users-sources/access-control/initial_permissions.mdx




* dewi's edits

* dominic's edits

* gergo edits and more dominic edits

* one more

* yet one more fix

* final gergo observation

* tweak

---------

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Tana M Berry <tana@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-05-01 22:25:36 +02:00
955d69d5b7 brands: fix CSS Migration not updating brands (cherry-pick #14306) (#14308)
brands: fix CSS Migration not updating brands (#14306)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-04-30 23:04:38 +02:00
fb01d8e96a website/docs: initial permissions: fix usage of term admin (cherry-pick #14300) (#14302)
Co-authored-by: Dominic R <dominic@sdko.org>
fix usage of term admin (#14300)
2025-04-30 15:44:44 +02:00
6d39efd3e3 ci: cleanup post uv migration (cherry-pick #13538) (#14297)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-04-30 15:05:46 +02:00
3020c31bcd website/docs: add gateway API to release notes and documentation (cherry-pick #14278) (#14298)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-04-30 14:52:38 +02:00
22412729e2 release: 2025.4.0 2025-04-29 16:16:32 -03:00
a02868a27d website/docs: Release notes 2025.4.0 (#14281)
* remove rc notice and enterprise tag for the span

* Edit sidebar and security.md

* Add api changes and minor fixes

* Fix linting

* fix netlify linter

* Update website/docs/releases/2025/v2025.4.md

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/releases/2025/v2025.4.md

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* remove changelog entries that shouldn't be there

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

* Update v2025.4.md

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update v2025.4.md

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* fix linting

---------

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-04-29 15:56:28 -03:00
bfbb4a8ebc website/docs: sessions in database (#13507)
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-04-29 15:56:21 -03:00
6c0e827677 website/docs: add LDAP 'Lookup using user attribute' docs (#13966)
* website/docs: add LDAP 'Lookup using user attribute' docs

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

* Updated the doc to new template, removed incorrect screenshot, clarified instructions

* Change in group field explanation as per Marc's comment

* Added examples for filters and changed some language.

* Removed additional info link

* fixup

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

* Minor formatting changes

* Update website/docs/users-sources/sources/protocols/ldap/index.md

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/users-sources/sources/directory-sync/active-directory/index.md

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/users-sources/sources/directory-sync/active-directory/index.md

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* Added more information to service account creation and LDAPS testing

* Added examples for fields based on issue #3801

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-04-29 15:56:13 -03:00
29884cbf81 website/docs: add postgres pool configuration (#14060)
* website/docs: add postgres pool configuration

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

* Update website/docs/install-config/configuration/configuration.mdx

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

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-04-29 15:56:01 -03:00
0f02985b0c website/docs: docs about initial perms (#14263)
* basic procedural steps

* more questions, more typos

* more typos

* tweaks

* more content, new links

* fixed link

* tweak

* fix things

* more fixes

* yet more fixes

* Apply suggestions from code review

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/users-sources/access-control/initial_permissions.mdx

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* dewi's edits

* dominic's edits

* gergo edits and more dominic edits

* one more

* yet one more fix

* final gergo observation

* tweak

---------

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Tana M Berry <tana@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-04-29 15:55:54 -03:00
2244e026c2 website/docs: Revert "website/docs: revert token_expiry format in example blueprint… (#14280)
Revert "website/docs: revert token_expiry format in example blueprint (#13582)"

This reverts commit 9538cf4690.
2025-04-29 15:55:47 -03:00
429c03021c website/docs: Password Uniqueness Policy (#13686)
* First draft docs for policies/unique_password

* simplify documentation

* fix styling

* Add clarification about when this policy takes effect

* change wording in how it works

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Marcelo Elizeche Landó <marce@melizeche.com>

* Take the user by the hand and tell them where to go

* Improve wording in Configuration options

* add suggestion from PR

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Marcelo Elizeche Landó <marce@melizeche.com>

* Update website/docs/customize/policies/unique_password.md

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Apply suggestions from code review

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marcelo Elizeche Landó <marce@melizeche.com>

* fix linting and wording

* Add instructions for binding

* Remove conf options section, add to sidebar

* Update website/docs/customize/policies/unique_password.md

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

---------

Signed-off-by: Marcelo Elizeche Landó <marce@melizeche.com>
Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2025-04-29 15:55:41 -03:00
f47e8d9d72 docs/website: Update 2025.4 notes (#14272)
Fix styling
2025-04-29 15:55:35 -03:00
3e7d2587c4 website/docs: update 2025.4 release notes (#14251)
* Update release notes for 2025.4

* fix typo

* Add/improve highlights, features and descriptions

* Fix linting and remove API changes

* remove minor changes

* fix linting

* Add helm chart stuff and integrations guide

* fix linting

* Restore SECURITY.md and sidebar.js

* Update website/docs/releases/2025/v2025.4.md

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/releases/2025/v2025.4.md

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/releases/2025/v2025.4.md

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/releases/2025/v2025.4.md

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/releases/2025/v2025.4.md

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/releases/2025/v2025.4.md

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* Update website/docs/releases/2025/v2025.4.md

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* password history - add compliance note

Signed-off-by: Fletcher Heisler <fheisler@users.noreply.github.com>

* Update website/docs/releases/2025/v2025.4.md

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>

* please the linter

* use current version

* add .md

* fix badges

---------

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Fletcher Heisler <fheisler@users.noreply.github.com>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Fletcher Heisler <fheisler@users.noreply.github.com>
2025-04-29 15:54:55 -03:00
55a38d4a36 rbac: add name to Permissions search (cherry-pick #14269) (#14270)
rbac: add `name` to Permissions search (#14269)

Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
2025-04-28 19:04:05 +02:00
6021bb932d web: fix bug that was causing charts to be too tall (cherry-pick #14253) (#14254)
Co-authored-by: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com>
fix bug that was causing charts to be too tall (#14253)
2025-04-28 13:51:49 +02:00
54a5d95717 release: 2025.4.0-rc2 2025-04-25 13:50:44 +02:00
a0a1275452 lifecycle: fix test-all in docker (cherry-pick #14244) (#14246)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
fix test-all in docker (#14244)
2025-04-25 13:50:27 +02:00
919aa5df59 core, web: update translations (cherry-pick #14243) (#14245)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
Co-authored-by: rissson <18313093+rissson@users.noreply.github.com>
2025-04-25 13:39:50 +02:00
cedf7cf683 release: 2025.4.0-rc1 2025-04-25 01:53:53 -03:00
394 changed files with 9670 additions and 15678 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2025.4.1
current_version = 2025.4.3
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?

View File

@ -36,7 +36,7 @@ runs:
with:
go-version-file: "go.mod"
- name: Setup docker cache
uses: ScribeMD/docker-cache@0.5.0
uses: AndreKurait/docker-cache@0fe76702a40db986d9663c24954fc14c6a6031b7
with:
key: docker-images-${{ runner.os }}-${{ hashFiles('.github/actions/setup/docker-compose.yml', 'Makefile') }}-${{ inputs.postgresql_version }}
- name: Setup dependencies

View File

@ -118,15 +118,3 @@ updates:
prefix: "core:"
labels:
- dependencies
- package-ecosystem: docker-compose
directories:
# - /scripts # Maybe
- /tests/e2e
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
commit-message:
prefix: "core:"
labels:
- dependencies

View File

@ -200,7 +200,7 @@ jobs:
uses: actions/cache@v4
with:
path: web/dist
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**') }}
- name: prepare web ui
if: steps.cache-web.outputs.cache-hit != 'true'
working-directory: web
@ -208,7 +208,6 @@ jobs:
npm ci
make -C .. gen-client-ts
npm run build
npm run build:sfe
- name: run e2e
run: |
uv run coverage run manage.py test ${{ matrix.job.glob }}

View File

@ -29,7 +29,7 @@ jobs:
- name: Generate API
run: make gen-client-go
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
uses: golangci/golangci-lint-action@v7
with:
version: latest
args: --timeout 5000s --verbose

View File

@ -16,7 +16,7 @@
],
"typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.importModuleSpecifierEnding": "index",
"typescript.tsdk": "./node_modules/typescript/lib",
"typescript.tsdk": "./web/node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"yaml.schemas": {
"./blueprints/schema.json": "blueprints/**/*.yaml"
@ -30,5 +30,7 @@
}
],
"go.testFlags": ["-count=1"],
"github-actions.workflows.pinned.workflows": [".github/workflows/ci-main.yml"]
"github-actions.workflows.pinned.workflows": [
".github/workflows/ci-main.yml"
]
}

View File

@ -86,17 +86,18 @@ FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.1.0 AS geoip
ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN"
ENV GEOIPUPDATE_VERBOSE="1"
ENV GEOIPUPDATE_ACCOUNT_ID_FILE="/run/secrets/GEOIPUPDATE_ACCOUNT_ID"
ENV GEOIPUPDATE_LICENSE_KEY_FILE="/run/secrets/GEOIPUPDATE_LICENSE_KEY"
USER root
RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
--mount=type=secret,id=GEOIPUPDATE_LICENSE_KEY \
mkdir -p /usr/share/GeoIP && \
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
# Stage 5: Download uv
FROM ghcr.io/astral-sh/uv:0.7.4 AS uv
FROM ghcr.io/astral-sh/uv:0.6.16 AS uv
# Stage 6: Base python image
FROM ghcr.io/goauthentik/fips-python:3.13.3-slim-bookworm-fips AS python-base
FROM ghcr.io/goauthentik/fips-python:3.12.10-slim-bookworm-fips AS python-base
ENV VENV_PATH="/ak-root/.venv" \
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \

View File

@ -42,4 +42,4 @@ See [SECURITY.md](SECURITY.md)
## Adoption and Contributions
Your organization uses authentik? We'd love to add your logo to the readme and our website! Email us @ hello@goauthentik.io or open a GitHub Issue/PR! For more information on how to contribute to authentik, please refer to our [contribution guide](https://docs.goauthentik.io/docs/developer-docs?utm_source=github).
Your organization uses authentik? We'd love to add your logo to the readme and our website! Email us @ hello@goauthentik.io or open a GitHub Issue/PR! For more information on how to contribute to authentik, please refer to our [CONTRIBUTING.md file](./CONTRIBUTING.md).

View File

@ -2,7 +2,7 @@
from os import environ
__version__ = "2025.4.1"
__version__ = "2025.4.3"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -54,7 +54,7 @@ def create_component(generator: SchemaGenerator, name, schema, type_=ResolvedCom
return component
def postprocess_schema_responses(result, generator: SchemaGenerator, **kwargs):
def postprocess_schema_responses(result, generator: SchemaGenerator, **kwargs): # noqa: W0613
"""Workaround to set a default response for endpoints.
Workaround suggested at
<https://github.com/tfranzel/drf-spectacular/issues/119#issuecomment-656970357>

View File

@ -164,7 +164,9 @@ class BlueprintEntry:
"""Get the blueprint model, with yaml tags resolved if present"""
return str(self.tag_resolver(self.model, blueprint))
def get_permissions(self, blueprint: "Blueprint") -> Generator[BlueprintEntryPermission]:
def get_permissions(
self, blueprint: "Blueprint"
) -> Generator[BlueprintEntryPermission, None, None]:
"""Get permissions of this entry, with all yaml tags resolved"""
for perm in self.permissions:
yield BlueprintEntryPermission(

View File

@ -5,10 +5,10 @@ from typing import Any
from django.db.models import F, Q
from django.db.models import Value as V
from django.http.request import HttpRequest
from sentry_sdk import get_current_span
from authentik import get_full_version
from authentik.brands.models import Brand
from authentik.lib.sentry import get_http_meta
from authentik.tenants.models import Tenant
_q_default = Q(default=True)
@ -32,9 +32,13 @@ def context_processor(request: HttpRequest) -> dict[str, Any]:
"""Context Processor that injects brand object into every template"""
brand = getattr(request, "brand", DEFAULT_BRAND)
tenant = getattr(request, "tenant", Tenant())
trace = ""
span = get_current_span()
if span:
trace = span.to_traceparent()
return {
"brand": brand,
"footer_links": tenant.footer_links,
"html_meta": {**get_http_meta()},
"sentry_trace": trace,
"version": get_full_version(),
}

View File

@ -79,6 +79,7 @@ def _migrate_session(
AuthenticatedSession.objects.using(db_alias).create(
session=session,
user=old_auth_session.user,
uuid=old_auth_session.uuid,
)

View File

@ -1,10 +1,81 @@
# Generated by Django 5.1.9 on 2025-05-14 11:15
from django.apps.registry import Apps
from django.apps.registry import Apps, apps as global_apps
from django.db import migrations
from django.contrib.contenttypes.management import create_contenttypes
from django.contrib.auth.management import create_permissions
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def migrate_authenticated_session_permissions(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
"""Migrate permissions from OldAuthenticatedSession to AuthenticatedSession"""
db_alias = schema_editor.connection.alias
# `apps` here is just an instance of `django.db.migrations.state.AppConfigStub`, we need the
# real config for creating permissions and content types
authentik_core_config = global_apps.get_app_config("authentik_core")
# These are only ran by django after all migrations, but we need them right now.
# `global_apps` is needed,
create_permissions(authentik_core_config, using=db_alias, verbosity=1)
create_contenttypes(authentik_core_config, using=db_alias, verbosity=1)
# But from now on, this is just a regular migration, so use `apps`
Permission = apps.get_model("auth", "Permission")
ContentType = apps.get_model("contenttypes", "ContentType")
try:
old_ct = ContentType.objects.using(db_alias).get(
app_label="authentik_core", model="oldauthenticatedsession"
)
new_ct = ContentType.objects.using(db_alias).get(
app_label="authentik_core", model="authenticatedsession"
)
except ContentType.DoesNotExist:
# This should exist at this point, but if not, let's cut our losses
return
# Get all permissions for the old content type
old_perms = Permission.objects.using(db_alias).filter(content_type=old_ct)
# Create equivalent permissions for the new content type
for old_perm in old_perms:
new_perm = (
Permission.objects.using(db_alias)
.filter(
content_type=new_ct,
codename=old_perm.codename,
)
.first()
)
if not new_perm:
# This should exist at this point, but if not, let's cut our losses
continue
# Global user permissions
User = apps.get_model("authentik_core", "User")
User.user_permissions.through.objects.using(db_alias).filter(
permission=old_perm
).all().update(permission=new_perm)
# Global role permissions
DjangoGroup = apps.get_model("auth", "Group")
DjangoGroup.permissions.through.objects.using(db_alias).filter(
permission=old_perm
).all().update(permission=new_perm)
# Object user permissions
UserObjectPermission = apps.get_model("guardian", "UserObjectPermission")
UserObjectPermission.objects.using(db_alias).filter(permission=old_perm).all().update(
permission=new_perm, content_type=new_ct
)
# Object role permissions
GroupObjectPermission = apps.get_model("guardian", "GroupObjectPermission")
GroupObjectPermission.objects.using(db_alias).filter(permission=old_perm).all().update(
permission=new_perm, content_type=new_ct
)
def remove_old_authenticated_session_content_type(
apps: Apps, schema_editor: BaseDatabaseSchemaEditor
):
@ -21,7 +92,12 @@ class Migration(migrations.Migration):
]
operations = [
migrations.RunPython(
code=migrate_authenticated_session_permissions,
reverse_code=migrations.RunPython.noop,
),
migrations.RunPython(
code=remove_old_authenticated_session_content_type,
reverse_code=migrations.RunPython.noop,
),
]

View File

@ -21,9 +21,7 @@
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>
{% block head %}
{% endblock %}
{% for key, value in html_meta.items %}
<meta name="{{key}}" content="{{ value }}" />
{% endfor %}
<meta name="sentry-trace" content="{{ sentry_trace }}" />
</head>
<body>
{% block body %}

View File

@ -57,7 +57,7 @@ class LogEventSerializer(PassiveSerializer):
@contextmanager
def capture_logs(log_default_output=True) -> Generator[list[LogEvent]]:
def capture_logs(log_default_output=True) -> Generator[list[LogEvent], None, None]:
"""Capture log entries created"""
logs = []
cap = LogCapture()

View File

@ -7,7 +7,7 @@
{{ block.super }}
<link rel="prefetch" href="{{ flow_background_url }}" />
{% if flow.compatibility_mode and not inspector %}
<script>ShadyDOM = { force: true };</script>
<script>ShadyDOM = { force: !navigator.webdriver };</script>
{% endif %}
{% include "base/header_js.html" %}
<script>

View File

@ -17,7 +17,7 @@ from ldap3.core.exceptions import LDAPException
from redis.exceptions import ConnectionError as RedisConnectionError
from redis.exceptions import RedisError, ResponseError
from rest_framework.exceptions import APIException
from sentry_sdk import HttpTransport, get_current_scope
from sentry_sdk import HttpTransport
from sentry_sdk import init as sentry_sdk_init
from sentry_sdk.api import set_tag
from sentry_sdk.integrations.argv import ArgvIntegration
@ -27,7 +27,6 @@ from sentry_sdk.integrations.redis import RedisIntegration
from sentry_sdk.integrations.socket import SocketIntegration
from sentry_sdk.integrations.stdlib import StdlibIntegration
from sentry_sdk.integrations.threading import ThreadingIntegration
from sentry_sdk.tracing import BAGGAGE_HEADER_NAME, SENTRY_TRACE_HEADER_NAME
from structlog.stdlib import get_logger
from websockets.exceptions import WebSocketException
@ -96,8 +95,6 @@ def traces_sampler(sampling_context: dict) -> float:
return 0
if _type == "websocket":
return 0
if CONFIG.get_bool("debug"):
return 1
return float(CONFIG.get("error_reporting.sample_rate", 0.1))
@ -170,14 +167,3 @@ def before_send(event: dict, hint: dict) -> dict | None:
if settings.DEBUG:
return None
return event
def get_http_meta():
"""Get sentry-related meta key-values"""
scope = get_current_scope()
meta = {
SENTRY_TRACE_HEADER_NAME: scope.get_traceparent() or "",
}
if bag := scope.get_baggage():
meta[BAGGAGE_HEADER_NAME] = bag.serialize()
return meta

View File

@ -59,7 +59,7 @@ class PropertyMappingManager:
request: HttpRequest | None,
return_mapping: bool = False,
**kwargs,
) -> Generator[tuple[dict, PropertyMapping]]:
) -> Generator[tuple[dict, PropertyMapping], None]:
"""Iterate over all mappings that were pre-compiled and
execute all of them with the given context"""
if not self.__has_compiled:

View File

@ -66,7 +66,10 @@ class RACClientConsumer(AsyncWebsocketConsumer):
def init_outpost_connection(self):
"""Initialize guac connection settings"""
self.token = (
ConnectionToken.filter_not_expired(token=self.scope["url_route"]["kwargs"]["token"])
ConnectionToken.filter_not_expired(
token=self.scope["url_route"]["kwargs"]["token"],
session__session__session_key=self.scope["session"].session_key,
)
.select_related("endpoint", "provider", "session", "session__user")
.first()
)

View File

@ -87,3 +87,22 @@ class TestRACViews(APITestCase):
)
body = loads(flow_response.content)
self.assertEqual(body["component"], "ak-stage-access-denied")
def test_different_session(self):
"""Test request"""
self.client.force_login(self.user)
response = self.client.get(
reverse(
"authentik_providers_rac:start",
kwargs={"app": self.app.slug, "endpoint": str(self.endpoint.pk)},
)
)
self.assertEqual(response.status_code, 302)
flow_response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
body = loads(flow_response.content)
next_url = body["to"]
self.client.logout()
final_response = self.client.get(next_url)
self.assertEqual(final_response.url, reverse("authentik_core:if-user"))

View File

@ -65,7 +65,10 @@ class RACInterface(InterfaceView):
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
# Early sanity check to ensure token still exists
token = ConnectionToken.filter_not_expired(token=self.kwargs["token"]).first()
token = ConnectionToken.filter_not_expired(
token=self.kwargs["token"],
session__session__session_key=request.session.session_key,
).first()
if not token:
return redirect("authentik_core:if-user")
self.token = token

View File

@ -199,7 +199,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
chunk_size = len(ops)
if len(ops) < 1:
return
for chunk in batched(ops, chunk_size, strict=False):
for chunk in batched(ops, chunk_size):
req = PatchRequest(Operations=list(chunk))
self._request(
"PATCH",

View File

@ -11,7 +11,7 @@ from django.test.runner import DiscoverRunner
from authentik.lib.config import CONFIG
from authentik.lib.sentry import sentry_init
from authentik.root.signals import post_startup, pre_startup, startup
from tests.docker import get_docker_tag
from tests.e2e.utils import get_docker_tag
# globally set maxDiff to none to show full assert error
TestCase.maxDiff = None

View File

@ -97,7 +97,8 @@ class GroupsView(SCIMObjectView):
self.logger.warning("Invalid group member", exc=exc)
continue
query |= Q(uuid=member.value)
group.users.set(User.objects.filter(query))
if query:
group.users.set(User.objects.filter(query))
if not connection:
connection, _ = SCIMSourceGroup.objects.get_or_create(
source=self.source,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "https://goauthentik.io/blueprints/schema.json",
"type": "object",
"title": "authentik 2025.4.1 Blueprint schema",
"title": "authentik 2025.4.3 Blueprint schema",
"required": [
"version",
"entries"

View File

@ -31,7 +31,7 @@ services:
volumes:
- redis:/data
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.1}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.3}
restart: unless-stopped
command: server
environment:
@ -55,7 +55,7 @@ services:
redis:
condition: service_healthy
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.1}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.4.3}
restart: unless-stopped
command: worker
environment:

16
go.mod
View File

@ -5,7 +5,7 @@ go 1.24.0
require (
beryju.io/ldap v0.1.0
github.com/coreos/go-oidc/v3 v3.14.1
github.com/getsentry/sentry-go v0.33.0
github.com/getsentry/sentry-go v0.32.0
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-ldap/ldap/v3 v3.4.11
github.com/go-openapi/runtime v0.28.0
@ -19,18 +19,18 @@ require (
github.com/jellydator/ttlcache/v3 v3.3.0
github.com/mitchellh/mapstructure v1.5.0
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
github.com/pires/go-proxyproto v0.8.1
github.com/pires/go-proxyproto v0.8.0
github.com/prometheus/client_golang v1.22.0
github.com/redis/go-redis/v9 v9.8.0
github.com/sethvargo/go-envconfig v1.3.0
github.com/redis/go-redis/v9 v9.7.3
github.com/sethvargo/go-envconfig v1.2.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2025041.1
goauthentik.io/api/v3 v3.2025024.9
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.14.0
golang.org/x/oauth2 v0.29.0
golang.org/x/sync v0.13.0
gopkg.in/yaml.v2 v2.4.0
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
)
@ -75,7 +75,7 @@ require (
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/text v0.23.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

36
go.sum
View File

@ -69,8 +69,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/getsentry/sentry-go v0.33.0 h1:YWyDii0KGVov3xOaamOnF0mjOrqSjBqwv48UEzn7QFg=
github.com/getsentry/sentry-go v0.33.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
github.com/getsentry/sentry-go v0.32.0 h1:YKs+//QmwE3DcYtfKRH8/KyOOF/I6Qnx7qYGNHCGmCY=
github.com/getsentry/sentry-go v0.32.0/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
@ -230,8 +230,8 @@ github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -245,14 +245,14 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sethvargo/go-envconfig v1.3.0 h1:gJs+Fuv8+f05omTpwWIu6KmuseFAXKrIaOZSh8RMt0U=
github.com/sethvargo/go-envconfig v1.3.0/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw=
github.com/sethvargo/go-envconfig v1.2.0 h1:q3XkOZWkC+G1sMLCrw9oPGTjYexygLOXDmGUit1ti8Q=
github.com/sethvargo/go-envconfig v1.2.0/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@ -290,8 +290,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
goauthentik.io/api/v3 v3.2025041.1 h1:GAN6AoTmfnCGgx1SyM07jP4/LR/T3rkTEyShSBd3Co8=
goauthentik.io/api/v3 v3.2025041.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
goauthentik.io/api/v3 v3.2025024.9 h1:i3tbkyotE32ZpJ729BsPWTuLQUdtZ54Li4aP1amZzsM=
goauthentik.io/api/v3 v3.2025024.9/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -358,16 +358,16 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -376,8 +376,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -412,8 +412,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View File

@ -29,4 +29,4 @@ func UserAgent() string {
return fmt.Sprintf("authentik@%s", FullVersion())
}
const VERSION = "2025.4.1"
const VERSION = "2025.4.3"

View File

@ -83,7 +83,8 @@ if [[ "$1" == "server" ]]; then
run_authentik
elif [[ "$1" == "worker" ]]; then
set_mode "worker"
check_if_root "python -m manage worker"
shift
check_if_root "python -m manage worker $@"
elif [[ "$1" == "worker-status" ]]; then
wait_for_db
celery -A authentik.root.celery flower \

View File

@ -9,7 +9,7 @@
"version": "0.0.0",
"license": "MIT",
"devDependencies": {
"aws-cdk": "^2.1015.0",
"aws-cdk": "^2.1012.0",
"cross-env": "^7.0.3"
},
"engines": {
@ -17,9 +17,9 @@
}
},
"node_modules/aws-cdk": {
"version": "2.1015.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1015.0.tgz",
"integrity": "sha512-txd+yMVVybtLfiwT409+fahbP0SkiwhmQvQf6PVVYnWzDPSknxYlUNJHisHV4tJEcbHWn1QPsLmqqMT0bw8hBg==",
"version": "2.1012.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1012.0.tgz",
"integrity": "sha512-C6jSWkqP0hkY2Cs300VJHjspmTXDTMfB813kwZvRbd/OsKBfTBJBbYU16VoLAp1LVEOnQMf8otSlaSgzVF0X9A==",
"dev": true,
"license": "Apache-2.0",
"bin": {

View File

@ -10,7 +10,7 @@
"node": ">=20"
},
"devDependencies": {
"aws-cdk": "^2.1015.0",
"aws-cdk": "^2.1012.0",
"cross-env": "^7.0.3"
}
}

View File

@ -26,7 +26,7 @@ Parameters:
Description: authentik Docker image
AuthentikVersion:
Type: String
Default: 2025.4.1
Default: 2025.4.3
Description: authentik Docker image tag
AuthentikServerCPU:
Type: Number

Binary file not shown.

View File

@ -12,8 +12,8 @@
# tmassimi, 2024
# Marc Schmitt, 2024
# albanobattistella <albanobattistella@gmail.com>, 2024
# Matteo Piccina <altermatte@gmail.com>, 2025
# Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025
# Matteo Piccina <altermatte@gmail.com>, 2025
#
#, fuzzy
msgid ""
@ -22,7 +22,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-23 09:00+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025\n"
"Last-Translator: Matteo Piccina <altermatte@gmail.com>, 2025\n"
"Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -383,7 +383,7 @@ msgstr "Mappatura delle proprietà"
#: authentik/core/models.py
msgid "session data"
msgstr "dati sessione"
msgstr ""
#: authentik/core/models.py
msgid "Session"
@ -509,7 +509,7 @@ msgstr ""
#: authentik/enterprise/policies/unique_password/models.py
msgid "Number of passwords to check against."
msgstr "Numero di password da verificare."
msgstr ""
#: authentik/enterprise/policies/unique_password/models.py
#: authentik/policies/password/models.py
@ -519,19 +519,18 @@ msgstr "Password non impostata nel contesto"
#: authentik/enterprise/policies/unique_password/models.py
msgid "This password has been used previously. Please choose a different one."
msgstr ""
"Questa password è già stata utilizzata in precedenza. Scegline una diversa."
#: authentik/enterprise/policies/unique_password/models.py
msgid "Password Uniqueness Policy"
msgstr "Politica di unicità della password"
msgstr ""
#: authentik/enterprise/policies/unique_password/models.py
msgid "Password Uniqueness Policies"
msgstr "Criteri di unicità delle password"
msgstr ""
#: authentik/enterprise/policies/unique_password/models.py
msgid "User Password History"
msgstr "Cronologia password utente"
msgstr ""
#: authentik/enterprise/policy.py
msgid "Enterprise required to access this feature."
@ -2204,7 +2203,7 @@ msgstr "Ruoli"
#: authentik/rbac/models.py
msgid "Initial Permissions"
msgstr "Permessi Iniziali"
msgstr ""
#: authentik/rbac/models.py
msgid "System permission"
@ -2459,9 +2458,6 @@ msgid ""
"attribute. This allows nested group resolution on systems like FreeIPA and "
"Active Directory"
msgstr ""
"Cerca l'appartenenza al gruppo in base a un attributo utente anziché a un "
"attributo di gruppo. Questo consente la risoluzione di gruppi nidificati su "
"sistemi come FreeIPA e Active Directory."
#: authentik/sources/ldap/models.py
msgid "LDAP Source"
@ -2481,19 +2477,19 @@ msgstr "Mappature delle proprietà della sorgente LDAP"
#: authentik/sources/ldap/models.py
msgid "User LDAP Source Connection"
msgstr "Connessione Sorgente LDAP Utente"
msgstr ""
#: authentik/sources/ldap/models.py
msgid "User LDAP Source Connections"
msgstr "Connessioni Sorgente LDAP Utente"
msgstr ""
#: authentik/sources/ldap/models.py
msgid "Group LDAP Source Connection"
msgstr "Connessione Sorgente LDAP Gruppo"
msgstr ""
#: authentik/sources/ldap/models.py
msgid "Group LDAP Source Connections"
msgstr "Connessioni Sorgente LDAP Gruppo"
msgstr ""
#: authentik/sources/ldap/signals.py
msgid "Password does not match Active Directory Complexity."
@ -2505,11 +2501,11 @@ msgstr "Nessun token ricevuto."
#: authentik/sources/oauth/models.py
msgid "HTTP Basic Authentication"
msgstr "HTTP Basic Authentication"
msgstr ""
#: authentik/sources/oauth/models.py
msgid "Include the client ID and secret as request parameters"
msgstr "Includi il client ID e il segreto come parametri di richiesta"
msgstr ""
#: authentik/sources/oauth/models.py
msgid "Request Token URL"
@ -2556,8 +2552,6 @@ msgid ""
"How to perform authentication during an authorization_code token request "
"flow"
msgstr ""
"Come eseguire l'autenticazione durante un flusso di richiesta del token "
"authorization_code"
#: authentik/sources/oauth/models.py
msgid "OAuth Source"
@ -3490,9 +3484,6 @@ msgid ""
"Show the user the 'Remember me on this device' toggle, allowing repeat users"
" to skip straight to entering their password."
msgstr ""
"Mostra all'utente il pulsante \"Ricordami su questo dispositivo\", "
"consentendo agli utenti abituali di passare direttamente all'inserimento "
"della password."
#: authentik/stages/identification/models.py
msgid "Optional enrollment flow, which is linked at the bottom of the page."
@ -3882,11 +3873,11 @@ msgstr ""
#: authentik/tenants/models.py
msgid "Reputation cannot decrease lower than this value. Zero or negative."
msgstr "La reputazione non può scendere sotto questo valore. Zero o negativo."
msgstr ""
#: authentik/tenants/models.py
msgid "Reputation cannot increase higher than this value. Zero or positive."
msgstr "La reputazione non può superare questo valore. Zero o positivo."
msgstr ""
#: authentik/tenants/models.py
msgid "The option configures the footer links on the flow executor pages."

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

538
package-lock.json generated
View File

@ -1,546 +1,12 @@
{
"name": "@goauthentik/authentik",
"version": "2025.4.0",
"version": "2025.2.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@goauthentik/authentik",
"version": "2025.4.0",
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"prettier": "^3.3.3",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-packagejson": "^2.5.10",
"typescript": "^5.6.2"
}
},
"node_modules/@babel/code-frame": {
"version": "7.26.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
"integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.25.9",
"js-tokens": "^4.0.0",
"picocolors": "^1.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/generator": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz",
"integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.27.0",
"@babel/types": "^7.27.0",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^3.0.2"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
"integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.27.0"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/template": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
"integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
"@babel/parser": "^7.27.0",
"@babel/types": "^7.27.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz",
"integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
"@babel/generator": "^7.27.0",
"@babel/parser": "^7.27.0",
"@babel/template": "^7.27.0",
"@babel/types": "^7.27.0",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/types": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
"integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.25.9",
"@babel/helper-validator-identifier": "^7.25.9"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@pkgr/core": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.2.tgz",
"integrity": "sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/unts"
}
},
"node_modules/@trivago/prettier-plugin-sort-imports": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-5.2.2.tgz",
"integrity": "sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@babel/generator": "^7.26.5",
"@babel/parser": "^7.26.7",
"@babel/traverse": "^7.26.7",
"@babel/types": "^7.26.7",
"javascript-natural-sort": "^0.7.1",
"lodash": "^4.17.21"
},
"engines": {
"node": ">18.12"
},
"peerDependencies": {
"@vue/compiler-sfc": "3.x",
"prettier": "2.x - 3.x",
"prettier-plugin-svelte": "3.x",
"svelte": "4.x || 5.x"
},
"peerDependenciesMeta": {
"@vue/compiler-sfc": {
"optional": true
},
"prettier-plugin-svelte": {
"optional": true
},
"svelte": {
"optional": true
}
}
},
"node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/detect-indent": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-7.0.1.tgz",
"integrity": "sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.20"
}
},
"node_modules/detect-newline": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-4.0.1.tgz",
"integrity": "sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/fdir": {
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/get-stdin": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz",
"integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/git-hooks-list": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-3.2.0.tgz",
"integrity": "sha512-ZHG9a1gEhUMX1TvGrLdyWb9kDopCBbTnI8z4JgRMYxsijWipgjSEYoPWqBuIB0DnRnvqlQSEeVmzpeuPm7NdFQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/fisker/git-hooks-list?sponsor=1"
}
},
"node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/is-plain-obj": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
"integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/javascript-natural-sort": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==",
"dev": true,
"license": "MIT"
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
"dev": true,
"license": "MIT",
"bin": {
"jsesc": "bin/jsesc"
},
"engines": {
"node": ">=6"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true,
"license": "MIT"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-plugin-organize-imports": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz",
"integrity": "sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"prettier": ">=2.0",
"typescript": ">=2.9",
"vue-tsc": "^2.1.0"
},
"peerDependenciesMeta": {
"vue-tsc": {
"optional": true
}
}
},
"node_modules/prettier-plugin-packagejson": {
"version": "2.5.10",
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.10.tgz",
"integrity": "sha512-LUxATI5YsImIVSaaLJlJ3aE6wTD+nvots18U3GuQMJpUyClChaZlQrqx3dBnbhF20OnKWZyx8EgyZypQtBDtgQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"sort-package-json": "2.15.1",
"synckit": "0.9.2"
},
"peerDependencies": {
"prettier": ">= 1.16.0"
},
"peerDependenciesMeta": {
"prettier": {
"optional": true
}
}
},
"node_modules/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/sort-object-keys": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-1.1.3.tgz",
"integrity": "sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==",
"dev": true,
"license": "MIT"
},
"node_modules/sort-package-json": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-2.15.1.tgz",
"integrity": "sha512-9x9+o8krTT2saA9liI4BljNjwAbvUnWf11Wq+i/iZt8nl2UGYnf3TH5uBydE7VALmP7AGwlfszuEeL8BDyb0YA==",
"dev": true,
"license": "MIT",
"dependencies": {
"detect-indent": "^7.0.1",
"detect-newline": "^4.0.0",
"get-stdin": "^9.0.0",
"git-hooks-list": "^3.0.0",
"is-plain-obj": "^4.1.0",
"semver": "^7.6.0",
"sort-object-keys": "^1.1.3",
"tinyglobby": "^0.2.9"
},
"bin": {
"sort-package-json": "cli.js"
}
},
"node_modules/synckit": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
"integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.1.0",
"tslib": "^2.6.2"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/unts"
}
},
"node_modules/tinyglobby": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
"integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.4.4",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD"
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
"version": "2025.2.1"
}
}
}

View File

@ -1,15 +1,5 @@
{
"name": "@goauthentik/authentik",
"version": "2025.4.1",
"private": true,
"type": "module",
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"prettier": "^3.3.3",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-packagejson": "^2.5.10",
"typescript": "^5.6.2"
},
"workspaces": [],
"prettier": "./packages/prettier-config/index.js"
"version": "2025.4.3",
"private": true
}

View File

@ -1,12 +1,12 @@
{
"name": "@goauthentik/docusaurus-config",
"version": "1.0.6",
"version": "1.0.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@goauthentik/docusaurus-config",
"version": "1.0.6",
"version": "1.0.5",
"license": "MIT",
"dependencies": {
"deepmerge-ts": "^7.1.5",

View File

@ -1,6 +1,6 @@
{
"name": "@goauthentik/docusaurus-config",
"version": "1.0.6",
"version": "1.0.5",
"description": "authentik's Docusaurus config",
"license": "MIT",
"scripts": {

View File

@ -2,3 +2,4 @@
This package contains utility scripts common to all TypeScript and JavaScript packages in the
`@goauthentik` monorepo.

View File

@ -1,9 +1,8 @@
/**
* @file Constants for JavaScript and TypeScript files.
*
*/
/// <reference types="../../types/global.js" />
/**
* The current Node.js environment, defaulting to "development" when not set.
*
@ -13,4 +12,6 @@
* ensure that module tree-shaking works correctly.
*
*/
export const NodeEnvironment = process.env.NODE_ENV || "development";
export const NodeEnvironment = /** @type {'development' | 'production'} */ (
process.env.NODE_ENV || "development"
);

View File

@ -1,7 +1,4 @@
/// <reference types="./types/global.js" />
export * from "./paths.js";
export * from "./constants.js";
export * from "./build.js";
export * from "./version.js";
export * from "./scripting.js";

View File

@ -0,0 +1,19 @@
{
"name": "@goauthentik/monorepo",
"version": "1.0.0",
"description": "Utilities for the authentik monorepo.",
"private": true,
"license": "MIT",
"type": "module",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./index.js",
"types": "./out/index.d.ts"
}
},
"types": "./out/index.d.ts",
"engines": {
"node": ">=20.11"
}
}

View File

@ -0,0 +1,30 @@
import { createRequire } from "node:module";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
/**
* @typedef {'~authentik'} MonoRepoRoot
*/
/**
* The root of the authentik monorepo.
*/
export const MonoRepoRoot = /** @type {MonoRepoRoot} */ (resolve(__dirname, "..", ".."));
const require = createRequire(import.meta.url);
/**
* Resolve a package name to its location in the monorepo to the single node_modules directory.
* @param {string} packageName
* @returns {string} The resolved path to the package.
* @throws {Error} If the package cannot be resolved.
*/
export function resolvePackage(packageName) {
const packageJSONPath = require.resolve(join(packageName, "package.json"), {
paths: [MonoRepoRoot],
});
return dirname(packageJSONPath);
}

View File

View File

@ -1,6 +1,6 @@
import { execSync } from "node:child_process";
import PackageJSON from "../../../package.json" with { type: "json" };
import PackageJSON from "../../package.json" with { type: "json" };
import { MonoRepoRoot } from "./paths.js";
/**

View File

@ -1,116 +1,104 @@
[project]
name = "authentik"
version = "2025.4.1"
version = "2025.4.3"
description = ""
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
requires-python = "==3.13.*"
requires-python = "==3.12.*"
dependencies = [
"argon2-cffi==23.1.0",
"celery==5.5.2",
"channels==4.2.2",
"channels-redis==4.2.1",
"cryptography==44.0.3",
"dacite==1.9.2",
"deepmerge==2.0",
"defusedxml==0.7.1",
"django==5.1.9",
"django-countries==7.6.1",
"django-cte==1.3.3",
"django-filter==25.1",
"django-guardian<3.0.0",
"django-model-utils==5.0.0",
"django-pglock==1.7.2",
"django-prometheus==2.3.1",
"django-redis==5.4.0",
"django-storages[s3]==1.14.6",
"django-tenants==3.7.0",
"djangorestframework==3.16.0",
"djangorestframework-guardian==0.3.0",
"docker==7.1.0",
"drf-orjson-renderer==1.7.3",
"drf-spectacular==0.28.0",
"dumb-init==1.2.5.post1",
"duo-client==5.5.0",
"fido2==1.2.0",
"flower==2.0.1",
"geoip2==5.1.0",
"geopy==2.4.1",
"google-api-python-client==2.169.0",
"gssapi==1.9.0",
"gunicorn==23.0.0",
"jsonpatch==1.33",
"jwcrypto==1.5.6",
"kubernetes==32.0.1",
"ldap3==2.9.1",
"lxml==5.4.0",
"msgraph-sdk==1.30.0",
"opencontainers==0.0.14",
"packaging==25.0",
"paramiko==3.5.1",
"psycopg[c,pool]==3.2.9",
"pydantic==2.11.4",
"pydantic-scim==0.0.8",
"pyjwt==2.10.1",
"pyrad==2.4",
"python-kadmin-rs==0.6.0",
"pyyaml==6.0.2",
"requests-oauthlib==2.0.0",
"scim2-filter-parser==0.7.0",
"sentry-sdk==2.28.0",
"service-identity==24.2.0",
"setproctitle==1.3.6",
"structlog==25.3.0",
"swagger-spec-validator==3.0.4",
"tenant-schemas-celery==4.0.1",
"twilio==9.6.1",
"ua-parser==1.0.1",
"unidecode==1.4.0",
"urllib3<3",
"uvicorn[standard]==0.34.2",
"watchdog==6.0.0",
"webauthn==2.5.2",
"wsproto==1.2.0",
"xmlsec==1.3.15",
"zxcvbn==4.5.0",
"argon2-cffi",
"celery",
"channels",
"channels-redis",
"cryptography",
"dacite",
"deepmerge",
"defusedxml",
"django",
"django-countries",
"django-cte",
"django-filter",
"django-guardian",
"django-model-utils",
"django-pglock",
"django-prometheus",
"django-redis",
"django-storages[s3]",
"django-tenants",
"djangorestframework",
"djangorestframework-guardian",
"docker",
"drf-orjson-renderer",
"drf-spectacular",
"dumb-init",
"duo-client",
"fido2",
"flower",
"geoip2",
"geopy",
"google-api-python-client",
"gssapi",
"gunicorn",
"jsonpatch",
"jwcrypto",
"kubernetes",
"ldap3",
"lxml",
"msgraph-sdk",
"opencontainers",
"packaging",
"paramiko",
"psycopg[c, pool]",
"pydantic",
"pydantic-scim",
"pyjwt",
"pyrad",
"python-kadmin-rs ==0.6.0",
"pyyaml",
"requests-oauthlib",
"scim2-filter-parser",
"sentry-sdk",
"service_identity",
"setproctitle",
"structlog",
"swagger-spec-validator",
"tenant-schemas-celery",
"twilio",
"ua-parser",
"unidecode",
"urllib3 <3",
"uvicorn[standard]",
"watchdog",
"webauthn",
"wsproto",
"xmlsec <= 1.3.14",
"zxcvbn",
]
[dependency-groups]
dev = [
"aws-cdk-lib==2.188.0",
"bandit==1.8.3",
"black==25.1.0",
"bump2version==1.0.1",
"channels[daphne]==4.2.2",
"codespell==2.4.1",
"colorama==0.4.6",
"constructs==10.4.2",
"coverage[toml]==7.8.0",
"debugpy==1.8.14",
"drf-jsonschema-serializer==3.0.0",
"freezegun==1.5.1",
"importlib-metadata==8.6.1",
"k5test==0.10.4",
"pdoc==15.0.3",
"pytest==8.3.5",
"pytest-django==4.11.1",
"pytest-github-actions-annotate-failures==0.3.0",
"pytest-randomly==3.16.0",
"pytest-timeout==2.4.0",
"requests-mock==1.12.1",
"ruff==0.11.9",
"selenium==4.32.0",
]
[tool.uv]
no-binary-package = [
# This differs from the no-binary packages in the Dockerfile. This is due to the fact
# that these packages are built from source for different reasons than cryptography and kadmin.
# These packages are built from source to link against the libxml2 on the system which is
# required for functionality and to stay up-to-date on both libraries.
# The other packages specified in the dockerfile are compiled from source to link against the
# correct FIPS OpenSSL libraries
"lxml",
"xmlsec",
"aws-cdk-lib",
"bandit",
"black",
"bump2version",
"channels[daphne]",
"codespell",
"colorama",
"constructs",
"coverage[toml]",
"debugpy",
"drf-jsonschema-serializer",
"freezegun",
"importlib-metadata",
"k5test",
"pdoc",
"pytest",
"pytest-django",
"pytest-github-actions-annotate-failures",
"pytest-randomly",
"pytest-timeout",
"requests-mock",
"ruff",
"selenium",
]
[tool.uv.sources]
@ -155,12 +143,12 @@ ignore-words = ".github/codespell-words.txt"
[tool.black]
line-length = 100
target-version = ['py313']
target-version = ['py312']
exclude = 'node_modules'
[tool.ruff]
line-length = 100
target-version = "py313"
target-version = "py312"
exclude = ["**/migrations/**", "**/node_modules/**"]
[tool.ruff.lint]

View File

@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: authentik
version: 2025.4.1
version: 2025.4.3
description: Making authentication simple.
contact:
email: hello@goauthentik.io

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,12 +0,0 @@
import socket
from os import environ
IS_CI = "CI" in environ
RETRIES = int(environ.get("RETRIES", "3")) if IS_CI else 1
def get_local_ip() -> str:
"""Get the local machine's IP"""
hostname = socket.gethostname()
ip_addr = socket.gethostbyname(hostname)
return ip_addr

View File

@ -1,190 +0,0 @@
"""authentik e2e testing utilities"""
# This file cannot import anything django or anything that will load django
import json
from sys import stderr
from time import sleep
from typing import TYPE_CHECKING
from unittest.case import TestCase
from urllib.parse import urlencode
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.urls import reverse
from selenium import webdriver
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.remote.command import Command
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.wait import WebDriverWait
from structlog.stdlib import get_logger
from tests import IS_CI, RETRIES, get_local_ip
from tests.websocket import BaseWebsocketTestCase
if TYPE_CHECKING:
from authentik.core.models import User
class BaseSeleniumTestCase(TestCase):
"""Mixin which adds helpers for spinning up Selenium"""
host = get_local_ip()
wait_timeout: int
user: "User"
def setUp(self):
if IS_CI:
print("::group::authentik Logs", file=stderr)
from django.apps import apps
from authentik.core.tests.utils import create_test_admin_user
apps.get_app_config("authentik_tenants").ready()
self.wait_timeout = 60
self.driver = self._get_driver()
self.driver.implicitly_wait(30)
self.wait = WebDriverWait(self.driver, self.wait_timeout)
self.logger = get_logger()
self.user = create_test_admin_user()
super().setUp()
def _get_driver(self) -> WebDriver:
count = 0
try:
opts = webdriver.ChromeOptions()
opts.add_argument("--disable-search-engine-choice-screen")
return webdriver.Chrome(options=opts)
except WebDriverException:
pass
while count < RETRIES:
try:
driver = webdriver.Remote(
command_executor="http://localhost:4444/wd/hub",
options=webdriver.ChromeOptions(),
)
driver.maximize_window()
return driver
except WebDriverException:
count += 1
raise ValueError(f"Webdriver failed after {RETRIES}.")
def tearDown(self):
if IS_CI:
print("::endgroup::", file=stderr)
super().tearDown()
if IS_CI:
print("::group::Browser logs")
# Very verbose way to get browser logs
# https://github.com/SeleniumHQ/selenium/pull/15641
# for some reason this removes the `get_log` API from Remote Webdriver
# and only keeps it on the local Chrome web driver, even when using
# a remote chrome driver...? (nvm the fact this was released as a minor version)
for line in self.driver.execute(Command.GET_LOG, {"type": "browser"})["value"]:
print(line["message"])
if IS_CI:
print("::endgroup::")
self.driver.quit()
def wait_for_url(self, desired_url):
"""Wait until URL is `desired_url`."""
self.wait.until(
lambda driver: driver.current_url == desired_url,
f"URL {self.driver.current_url} doesn't match expected URL {desired_url}",
)
def url(self, view, query: dict | None = None, **kwargs) -> str:
"""reverse `view` with `**kwargs` into full URL using live_server_url"""
url = self.live_server_url + reverse(view, kwargs=kwargs)
if query:
return url + "?" + urlencode(query)
return url
def if_user_url(self, path: str | None = None) -> str:
"""same as self.url() but show URL in shell"""
url = self.url("authentik_core:if-user")
if path:
return f"{url}#{path}"
return url
def get_shadow_root(
self, selector: str, container: WebElement | WebDriver | None = None
) -> WebElement:
"""Get shadow root element's inner shadowRoot"""
if not container:
container = self.driver
shadow_root = container.find_element(By.CSS_SELECTOR, selector)
element = self.driver.execute_script("return arguments[0].shadowRoot", shadow_root)
return element
def shady_dom(self) -> WebElement:
class wrapper:
def __init__(self, container: WebDriver):
self.container = container
def find_element(self, by: str, selector: str) -> WebElement:
return self.container.execute_script(
"return document.__shady_native_querySelector(arguments[0])", selector
)
return wrapper(self.driver)
def login(self, shadow_dom=True):
"""Do entire login flow"""
if shadow_dom:
flow_executor = self.get_shadow_root("ak-flow-executor")
identification_stage = self.get_shadow_root("ak-stage-identification", flow_executor)
else:
flow_executor = self.shady_dom()
identification_stage = self.shady_dom()
wait = WebDriverWait(identification_stage, self.wait_timeout)
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "input[name=uidField]")))
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uidField]").click()
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uidField]").send_keys(
self.user.username
)
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uidField]").send_keys(
Keys.ENTER
)
if shadow_dom:
flow_executor = self.get_shadow_root("ak-flow-executor")
password_stage = self.get_shadow_root("ak-stage-password", flow_executor)
else:
flow_executor = self.shady_dom()
password_stage = self.shady_dom()
wait = WebDriverWait(password_stage, self.wait_timeout)
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "input[name=password]")))
password_stage.find_element(By.CSS_SELECTOR, "input[name=password]").send_keys(
self.user.username
)
password_stage.find_element(By.CSS_SELECTOR, "input[name=password]").send_keys(Keys.ENTER)
sleep(1)
def assert_user(self, expected_user: "User"):
"""Check users/me API and assert it matches expected_user"""
from authentik.core.api.users import UserSerializer
self.driver.get(self.url("authentik_api:user-me") + "?format=json")
user_json = self.driver.find_element(By.CSS_SELECTOR, "pre").text
user = UserSerializer(data=json.loads(user_json)["user"])
user.is_valid()
self.assertEqual(user["username"].value, expected_user.username)
self.assertEqual(user["name"].value, expected_user.name)
self.assertEqual(user["email"].value, expected_user.email)
class SeleniumTestCase(BaseSeleniumTestCase, StaticLiveServerTestCase):
"""Test case which spins up a selenium instance and a HTTP-only test server"""
class WebsocketSeleniumTestCase(BaseSeleniumTestCase, BaseWebsocketTestCase):
"""Test case which spins up a selenium instance and a Websocket/HTTP test server"""

View File

@ -1,48 +0,0 @@
"""authentik e2e testing utilities"""
from collections.abc import Callable
from functools import wraps
from django.test.testcases import TransactionTestCase
from selenium.common.exceptions import NoSuchElementException, TimeoutException, WebDriverException
from structlog.stdlib import get_logger
from tests import RETRIES
def retry(max_retires=RETRIES, exceptions=None):
"""Retry test multiple times. Default to catching Selenium Timeout Exception"""
if not exceptions:
exceptions = [WebDriverException, TimeoutException, NoSuchElementException]
logger = get_logger()
def retry_actual(func: Callable):
"""Retry test multiple times"""
count = 1
@wraps(func)
def wrapper(self: TransactionTestCase, *args, **kwargs):
"""Run test again if we're below max_retries, including tearDown and
setUp. Otherwise raise the error"""
nonlocal count
try:
return func(self, *args, **kwargs)
except tuple(exceptions) as exc:
count += 1
if count > max_retires:
logger.debug("Exceeded retry count", exc=exc, test=self)
raise exc
logger.debug("Retrying on error", exc=exc, test=self)
self.tearDown()
self._post_teardown()
self._pre_setup()
self.setUp()
return wrapper(self, *args, **kwargs)
return wrapper
return retry_actual

View File

@ -1,139 +0,0 @@
"""Docker testing helpers"""
import os
from time import sleep
from typing import TYPE_CHECKING, Any
from unittest.case import TestCase
from docker import DockerClient, from_env
from docker.errors import DockerException
from docker.models.containers import Container
from docker.models.networks import Network
from authentik.lib.generators import generate_id
from tests import IS_CI
if TYPE_CHECKING:
from authentik.outposts.models import Outpost
def get_docker_tag() -> str:
"""Get docker-tag based off of CI variables"""
env_pr_branch = "GITHUB_HEAD_REF"
default_branch = "GITHUB_REF"
branch_name = os.environ.get(default_branch, "main")
if os.environ.get(env_pr_branch, "") != "":
branch_name = os.environ[env_pr_branch]
branch_name = branch_name.replace("refs/heads/", "").replace("/", "-")
return f"gh-{branch_name}"
class DockerTestCase(TestCase):
"""Mixin for dealing with containers"""
max_healthcheck_attempts = 30
__client: DockerClient
__network: Network
__label_id = generate_id()
def setUp(self) -> None:
self.__client = from_env()
self.__network = self.docker_client.networks.create(
name=f"authentik-test-{self.__label_id}"
)
super().setUp()
@property
def docker_client(self) -> DockerClient:
return self.__client
@property
def docker_network(self) -> Network:
return self.__network
@property
def docker_labels(self) -> dict:
return {"io.goauthentik.test": self.__label_id}
def get_container_image(self, base: str) -> str:
"""Try to pull docker image based on git branch, fallback to main if not found."""
image = f"{base}:gh-main"
if not IS_CI:
return image
try:
branch_image = f"{base}:{get_docker_tag()}"
self.docker_client.images.pull(branch_image)
return branch_image
except DockerException:
self.docker_client.images.pull(image)
return image
def run_container(self, **specs: dict[str, Any]) -> Container:
if "network_mode" not in specs:
specs["network"] = self.__network.name
specs["labels"] = self.docker_labels
specs["detach"] = True
if hasattr(self, "live_server_url"):
specs.setdefault("environment", {})
specs["environment"]["AUTHENTIK_HOST"] = self.live_server_url
container = self.docker_client.containers.run(**specs)
container.reload()
state = container.attrs.get("State", {})
if "Health" not in state:
return container
self.wait_for_container(container)
return container
def output_container_logs(self, container: Container | None = None):
"""Output the container logs to our STDOUT"""
if IS_CI:
image = container.image
tags = image.tags[0] if len(image.tags) > 0 else str(image)
print(f"::group::Container logs - {tags}")
for log in container.logs().decode().split("\n"):
print(log)
if IS_CI:
print("::endgroup::")
def tearDown(self):
containers: list[Container] = self.docker_client.containers.list(
filters={"label": ",".join(f"{x}={y}" for x, y in self.docker_labels.items())}
)
for container in containers:
self.output_container_logs(container)
try:
container.stop()
except DockerException:
pass
try:
container.remove(force=True)
except DockerException:
pass
self.__network.remove()
super().tearDown()
def wait_for_container(self, container: Container):
"""Check that container is health"""
attempt = 0
while attempt < self.max_healthcheck_attempts:
container.reload()
status = container.attrs.get("State", {}).get("Health", {}).get("Status")
if status == "healthy":
return container
attempt += 1
sleep(0.5)
self.failureException("Container failed to start")
def wait_for_outpost(self, outpost: "Outpost"):
# Wait until outpost healthcheck succeeds
attempt = 0
while attempt < self.max_healthcheck_attempts:
if len(outpost.state) > 0:
state = outpost.state[0]
if state.last_seen:
return
attempt += 1
sleep(0.5)
self.failureException("Outpost failed to become healthy")

View File

@ -1,12 +1,12 @@
services:
chrome:
image: docker.io/selenium/standalone-chrome:136.0
image: docker.io/selenium/standalone-chrome:122.0
volumes:
- /dev/shm:/dev/shm
network_mode: host
restart: always
mailpit:
image: docker.io/axllent/mailpit:v1.24.2
image: docker.io/axllent/mailpit:v1.6.5
ports:
- 1025:1025
- 8025:8025

View File

@ -18,12 +18,10 @@ from authentik.stages.authenticator_static.models import (
StaticToken,
)
from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage, TOTPDevice
from tests.browser import SeleniumTestCase
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.e2e.utils import SeleniumTestCase, retry
class TestFlowsAuthenticator(DockerTestCase, SeleniumTestCase):
class TestFlowsAuthenticator(SeleniumTestCase):
"""test flow with otp stages"""
@retry()

View File

@ -11,12 +11,10 @@ from authentik.core.models import User
from authentik.flows.models import Flow
from authentik.lib.config import CONFIG
from authentik.stages.identification.models import IdentificationStage
from tests.browser import SeleniumTestCase
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.e2e.utils import SeleniumTestCase, retry
class TestFlowsEnroll(DockerTestCase, SeleniumTestCase):
class TestFlowsEnroll(SeleniumTestCase):
"""Test Enroll flow"""
@retry()

View File

@ -1,21 +1,12 @@
"""test default login flow"""
from authentik.blueprints.tests import apply_blueprint
from authentik.flows.models import Flow
from tests.browser import SeleniumTestCase
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.e2e.utils import SeleniumTestCase, retry
class TestFlowsLogin(DockerTestCase, SeleniumTestCase):
class TestFlowsLogin(SeleniumTestCase):
"""test default login flow"""
def tearDown(self):
# Reset authentication flow's compatibility mode; we need to do this as its
# not specified in the blueprint
Flow.objects.filter(slug="default-authentication-flow").update(compatibility_mode=False)
return super().tearDown()
@retry()
@apply_blueprint(
"default/flow-default-authentication-flow.yaml",
@ -32,21 +23,3 @@ class TestFlowsLogin(DockerTestCase, SeleniumTestCase):
self.login()
self.wait_for_url(self.if_user_url("/library"))
self.assert_user(self.user)
@retry()
@apply_blueprint(
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
def test_login_compatibility_mode(self):
"""test default login flow with compatibility mode enabled"""
Flow.objects.filter(slug="default-authentication-flow").update(compatibility_mode=True)
self.driver.get(
self.url(
"authentik_core:if-flow",
flow_slug="default-authentication-flow",
)
)
self.login(shadow_dom=False)
self.wait_for_url(self.if_user_url("/library"))
self.assert_user(self.user)

View File

@ -1,53 +0,0 @@
"""test default login (using SFE interface) flow"""
from time import sleep
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from authentik.blueprints.tests import apply_blueprint
from tests.browser import SeleniumTestCase
from tests.decorators import retry
from tests.docker import DockerTestCase
class TestFlowsLoginSFE(DockerTestCase, SeleniumTestCase):
"""test default login flow"""
def login(self):
"""Do entire login flow adjusted for SFE"""
flow_executor = self.driver.find_element(By.ID, "flow-sfe-container")
identification_stage = flow_executor.find_element(By.ID, "ident-form")
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uid_field]").click()
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uid_field]").send_keys(
self.user.username
)
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uid_field]").send_keys(
Keys.ENTER
)
password_stage = flow_executor.find_element(By.ID, "password-form")
password_stage.find_element(By.CSS_SELECTOR, "input[name=password]").send_keys(
self.user.username
)
password_stage.find_element(By.CSS_SELECTOR, "input[name=password]").send_keys(Keys.ENTER)
sleep(1)
@retry()
@apply_blueprint(
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
def test_login(self):
"""test default login flow"""
self.driver.get(
self.url(
"authentik_core:if-flow",
flow_slug="default-authentication-flow",
query={"sfe": True},
)
)
self.login()
self.wait_for_url(self.if_user_url("/library"))
self.assert_user(self.user)

View File

@ -13,12 +13,10 @@ from authentik.flows.models import Flow
from authentik.lib.config import CONFIG
from authentik.lib.generators import generate_id
from authentik.stages.identification.models import IdentificationStage
from tests.browser import SeleniumTestCase
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.e2e.utils import SeleniumTestCase, retry
class TestFlowsRecovery(DockerTestCase, SeleniumTestCase):
class TestFlowsRecovery(SeleniumTestCase):
"""Test Recovery flow"""
def initial_stages(self, user: User):

View File

@ -8,12 +8,10 @@ from authentik.core.models import User
from authentik.flows.models import Flow, FlowDesignation
from authentik.lib.generators import generate_key
from authentik.stages.password.models import PasswordStage
from tests.browser import SeleniumTestCase
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.e2e.utils import SeleniumTestCase, retry
class TestFlowsStageSetup(DockerTestCase, SeleniumTestCase):
class TestFlowsStageSetup(SeleniumTestCase):
"""test stage setup flows"""
@retry()

View File

@ -16,12 +16,10 @@ from authentik.lib.generators import generate_id
from authentik.outposts.apps import MANAGED_OUTPOST
from authentik.outposts.models import Outpost, OutpostConfig, OutpostType
from authentik.providers.ldap.models import APIAccessMode, LDAPProvider
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.websocket import WebsocketTestCase
from tests.e2e.utils import SeleniumTestCase, retry
class TestProviderLDAP(DockerTestCase, WebsocketTestCase):
class TestProviderLDAP(SeleniumTestCase):
"""LDAP and Outpost e2e tests"""
def start_ldap(self, outpost: Outpost):

View File

@ -18,12 +18,10 @@ from authentik.providers.oauth2.models import (
RedirectURI,
RedirectURIMatchingMode,
)
from tests.browser import SeleniumTestCase
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.e2e.utils import SeleniumTestCase, retry
class TestProviderOAuth2Github(DockerTestCase, SeleniumTestCase):
class TestProviderOAuth2Github(SeleniumTestCase):
"""test OAuth Provider flow"""
def setUp(self):

View File

@ -26,12 +26,10 @@ from authentik.providers.oauth2.models import (
RedirectURIMatchingMode,
ScopeMapping,
)
from tests.browser import SeleniumTestCase
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.e2e.utils import SeleniumTestCase, retry
class TestProviderOAuth2OAuth(DockerTestCase, SeleniumTestCase):
class TestProviderOAuth2OAuth(SeleniumTestCase):
"""test OAuth with OAuth Provider flow"""
def setUp(self):

View File

@ -26,12 +26,10 @@ from authentik.providers.oauth2.models import (
RedirectURIMatchingMode,
ScopeMapping,
)
from tests.browser import SeleniumTestCase
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.e2e.utils import SeleniumTestCase, retry
class TestProviderOAuth2OIDC(DockerTestCase, SeleniumTestCase):
class TestProviderOAuth2OIDC(SeleniumTestCase):
"""test OAuth with OpenID Provider flow"""
def setUp(self):

View File

@ -26,12 +26,10 @@ from authentik.providers.oauth2.models import (
RedirectURIMatchingMode,
ScopeMapping,
)
from tests.browser import SeleniumTestCase
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.e2e.utils import SeleniumTestCase, retry
class TestProviderOAuth2OIDCImplicit(DockerTestCase, SeleniumTestCase):
class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
"""test OAuth with OpenID Provider flow"""
def setUp(self):

View File

@ -3,8 +3,11 @@
from base64 import b64encode
from dataclasses import asdict
from json import loads
from sys import platform
from time import sleep
from unittest.case import skip, skipUnless
from channels.testing import ChannelsLiveServerTestCase
from jwt import decode
from selenium.webdriver.common.by import By
@ -15,13 +18,10 @@ from authentik.lib.generators import generate_id
from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostConfig, OutpostType
from authentik.outposts.tasks import outpost_connection_discovery
from authentik.providers.proxy.models import ProxyProvider
from tests.browser import SeleniumTestCase
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.websocket import WebsocketTestCase
from tests.e2e.utils import SeleniumTestCase, retry
class TestProviderProxy(DockerTestCase, SeleniumTestCase):
class TestProviderProxy(SeleniumTestCase):
"""Proxy and Outpost e2e tests"""
def setUp(self):
@ -37,41 +37,13 @@ class TestProviderProxy(DockerTestCase, SeleniumTestCase):
"""Start proxy container based on outpost created"""
self.run_container(
image=self.get_container_image("ghcr.io/goauthentik/dev-proxy"),
ports={"9000": "9000"},
environment={"AUTHENTIK_TOKEN": outpost.token.key},
ports={
"9000": "9000",
},
environment={
"AUTHENTIK_TOKEN": outpost.token.key,
},
)
self.wait_for_outpost(outpost)
def _prepare(self):
# set additionalHeaders to test later
self.user.attributes["additionalHeaders"] = {"X-Foo": "bar"}
self.user.save()
proxy: ProxyProvider = ProxyProvider.objects.create(
name=generate_id(),
authorization_flow=Flow.objects.get(
slug="default-provider-authorization-implicit-consent"
),
invalidation_flow=Flow.objects.get(slug="default-provider-invalidation-flow"),
internal_host=f"http://{self.host}",
external_host="http://localhost:9000",
basic_auth_enabled=True,
basic_auth_user_attribute="basic-username",
basic_auth_password_attribute="basic-password", # nosec
)
# Ensure OAuth2 Params are set
proxy.set_oauth_defaults()
proxy.save()
# we need to create an application to actually access the proxy
Application.objects.create(name=generate_id(), slug=generate_id(), provider=proxy)
outpost: Outpost = Outpost.objects.create(
name=generate_id(),
type=OutpostType.PROXY,
)
outpost.providers.add(proxy)
outpost.build_user_permissions(outpost.user)
self.start_proxy(outpost)
@retry()
@apply_blueprint(
@ -89,7 +61,44 @@ class TestProviderProxy(DockerTestCase, SeleniumTestCase):
@reconcile_app("authentik_crypto")
def test_proxy_simple(self):
"""Test simple outpost setup with single provider"""
self._prepare()
# set additionalHeaders to test later
self.user.attributes["additionalHeaders"] = {"X-Foo": "bar"}
self.user.save()
proxy: ProxyProvider = ProxyProvider.objects.create(
name=generate_id(),
authorization_flow=Flow.objects.get(
slug="default-provider-authorization-implicit-consent"
),
invalidation_flow=Flow.objects.get(slug="default-provider-invalidation-flow"),
internal_host=f"http://{self.host}",
external_host="http://localhost:9000",
)
# Ensure OAuth2 Params are set
proxy.set_oauth_defaults()
proxy.save()
# we need to create an application to actually access the proxy
Application.objects.create(name=generate_id(), slug=generate_id(), provider=proxy)
outpost: Outpost = Outpost.objects.create(
name=generate_id(),
type=OutpostType.PROXY,
)
outpost.providers.add(proxy)
outpost.build_user_permissions(outpost.user)
self.start_proxy(outpost)
# Wait until outpost healthcheck succeeds
healthcheck_retries = 0
while healthcheck_retries < 50: # noqa: PLR2004
if len(outpost.state) > 0:
state = outpost.state[0]
if state.last_seen:
break
healthcheck_retries += 1
sleep(0.5)
sleep(5)
self.driver.get("http://localhost:9000/api")
self.login()
sleep(1)
@ -128,13 +137,49 @@ class TestProviderProxy(DockerTestCase, SeleniumTestCase):
@reconcile_app("authentik_crypto")
def test_proxy_basic_auth(self):
"""Test simple outpost setup with single provider"""
self._prepare()
# Setup basic auth
cred = generate_id()
attr = "basic-password" # nosec
self.user.attributes["basic-username"] = cred
self.user.attributes["basic-password"] = cred
self.user.attributes[attr] = cred
self.user.save()
proxy: ProxyProvider = ProxyProvider.objects.create(
name=generate_id(),
authorization_flow=Flow.objects.get(
slug="default-provider-authorization-implicit-consent"
),
invalidation_flow=Flow.objects.get(slug="default-provider-invalidation-flow"),
internal_host=f"http://{self.host}",
external_host="http://localhost:9000",
basic_auth_enabled=True,
basic_auth_user_attribute="basic-username",
basic_auth_password_attribute=attr,
)
# Ensure OAuth2 Params are set
proxy.set_oauth_defaults()
proxy.save()
# we need to create an application to actually access the proxy
Application.objects.create(name=generate_id(), slug=generate_id(), provider=proxy)
outpost: Outpost = Outpost.objects.create(
name=generate_id(),
type=OutpostType.PROXY,
)
outpost.providers.add(proxy)
outpost.build_user_permissions(outpost.user)
self.start_proxy(outpost)
# Wait until outpost healthcheck succeeds
healthcheck_retries = 0
while healthcheck_retries < 50: # noqa: PLR2004
if len(outpost.state) > 0:
state = outpost.state[0]
if state.last_seen:
break
healthcheck_retries += 1
sleep(0.5)
sleep(5)
self.driver.get("http://localhost:9000/api")
self.login()
sleep(1)
@ -142,9 +187,9 @@ class TestProviderProxy(DockerTestCase, SeleniumTestCase):
full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text
body = loads(full_body_text)
self.assertEqual(body.get("headers").get("X-Authentik-Username"), [self.user.username])
self.assertEqual(body["headers"]["X-Authentik-Username"], [self.user.username])
auth_header = b64encode(f"{cred}:{cred}".encode()).decode()
self.assertEqual(body.get("headers").get("Authorization"), [f"Basic {auth_header}"])
self.assertEqual(body["headers"]["Authorization"], [f"Basic {auth_header}"])
self.driver.get("http://localhost:9000/outpost.goauthentik.io/sign_out")
sleep(2)
@ -154,7 +199,10 @@ class TestProviderProxy(DockerTestCase, SeleniumTestCase):
self.assertIn("You've logged out of", title)
class TestProviderProxyConnect(DockerTestCase, WebsocketTestCase):
# TODO: Fix flaky test
@skip("Flaky test")
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestProviderProxyConnect(ChannelsLiveServerTestCase):
"""Test Proxy connectivity over websockets"""
@retry(exceptions=[AssertionError])
@ -193,7 +241,14 @@ class TestProviderProxyConnect(DockerTestCase, WebsocketTestCase):
outpost.build_user_permissions(outpost.user)
# Wait until outpost healthcheck succeeds
self.wait_for_outpost(outpost)
healthcheck_retries = 0
while healthcheck_retries < 50: # noqa: PLR2004
if len(outpost.state) > 0:
state = outpost.state[0]
if state.last_seen and state.version:
break
healthcheck_retries += 1
sleep(0.5)
state = outpost.state
self.assertGreaterEqual(len(state), 1)

View File

@ -13,12 +13,10 @@ from authentik.flows.models import Flow
from authentik.lib.generators import generate_id
from authentik.outposts.models import Outpost, OutpostType
from authentik.providers.proxy.models import ProxyMode, ProxyProvider
from tests.browser import SeleniumTestCase
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.e2e.utils import SeleniumTestCase, retry
class TestProviderProxyForward(DockerTestCase, SeleniumTestCase):
class TestProviderProxyForward(SeleniumTestCase):
"""Proxy and Outpost e2e tests"""
def setUp(self):
@ -32,11 +30,14 @@ class TestProviderProxyForward(DockerTestCase, SeleniumTestCase):
"""Start proxy container based on outpost created"""
self.run_container(
image=self.get_container_image("ghcr.io/goauthentik/dev-proxy"),
ports={"9000": "9000"},
environment={"AUTHENTIK_TOKEN": outpost.token.key},
ports={
"9000": "9000",
},
environment={
"AUTHENTIK_TOKEN": outpost.token.key,
},
name="ak-test-outpost",
)
self.wait_for_outpost(outpost)
@apply_blueprint(
"default/flow-default-authentication-flow.yaml",
@ -76,6 +77,17 @@ class TestProviderProxyForward(DockerTestCase, SeleniumTestCase):
self.start_outpost(outpost)
# Wait until outpost healthcheck succeeds
healthcheck_retries = 0
while healthcheck_retries < 50: # noqa: PLR2004
if len(outpost.state) > 0:
state = outpost.state[0]
if state.last_seen:
break
healthcheck_retries += 1
sleep(0.5)
sleep(5)
@retry()
def test_traefik(self):
"""Test traefik"""

View File

@ -1,6 +1,7 @@
"""Radius e2e tests"""
from dataclasses import asdict
from time import sleep
from pyrad.client import Client
from pyrad.dictionary import Dictionary
@ -8,17 +9,14 @@ from pyrad.packet import AccessAccept, AccessReject, AccessRequest
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application, User
from authentik.core.tests.utils import create_test_user
from authentik.flows.models import Flow
from authentik.lib.generators import generate_id, generate_key
from authentik.outposts.models import Outpost, OutpostConfig, OutpostType
from authentik.providers.radius.models import RadiusProvider
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.websocket import WebsocketTestCase
from tests.e2e.utils import SeleniumTestCase, retry
class TestProviderRadius(DockerTestCase, WebsocketTestCase):
class TestProviderRadius(SeleniumTestCase):
"""Radius Outpost e2e tests"""
def setUp(self):
@ -30,13 +28,13 @@ class TestProviderRadius(DockerTestCase, WebsocketTestCase):
self.run_container(
image=self.get_container_image("ghcr.io/goauthentik/dev-radius"),
ports={"1812/udp": "1812/udp"},
environment={"AUTHENTIK_TOKEN": outpost.token.key},
environment={
"AUTHENTIK_TOKEN": outpost.token.key,
},
)
self.wait_for_outpost(outpost)
def _prepare(self) -> User:
"""prepare user, provider, app and container"""
self.user = create_test_user()
radius: RadiusProvider = RadiusProvider.objects.create(
name=generate_id(),
authorization_flow=Flow.objects.get(slug="default-authentication-flow"),
@ -52,6 +50,17 @@ class TestProviderRadius(DockerTestCase, WebsocketTestCase):
outpost.providers.add(radius)
self.start_radius(outpost)
# Wait until outpost healthcheck succeeds
healthcheck_retries = 0
while healthcheck_retries < 50: # noqa: PLR2004
if len(outpost.state) > 0:
state = outpost.state[0]
if state.last_seen:
break
healthcheck_retries += 1
sleep(0.5)
sleep(5)
return outpost
@retry()

View File

@ -14,12 +14,10 @@ from authentik.policies.expression.models import ExpressionPolicy
from authentik.policies.models import PolicyBinding
from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider
from authentik.sources.saml.processors.constants import SAML_BINDING_POST
from tests.browser import SeleniumTestCase
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.e2e.utils import SeleniumTestCase, retry
class TestProviderSAML(DockerTestCase, SeleniumTestCase):
class TestProviderSAML(SeleniumTestCase):
"""test SAML Provider flow"""
def setup_client(self, provider: SAMLProvider, force_post: bool = False):

View File

@ -11,12 +11,10 @@ from authentik.sources.ldap.models import LDAPSource, LDAPSourcePropertyMapping
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
from tests.browser import SeleniumTestCase
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.e2e.utils import SeleniumTestCase, retry
class TestSourceLDAPSamba(DockerTestCase, SeleniumTestCase):
class TestSourceLDAPSamba(SeleniumTestCase):
"""test LDAP Source"""
def setUp(self):

View File

@ -16,9 +16,7 @@ from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.types.registry import SourceType, registry
from authentik.sources.oauth.views.callback import OAuthCallback
from authentik.stages.identification.models import IdentificationStage
from tests.browser import SeleniumTestCase
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.e2e.utils import SeleniumTestCase, retry
class OAuth1Callback(OAuthCallback):
@ -50,7 +48,7 @@ class OAUth1Type(SourceType):
}
class TestSourceOAuth1(DockerTestCase, SeleniumTestCase):
class TestSourceOAuth1(SeleniumTestCase):
"""Test OAuth1 Source"""
def setUp(self) -> None:

View File

@ -16,12 +16,10 @@ from authentik.flows.models import Flow
from authentik.lib.generators import generate_id
from authentik.sources.oauth.models import OAuthSource
from authentik.stages.identification.models import IdentificationStage
from tests.browser import SeleniumTestCase
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.e2e.utils import SeleniumTestCase, retry
class TestSourceOAuth2(DockerTestCase, SeleniumTestCase):
class TestSourceOAuth2(SeleniumTestCase):
"""test OAuth Source flow"""
def setUp(self):

View File

@ -16,9 +16,7 @@ from authentik.flows.models import Flow
from authentik.lib.generators import generate_id
from authentik.sources.saml.models import SAMLBindingTypes, SAMLSource
from authentik.stages.identification.models import IdentificationStage
from tests.browser import SeleniumTestCase
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.e2e.utils import SeleniumTestCase, retry
IDP_CERT = """-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJALmVVuDWu4NYMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
@ -72,7 +70,7 @@ Sm75WXsflOxuTn08LbgGc4s=
-----END PRIVATE KEY-----"""
class TestSourceSAML(DockerTestCase, SeleniumTestCase):
class TestSourceSAML(SeleniumTestCase):
"""test SAML Source flow"""
def setUp(self):

View File

@ -8,14 +8,12 @@ from docker.types import Healthcheck
from authentik.lib.generators import generate_id
from authentik.lib.utils.http import get_http_session
from authentik.sources.scim.models import SCIMSource
from tests.browser import SeleniumTestCase
from tests.decorators import retry
from tests.docker import DockerTestCase
from tests.e2e.utils import SeleniumTestCase, retry
TEST_POLL_MAX = 25
class TestSourceSCIM(DockerTestCase, SeleniumTestCase):
class TestSourceSCIM(SeleniumTestCase):
"""test SCIM Source flow"""
def setUp(self):

311
tests/e2e/utils.py Normal file
View File

@ -0,0 +1,311 @@
"""authentik e2e testing utilities"""
import json
import os
import socket
from collections.abc import Callable
from functools import lru_cache, wraps
from os import environ
from sys import stderr
from time import sleep
from typing import Any
from unittest.case import TestCase
from urllib.parse import urlencode
from django.apps import apps
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.db import connection
from django.db.migrations.loader import MigrationLoader
from django.test.testcases import TransactionTestCase
from django.urls import reverse
from docker import DockerClient, from_env
from docker.errors import DockerException
from docker.models.containers import Container
from docker.models.networks import Network
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException, TimeoutException, WebDriverException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support.wait import WebDriverWait
from structlog.stdlib import get_logger
from authentik.core.api.users import UserSerializer
from authentik.core.models import User
from authentik.core.tests.utils import create_test_admin_user
from authentik.lib.generators import generate_id
RETRIES = int(environ.get("RETRIES", "3"))
IS_CI = "CI" in environ
def get_docker_tag() -> str:
"""Get docker-tag based off of CI variables"""
env_pr_branch = "GITHUB_HEAD_REF"
default_branch = "GITHUB_REF"
branch_name = os.environ.get(default_branch, "main")
if os.environ.get(env_pr_branch, "") != "":
branch_name = os.environ[env_pr_branch]
branch_name = branch_name.replace("refs/heads/", "").replace("/", "-")
return f"gh-{branch_name}"
def get_local_ip() -> str:
"""Get the local machine's IP"""
hostname = socket.gethostname()
ip_addr = socket.gethostbyname(hostname)
return ip_addr
class DockerTestCase(TestCase):
"""Mixin for dealing with containers"""
max_healthcheck_attempts = 30
__client: DockerClient
__network: Network
__label_id = generate_id()
def setUp(self) -> None:
self.__client = from_env()
self.__network = self.docker_client.networks.create(name=f"authentik-test-{generate_id()}")
@property
def docker_client(self) -> DockerClient:
return self.__client
@property
def docker_network(self) -> Network:
return self.__network
@property
def docker_labels(self) -> dict:
return {"io.goauthentik.test": self.__label_id}
def wait_for_container(self, container: Container):
"""Check that container is health"""
attempt = 0
while True:
container.reload()
status = container.attrs.get("State", {}).get("Health", {}).get("Status")
if status == "healthy":
return container
sleep(1)
attempt += 1
if attempt >= self.max_healthcheck_attempts:
self.failureException("Container failed to start")
def get_container_image(self, base: str) -> str:
"""Try to pull docker image based on git branch, fallback to main if not found."""
image = f"{base}:gh-main"
try:
branch_image = f"{base}:{get_docker_tag()}"
self.docker_client.images.pull(branch_image)
return branch_image
except DockerException:
self.docker_client.images.pull(image)
return image
def run_container(self, **specs: dict[str, Any]) -> Container:
if "network_mode" not in specs:
specs["network"] = self.__network.name
specs["labels"] = self.docker_labels
specs["detach"] = True
if hasattr(self, "live_server_url"):
specs.setdefault("environment", {})
specs["environment"]["AUTHENTIK_HOST"] = self.live_server_url
container = self.docker_client.containers.run(**specs)
container.reload()
state = container.attrs.get("State", {})
if "Health" not in state:
return container
self.wait_for_container(container)
return container
def output_container_logs(self, container: Container | None = None):
"""Output the container logs to our STDOUT"""
if IS_CI:
image = container.image
tags = image.tags[0] if len(image.tags) > 0 else str(image)
print(f"::group::Container logs - {tags}")
for log in container.logs().decode().split("\n"):
print(log)
if IS_CI:
print("::endgroup::")
def tearDown(self):
containers: list[Container] = self.docker_client.containers.list(
filters={"label": ",".join(f"{x}={y}" for x, y in self.docker_labels.items())}
)
for container in containers:
self.output_container_logs(container)
try:
container.kill()
except DockerException:
pass
try:
container.remove(force=True)
except DockerException:
pass
self.__network.remove()
class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
"""StaticLiveServerTestCase which automatically creates a Webdriver instance"""
host = get_local_ip()
wait_timeout: int
user: User
def setUp(self):
if IS_CI:
print("::group::authentik Logs", file=stderr)
apps.get_app_config("authentik_tenants").ready()
self.wait_timeout = 60
self.driver = self._get_driver()
self.driver.implicitly_wait(30)
self.wait = WebDriverWait(self.driver, self.wait_timeout)
self.logger = get_logger()
self.user = create_test_admin_user()
super().setUp()
def _get_driver(self) -> WebDriver:
count = 0
try:
opts = webdriver.ChromeOptions()
opts.add_argument("--disable-search-engine-choice-screen")
return webdriver.Chrome(options=opts)
except WebDriverException:
pass
while count < RETRIES:
try:
driver = webdriver.Remote(
command_executor="http://localhost:4444/wd/hub",
options=webdriver.ChromeOptions(),
)
driver.maximize_window()
return driver
except WebDriverException:
count += 1
raise ValueError(f"Webdriver failed after {RETRIES}.")
def tearDown(self):
if IS_CI:
print("::endgroup::", file=stderr)
super().tearDown()
if IS_CI:
print("::group::Browser logs")
for line in self.driver.get_log("browser"):
print(line["message"])
if IS_CI:
print("::endgroup::")
self.driver.quit()
def wait_for_url(self, desired_url):
"""Wait until URL is `desired_url`."""
self.wait.until(
lambda driver: driver.current_url == desired_url,
f"URL {self.driver.current_url} doesn't match expected URL {desired_url}",
)
def url(self, view, query: dict | None = None, **kwargs) -> str:
"""reverse `view` with `**kwargs` into full URL using live_server_url"""
url = self.live_server_url + reverse(view, kwargs=kwargs)
if query:
return url + "?" + urlencode(query)
return url
def if_user_url(self, path: str | None = None) -> str:
"""same as self.url() but show URL in shell"""
url = self.url("authentik_core:if-user")
if path:
return f"{url}#{path}"
return url
def get_shadow_root(
self, selector: str, container: WebElement | WebDriver | None = None
) -> WebElement:
"""Get shadow root element's inner shadowRoot"""
if not container:
container = self.driver
shadow_root = container.find_element(By.CSS_SELECTOR, selector)
element = self.driver.execute_script("return arguments[0].shadowRoot", shadow_root)
return element
def login(self):
"""Do entire login flow and check user afterwards"""
flow_executor = self.get_shadow_root("ak-flow-executor")
identification_stage = self.get_shadow_root("ak-stage-identification", flow_executor)
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uidField]").click()
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uidField]").send_keys(
self.user.username
)
identification_stage.find_element(By.CSS_SELECTOR, "input[name=uidField]").send_keys(
Keys.ENTER
)
flow_executor = self.get_shadow_root("ak-flow-executor")
password_stage = self.get_shadow_root("ak-stage-password", flow_executor)
password_stage.find_element(By.CSS_SELECTOR, "input[name=password]").send_keys(
self.user.username
)
password_stage.find_element(By.CSS_SELECTOR, "input[name=password]").send_keys(Keys.ENTER)
sleep(1)
def assert_user(self, expected_user: User):
"""Check users/me API and assert it matches expected_user"""
self.driver.get(self.url("authentik_api:user-me") + "?format=json")
user_json = self.driver.find_element(By.CSS_SELECTOR, "pre").text
user = UserSerializer(data=json.loads(user_json)["user"])
user.is_valid()
self.assertEqual(user["username"].value, expected_user.username)
self.assertEqual(user["name"].value, expected_user.name)
self.assertEqual(user["email"].value, expected_user.email)
@lru_cache
def get_loader():
"""Thin wrapper to lazily get a Migration Loader, only when it's needed
and only once"""
return MigrationLoader(connection)
def retry(max_retires=RETRIES, exceptions=None):
"""Retry test multiple times. Default to catching Selenium Timeout Exception"""
if not exceptions:
exceptions = [WebDriverException, TimeoutException, NoSuchElementException]
logger = get_logger()
def retry_actual(func: Callable):
"""Retry test multiple times"""
count = 1
@wraps(func)
def wrapper(self: TransactionTestCase, *args, **kwargs):
"""Run test again if we're below max_retries, including tearDown and
setUp. Otherwise raise the error"""
nonlocal count
try:
return func(self, *args, **kwargs)
except tuple(exceptions) as exc:
count += 1
if count > max_retires:
logger.debug("Exceeded retry count", exc=exc, test=self)
raise exc
logger.debug("Retrying on error", exc=exc, test=self)
self.tearDown()
self._post_teardown()
self._pre_setup()
self.setUp()
return wrapper(self, *args, **kwargs)
return wrapper
return retry_actual

View File

@ -19,7 +19,7 @@ from authentik.outposts.models import (
)
from authentik.outposts.tasks import outpost_connection_discovery
from authentik.providers.proxy.models import ProxyProvider
from tests.docker import DockerTestCase, get_docker_tag
from tests.e2e.utils import DockerTestCase, get_docker_tag
class OutpostDockerTests(DockerTestCase, ChannelsLiveServerTestCase):

View File

@ -19,7 +19,7 @@ from authentik.outposts.models import (
from authentik.outposts.tasks import outpost_connection_discovery
from authentik.providers.proxy.controllers.docker import DockerController
from authentik.providers.proxy.models import ProxyProvider
from tests.docker import DockerTestCase, get_docker_tag
from tests.e2e.utils import DockerTestCase, get_docker_tag
class TestProxyDocker(DockerTestCase, ChannelsLiveServerTestCase):

View File

@ -1,52 +0,0 @@
# This file cannot import anything django or anything that will load django
from sys import stderr
from channels.testing import ChannelsLiveServerTestCase
from daphne.testing import DaphneProcess
from structlog.stdlib import get_logger
from tests import IS_CI, get_local_ip
def set_database_connection():
from django.conf import settings
settings.DATABASES["default"]["NAME"] = settings.DATABASES["default"]["TEST"]["NAME"]
settings.TEST = True
class DatabasePatchDaphneProcess(DaphneProcess):
# See https://github.com/django/channels/issues/2048
# See https://github.com/django/channels/pull/2033
def __init__(self, host, get_application, kwargs=None, setup=None, teardown=None):
super().__init__(host, get_application, kwargs, setup, teardown)
self.setup = set_database_connection
class BaseWebsocketTestCase(ChannelsLiveServerTestCase):
"""Base channels test case"""
host = get_local_ip()
ProtocolServerProcess = DatabasePatchDaphneProcess
class WebsocketTestCase(BaseWebsocketTestCase):
"""Test case to allow testing against a running Websocket/HTTP server"""
def setUp(self):
if IS_CI:
print("::group::authentik Logs", file=stderr)
from django.apps import apps
from authentik.core.tests.utils import create_test_admin_user
apps.get_app_config("authentik_tenants").ready()
self.logger = get_logger()
self.user = create_test_admin_user()
super().setUp()
def tearDown(self):
if IS_CI:
print("::endgroup::", file=stderr)
super().tearDown()

View File

@ -1,28 +0,0 @@
// TypeScript Project Configuration
{
"extends": "./packages/tsconfig/tsconfig.json",
"compilerOptions": {
"baseUrl": "."
},
"watchOptions": {
"excludeDirectories": [
"**/.git", // Git
"**/.yarn", // Yarn
"**/.vscode", // VS Code
"**/.vscode-test-web", // VS Code Web Test
"**/dist", // Distributed build files
"**/out", // Output build files
"**/.drafts", // Drafts
"**/.github", // GitHub
"**/node_modules" // Node modules
]
},
// The root project has no sources of its own. By setting `files` to an empty
// list, TS won't automatically include all sources below root (the default).
"files": [],
"references": [
// Note that references are in the order we want them to be built.
// TODO: Left blank until TypeScript workspaces are complete.
]
}

2188
uv.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -2,11 +2,15 @@
node_modules
# don't lint build output (make sure it's set to your correct build folder name)
dist
out
# don't lint nyc coverage output
coverage
# Import order matters
poly.ts
src/locale-codes.ts
src/locales/
storybook-static/
# Prettier breaks the tsconfig file
tsconfig.json
.storybook/css-import-maps*
package.json
packages/**/package.json

View File

@ -0,0 +1,11 @@
import { create } from "@storybook/theming/create";
const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
export default create({
base: isDarkMode ? "dark" : "light",
brandTitle: "authentik Storybook",
brandUrl: "https://goauthentik.io",
brandImage: "https://goauthentik.io/img/icon_left_brand_colour.svg",
brandTarget: "_self",
});

View File

@ -1,69 +0,0 @@
/**
* @file Storybook configuration.
* @import { StorybookConfig } from "@storybook/web-components-vite";
* @import { InlineConfig, Plugin } from "vite";
*/
import { cwd } from "process";
import postcssLit from "rollup-plugin-postcss-lit";
import tsconfigPaths from "vite-tsconfig-paths";
const NODE_ENV = process.env.NODE_ENV || "development";
const CSSImportPattern = /import [\w\$]+ from .+\.(css)/g;
const JavaScriptFilePattern = /\.m?(js|ts|tsx)$/;
/**
* @satisfies {Plugin<never>}
*/
const inlineCSSPlugin = {
name: "inline-css-plugin",
transform: (source, id) => {
if (!JavaScriptFilePattern.test(id)) return;
const code = source.replace(CSSImportPattern, (match) => {
return `${match}?inline`;
});
return {
code,
};
},
};
/**
* @satisfies {StorybookConfig}
*/
const config = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: [
"@storybook/addon-controls",
"@storybook/addon-links",
"@storybook/addon-essentials",
"storybook-addon-mock",
],
framework: {
name: "@storybook/web-components-vite",
options: {},
},
docs: {
autodocs: "tag",
},
viteFinal({ plugins = [], ...config }) {
/**
* @satisfies {InlineConfig}
*/
const mergedConfig = {
...config,
define: {
"process.env.NODE_ENV": JSON.stringify(NODE_ENV),
"process.env.CWD": JSON.stringify(cwd()),
"process.env.AK_API_BASE_PATH": JSON.stringify(process.env.AK_API_BASE_PATH || ""),
},
plugins: [inlineCSSPlugin, ...plugins, postcssLit(), tsconfigPaths()],
};
return mergedConfig;
},
};
export default config;

81
web/.storybook/main.ts Normal file
View File

@ -0,0 +1,81 @@
import replace from "@rollup/plugin-replace";
import type { StorybookConfig } from "@storybook/web-components-vite";
import { cwd } from "process";
import modify from "rollup-plugin-modify";
import postcssLit from "rollup-plugin-postcss-lit";
import tsconfigPaths from "vite-tsconfig-paths";
export const isProdBuild = process.env.NODE_ENV === "production";
export const apiBasePath = process.env.AK_API_BASE_PATH || "";
const importInlinePatterns = [
'import AKGlobal from "(\\.\\./)*common/styles/authentik\\.css',
'import AKGlobal from "@goauthentik/common/styles/authentik\\.css',
'import PF.+ from "@patternfly/patternfly/\\S+\\.css',
'import ThemeDark from "@goauthentik/common/styles/theme-dark\\.css',
'import OneDark from "@goauthentik/common/styles/one-dark\\.css',
'import styles from "\\./LibraryPageImpl\\.css',
];
const importInlineRegexp = new RegExp(importInlinePatterns.map((a) => `(${a})`).join("|"));
const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: [
"@storybook/addon-controls",
"@storybook/addon-links",
"@storybook/addon-essentials",
"storybook-addon-mock",
],
staticDirs: [
{
from: "../node_modules/@patternfly/patternfly/patternfly-base.css",
to: "@patternfly/patternfly/patternfly-base.css",
},
{
from: "../src/common/styles/authentik.css",
to: "@goauthentik/common/styles/authentik.css",
},
{
from: "../src/common/styles/theme-dark.css",
to: "@goauthentik/common/styles/theme-dark.css",
},
{
from: "../src/common/styles/one-dark.css",
to: "@goauthentik/common/styles/one-dark.css",
},
],
framework: {
name: "@storybook/web-components-vite",
options: {},
},
docs: {
autodocs: "tag",
},
async viteFinal(config) {
return {
...config,
plugins: [
modify({
find: importInlineRegexp,
replace: (match: RegExpMatchArray) => {
return `${match}?inline`;
},
}),
replace({
"process.env.NODE_ENV": JSON.stringify(
isProdBuild ? "production" : "development",
),
"process.env.CWD": JSON.stringify(cwd()),
"process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath),
"preventAssignment": true,
}),
...config.plugins,
postcssLit(),
tsconfigPaths(),
],
};
},
};
export default config;

View File

@ -1,38 +0,0 @@
/**
* @file Storybook manager configuration.
*
* @import { ThemeVarsPartial } from "storybook/internal/theming";
*/
import { createUIThemeEffect, resolveUITheme } from "@goauthentik/web/common/theme.ts";
import { addons } from "@storybook/manager-api";
import { create } from "@storybook/theming/create";
/**
* @satisfies {Partial<ThemeVarsPartial>}
*/
const baseTheme = {
brandTitle: "authentik Storybook",
brandUrl: "https://goauthentik.io",
brandImage: "https://goauthentik.io/img/icon_left_brand_colour.svg",
brandTarget: "_self",
};
const uiTheme = resolveUITheme();
addons.setConfig({
theme: create({
...baseTheme,
base: uiTheme,
}),
enableShortcuts: false,
});
createUIThemeEffect((nextUITheme) => {
addons.setConfig({
theme: create({
...baseTheme,
base: nextUITheme,
}),
enableShortcuts: false,
});
});

View File

@ -0,0 +1,9 @@
// .storybook/manager.js
import { addons } from "@storybook/manager-api";
import authentikTheme from "./authentikTheme";
addons.setConfig({
theme: authentikTheme,
enableShortcuts: false,
});

View File

@ -1,3 +1,5 @@
<link rel="stylesheet" href="@patternfly/patternfly/patternfly-base.css" />
<link rel="stylesheet" href="@goauthentik/common/styles/authentik.css" />
<style>
body {
overflow-y: scroll;

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