Compare commits

..

47 Commits

Author SHA1 Message Date
Jens Langhammer
8469213d82 release: 2024.6.5 2024-09-27 16:21:30 +02:00
gcp-cherry-pick-bot[bot]
78f7b04d5a security: fix CVE-2024-47070 (cherry-pick #11536) (#11540)
security: fix CVE-2024-47070 (#11536)

* security: fix CVE-2024-47070



* Update website/docs/security/CVE-2024-47070.md




---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens L. <jens@beryju.org>
Co-authored-by: Jens L. <jens@goauthentik.io>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2024-09-27 16:20:38 +02:00
gcp-cherry-pick-bot[bot]
22e586bd8c security: fix CVE-2024-47077 (cherry-pick #11535) (#11538)
security: fix CVE-2024-47077 (#11535)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2024-09-27 16:19:15 +02:00
Jens Langhammer
8a0b31b922 release: 2024.6.4 2024-08-22 17:19:24 +02:00
gcp-cherry-pick-bot[bot]
359b343f51 security: fix CVE-2024-42490 (cherry-pick #11022) (#11025)
security: fix CVE-2024-42490 (#11022)

CVE-2024-42490

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2024-08-22 17:18:58 +02:00
gcp-cherry-pick-bot[bot]
b727656b05 sources/ldap: Add enabled filter for ldap_password_validate signal (cherry-pick #10823) (#10825)
sources/ldap: Add enabled filter for ldap_password_validate signal (#10823)

Co-authored-by: Allen <63997543+aaw3@users.noreply.github.com>
2024-08-08 14:23:44 +02:00
gcp-cherry-pick-bot[bot]
8f09c2c21c web/admin: fix selectable card colour in dark theme (cherry-pick #10794) (#10795)
web/admin: fix selectable card colour in dark theme (#10794)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2024-08-06 13:46:43 +02:00
Jens Langhammer
8f207c7504 release: 2024.6.3 2024-08-05 18:35:33 +02:00
Marc 'risson' Schmitt
34d30bb549 root: fix opencontainers ref (#10776)
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
# Conflicts:
#	poetry.lock
2024-08-05 16:30:54 +02:00
Jens L
b4f04881e0 root: remove warnings (#10774)
* remove facebook sdk

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

* switch to newer opencontainers fork

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
# Conflicts:
#	poetry.lock
2024-08-05 14:52:20 +02:00
gcp-cherry-pick-bot[bot]
5314485426 enterprise/rac: fix error when listing connection tokens as non-superuser (cherry-pick #10771) (#10773)
enterprise/rac: fix error when listing connection tokens as non-superuser (#10771)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2024-08-05 14:09:24 +02:00
gcp-cherry-pick-bot[bot]
ad6b6e4576 web: replace all occurences of the theme placeholder (cherry-pick #10749) (#10750)
web: replace all occurences of the theme placeholder (#10749)

Replace all occurences of the theme placeholder

This allows the placeholder to occur multiple times in the theme url.

Signed-off-by: Chasethechicken <neuringe1234@gmail.com>
Co-authored-by: Chasethechicken <neuringe1234@gmail.com>
2024-08-05 11:57:32 +02:00
Marc 'risson' Schmitt
fb9aa9d7f7 sources/scim: fix duplicate service account users and changing token (cherry-pick #10735) (#10737)
sources/scim: fix duplicate service account users and changing token (#10735)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2024-08-02 14:12:23 +02:00
gcp-cherry-pick-bot[bot]
fe7662f80d web: fix theme not applying to document correctly (cherry-pick #10721) (#10722)
web: fix theme not applying to document correctly (#10721)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2024-08-01 15:09:38 +02:00
Jens Langhammer
d6904b6aa1 release: 2024.6.2 2024-07-31 16:54:24 +02:00
gcp-cherry-pick-bot[bot]
cd581efacd tests/e2e: fix ldap tests following #10270 (cherry-pick #10288) (#10703)
tests/e2e: fix ldap tests following #10270 (#10288)

Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-07-31 16:01:32 +02:00
gcp-cherry-pick-bot[bot]
6c159d120b outposts: ensure minimum refresh interval (cherry-pick #10701) (#10702)
outposts: ensure minimum refresh interval (#10701)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2024-07-31 14:59:14 +02:00
gcp-cherry-pick-bot[bot]
4ddd4e7f88 outposts: make refresh interval configurable (cherry-pick #10138) (#10700)
* outposts: make refresh interval configurable (#10138)

* outposts: make refresh interval configurable

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

* frontend

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

* black again

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

* switch to using config attribute

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

* lint

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

---------

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

* bump api

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

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2024-07-31 14:38:09 +02:00
gcp-cherry-pick-bot[bot]
441912414f web/admin: show matching user reputation scores in user details (cherry-pick #10276) (#10699)
* web/admin: show matching user reputation scores in user details (#10276)

Co-authored-by: Jens Langhammer <jens@goauthentik.io>

* bump api

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2024-07-31 14:37:58 +02:00
Jens L
9e177ed5c0 web: fix dark theme and theme switch (#10667)
* base locale off of ak-element

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

* revert temp theme fixes

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

* fix theme switching

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

* add basic support for theme-different images

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

* sort outposts in card

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

* set default theme based on pre-hydrated brand settings

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

* activate global theme before root in shadow dom

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

* logging

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

* when using _applyTheme, check media matcher

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

* add docs

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
# Conflicts:
#	web/src/elements/Base.ts
#	website/docs/core/brands.md
2024-07-29 20:26:44 +02:00
gcp-cherry-pick-bot[bot]
881548176f events: associate login_failed events to a user if possible (cherry-pick #10270) (#10676)
* events: associate login_failed events to a user if possible (#10270)

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

* format

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

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2024-07-29 20:00:13 +02:00
gcp-cherry-pick-bot[bot]
56739d0dc4 web/flows: remove continue button from AutoSubmit stage (cherry-pick #10253) (#10677)
web/flows: remove continue button from AutoSubmit stage (#10253)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2024-07-29 19:32:29 +02:00
gcp-cherry-pick-bot[bot]
b23972e9c9 lifecycle: only create tenant media root if needed (cherry-pick #10616) (#10617)
lifecycle: only create tenant media root if needed (#10616)

Co-authored-by: Jens L. <jens@goauthentik.io>
2024-07-24 21:12:48 +02:00
gcp-cherry-pick-bot[bot]
0a9595089e web/admin: fix missing SAML Provider ECDSA options (cherry-pick #10612) (#10618)
web/admin: fix missing SAML Provider ECDSA options (#10612)

* web/admin: fix missing SAML Provider ECDSA options



* deduplicate



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2024-07-24 21:12:23 +02:00
gcp-cherry-pick-bot[bot]
72c22b5fab core: remove html language tag for pages that are translated (cherry-pick #10611) (#10613)
core: remove html language tag for pages that are translated (#10611)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2024-07-24 19:42:48 +02:00
gcp-cherry-pick-bot[bot]
84cdbb0a03 events: fix race condition (cherry-pick #10602) (#10609)
events: fix race condition (#10602)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2024-07-24 16:53:03 +02:00
gcp-cherry-pick-bot[bot]
9fc659f121 stages/prompt: fix prompt not editable with invalid expression (cherry-pick #10603) (#10604)
stages/prompt: fix prompt not editable with invalid expression (#10603)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2024-07-24 14:36:33 +02:00
gcp-cherry-pick-bot[bot]
db6abf61b8 lib/sync: handle SkipObject in direct triggered tasks (cherry-pick #10590) (#10591)
lib/sync: handle SkipObject in direct triggered tasks (#10590)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2024-07-23 15:38:37 +02:00
gcp-cherry-pick-bot[bot]
6426a1d177 core: improve error handling on ASGI level (cherry-pick #10547) (#10552)
core: improve error handling on ASGI level (#10547)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2024-07-19 17:19:29 +02:00
Jens Langhammer
9075270b01 release: 2024.6.1 2024-07-11 21:45:54 +02:00
gcp-cherry-pick-bot[bot]
d17a39a431 website/docs: add 2024.6.1 release notes (cherry-pick #10456) (#10458)
website/docs: add 2024.6.1 release notes (#10456)

* website/docs: add 2024.6.1 release notes



* update



* fix version requirement for sfe



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2024-07-11 19:11:28 +02:00
gcp-cherry-pick-bot[bot]
db1d091d2e core: revert backchannel only filtering (cherry-pick #10455) (#10457)
core: revert backchannel only filtering (#10455)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2024-07-11 16:58:29 +02:00
gcp-cherry-pick-bot[bot]
f98204e78e core: fix source flow_manager not resuming flow when linking (cherry-pick #10436) (#10438)
core: fix source flow_manager not resuming flow when linking (#10436)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2024-07-10 15:20:15 +02:00
gcp-cherry-pick-bot[bot]
3f663cab0f web/admin: fix access token list calling wrong API (cherry-pick #10434) (#10435)
web/admin: fix access token list calling wrong API (#10434)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2024-07-10 14:17:47 +02:00
gcp-cherry-pick-bot[bot]
3fe129e107 core: fix migrations missing using db_alias (cherry-pick #10409) (#10410)
core: fix migrations missing using db_alias (#10409)

Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-07-09 10:48:29 +02:00
authentik-automation[bot]
f26d41aef9 web: bump API Client version (#10389)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
# Conflicts:
#	web/package-lock.json
#	web/package.json
2024-07-05 20:49:31 +02:00
Jens L
5d8b5998ae web/flows: Simplified flow executor (#10296)
* initial sfe

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

* build sfe

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

* downgrade bootstrap

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

* fix path

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

* make IE compatible

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

* fix query string missing

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

* add autosubmit stage

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

* add background image

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

* add code support

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

* add support for combo ident/password

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

* fix logo rendering

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

* only use for edge 18 and before

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

* fix lint

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

* add docs

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

* add webauthn support

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

* migrate to TS for some creature comforts

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

* fix ci

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

* dedupe dependabot

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

* use API client...kinda

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

* add more docs

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

* add more polyfills yay

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

* fix

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

* turn powered by into span

prevent issues in restricted browsers where users might not be able to return

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

* allow non-link footer entries

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

* fix tsc errors

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

* Apply suggestions from code review

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Jens L. <jens@beryju.org>

* auto switch for macos

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

* reword

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

* Update website/docs/flow/executors/if-flow.md

Signed-off-by: Jens L. <jens@beryju.org>

* format

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens L. <jens@beryju.org>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
# Conflicts:
#	.github/workflows/ci-web.yml
#	Dockerfile
#	website/developer-docs/api/flow-executor.md
2024-07-05 20:43:14 +02:00
gcp-cherry-pick-bot[bot]
7a5e136346 stages/authenticator_validate: fix friendly_name being required (cherry-pick #10382) (#10385)
stages/authenticator_validate: fix friendly_name being required (#10382)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2024-07-05 15:50:14 +02:00
gcp-cherry-pick-bot[bot]
bfbab6357a sources/oauth: fix link not being saved (cherry-pick #10374) (#10376)
sources/oauth: fix link not being saved (#10374)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2024-07-04 16:58:38 +02:00
gcp-cherry-pick-bot[bot]
5997b93f15 sources/saml: fix pickle error, add saml auth tests (cherry-pick #10348) (#10352)
sources/saml: fix pickle error, add saml auth tests (#10348)

* test with persistent nameid



* fix pickle



* user_write: dont attempt to write to read only property



* add test for enroll + auth



* unwrap lazy user



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2024-07-03 18:34:22 +02:00
gcp-cherry-pick-bot[bot]
6cdae09dc0 providers/saml: fix metadata import error handling (cherry-pick #10349) (#10350)
Co-authored-by: Jens L <jens@goauthentik.io>
fix metadata import error handling (#10349)
2024-07-03 16:01:50 +00:00
Jens L
ff0ef7a2b3 web: set noopener and noreferrer on all external links (#10304)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-07-02 14:54:03 +02:00
gcp-cherry-pick-bot[bot]
3986104a20 provider/scim: Fix exception handling for missing ServiceProviderConfig (cherry-pick #10322) (#10335)
provider/scim: Fix exception handling for missing ServiceProviderConfig (#10322)

Co-authored-by: Michael Poutre <m1kep.my.mail@gmail.com>
2024-07-02 13:53:27 +02:00
gcp-cherry-pick-bot[bot]
1aa60e7864 core: remove transitionary old JS urls (cherry-pick #10317) (#10321)
core: remove transitionary old JS urls (#10317)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2024-07-01 21:00:05 +02:00
gcp-cherry-pick-bot[bot]
045578dd07 web/flows: remove background image link (cherry-pick #10318) (#10320)
web/flows: remove background image link (#10318)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2024-07-01 20:28:30 +02:00
gcp-cherry-pick-bot[bot]
f23d70dc75 stages/user_login: fix ?next parameter not carried through broken session binding (cherry-pick #10301) (#10302)
stages/user_login: fix ?next parameter not carried through broken session binding (#10301)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2024-06-29 23:17:13 +02:00
gcp-cherry-pick-bot[bot]
496f3426d9 website/docs: update geoip and asn documentation following field changes (cherry-pick #10265) (#10266)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2024-06-27 13:26:31 +00:00
3951 changed files with 291349 additions and 685865 deletions

32
.bumpversion.cfg Normal file
View File

@@ -0,0 +1,32 @@
[bumpversion]
current_version = 2024.6.5
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*))?
serialize =
{major}.{minor}.{patch}-{rc_t}{rc_n}
{major}.{minor}.{patch}
message = release: {new_version}
tag_name = version/{new_version}
[bumpversion:part:rc_t]
values =
rc
final
optional_value = final
[bumpversion:file:pyproject.toml]
[bumpversion:file:package.json]
[bumpversion:file:docker-compose.yml]
[bumpversion:file:schema.yml]
[bumpversion:file:blueprints/schema.json]
[bumpversion:file:authentik/__init__.py]
[bumpversion:file:internal/constants/constants.go]
[bumpversion:file:web/src/common/constants.ts]

View File

@@ -1,63 +0,0 @@
# syntax=docker/dockerfile:1
# Start from the same FIPS Python base as production (python-base stage)
FROM ghcr.io/goauthentik/fips-python:3.13.9-slim-trixie-fips@sha256:700fc8c1e290bd14e5eaca50b1d8e8c748c820010559cbfb4c4f8dfbe2c4c9ff
USER root
# Setup environment matching production python-base stage
ENV VENV_PATH="/ak-root/.venv" \
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \
UV_COMPILE_BYTECODE=1 \
UV_LINK_MODE=copy \
UV_NATIVE_TLS=1 \
UV_PYTHON_DOWNLOADS=0
WORKDIR /ak-root
# Copy uv package manager
COPY --from=ghcr.io/astral-sh/uv:0.9.7@sha256:ba4857bf2a068e9bc0e64eed8563b065908a4cd6bfb66b531a9c424c8e25e142 /uv /uvx /bin/
# Install build dependencies
RUN rm -f /etc/apt/apt.conf.d/docker-clean && \
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache && \
apt-get update && \
apt-get install -y --no-install-recommends \
# Build essentials
build-essential pkg-config libffi-dev git binutils \
# cryptography
curl \
# libxml
libxslt-dev zlib1g-dev \
# postgresql
libpq-dev \
# python-kadmin-rs and kerberos testing
clang libkrb5-dev sccache krb5-kdc krb5-admin-server \
# xmlsec
libltdl-dev \
# runit (for chpst command used by lifecycle/ak)
runit \
# sudo (required by devcontainer features)
sudo && \
rm -rf /var/lib/apt/lists/*
# Environment for building native Python packages
ENV UV_NO_BINARY_PACKAGE="cryptography lxml python-kadmin-rs xmlsec" \
RUSTUP_PERMIT_COPY_RENAME="true"
# Create authentik user with proper home directory (required for devcontainer features)
RUN adduser --disabled-password --gecos "" --uid 1000 --home /home/authentik authentik && \
mkdir -p /certs /media /ak-root && \
chown -R authentik:authentik /certs /media /ak-root /home/authentik && \
echo "authentik ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/authentik
# FIPS configuration for Go development
# Don't set GOFIPS/GOFIPS140 globally to avoid breaking Go tools like docker-compose
# These will be set when building/running authentik Go code (see lifecycle/ak and Makefile)
ENV CGO_ENABLED=1
# Set TMPDIR for PID files and temp data
# Use /tmp instead of /dev/shm for development because go run needs to execute binaries
ENV TMPDIR=/tmp
USER authentik

View File

@@ -1,68 +0,0 @@
{
"name": "authentik",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/ak-root",
"containerUser": "authentik",
"remoteUser": "authentik",
"shutdownAction": "stopCompose",
"containerEnv": {
"LOCAL_PROJECT_DIR": "/ak-root"
},
"features": {
"ghcr.io/devcontainers/features/go:1": {
"version": "1.24"
},
"ghcr.io/devcontainers/features/node:1": {
"version": "24"
},
"ghcr.io/devcontainers/features/rust:1": {
"version": "latest"
},
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"version": "latest",
"moby": false
}
},
"mounts": [],
"forwardPorts": [9000, 9443],
"portsAttributes": {
"8000": {
"onAutoForward": "ignore"
},
"3963": {
"onAutoForward": "ignore"
},
"35151": {
"onAutoForward": "ignore"
},
"9901": {
"onAutoForward": "ignore"
}
},
"postCreateCommand": "bash .devcontainer/setup.sh",
"customizations": {
"vscode": {
"extensions": [
"EditorConfig.EditorConfig",
"bashmish.es6-string-css",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"golang.go",
"Gruntfuggly.todo-tree",
"ms-python.black-formatter",
"ms-python.isort",
"ms-python.pylint",
"ms-python.python",
"ms-python.vscode-pylance",
"redhat.vscode-yaml",
"Tobermory.es6-string-html",
"charliermarsh.ruff"
],
"settings": {
"python.defaultInterpreterPath": "/ak-root/.venv/bin/python",
"python.terminal.activateEnvironment": true
}
}
}
}

View File

@@ -1,50 +0,0 @@
services:
app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile
user: authentik
privileged: true
volumes:
- ../:/ak-root
entrypoint: []
command: sleep infinity
depends_on:
postgresql:
condition: service_healthy
env_file: .env
environment:
PATH: "/ak-root/.venv/bin:${PATH}"
ports:
- "9000:9000"
- "9443:9443"
postgresql:
image: docker.io/library/postgres:16
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -d authentik -U postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
volumes:
- postgres-data:/var/lib/postgresql/data
env_file: .env
command: ["postgres", "-c", "log_statement=all", "-c", "log_destination=stderr"]
s3:
image: docker.io/zenko/cloudserver
env_file: .env
environment:
REMOTE_MANAGEMENT_DISABLE: "1"
ports:
- "8020:8000"
volumes:
- s3-data:/usr/src/app/localData
- s3-metadata:/usr/src/app/localMetadata
volumes:
postgres-data:
s3-data:
s3-metadata:

View File

@@ -1,37 +0,0 @@
#!/usr/bin/env bash
set -e
echo "======================================"
echo "Running authentik devcontainer setup"
echo "======================================"
echo ""
echo "Step 1/5: Installing dependencies"
make install
echo ""
echo "Step 2/5: Generating development config"
make gen-dev-config
echo ""
echo "Step 3/5: Running database migrations"
make migrate
echo ""
echo "Step 4/5: Generating API clients"
make gen
echo ""
echo "Step 5/5: Building web assets"
make web
echo ""
echo "======================================"
echo "Setup complete!"
echo "======================================"
echo ""
echo "You can now run:"
echo " - 'make run-server' to start the backend server"
echo " - 'make run-worker' to start the worker (must be ran once after initial setup)"
echo " - 'make web-watch' for live web development"
echo ""

View File

@@ -1,15 +1,12 @@
htmlcov
*.env.yml
node_modules
**/node_modules
dist/**
build/**
build_docs/**
*Dockerfile
**/*Dockerfile
blueprints/local
.git
!gen-ts-api/node_modules
!gen-ts-api/dist/**
!gen-go-api/
.venv

View File

@@ -7,9 +7,6 @@ charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.toml]
indent_size = 2
[*.html]
indent_size = 2

View File

@@ -1,81 +0,0 @@
name: Bug report
description: Create a report to help us improve
labels: ["bug", "triage"]
type: bug
body:
- type: markdown
attributes:
value: |
Thank you for taking the time to fill out this bug report!
- type: textarea
id: describe-the-bug
attributes:
label: Describe the bug
description: "A clear and concise description of what the bug is."
placeholder: "Describe the issue"
validations:
required: true
- type: textarea
id: how-to-reproduce
attributes:
label: How to reproduce
description: "Steps to reproduce the behavior."
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
description: "A clear and concise description of what you expected to happen."
placeholder: "The behavior that I expect to see is [...]"
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: "If applicable, add screenshots to help explain your problem."
validations:
required: false
- type: textarea
id: additional-context
attributes:
label: Additional context
description: "Add any other context about the problem here."
placeholder: "Also note that [...]"
validations:
required: false
- type: dropdown
id: deployment-method
attributes:
label: Deployment Method
description: "What deployment method are you using for authentik? Only Docker, Kubernetes and AWS CloudFormation are supported."
options:
- Docker
- Kubernetes
- AWS CloudFormation
- Other (please specify)
default: 0
validations:
required: true
- type: input
id: version
attributes:
label: Version
description: "What version of authentik are you using?"
placeholder: "[e.g. 2025.10.1]"
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log output
description: "Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks."
render: shell
validations:
required: false

View File

@@ -1,49 +0,0 @@
name: Documentation suggestion/problem
description: Suggest an improvement or report a problem in our docs
labels: ["area: docs", "triage"]
type: task
body:
- type: markdown
attributes:
value: |
Thank you for taking the time to fill out this documentation issue!
- type: markdown
attributes:
value: |
**Consider opening a PR!**
If the issue is one that you can fix, or even make a good pass at, we'd appreciate a PR.
For more information about making a contribution to the docs, and using our Style Guide and our templates, refer to ["Writing documentation"](https://docs.goauthentik.io/docs/developer-docs/docs/writing-documentation).
- type: textarea
id: issue
attributes:
label: Do you see an area that can be clarified or expanded, a technical inaccuracy, or a broken link?
description: "A clear and concise description of what the problem is, or where the document can be improved."
placeholder: "I believe we need more details about [...]"
validations:
required: true
- type: input
id: link
attributes:
label: Link
description: "Provide the URL or link to the exact page in the documentation to which you are referring."
placeholder: "If there are multiple pages, list them all"
validations:
required: true
- type: textarea
id: solution
attributes:
label: Solution
description: "A clear and concise description of what you suggest as a solution"
placeholder: "This issue could be resolved by [...]"
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Additional context
description: "Add any other context or screenshots about the documentation issue here."
placeholder: "Also note that [...]"
validations:
required: false

View File

@@ -1,41 +0,0 @@
name: Feature request
description: Suggest an idea for a feature
labels: ["enhancement", "triage"]
type: feature
body:
- type: markdown
attributes:
value: |
Thank you for taking the time to fill out this feature request!
- type: textarea
id: related-to-problem
attributes:
label: Is your feature request related to a problem?
description: "A clear and concise description of what the problem is."
placeholder: "I'm always frustrated when [...]"
validations:
required: true
- type: textarea
id: feature
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
placeholder: "I'd like authentik to have [...]"
validations:
required: false
- type: textarea
id: alternatives
attributes:
label: Describe alternatives that you've considered
description: "A clear and concise description of any alternative solutions or features you've considered."
placeholder: "I've tried this but [...]"
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Additional context
description: "Add any other context or screenshots about the feature request here."
placeholder: "Also note that [...]"
validations:
required: false

35
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,35 @@
---
name: Bug report
about: Create a report to help us improve
title: ""
labels: bug
assignees: ""
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Logs**
Output of docker-compose logs or kubectl logs respectively
**Version and Deployment (please complete the following information):**
- authentik version: [e.g. 2021.8.5]
- Deployment: [e.g. docker-compose, helm]
**Additional context**
Add any other context about the problem here.

View File

@@ -1,8 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Question
url: https://github.com/goauthentik/authentik/discussions
about: Please ask questions via GitHub Discussions rather than creating issues.
- name: authentik Discord
url: https://discord.com/invite/jg33eMhnj6
about: For community support, visit our Discord server.

View File

@@ -0,0 +1,19 @@
---
name: Feature request
about: Suggest an idea for this project
title: ""
labels: enhancement
assignees: ""
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,17 @@
---
name: Hackathon Idea
about: Propose an idea for the hackathon
title: ""
labels: hackathon
assignees: ""
---
**Describe the idea**
A clear concise description of the idea you want to implement
You're also free to work on existing GitHub issues, whether they be feature requests or bugs, just link the existing GitHub issue here.
<!-- Don't modify below here -->
If you want to help working on this idea or want to contribute in any other way, react to this issue with a :rocket:

View File

@@ -1,7 +0,0 @@
---
name: Blank issue
about: This issue type is only for internal use
title:
labels:
assignees:
---

27
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@@ -0,0 +1,27 @@
---
name: Question
about: Ask a question about a feature or specific configuration
title: ""
labels: question
assignees: ""
---
**Describe your question/**
A clear and concise description of what you're trying to do.
**Relevant info**
i.e. Version of other software you're using, specifics of your setup
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Logs**
Output of docker-compose logs or kubectl logs respectively
**Version and Deployment (please complete the following information):**
- authentik version: [e.g. 2021.8.5]
- Deployment: [e.g. docker-compose, helm]
**Additional context**
Add any other context about the problem here.

View File

@@ -1,267 +0,0 @@
name: "Cherry-picker"
description: "Cherry-pick PRs based on their labels"
inputs:
token:
description: "GitHub Token"
required: true
git_user:
description: "Git user for pushing the cherry-pick PR"
required: true
git_user_email:
description: "Git user email for pushing the cherry-pick PR"
required: true
runs:
using: "composite"
steps:
- name: Check if workflow should run
id: should_run
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.token }}
run: |
set -e -o pipefail
# For issues events, check if it's actually a PR
if [ "${{ github.event_name }}" = "issues" ]; then
# Check if this issue is actually a PR
PR_DATA=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }} 2>/dev/null || echo "null")
if [ "$PR_DATA" = "null" ]; then
echo "should_run=false" >> $GITHUB_OUTPUT
echo "reason=not_a_pr" >> $GITHUB_OUTPUT
echo "This is an issue, not a PR. Skipping."
exit 0
fi
# Get PR data
PR_MERGED=$(echo "$PR_DATA" | jq -r '.merged')
PR_NUMBER="${{ github.event.issue.number }}"
MERGE_COMMIT_SHA=$(echo "$PR_DATA" | jq -r '.merge_commit_sha')
# Check if it's a backport label
LABEL_NAME="${{ github.event.label.name }}"
if [[ "$LABEL_NAME" =~ ^backport/(.+)$ ]]; then
if [ "$PR_MERGED" = "true" ]; then
echo "should_run=true" >> $GITHUB_OUTPUT
echo "reason=label_added_to_merged_pr" >> $GITHUB_OUTPUT
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "merge_commit_sha=$MERGE_COMMIT_SHA" >> $GITHUB_OUTPUT
exit 0
else
echo "should_run=false" >> $GITHUB_OUTPUT
echo "reason=label_added_to_open_pr" >> $GITHUB_OUTPUT
echo "Backport label added to open PR. Will run after PR is merged."
exit 0
fi
else
echo "should_run=false" >> $GITHUB_OUTPUT
echo "reason=non_backport_label" >> $GITHUB_OUTPUT
exit 0
fi
fi
# For pull_request and pull_request_target events
PR_NUMBER="${{ github.event.pull_request.number }}"
MERGE_COMMIT_SHA="${{ github.event.pull_request.merge_commit_sha }}"
# Case 1: PR was just merged (closed + merged = true)
if [ "${{ github.event.action }}" = "closed" ] && [ "${{ github.event.pull_request.merged }}" = "true" ]; then
echo "should_run=true" >> $GITHUB_OUTPUT
echo "reason=pr_merged" >> $GITHUB_OUTPUT
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "merge_commit_sha=$MERGE_COMMIT_SHA" >> $GITHUB_OUTPUT
exit 0
fi
# Case 2: Label was added
if [ "${{ github.event.action }}" = "labeled" ]; then
LABEL_NAME="${{ github.event.label.name }}"
# Check if it's a backport label
if [[ "$LABEL_NAME" =~ ^backport/(.+)$ ]]; then
# Check if PR is already merged
if [ "${{ github.event.pull_request.merged }}" = "true" ]; then
echo "should_run=true" >> $GITHUB_OUTPUT
echo "reason=label_added_to_merged_pr" >> $GITHUB_OUTPUT
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "merge_commit_sha=$MERGE_COMMIT_SHA" >> $GITHUB_OUTPUT
exit 0
else
echo "should_run=false" >> $GITHUB_OUTPUT
echo "reason=label_added_to_open_pr" >> $GITHUB_OUTPUT
echo "Backport label added to open PR. Will run after PR is merged."
exit 0
fi
else
echo "should_run=false" >> $GITHUB_OUTPUT
echo "reason=non_backport_label" >> $GITHUB_OUTPUT
exit 0
fi
fi
echo "should_run=false" >> $GITHUB_OUTPUT
echo "reason=unknown" >> $GITHUB_OUTPUT
- name: Configure Git
if: steps.should_run.outputs.should_run == 'true'
shell: bash
env:
user: ${{ inputs.git_user }}
email: ${{ inputs.git_user_email }}
run: |
git config --global user.name "${user}"
git config --global user.email "${email}"
- name: Get PR details and extract backport labels
if: steps.should_run.outputs.should_run == 'true'
id: pr_details
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.token }}
run: |
set -e -o pipefail
PR_NUMBER="${{ steps.should_run.outputs.pr_number }}"
# Get PR details
PR_DATA=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER)
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.user.login')
echo "pr_title=$PR_TITLE" >> $GITHUB_OUTPUT
echo "pr_author=$PR_AUTHOR" >> $GITHUB_OUTPUT
# Determine which labels to process
if [ "${{ steps.should_run.outputs.reason }}" = "label_added_to_merged_pr" ]; then
# Only process the specific label that was just added
if [ "${{ github.event_name }}" = "issues" ]; then
LABEL_NAME="${{ github.event.label.name }}"
else
LABEL_NAME="${{ github.event.label.name }}"
fi
if [[ "$LABEL_NAME" =~ ^backport/(.+)$ ]]; then
echo "labels=$LABEL_NAME" >> $GITHUB_OUTPUT
else
echo "Label $LABEL_NAME does not match backport pattern"
echo "labels=" >> $GITHUB_OUTPUT
fi
else
# PR was just merged, process all backport labels
LABELS=$(gh pr view $PR_NUMBER --json labels --jq '.labels[].name' | grep '^backport/' | tr '\n' ' ' || true)
echo "labels=$LABELS" >> $GITHUB_OUTPUT
fi
- name: Cherry-pick to target branches
if: steps.should_run.outputs.should_run == 'true' && steps.pr_details.outputs.labels != ''
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.token }}
run: |
set -e -o pipefail
PR_NUMBER='${{ steps.should_run.outputs.pr_number }}'
COMMIT_SHA='${{ steps.should_run.outputs.merge_commit_sha }}'
PR_TITLE='${{ steps.pr_details.outputs.pr_title }}'
PR_AUTHOR='${{ steps.pr_details.outputs.pr_author }}'
LABELS='${{ steps.pr_details.outputs.labels }}'
echo "Processing PR #$PR_NUMBER (reason: ${{ steps.should_run.outputs.reason }})"
echo "Found backport labels: $LABELS"
# Process each backport label
for label in $LABELS; do
if [[ "$label" =~ ^backport/(.+)$ ]]; then
TARGET_BRANCH="${BASH_REMATCH[1]}"
echo "Processing backport to branch: $TARGET_BRANCH"
# Check if target branch exists
if ! git ls-remote --heads origin "$TARGET_BRANCH" | grep -q "$TARGET_BRANCH"; then
echo "❌ Target branch $TARGET_BRANCH does not exist, skipping"
# Comment on the original PR about the missing branch
gh pr comment $PR_NUMBER --body "⚠️ Cannot backport to \`$TARGET_BRANCH\`: branch does not exist."
continue
fi
# Create a unique branch name for the cherry-pick
CHERRY_PICK_BRANCH="cherry-pick/${PR_NUMBER}-to-${TARGET_BRANCH}"
# Check if a cherry-pick PR already exists
EXISTING_PR=$(gh pr list --head "$CHERRY_PICK_BRANCH" --json number --jq '.[0].number' 2>/dev/null || echo "")
if [ -n "$EXISTING_PR" ]; then
echo "⚠️ Cherry-pick PR already exists: #$EXISTING_PR"
gh pr comment $PR_NUMBER --body "Cherry-pick to \`$TARGET_BRANCH\` already exists: #$EXISTING_PR"
continue
fi
# Fetch and checkout target branch
git fetch origin "$TARGET_BRANCH"
git checkout -b "$CHERRY_PICK_BRANCH" "origin/$TARGET_BRANCH"
# Attempt cherry-pick
if git cherry-pick "$COMMIT_SHA"; then
echo "✅ Cherry-pick successful for $TARGET_BRANCH"
# Push the cherry-pick branch
git push origin "$CHERRY_PICK_BRANCH"
# Create PR for the cherry-pick
CHERRY_PICK_TITLE="$PR_TITLE (cherry-pick #$PR_NUMBER to $TARGET_BRANCH)"
CHERRY_PICK_BODY="Cherry-pick of #$PR_NUMBER to \`$TARGET_BRANCH\` branch.
**Original PR:** #$PR_NUMBER
**Original Author:** @$PR_AUTHOR
**Cherry-picked commit:** $COMMIT_SHA"
NEW_PR=$(gh pr create \
--title "$CHERRY_PICK_TITLE" \
--body "$CHERRY_PICK_BODY" \
--base "$TARGET_BRANCH" \
--head "$CHERRY_PICK_BRANCH" \
--label "cherry-pick")
echo "✅ Created cherry-pick PR $NEW_PR for $TARGET_BRANCH"
# Comment on original PR
gh pr comment $PR_NUMBER --body "🍒 Cherry-pick to \`$TARGET_BRANCH\` created: $NEW_PR"
else
echo "⚠️ Cherry-pick failed for $TARGET_BRANCH, creating conflict resolution PR"
# Add conflicted files and commit
git add .
git commit -m "Cherry-pick #$PR_NUMBER to $TARGET_BRANCH (with conflicts)
This cherry-pick has conflicts that need manual resolution.
Original PR: #$PR_NUMBER
Original commit: $COMMIT_SHA"
# Push the branch with conflicts
git push origin "$CHERRY_PICK_BRANCH"
# Create PR with conflict notice
CONFLICT_TITLE="$PR_TITLE (cherry-pick #$PR_NUMBER to $TARGET_BRANCH)"
CONFLICT_BODY="⚠️ **This cherry-pick has conflicts that require manual resolution.**
Cherry-pick of #$PR_NUMBER to \`$TARGET_BRANCH\` branch.
**Original PR:** #$PR_NUMBER
**Original Author:** @$PR_AUTHOR
**Cherry-picked commit:** $COMMIT_SHA
**Please resolve the conflicts in this PR before merging.**"
NEW_PR=$(gh pr create \
--title "$CONFLICT_TITLE" \
--body "$CONFLICT_BODY" \
--base "$TARGET_BRANCH" \
--head "$CHERRY_PICK_BRANCH" \
--label "cherry-pick")
echo "⚠️ Created conflict resolution PR $NEW_PR for $TARGET_BRANCH"
# Comment on original PR
gh pr comment $PR_NUMBER --body "⚠️ Cherry-pick to \`$TARGET_BRANCH\` has conflicts: $NEW_PR"
fi
# Clean up - go back to main branch
git checkout main
git branch -D "$CHERRY_PICK_BRANCH" 2>/dev/null || true
fi
done

View File

@@ -10,14 +10,14 @@ runs:
using: "composite"
steps:
- name: Find Comment
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v2
uses: peter-evans/find-comment@v2
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: "github-actions[bot]"
body-includes: authentik PR Installation instructions
- name: Create or update comment
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v2
uses: peter-evans/create-or-update-comment@v2
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
@@ -35,6 +35,14 @@ runs:
AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE=ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
```
For arm64, use these values:
```shell
AUTHENTIK_IMAGE=ghcr.io/goauthentik/dev-server
AUTHENTIK_TAG=${{ inputs.tag }}-arm64
AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE=ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
```
Afterwards, run the upgrade commands from the latest release notes.
</details>
<details>
@@ -46,10 +54,20 @@ runs:
authentik:
outposts:
container_image_base: ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
global:
image:
repository: ghcr.io/goauthentik/dev-server
tag: ${{ inputs.tag }}
image:
repository: ghcr.io/goauthentik/dev-server
tag: ${{ inputs.tag }}
```
For arm64, use these values:
```yaml
authentik:
outposts:
container_image_base: ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
image:
repository: ghcr.io/goauthentik/dev-server
tag: ${{ inputs.tag }}-arm64
```
Afterwards, run the upgrade commands from the latest release notes.

View File

@@ -9,14 +9,11 @@ inputs:
image-arch:
required: false
description: "Docker image arch"
release:
required: true
description: "True if this is a release build, false if this is a dev/PR build"
outputs:
shouldPush:
description: "Whether to push the image or not"
value: ${{ steps.ev.outputs.shouldPush }}
shouldBuild:
description: "Whether to build image or not"
value: ${{ steps.ev.outputs.shouldBuild }}
sha:
description: "sha"
@@ -32,40 +29,19 @@ outputs:
imageTags:
description: "Docker image tags"
value: ${{ steps.ev.outputs.imageTags }}
imageTagsJSON:
description: "Docker image tags, as a JSON array"
value: ${{ steps.ev.outputs.imageTagsJSON }}
attestImageNames:
description: "Docker image names used for attestation"
value: ${{ steps.ev.outputs.attestImageNames }}
cacheTo:
description: "cache-to value for the docker build step"
value: ${{ steps.ev.outputs.cacheTo }}
imageMainTag:
description: "Docker image main tag"
value: ${{ steps.ev.outputs.imageMainTag }}
imageMainName:
description: "Docker image main name"
value: ${{ steps.ev.outputs.imageMainName }}
imageBuildArgs:
description: "Docker image build args"
value: ${{ steps.ev.outputs.imageBuildArgs }}
runs:
using: "composite"
steps:
- name: Setup authentik env
uses: ./.github/actions/setup
with:
dependencies: "python"
- name: Generate config
id: ev
shell: bash
env:
IMAGE_NAME: ${{ inputs.image-name }}
IMAGE_ARCH: ${{ inputs.image-arch }}
RELEASE: ${{ inputs.release }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
REF: ${{ github.ref }}
run: |
uv run python3 ${{ github.action_path }}/push_vars.py
python3 ${{ github.action_path }}/push_vars.py

View File

@@ -1,50 +1,29 @@
"""Helper script to get the actual branch name, docker safe"""
import configparser
import os
from json import dumps
from sys import exit as sysexit
from time import time
from authentik import authentik_version
parser = configparser.ConfigParser()
parser.read(".bumpversion.cfg")
def must_or_fail(input: str | None, error: str) -> str:
if not input:
print(f"::error::{error}")
sysexit(1)
return input
# Decide if we should push the image or not
should_push = True
if len(os.environ.get("DOCKER_USERNAME", "")) < 1:
# Don't push if we don't have DOCKER_USERNAME, i.e. no secrets are available
should_push = False
if (
must_or_fail(os.environ.get("GITHUB_REPOSITORY"), "Repo required").lower()
== "goauthentik/authentik-internal"
):
# Don't push on the internal repo
should_push = False
should_build = str(os.environ.get("DOCKER_USERNAME", None) is not None).lower()
branch_name = os.environ["GITHUB_REF"]
if os.environ.get("GITHUB_HEAD_REF", "") != "":
branch_name = os.environ["GITHUB_HEAD_REF"]
safe_branch_name = branch_name.replace("refs/heads/", "").replace("/", "-").replace("'", "-")
image_names = must_or_fail(os.getenv("IMAGE_NAME"), "Image name required").split(",")
image_names = os.getenv("IMAGE_NAME").split(",")
image_arch = os.getenv("IMAGE_ARCH") or None
is_pull_request = bool(os.getenv("PR_HEAD_SHA"))
is_release = "dev" not in image_names[0]
sha = must_or_fail(
os.environ["GITHUB_SHA"] if not is_pull_request else os.getenv("PR_HEAD_SHA"),
"could not determine SHA",
)
sha = os.environ["GITHUB_SHA"] if not is_pull_request else os.getenv("PR_HEAD_SHA")
# 2042.1.0 or 2042.1.0-rc1
version = authentik_version()
version = parser.get("bumpversion", "current_version")
# 2042.1
version_family = ".".join(version.split("-", 1)[0].split(".")[:-1])
prerelease = "-" in version
@@ -57,11 +36,12 @@ if is_release:
]
if not prerelease:
image_tags += [
f"{name}:latest",
f"{name}:{version_family}",
]
else:
suffix = ""
if image_arch:
if image_arch and image_arch != "amd64":
suffix = f"-{image_arch}"
for name in image_names:
image_tags += [
@@ -70,43 +50,13 @@ else:
f"{name}:gh-{safe_branch_name}-{int(time())}-{sha[:7]}{suffix}", # Use by FluxCD
]
image_main_tag = image_tags[0].split(":")[-1]
def get_attest_image_names(image_with_tags: list[str]) -> str:
"""Attestation only for GHCR"""
image_tags = []
for image_name in set(name.split(":")[0] for name in image_with_tags):
if not image_name.startswith("ghcr.io"):
continue
image_tags.append(image_name)
return ",".join(set(image_tags))
# Generate `cache-to` param
cache_to = ""
if should_push:
_cache_tag = "buildcache"
if image_arch:
_cache_tag += f"-{image_arch}"
cache_to = f"type=registry,ref={get_attest_image_names(image_tags)}:{_cache_tag},mode=max"
image_build_args = []
if os.getenv("RELEASE", "false").lower() == "true":
image_build_args = [f"VERSION={os.getenv('REF')}"]
else:
image_build_args = [f"GIT_BUILD_HASH={sha}"]
image_main_tag = image_tags[0]
image_tags_rendered = ",".join(image_tags)
with open(os.environ["GITHUB_OUTPUT"], "a+", encoding="utf-8") as _output:
print(f"shouldPush={str(should_push).lower()}", file=_output)
print(f"shouldBuild={should_build}", file=_output)
print(f"sha={sha}", file=_output)
print(f"version={version}", file=_output)
print(f"prerelease={prerelease}", file=_output)
print(f"imageTags={','.join(image_tags)}", file=_output)
print(f"imageTagsJSON={dumps(image_tags)}", file=_output)
print(f"attestImageNames={get_attest_image_names(image_tags)}", file=_output)
print(f"imageTags={image_tags_rendered}", file=_output)
print(f"imageMainTag={image_main_tag}", file=_output)
print(f"imageMainName={image_tags[0]}", file=_output)
print(f"cacheTo={cache_to}", file=_output)
print(f"imageBuildArgs={"\n".join(image_build_args)}", file=_output)

View File

@@ -1,18 +1,7 @@
#!/bin/bash -x
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# Non-pushing PR
GITHUB_OUTPUT=/dev/stdout \
GITHUB_REF=ref \
GITHUB_SHA=sha \
IMAGE_NAME=ghcr.io/goauthentik/server,authentik/server \
GITHUB_REPOSITORY=goauthentik/authentik \
python $SCRIPT_DIR/push_vars.py
# Pushing PR/main
GITHUB_OUTPUT=/dev/stdout \
GITHUB_REF=ref \
GITHUB_SHA=sha \
IMAGE_NAME=ghcr.io/goauthentik/server,authentik/server \
GITHUB_REPOSITORY=goauthentik/authentik \
DOCKER_USERNAME=foo \
IMAGE_NAME=ghcr.io/goauthentik/server,beryju/authentik \
python $SCRIPT_DIR/push_vars.py

View File

@@ -2,9 +2,6 @@ name: "Setup authentik testing environment"
description: "Setup authentik testing environment"
inputs:
dependencies:
description: "List of dependencies to setup"
default: "system,python,node,go,runtime"
postgresql_version:
description: "Optional postgresql image tag"
default: "16"
@@ -12,55 +9,36 @@ inputs:
runs:
using: "composite"
steps:
- name: Install apt deps
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies, 'python') }}
- name: Install poetry & deps
shell: bash
run: |
sudo apt-get remove --purge man-db
pipx install poetry || true
sudo apt-get update
sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext libkrb5-dev krb5-kdc krb5-user krb5-admin-server
- name: Install uv
if: ${{ contains(inputs.dependencies, 'python') }}
uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v5
with:
enable-cache: true
- name: Setup python
if: ${{ contains(inputs.dependencies, 'python') }}
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v5
sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext
- name: Setup python and restore poetry
uses: actions/setup-python@v5
with:
python-version-file: "pyproject.toml"
- name: Install Python deps
if: ${{ contains(inputs.dependencies, 'python') }}
shell: bash
run: uv sync --all-extras --dev --frozen
cache: "poetry"
- name: Setup node
if: ${{ contains(inputs.dependencies, 'node') }}
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4
uses: actions/setup-node@v4
with:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
registry-url: 'https://registry.npmjs.org'
- name: Setup go
if: ${{ contains(inputs.dependencies, 'go') }}
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v5
uses: actions/setup-go@v5
with:
go-version-file: "go.mod"
- name: Setup docker cache
if: ${{ contains(inputs.dependencies, 'runtime') }}
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
if: ${{ contains(inputs.dependencies, 'runtime') }}
shell: bash
run: |
export PSQL_TAG=${{ inputs.postgresql_version }}
docker compose -f .github/actions/setup/docker-compose.yml up -d
cd web && npm i
poetry install
cd web && npm ci
- name: Generate config
if: ${{ contains(inputs.dependencies, 'python') }}
shell: uv run python {0}
shell: poetry run python {0}
run: |
from authentik.lib.generators import generate_id
from yaml import safe_dump

View File

@@ -3,7 +3,6 @@ services:
image: docker.io/library/postgres:${PSQL_TAG:-16}
volumes:
- db-data:/var/lib/postgresql/data
command: "-c log_statement=all"
environment:
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
@@ -12,28 +11,11 @@ services:
- 5432:5432
restart: always
redis:
image: docker.io/library/redis:7
image: docker.io/library/redis
ports:
- 6379:6379
restart: always
s3:
container_name: s3
image: docker.io/zenko/cloudserver
environment:
REMOTE_MANAGEMENT_DISABLE: "1"
SCALITY_ACCESS_KEY_ID: accessKey1
SCALITY_SECRET_ACCESS_KEY: secretKey1
ports:
- 8020:8000
volumes:
- s3-data:/usr/src/app/localData
- s3-metadata:/usr/scr/app/localMetadata
restart: always
volumes:
db-data:
driver: local
s3-data:
driver: local
s3-metadata:
driver: local

View File

@@ -1,28 +0,0 @@
name: "Process test results"
description: Convert test results to JUnit, add them to GitHub Actions and codecov
inputs:
flags:
description: Codecov flags
runs:
using: "composite"
steps:
- uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
with:
flags: ${{ inputs.flags }}
use_oidc: true
- uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1
with:
flags: ${{ inputs.flags }}
file: unittest.xml
use_oidc: true
- name: PostgreSQL Logs
shell: bash
run: |
if [[ $ACTIONS_RUNNER_DEBUG == 'true' || $ACTIONS_STEP_DEBUG == 'true' ]]; then
docker stop setup-postgresql-1
echo "::group::PostgreSQL Logs"
docker logs setup-postgresql-1
echo "::endgroup::"
fi

2
.github/cherry-pick-bot.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
enabled: true
preservePullRequestTitle: true

View File

@@ -1,32 +1,7 @@
akadmin
asgi
assertIn
authentik
authn
crate
docstrings
entra
goauthentik
gunicorn
hass
jwe
jwks
keypair
keypairs
kubernetes
oidc
ontext
openid
passwordless
plex
saml
scim
singed
slo
sso
totp
traefik
# https://github.com/codespell-project/codespell/issues/1224
upToDate
hass
warmup
webauthn
ontext
singed
assertIn

184
.github/dependabot.yml vendored
View File

@@ -1,17 +1,7 @@
version: 2
updates:
#region Github Actions
- package-ecosystem: "github-actions"
directories:
- /
# Required to update composite actions
# https://github.com/dependabot/dependabot-core/issues/6704
- /.github/actions/cherry-pick
- /.github/actions/setup
- /.github/actions/docker-push-variables
- /.github/actions/comment-pr-instructions
- /.github/actions/test-results
directory: "/"
schedule:
interval: daily
time: "04:00"
@@ -20,11 +10,6 @@ updates:
prefix: "ci:"
labels:
- dependencies
#endregion
#region Golang
- package-ecosystem: gomod
directory: "/"
schedule:
@@ -35,16 +20,11 @@ updates:
prefix: "core:"
labels:
- dependencies
#endregion
#region Web
- package-ecosystem: npm
directories:
- "/"
- "/web"
- "/web/packages/*"
- "/tests/wdio"
- "/web/sfe"
schedule:
interval: daily
time: "04:00"
@@ -57,113 +37,30 @@ updates:
sentry:
patterns:
- "@sentry/*"
- "@spotlightjs/*"
babel:
patterns:
- "@babel/*"
- "babel-*"
eslint:
patterns:
- "@eslint/*"
- "@typescript-eslint/*"
- "eslint-*"
- "eslint"
- "typescript-eslint"
- "eslint-*"
storybook:
patterns:
- "@storybook/*"
- "*storybook*"
bundler:
esbuild:
patterns:
- "@esbuild/*"
- "esbuild*"
- "@vitest/*"
- "vitest"
rollup:
patterns:
- "@rollup/*"
- "rollup-*"
- "rollup*"
swc:
wdio:
patterns:
- "@swc/*"
- "swc-*"
goauthentik:
patterns:
- "@goauthentik/*"
react:
patterns:
- "react"
- "react-dom"
- "@types/react"
- "@types/react-dom"
#endregion
#region NPM Packages
- package-ecosystem: npm
directories:
- "/packages/esbuild-plugin-live-reload"
- "/packages/prettier-config"
- "/packages/tsconfig"
- "/packages/docusaurus-config"
- "/packages/eslint-config"
schedule:
interval: daily
time: "04:00"
labels:
- dependencies
open-pull-requests-limit: 10
commit-message:
prefix: "core, web:"
groups:
sentry:
patterns:
- "@sentry/*"
babel:
patterns:
- "@babel/*"
- "babel-*"
eslint:
patterns:
- "@eslint/*"
- "@typescript-eslint/*"
- "eslint-*"
- "eslint"
- "typescript-eslint"
storybook:
patterns:
- "@storybook/*"
- "*storybook*"
bundler:
patterns:
- "@esbuild/*"
- "esbuild*"
- "@vitest/*"
- "vitest"
rollup:
patterns:
- "@rollup/*"
- "rollup-*"
- "rollup*"
swc:
patterns:
- "@swc/*"
- "swc-*"
goauthentik:
patterns:
- "@goauthentik/*"
react:
patterns:
- "react"
- "react-dom"
- "@types/react"
- "@types/react-dom"
#endregion
# #region Documentation
- "@wdio/*"
- package-ecosystem: npm
directory: "/website"
schedule:
@@ -178,46 +75,7 @@ updates:
docusaurus:
patterns:
- "@docusaurus/*"
- "@goauthentik/docusaurus-config"
build:
patterns:
- "@swc/*"
- "swc-*"
- "lightningcss*"
- "@rspack/binding*"
goauthentik:
patterns:
- "@goauthentik/eslint-config"
- "@goauthentik/prettier-config"
- "@goauthentik/tsconfig"
eslint:
patterns:
- "@eslint/*"
- "@typescript-eslint/*"
- "eslint-*"
- "eslint"
- "typescript-eslint"
#endregion
# AWS Lifecycle
- package-ecosystem: npm
directory: "/lifecycle/aws"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
commit-message:
prefix: "lifecycle/aws:"
labels:
- dependencies
#endregion
#region Python
- package-ecosystem: uv
- package-ecosystem: pip
directory: "/"
schedule:
interval: daily
@@ -227,15 +85,8 @@ updates:
prefix: "core:"
labels:
- dependencies
#endregion
#region Docker
- package-ecosystem: docker
directories:
- /
- /website
directory: "/"
schedule:
interval: daily
time: "04:00"
@@ -244,18 +95,3 @@ updates:
prefix: "core:"
labels:
- dependencies
- package-ecosystem: docker-compose
directories:
# - /scripts # Maybe
- /scripts/api
- /tests/e2e
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
commit-message:
prefix: "core:"
labels:
- dependencies
#endregion

View File

@@ -1,11 +1,7 @@
<!--
👋 Hi there! Welcome.
Please check the Contributing guidelines: https://docs.goauthentik.io/docs/developer-docs/#how-can-i-contribute
⚠️ IMPORTANT: Make sure you are opening this PR from a FEATURE BRANCH, not from your main branch!
If you opened this PR from your main branch, please close it and create a new feature branch instead.
For more information, see: https://docs.goauthentik.io/developer-docs/contributing/#always-use-feature-branches
Please check the Contributing guidelines: https://goauthentik.io/developer-docs/#how-can-i-contribute
-->
## Details
@@ -35,4 +31,4 @@ If changes to the frontend have been made
If applicable
- [ ] The documentation has been updated
- [ ] The documentation has been formatted (`make docs`)
- [ ] The documentation has been formatted (`make website`)

View File

@@ -1,4 +1,3 @@
---
git:
filters:
- filter_type: file

View File

@@ -1,104 +0,0 @@
---
# Re-usable workflow for a single-architecture build
name: Reusable - Single-arch Container build
on:
workflow_call:
inputs:
image_name:
required: true
type: string
image_arch:
required: true
type: string
runs-on:
required: true
type: string
registry_dockerhub:
default: false
type: boolean
registry_ghcr:
default: false
type: boolean
release:
default: false
type: boolean
outputs:
image-digest:
value: ${{ jobs.build.outputs.image-digest }}
jobs:
build:
name: Build ${{ inputs.image_arch }}
runs-on: ${{ inputs.runs-on }}
outputs:
image-digest: ${{ steps.push.outputs.digest }}
permissions:
# Needed to upload container images to ghcr.io
packages: write
# Needed for attestation
id-token: write
attestations: write
# Needed for checkout
contents: read
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
with:
image-name: ${{ inputs.image_name }}
image-arch: ${{ inputs.image_arch }}
release: ${{ inputs.release }}
- name: Login to Docker Hub
if: ${{ inputs.registry_dockerhub }}
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
with:
username: ${{ secrets.DOCKER_CORP_USERNAME }}
password: ${{ secrets.DOCKER_CORP_PASSWORD }}
- name: Login to GitHub Container Registry
if: ${{ inputs.registry_ghcr }}
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: make empty clients
if: ${{ inputs.release }}
run: |
mkdir -p ./gen-ts-api
mkdir -p ./gen-go-api
- name: Setup node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5
with:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- name: generate ts client
run: make gen-client-ts
- name: Build Docker Image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
id: push
with:
context: .
push: ${{ steps.ev.outputs.shouldPush == 'true' }}
secrets: |
GEOIPUPDATE_ACCOUNT_ID=${{ secrets.GEOIPUPDATE_ACCOUNT_ID }}
GEOIPUPDATE_LICENSE_KEY=${{ secrets.GEOIPUPDATE_LICENSE_KEY }}
build-args: |
${{ steps.ev.outputs.imageBuildArgs }}
tags: ${{ steps.ev.outputs.imageTags }}
platforms: linux/${{ inputs.image_arch }}
cache-from: type=registry,ref=${{ steps.ev.outputs.attestImageNames }}:buildcache-${{ inputs.image_arch }}
cache-to: ${{ steps.ev.outputs.cacheTo }}
- uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3
id: attest
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true

View File

@@ -1,105 +0,0 @@
---
# Re-usable workflow for a multi-architecture build
name: Reusable - Multi-arch container build
on:
workflow_call:
inputs:
image_name:
required: true
type: string
registry_dockerhub:
default: false
type: boolean
registry_ghcr:
default: true
type: boolean
release:
default: false
type: boolean
outputs: {}
jobs:
build-server-amd64:
uses: ./.github/workflows/_reusable-docker-build-single.yml
secrets: inherit
with:
image_name: ${{ inputs.image_name }}
image_arch: amd64
runs-on: ubuntu-latest
registry_dockerhub: ${{ inputs.registry_dockerhub }}
registry_ghcr: ${{ inputs.registry_ghcr }}
release: ${{ inputs.release }}
build-server-arm64:
uses: ./.github/workflows/_reusable-docker-build-single.yml
secrets: inherit
with:
image_name: ${{ inputs.image_name }}
image_arch: arm64
runs-on: ubuntu-22.04-arm
registry_dockerhub: ${{ inputs.registry_dockerhub }}
registry_ghcr: ${{ inputs.registry_ghcr }}
release: ${{ inputs.release }}
get-tags:
runs-on: ubuntu-latest
needs:
- build-server-amd64
- build-server-arm64
outputs:
tags: ${{ steps.ev.outputs.imageTagsJSON }}
shouldPush: ${{ steps.ev.outputs.shouldPush }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
with:
image-name: ${{ inputs.image_name }}
merge-server:
runs-on: ubuntu-latest
if: ${{ needs.get-tags.outputs.shouldPush == 'true' }}
needs:
- get-tags
- build-server-amd64
- build-server-arm64
strategy:
fail-fast: false
matrix:
tag: ${{ fromJson(needs.get-tags.outputs.tags) }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
with:
image-name: ${{ inputs.image_name }}
- name: Login to Docker Hub
if: ${{ inputs.registry_dockerhub }}
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
with:
username: ${{ secrets.DOCKER_CORP_USERNAME }}
password: ${{ secrets.DOCKER_CORP_PASSWORD }}
- name: Login to GitHub Container Registry
if: ${{ inputs.registry_ghcr }}
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: int128/docker-manifest-create-action@b60433fd4312d7a64a56d769b76ebe3f45cf36b4 # v2
id: build
with:
tags: ${{ matrix.tag }}
sources: |
${{ steps.ev.outputs.attestImageNames }}@${{ needs.build-server-amd64.outputs.image-digest }}
${{ steps.ev.outputs.attestImageNames }}@${{ needs.build-server-arm64.outputs.image-digest }}
- uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3
id: attest
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true

65
.github/workflows/api-py-publish.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
name: authentik-api-py-publish
on:
push:
branches: [main]
paths:
- "schema.yml"
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- id: generate_token
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/checkout@v4
with:
token: ${{ steps.generate_token.outputs.token }}
- name: Install poetry & deps
shell: bash
run: |
pipx install poetry || true
sudo apt-get update
sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext
- name: Setup python and restore poetry
uses: actions/setup-python@v5
with:
python-version-file: "pyproject.toml"
cache: "poetry"
- name: Generate API Client
run: make gen-client-py
- name: Publish package
working-directory: gen-py-api/
run: |
poetry build
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: gen-py-api/dist/
# We can't easily upgrade the API client being used due to poetry being poetry
# so we'll have to rely on dependabot
# - name: Upgrade /
# run: |
# export VERSION=$(cd gen-py-api && poetry version -s)
# poetry add "authentik_client=$VERSION" --allow-prereleases --lock
# - uses: peter-evans/create-pull-request@v6
# id: cpr
# with:
# token: ${{ steps.generate_token.outputs.token }}
# branch: update-root-api-client
# commit-message: "root: bump API Client version"
# title: "root: bump API Client version"
# body: "root: bump API Client version"
# delete-branch: true
# signoff: true
# # ID from https://api.github.com/users/authentik-automation[bot]
# author: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
# - uses: peter-evans/enable-pull-request-automerge@v3
# with:
# token: ${{ steps.generate_token.outputs.token }}
# pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
# merge-method: squash

View File

@@ -1,31 +1,23 @@
---
name: API - Publish Typescript client
name: authentik-api-ts-publish
on:
push:
branches: [main]
paths:
- "schema.yml"
workflow_dispatch:
permissions:
# Required for NPM OIDC trusted publisher
id-token: write
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- id: generate_token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
uses: tibdex/github-app-token@v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/checkout@v4
with:
token: ${{ steps.generate_token.outputs.token }}
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5
- uses: actions/setup-node@v4
with:
node-version-file: web/package.json
registry-url: "https://registry.npmjs.org"
@@ -34,19 +26,21 @@ jobs:
- name: Publish package
working-directory: gen-ts-api/
run: |
npm i
npm publish --tag generated
npm ci
npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
- name: Upgrade /web
working-directory: web
run: |
export VERSION=`node -e 'console.log(require("../gen-ts-api/package.json").version)'`
npm i @goauthentik/api@$VERSION
- name: Upgrade /web/packages/sfe
working-directory: web/packages/sfe
- name: Upgrade /web/sfe
working-directory: web/sfe
run: |
export VERSION=`node -e 'console.log(require("../gen-ts-api/package.json").version)'`
npm i @goauthentik/api@$VERSION
- uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v7
- uses: peter-evans/create-pull-request@v6
id: cpr
with:
token: ${{ steps.generate_token.outputs.token }}
@@ -58,8 +52,7 @@ jobs:
signoff: true
# ID from https://api.github.com/users/authentik-automation[bot]
author: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
labels: dependencies
- uses: peter-evans/enable-pull-request-automerge@a660677d5469627102a1c1e11409dd063606628d # v3
- uses: peter-evans/enable-pull-request-automerge@v3
with:
token: ${{ steps.generate_token.outputs.token }}
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}

View File

@@ -1,95 +0,0 @@
---
name: CI - API Docs
on:
push:
branches:
- main
- next
- version-*
pull_request:
branches:
- main
- version-*
jobs:
lint:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
command:
- prettier-check
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- name: Install Dependencies
working-directory: website/
run: npm ci
- name: Lint
working-directory: website/
run: npm run ${{ matrix.command }}
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5
with:
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
name: Install Dependencies
run: npm ci
- uses: actions/cache@a7833574556fa59680c1b7cb190c1735db73ebf0 # v4
with:
path: |
${{ github.workspace }}/website/api/.docusaurus
${{ github.workspace }}/website/api/**/.cache
key: |
${{ runner.os }}-docusaurus-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
restore-keys: |
${{ runner.os }}-docusaurus-${{ hashFiles('**/package-lock.json') }}
- name: Build API Docs via Docusaurus
working-directory: website
env:
NODE_ENV: production
run: npm run build -w api
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v4
with:
name: api-docs
path: website/api/build
retention-days: 7
deploy:
runs-on: ubuntu-latest
needs:
- lint
- build
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v5
with:
name: api-docs
path: website/api/build
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5
with:
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- name: Deploy Netlify (Production)
working-directory: website/api
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
env:
NETLIFY_SITE_ID: authentik-api-docs.netlify.app
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
run: npx netlify deploy --no-build --prod
- name: Deploy Netlify (Preview)
if: github.event_name == 'pull_request' || github.ref != 'refs/heads/main'
working-directory: website/api
env:
NETLIFY_SITE_ID: authentik-api-docs.netlify.app
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
run: |
if [ -n "${VAR}" ]; then
npx netlify deploy --no-build --alias=deploy-preview-${{ github.event.number }}
fi

View File

@@ -1,47 +0,0 @@
---
name: CI - AWS cfn
on:
push:
branches:
- main
- next
- version-*
pull_request:
branches:
- main
- version-*
env:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
jobs:
check-changes-applied:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- name: Setup authentik env
uses: ./.github/actions/setup
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5
with:
node-version-file: lifecycle/aws/package.json
cache: "npm"
cache-dependency-path: lifecycle/aws/package-lock.json
- working-directory: lifecycle/aws/
run: |
npm ci
- name: Check changes have been applied
run: |
uv run make aws-cfn
git diff --exit-code lifecycle/aws/template.yaml
ci-aws-cfn-mark:
if: always()
needs:
- check-changes-applied
runs-on: ubuntu-latest
steps:
- uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
with:
jobs: ${{ toJSON(needs) }}

View File

@@ -1,32 +0,0 @@
---
name: CI - Source code docs
on:
push:
branches:
- main
env:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
jobs:
publish-source-docs:
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- name: Setup authentik env
uses: ./.github/actions/setup
- name: generate docs
run: |
uv run make migrate
uv run ak build_source_docs
- name: Publish
env:
NETLIFY_SITE_ID: eb246b7b-1d83-4f69-89f7-01a936b4ca59
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
run: |
npm install -g netlify-cli
netlify deploy --dir=source_docs --prod

View File

@@ -1,122 +0,0 @@
---
name: CI - Docs
on:
push:
branches:
- main
- next
- version-*
pull_request:
branches:
- main
- version-*
jobs:
lint:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
command:
- prettier-check
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- name: Install dependencies
working-directory: website/
run: npm ci
- name: Lint
working-directory: website/
run: npm run ${{ matrix.command }}
build-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5
with:
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
name: Install Dependencies
run: npm ci
- name: Build Documentation via Docusaurus
working-directory: website/
run: npm run build
build-integrations:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5
with:
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
name: Install Dependencies
run: npm ci
- name: Build Integrations via Docusaurus
working-directory: website/
run: npm run build -w integrations
build-container:
runs-on: ubuntu-latest
permissions:
# Needed to upload container images to ghcr.io
packages: write
# Needed for attestation
id-token: write
attestations: write
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
with:
image-name: ghcr.io/goauthentik/dev-docs
- name: Login to Container Registry
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker Image
id: push
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
with:
tags: ${{ steps.ev.outputs.imageTags }}
file: website/Dockerfile
push: ${{ steps.ev.outputs.shouldPush == 'true' }}
platforms: linux/amd64,linux/arm64
context: .
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-docs:buildcache
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' && 'type=registry,ref=ghcr.io/goauthentik/dev-docs:buildcache,mode=max' || '' }}
- uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3
id: attest
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
ci-website-mark:
if: always()
needs:
- lint
- build-docs
- build-integrations
- build-container
runs-on: ubuntu-latest
steps:
- uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
with:
jobs: ${{ toJSON(needs) }}

View File

@@ -1,28 +0,0 @@
---
name: CI - Main daily
on:
workflow_dispatch:
schedule:
# Every night at 3am
- cron: "0 3 * * *"
jobs:
test-container:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
version:
- docs
- version-2025-4
- version-2025-2
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- run: |
current="$(pwd)"
dir="/tmp/authentik/${{ matrix.version }}"
mkdir -p $dir
cd $dir
wget https://${{ matrix.version }}.goauthentik.io/docker-compose.yml
${current}/scripts/test_docker.sh

View File

@@ -1,5 +1,5 @@
---
name: CI - Main
name: authentik-ci-main
on:
push:
@@ -17,12 +17,6 @@ env:
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
permissions:
# Needed for checkout
contents: read
# Needed for codecov OIDC token
id-token: write
jobs:
lint:
strategy:
@@ -34,60 +28,43 @@ jobs:
- codespell
- pending-migrations
- ruff
- mypy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/checkout@v4
- name: Setup authentik env
uses: ./.github/actions/setup
- name: run job
run: uv run make ci-${{ matrix.job }}
run: poetry run make ci-${{ matrix.job }}
test-migrations:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/checkout@v4
- name: Setup authentik env
uses: ./.github/actions/setup
- name: run migrations
run: uv run python -m lifecycle.migrate
test-make-seed:
runs-on: ubuntu-latest
steps:
- id: seed
run: |
echo "seed=$(printf "%d\n" "0x$(openssl rand -hex 4)")" >> "$GITHUB_OUTPUT"
outputs:
seed: ${{ steps.seed.outputs.seed }}
run: poetry run python -m lifecycle.migrate
test-migrations-from-stable:
name: test-migrations-from-stable - PostgreSQL ${{ matrix.psql }} - Run ${{ matrix.run_id }}/5
name: test-migrations-from-stable - PostgreSQL ${{ matrix.psql }}
runs-on: ubuntu-latest
timeout-minutes: 30
needs: test-make-seed
strategy:
fail-fast: false
matrix:
psql:
- 14-alpine
- 18-alpine
run_id: [1, 2, 3, 4, 5]
- 15-alpine
- 16-alpine
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: checkout stable
run: |
set -e -o pipefail
# Delete all poetry envs
rm -rf /home/runner/.cache/pypoetry
# Copy current, latest config to local
cp authentik/lib/default.yml local.env.yml
cp -R .github ..
cp -R scripts ..
# Previous stable tag
prev_stable=$(git tag --sort=version:refname | grep '^version/' | grep -vE -- '-rc[0-9]+$' | tail -n1)
# Current version family based on
current_version_family=$(cat internal/constants/VERSION | grep -vE -- 'rc[0-9]+$' || true)
if [[ -n $current_version_family ]]; then
prev_stable=$current_version_family
fi
echo "::notice::Checking out ${prev_stable} as stable version..."
git checkout ${prev_stable}
git checkout $(git tag --sort=version:refname | grep '^version/' | grep -vE -- '-rc[0-9]+$' | tail -n1)
rm -rf .github/ scripts/
mv ../.github ../scripts .
- name: Setup authentik env (stable)
@@ -95,7 +72,7 @@ jobs:
with:
postgresql_version: ${{ matrix.psql }}
- name: run migrations to stable
run: uv run python -m lifecycle.migrate
run: poetry run python -m lifecycle.migrate
- name: checkout current code
run: |
set -x
@@ -103,72 +80,64 @@ jobs:
git reset --hard HEAD
git clean -d -fx .
git checkout $GITHUB_SHA
# Delete previous poetry env
rm -rf /home/runner/.cache/pypoetry/virtualenvs/*
- name: Setup authentik env (ensure latest deps are installed)
uses: ./.github/actions/setup
with:
postgresql_version: ${{ matrix.psql }}
- name: migrate to latest
run: |
uv run python -m lifecycle.migrate
poetry run python -m lifecycle.migrate
- name: run tests
env:
# Test in the main database that we just migrated from the previous stable version
AUTHENTIK_POSTGRESQL__TEST__NAME: authentik
CI_TEST_SEED: ${{ needs.test-make-seed.outputs.seed }}
CI_RUN_ID: ${{ matrix.run_id }}
CI_TOTAL_RUNS: "5"
run: |
uv run make ci-test
- uses: ./.github/actions/test-results
if: ${{ always() }}
with:
flags: unit-migrate
poetry run make test
test-unittest:
name: test-unittest - PostgreSQL ${{ matrix.psql }} - Run ${{ matrix.run_id }}/5
name: test-unittest - PostgreSQL ${{ matrix.psql }}
runs-on: ubuntu-latest
timeout-minutes: 30
needs: test-make-seed
strategy:
fail-fast: false
matrix:
psql:
- 14-alpine
- 18-alpine
run_id: [1, 2, 3, 4, 5]
- 15-alpine
- 16-alpine
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/checkout@v4
- name: Setup authentik env
uses: ./.github/actions/setup
with:
postgresql_version: ${{ matrix.psql }}
- name: run unittest
env:
CI_TEST_SEED: ${{ needs.test-make-seed.outputs.seed }}
CI_RUN_ID: ${{ matrix.run_id }}
CI_TOTAL_RUNS: "5"
run: |
uv run make ci-test
- uses: ./.github/actions/test-results
if: ${{ always() }}
poetry run make test
poetry run coverage xml
- if: ${{ always() }}
uses: codecov/codecov-action@v4
with:
flags: unit
token: ${{ secrets.CODECOV_TOKEN }}
test-integration:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/checkout@v4
- name: Setup authentik env
uses: ./.github/actions/setup
- name: Create k8s Kind Cluster
uses: helm/kind-action@92086f6be054225fa813e0a4b13787fc9088faab # v1.13.0
uses: helm/kind-action@v1.10.0
- name: run integration
run: |
uv run coverage run manage.py test tests/integration
uv run coverage xml
- uses: ./.github/actions/test-results
if: ${{ always() }}
poetry run coverage run manage.py test tests/integration
poetry run coverage xml
- if: ${{ always() }}
uses: codecov/codecov-action@v4
with:
flags: integration
token: ${{ secrets.CODECOV_TOKEN }}
test-e2e:
name: test-e2e (${{ matrix.job.name }})
runs-on: ubuntu-latest
@@ -194,17 +163,17 @@ jobs:
- name: flows
glob: tests/e2e/test_flows*
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/checkout@v4
- name: Setup authentik env
uses: ./.github/actions/setup
- name: Setup e2e env (chrome, etc)
run: |
docker compose -f tests/e2e/docker-compose.yml up -d --quiet-pull
docker compose -f tests/e2e/docker-compose.yml up -d
- id: cache-web
uses: actions/cache@a7833574556fa59680c1b7cb190c1735db73ebf0 # v4
uses: actions/cache@v4
with:
path: web/dist
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', '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
@@ -212,17 +181,16 @@ 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 }}
uv run coverage xml
- uses: ./.github/actions/test-results
if: ${{ always() }}
poetry run coverage run manage.py test ${{ matrix.job.glob }}
poetry run coverage xml
- if: ${{ always() }}
uses: codecov/codecov-action@v4
with:
flags: e2e
token: ${{ secrets.CODECOV_TOKEN }}
ci-core-mark:
if: always()
needs:
- lint
- test-migrations
@@ -232,24 +200,59 @@ jobs:
- test-e2e
runs-on: ubuntu-latest
steps:
- uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
with:
jobs: ${{ toJSON(needs) }}
- run: echo mark
build:
permissions:
# Needed to upload container images to ghcr.io
packages: write
# Needed for attestation
id-token: write
attestations: write
# Needed for checkout
contents: read
strategy:
fail-fast: false
matrix:
arch:
- amd64
- arm64
needs: ci-core-mark
uses: ./.github/workflows/_reusable-docker-build.yml
secrets: inherit
with:
image_name: ${{ github.repository == 'goauthentik/authentik-internal' && 'ghcr.io/goauthentik/internal-server' || 'ghcr.io/goauthentik/dev-server' }}
release: false
runs-on: ubuntu-latest
permissions:
# Needed to upload contianer images to ghcr.io
packages: write
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
with:
image-name: ghcr.io/goauthentik/dev-server
image-arch: ${{ matrix.arch }}
- name: Login to Container Registry
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: generate ts client
run: make gen-client-ts
- name: Build Docker Image
uses: docker/build-push-action@v5
with:
context: .
secrets: |
GEOIPUPDATE_ACCOUNT_ID=${{ secrets.GEOIPUPDATE_ACCOUNT_ID }}
GEOIPUPDATE_LICENSE_KEY=${{ secrets.GEOIPUPDATE_LICENSE_KEY }}
tags: ${{ steps.ev.outputs.imageTags }}
push: ${{ steps.ev.outputs.shouldBuild == 'true' }}
build-args: |
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-server:buildcache
cache-to: type=registry,ref=ghcr.io/goauthentik/dev-server:buildcache,mode=max
platforms: linux/${{ matrix.arch }}
pr-comment:
needs:
- build
@@ -260,18 +263,17 @@ jobs:
pull-requests: write
timeout-minutes: 120
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
with:
image-name: ghcr.io/goauthentik/dev-server
- name: Comment on PR
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
uses: ./.github/actions/comment-pr-instructions
with:
tag: ${{ steps.ev.outputs.imageMainTag }}
tag: gh-${{ steps.ev.outputs.imageMainTag }}

View File

@@ -1,5 +1,5 @@
---
name: CI - Outpost
name: authentik-ci-outpost
on:
push:
@@ -12,17 +12,12 @@ on:
- main
- version-*
env:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
jobs:
lint-golint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: "go.mod"
- name: Prepare and generate API
@@ -34,38 +29,32 @@ jobs:
- name: Generate API
run: make gen-client-go
- name: golangci-lint
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v8
uses: golangci/golangci-lint-action@v6
with:
version: latest
version: v1.54.2
args: --timeout 5000s --verbose
skip-cache: true
test-unittest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: "go.mod"
- name: Setup authentik env
uses: ./.github/actions/setup
- name: Generate API
run: make gen-client-go
- name: prepare database
run: |
uv run make migrate
- name: Go unittests
run: |
go test -timeout 0 -v -race -coverprofile=coverage.out -covermode=atomic -cover ./...
ci-outpost-mark:
if: always()
needs:
- lint-golint
- test-unittest
runs-on: ubuntu-latest
steps:
- uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
with:
jobs: ${{ toJSON(needs) }}
- run: echo mark
build-container:
timeout-minutes: 120
needs:
@@ -80,29 +69,26 @@ jobs:
- rac
runs-on: ubuntu-latest
permissions:
# Needed to upload container images to ghcr.io
# Needed to upload contianer images to ghcr.io
packages: write
# Needed for attestation
id-token: write
attestations: write
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
uses: docker/setup-qemu-action@v3.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
uses: docker/setup-buildx-action@v3
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
with:
image-name: ghcr.io/goauthentik/dev-${{ matrix.type }}
- name: Login to Container Registry
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -110,25 +96,17 @@ jobs:
- name: Generate API
run: make gen-client-go
- name: Build Docker Image
id: push
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
uses: docker/build-push-action@v5
with:
tags: ${{ steps.ev.outputs.imageTags }}
file: ${{ matrix.type }}.Dockerfile
push: ${{ steps.ev.outputs.shouldPush == 'true' }}
push: ${{ steps.ev.outputs.shouldBuild == 'true' }}
build-args: |
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
platforms: linux/amd64,linux/arm64
context: .
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-${{ matrix.type }}:buildcache
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' && format('type=registry,ref=ghcr.io/goauthentik/dev-{0}:buildcache,mode=max', matrix.type) || '' }}
- uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3
id: attest
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
cache-to: type=registry,ref=ghcr.io/goauthentik/dev-${{ matrix.type }}:buildcache,mode=max
build-binary:
timeout-minutes: 120
needs:
@@ -145,13 +123,13 @@ jobs:
goos: [linux]
goarch: [amd64, arm64]
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
- uses: actions/setup-go@v5
with:
go-version-file: "go.mod"
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5
- uses: actions/setup-node@v4
with:
node-version-file: web/package.json
cache: "npm"

View File

@@ -1,5 +1,4 @@
---
name: CI - Web
name: authentik-ci-web
on:
push:
@@ -13,43 +12,121 @@ on:
- version-*
jobs:
lint:
lint-eslint:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
command:
- lint
- lint:lockfile
- tsc
- prettier-check
project:
- web
- tests/wdio
include:
- command: tsc
project: web
- command: lit-analyse
project: web
extra_setup: |
cd sfe/ && npm ci
exclude:
- command: lint:lockfile
project: tests/wdio
- command: tsc
project: tests/wdio
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: ${{ matrix.project }}/package.json
cache: "npm"
cache-dependency-path: ${{ matrix.project }}/package-lock.json
- working-directory: ${{ matrix.project }}/
run: |
npm ci
run: npm ci
- name: Generate API
run: make gen-client-ts
- name: Lint
- name: Eslint
working-directory: ${{ matrix.project }}/
run: npm run ${{ matrix.command }}
build:
run: npm run lint
lint-lockfile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5
- uses: actions/checkout@v4
- working-directory: web/
run: |
[ -z "$(jq -r '.packages | to_entries[] | select((.key | startswith("node_modules")) and (.value | has("resolved") | not)) | .key' < package-lock.json)" ]
lint-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- working-directory: web/
run: npm ci
- name: Generate API
run: make gen-client-ts
- name: TSC
working-directory: web/
run: npm run tsc
lint-prettier:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
project:
- web
- tests/wdio
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: ${{ matrix.project }}/package.json
cache: "npm"
cache-dependency-path: ${{ matrix.project }}/package-lock.json
- working-directory: ${{ matrix.project }}/
run: npm ci
- name: Generate API
run: make gen-client-ts
- name: prettier
working-directory: ${{ matrix.project }}/
run: npm run prettier-check
lint-lit-analyse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- working-directory: web/
run: |
npm ci
# lit-analyse doesn't understand path rewrites, so make it
# belive it's an actual module
cd node_modules/@goauthentik
ln -s ../../src/ web
- name: Generate API
run: make gen-client-ts
- name: lit-analyse
working-directory: web/
run: npm run lit-analyse
ci-web-mark:
needs:
- lint-lockfile
- lint-eslint
- lint-prettier
- lint-lit-analyse
- lint-build
runs-on: ubuntu-latest
steps:
- run: echo mark
build:
needs:
- ci-web-mark
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: web/package.json
cache: "npm"
@@ -61,31 +138,3 @@ jobs:
- name: build
working-directory: web/
run: npm run build
ci-web-mark:
if: always()
needs:
- build
- lint
runs-on: ubuntu-latest
steps:
- uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
with:
jobs: ${{ toJSON(needs) }}
test:
needs:
- ci-web-mark
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5
with:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- working-directory: web/
run: npm ci
- name: Generate API
run: make gen-client-ts
- name: test
working-directory: web/
run: npm run test || exit 0

78
.github/workflows/ci-website.yml vendored Normal file
View File

@@ -0,0 +1,78 @@
name: authentik-ci-website
on:
push:
branches:
- main
- next
- version-*
pull_request:
branches:
- main
- version-*
jobs:
lint-lockfile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- working-directory: website/
run: |
[ -z "$(jq -r '.packages | to_entries[] | select((.key | startswith("node_modules")) and (.value | has("resolved") | not)) | .key' < package-lock.json)" ]
lint-prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
run: npm ci
- name: prettier
working-directory: website/
run: npm run prettier-check
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
run: npm ci
- name: test
working-directory: website/
run: npm test
build:
runs-on: ubuntu-latest
name: ${{ matrix.job }}
strategy:
fail-fast: false
matrix:
job:
- build
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
run: npm ci
- name: build
working-directory: website/
run: npm run ${{ matrix.job }}
ci-website-mark:
needs:
- lint-lockfile
- lint-prettier
- test
- build
runs-on: ubuntu-latest
steps:
- run: echo mark

36
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: "CodeQL"
on:
push:
branches: [main, "*", next, version*]
pull_request:
branches: [main]
schedule:
- cron: "30 6 * * 5"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ["go", "javascript", "python"]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup authentik env
uses: ./.github/actions/setup
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3

View File

@@ -1,62 +0,0 @@
---
name: Gen - Compress images
on:
push:
branches:
- main
paths:
- "**.jpg"
- "**.jpeg"
- "**.png"
- "**.webp"
pull_request:
paths:
- "**.jpg"
- "**.jpeg"
- "**.png"
- "**.webp"
workflow_dispatch:
jobs:
compress:
name: compress
runs-on: ubuntu-latest
# Don't run on forks. Token will not be available. Will run on main and open a PR anyway
if: |
github.repository == 'goauthentik/authentik' &&
(github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository)
steps:
- id: generate_token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
with:
token: ${{ steps.generate_token.outputs.token }}
- name: Compress images
id: compress
uses: calibreapp/image-actions@420075c115b26f8785e293c5bd5bef0911c506e5 # main
with:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
compressOnly: ${{ github.event_name != 'pull_request' }}
- uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v7
if: "${{ github.event_name != 'pull_request' && steps.compress.outputs.markdown != '' }}"
id: cpr
with:
token: ${{ steps.generate_token.outputs.token }}
title: "*: Auto compress images"
branch-suffix: timestamp
commit-message: "*: compress images"
body: ${{ steps.compress.outputs.markdown }}
delete-branch: true
signoff: true
labels: dependencies
- uses: peter-evans/enable-pull-request-automerge@a660677d5469627102a1c1e11409dd063606628d # v3
if: "${{ github.event_name != 'pull_request' && steps.compress.outputs.markdown != '' }}"
with:
token: ${{ steps.generate_token.outputs.token }}
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
merge-method: squash

View File

@@ -1,10 +1,8 @@
---
name: Gen - Webauthn MDS
name: authentik-gen-update-webauthn-mds
on:
workflow_dispatch:
schedule:
- cron: "30 1 1,15 * *"
- cron: '30 1 1,15 * *'
env:
POSTGRES_DB: authentik
@@ -16,17 +14,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- id: generate_token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
uses: tibdex/github-app-token@v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/checkout@v4
with:
token: ${{ steps.generate_token.outputs.token }}
- name: Setup authentik env
uses: ./.github/actions/setup
- run: uv run ak update_webauthn_mds
- uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v7
- run: poetry run ak update_webauthn_mds
- uses: peter-evans/create-pull-request@v6
id: cpr
with:
token: ${{ steps.generate_token.outputs.token }}
@@ -38,8 +36,7 @@ jobs:
signoff: true
# ID from https://api.github.com/users/authentik-automation[bot]
author: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
labels: dependencies
- uses: peter-evans/enable-pull-request-automerge@a660677d5469627102a1c1e11409dd063606628d # v3
- uses: peter-evans/enable-pull-request-automerge@v3
with:
token: ${{ steps.generate_token.outputs.token }}
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}

View File

@@ -1,36 +0,0 @@
name: GH - Cherry-pick
on:
pull_request_target:
types: [closed, labeled]
jobs:
cherry-pick:
runs-on: ubuntu-latest
steps:
- id: app-token
name: Generate app token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
if: ${{ env.GH_APP_ID != '' }}
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
env:
GH_APP_ID: ${{ secrets.GH_APP_ID }}
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
if: ${{ steps.app-token.outcome != 'skipped' }}
with:
fetch-depth: 0
token: "${{ steps.app-token.outputs.token }}"
- id: get-user-id
if: ${{ steps.app-token.outcome != 'skipped' }}
name: Get GitHub app user ID
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: "${{ steps.app-token.outputs.token }}"
- uses: ./.github/actions/cherry-pick
if: ${{ steps.app-token.outcome != 'skipped' }}
with:
token: ${{ steps.app-token.outputs.token }}
git_user: ${{ steps.app-token.outputs.app-slug }}[bot]
git_user_email: '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com'

View File

@@ -1,39 +0,0 @@
---
# See https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries
name: GH - Cleanup actions cache after PR is closed
on:
pull_request:
types:
- closed
permissions:
# Permission to delete cache
actions: write
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- name: Cleanup
run: |
gh extension install actions/gh-actions-cache
REPO=${{ github.repository }}
BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge"
echo "Fetching list of cache key"
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 )
# Setting this to not fail the workflow while deleting cache keys.
set +e
echo "Deleting caches..."
for cacheKey in $cacheKeysForPR; do
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,32 +0,0 @@
---
name: GH - GHCR retention
on:
# schedule:
# - cron: "0 0 * * *" # every day at midnight
workflow_dispatch:
inputs:
dry-run:
type: boolean
description: Enable dry-run mode
jobs:
clean-ghcr:
name: Delete old unused container images
runs-on: ubuntu-latest
steps:
- id: generate_token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Delete 'dev' containers older than a week
uses: snok/container-retention-policy@3b0972b2276b171b212f8c4efbca59ebba26eceb # v3.0.1
with:
image-names: dev-server,dev-ldap,dev-proxy
image-tags: "!gh-next,!gh-main"
cut-off: One week ago UTC
account: goauthentik
tag-selection: untagged
token: ${{ steps.generate_token.outputs.token }}
dry-run: ${{ inputs.dry-run }}

38
.github/workflows/gha-cache-cleanup.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
---
# See https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries
name: Cleanup cache after PR is closed
on:
pull_request:
types:
- closed
permissions:
# Permission to delete cache
actions: write
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Cleanup
run: |
gh extension install actions/gh-actions-cache
REPO=${{ github.repository }}
BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge"
echo "Fetching list of cache key"
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 )
# Setting this to not fail the workflow while deleting cache keys.
set +e
echo "Deleting caches..."
for cacheKey in $cacheKeysForPR; do
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

27
.github/workflows/ghcr-retention.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: ghcr-retention
on:
# schedule:
# - cron: "0 0 * * *" # every day at midnight
workflow_dispatch:
jobs:
clean-ghcr:
name: Delete old unused container images
runs-on: ubuntu-latest
steps:
- id: generate_token
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Delete 'dev' containers older than a week
uses: snok/container-retention-policy@v2
with:
image-names: dev-server,dev-ldap,dev-proxy
cut-off: One week ago UTC
account-type: org
org-name: goauthentik
untagged-only: false
token: ${{ steps.generate_token.outputs.token }}
skip-tags: gh-next,gh-main

61
.github/workflows/image-compress.yml vendored Normal file
View File

@@ -0,0 +1,61 @@
---
name: authentik-compress-images
on:
push:
branches:
- main
paths:
- "**.jpg"
- "**.jpeg"
- "**.png"
- "**.webp"
pull_request:
paths:
- "**.jpg"
- "**.jpeg"
- "**.png"
- "**.webp"
workflow_dispatch:
jobs:
compress:
name: compress
runs-on: ubuntu-latest
# Don't run on forks. Token will not be available. Will run on main and open a PR anyway
if: |
github.repository == 'goauthentik/authentik' &&
(github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository)
steps:
- id: generate_token
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/checkout@v4
with:
token: ${{ steps.generate_token.outputs.token }}
- name: Compress images
id: compress
uses: calibreapp/image-actions@main
with:
githubToken: ${{ steps.generate_token.outputs.token }}
compressOnly: ${{ github.event_name != 'pull_request' }}
- uses: peter-evans/create-pull-request@v6
if: "${{ github.event_name != 'pull_request' && steps.compress.outputs.markdown != '' }}"
id: cpr
with:
token: ${{ steps.generate_token.outputs.token }}
title: "*: Auto compress images"
branch-suffix: timestamp
commit-messsage: "*: compress images"
body: ${{ steps.compress.outputs.markdown }}
delete-branch: true
signoff: true
- uses: peter-evans/enable-pull-request-automerge@v3
if: "${{ github.event_name != 'pull_request' && steps.compress.outputs.markdown != '' }}"
with:
token: ${{ steps.generate_token.outputs.token }}
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
merge-method: squash

View File

@@ -1,55 +0,0 @@
---
name: Packages - Publish NPM packages
on:
push:
branches: [main]
paths:
- packages/tsconfig/**
- packages/eslint-config/**
- packages/prettier-config/**
- packages/docusaurus-config/**
- packages/esbuild-plugin-live-reload/**
workflow_dispatch:
permissions:
# Required for NPM OIDC trusted publisher
id-token: write
contents: read
jobs:
publish:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
package:
# The order of the `*config` packages should not be changed, as they depend on each other.
- packages/tsconfig
- packages/eslint-config
- packages/prettier-config
- packages/docusaurus-config
- packages/esbuild-plugin-live-reload
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
with:
fetch-depth: 2
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5
with:
node-version-file: ${{ matrix.package }}/package.json
registry-url: "https://registry.npmjs.org"
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # 24d32ffd492484c1d75e0c0b894501ddb9d30d62
with:
files: |
${{ matrix.package }}/package.json
- name: Install Dependencies
run: npm ci
- name: Publish package
if: steps.changed-files.outputs.any_changed == 'true'
working-directory: ${{ matrix.package }}
run: |
npm ci
npm run build
npm publish

View File

@@ -0,0 +1,31 @@
name: authentik-publish-source-docs
on:
push:
branches:
- main
env:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
jobs:
publish-source-docs:
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- name: Setup authentik env
uses: ./.github/actions/setup
- name: generate docs
run: |
poetry run make migrate
poetry run ak build_source_docs
- name: Publish
uses: netlify/actions/cli@master
with:
args: deploy --dir=source_docs --prod
env:
NETLIFY_SITE_ID: eb246b7b-1d83-4f69-89f7-01a936b4ca59
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}

View File

@@ -1,37 +0,0 @@
---
name: QA - CodeQL
on:
push:
branches: [main, next, version*]
pull_request:
branches: [main]
schedule:
- cron: "30 6 * * 5"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ["go", "javascript", "python"]
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- name: Setup authentik env
uses: ./.github/actions/setup
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4

View File

@@ -1,30 +0,0 @@
---
name: QA - Semgrep
on:
workflow_dispatch: {}
pull_request: {}
push:
branches:
- main
- master
paths:
- .github/workflows/qa-semgrep.yml
schedule:
# random HH:MM to avoid a load spike on GitHub Actions at 00:00
- cron: '12 15 * * *'
jobs:
semgrep:
name: semgrep/ci
runs-on: ubuntu-latest
permissions:
contents: read
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
container:
image: semgrep/semgrep
if: (github.actor != 'dependabot[bot]')
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- run: semgrep ci

View File

@@ -1,86 +0,0 @@
---
name: Release - Branch-off
on:
workflow_dispatch:
inputs:
next_version:
description: Next major version (for example, if releasing 2042.2, this is 2042.4)
required: true
type: string
env:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
jobs:
check-inputs:
name: Check inputs validity
runs-on: ubuntu-latest
steps:
- run: |
echo "${{ inputs.next_version }}" | grep -E "^[0-9]{4}\.[0-9]{1,2}$"
branch-off:
name: Branch-off
needs:
- check-inputs
runs-on: ubuntu-latest
steps:
- id: app-token
name: Generate app token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Checkout main
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
with:
ref: main
token: "${{ steps.app-token.outputs.token }}"
- name: Setup authentik env
uses: ./.github/actions/setup
with:
dependencies: python
- name: Create version branch
env:
GH_TOKEN: "${{ steps.app-token.outputs.token }}"
run: |
current_major_version="$(uv version --short | grep -oE "^[0-9]{4}\.[0-9]{1,2}")"
git checkout -b "version-${current_major_version}"
git push origin "version-${current_major_version}"
gh label create "backport/version-${current_major_version}" --description "Add this label to PRs to backport changes to version-${current_major_version}" --color "fbca04"
bump-version-pr:
name: Open version bump PR
needs:
- branch-off
runs-on: ubuntu-latest
steps:
- id: generate_token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Checkout main
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
with:
ref: main
token: ${{ steps.generate_token.outputs.token }}
- name: Setup authentik env
uses: ./.github/actions/setup
- name: Run migrations
run: make migrate
- name: Bump version
run: "make bump version=${{ inputs.next_version }}.0-rc1"
- name: Create pull request
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v7
with:
token: ${{ steps.generate_token.outputs.token }}
branch: release-bump-${{ inputs.next_version }}
commit-message: "root: bump version to ${{ inputs.next_version }}.0-rc1"
title: "root: bump version to ${{ inputs.next_version }}.0-rc1"
body: "root: bump version to ${{ inputs.next_version }}.0-rc1"
delete-branch: true
signoff: true
# ID from https://api.github.com/users/authentik-automation[bot]
author: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>

View File

@@ -1,5 +1,4 @@
---
name: Release - Update next branch
name: authentik-on-release-next-branch
on:
schedule:
@@ -15,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
environment: internal-production
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/checkout@v4
with:
ref: main
- run: |

View File

@@ -1,5 +1,5 @@
---
name: Release - On publish
name: authentik-on-release
on:
release:
@@ -7,73 +7,53 @@ on:
jobs:
build-server:
uses: ./.github/workflows/_reusable-docker-build.yml
secrets: inherit
permissions:
contents: read
# Needed to upload container images to ghcr.io
packages: write
# Needed for attestation
id-token: write
attestations: write
with:
image_name: ghcr.io/goauthentik/server,authentik/server
release: true
registry_dockerhub: true
registry_ghcr: true
build-docs:
runs-on: ubuntu-latest
permissions:
contents: read
# Needed to upload container images to ghcr.io
# Needed to upload contianer images to ghcr.io
packages: write
# Needed for attestation
id-token: write
attestations: write
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
uses: docker/setup-qemu-action@v3.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
uses: docker/setup-buildx-action@v3
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
with:
image-name: ghcr.io/goauthentik/docs
image-name: ghcr.io/goauthentik/server,beryju/authentik
- name: Docker Login Registry
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: make empty clients
run: |
mkdir -p ./gen-ts-api
mkdir -p ./gen-go-api
- name: Build Docker Image
id: push
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
uses: docker/build-push-action@v5
with:
tags: ${{ steps.ev.outputs.imageTags }}
file: website/Dockerfile
push: true
platforms: linux/amd64,linux/arm64
context: .
- uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3
id: attest
if: true
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
push: true
secrets: |
GEOIPUPDATE_ACCOUNT_ID=${{ secrets.GEOIPUPDATE_ACCOUNT_ID }}
GEOIPUPDATE_LICENSE_KEY=${{ secrets.GEOIPUPDATE_LICENSE_KEY }}
tags: ${{ steps.ev.outputs.imageTags }}
platforms: linux/amd64,linux/arm64
build-outpost:
runs-on: ubuntu-latest
permissions:
contents: read
# Needed to upload container images to ghcr.io
# Needed to upload contianer images to ghcr.io
packages: write
# Needed for attestation
id-token: write
attestations: write
strategy:
fail-fast: false
matrix:
@@ -83,53 +63,44 @@ jobs:
- radius
- rac
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: "go.mod"
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
uses: docker/setup-qemu-action@v3.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
uses: docker/setup-buildx-action@v3
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
with:
image-name: ghcr.io/goauthentik/${{ matrix.type }},authentik/${{ matrix.type }}
image-name: ghcr.io/goauthentik/${{ matrix.type }},beryju/authentik-${{ matrix.type }}
- name: make empty clients
run: |
mkdir -p ./gen-ts-api
mkdir -p ./gen-go-api
- name: Docker Login Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_CORP_USERNAME }}
password: ${{ secrets.DOCKER_CORP_PASSWORD }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker Image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
id: push
uses: docker/build-push-action@v5
with:
push: true
build-args: |
VERSION=${{ github.ref }}
tags: ${{ steps.ev.outputs.imageTags }}
file: ${{ matrix.type }}.Dockerfile
platforms: linux/amd64,linux/arm64
context: .
- uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3
id: attest
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
build-outpost-binary:
timeout-minutes: 120
runs-on: ubuntu-latest
@@ -146,11 +117,11 @@ jobs:
goos: [linux, darwin]
goarch: [amd64, arm64]
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: "go.mod"
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5
- uses: actions/setup-node@v4
with:
node-version-file: web/package.json
cache: "npm"
@@ -168,33 +139,12 @@ jobs:
export CGO_ENABLED=0
go build -tags=outpost_static_embed -v -o ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }} ./cmd/${{ matrix.type }}
- name: Upload binaries to release
uses: svenstaro/upload-release-action@6b7fa9f267e90b50a19fef07b3596790bb941741 # v2
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}
asset_name: authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}
tag: ${{ github.ref }}
upload-aws-cfn-template:
permissions:
# Needed for AWS login
id-token: write
contents: read
needs:
- build-server
- build-outpost
env:
AWS_REGION: eu-central-1
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5
with:
role-to-assume: "arn:aws:iam::016170277896:role/github_goauthentik_authentik"
aws-region: ${{ env.AWS_REGION }}
- name: Upload template
run: |
aws s3 cp --acl=public-read lifecycle/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.${{ github.ref }}.yaml
aws s3 cp --acl=public-read lifecycle/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.latest.yaml
test-release:
needs:
- build-server
@@ -202,14 +152,14 @@ jobs:
- build-outpost-binary
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/checkout@v4
- name: Run test suite in final docker images
run: |
echo "PG_PASS=$(openssl rand 32 | base64 -w 0)" >> .env
echo "AUTHENTIK_SECRET_KEY=$(openssl rand 32 | base64 -w 0)" >> .env
echo "PG_PASS=$(openssl rand 32 | base64)" >> .env
echo "AUTHENTIK_SECRET_KEY=$(openssl rand 32 | base64)" >> .env
docker compose pull -q
docker compose up --no-start
docker compose start postgresql
docker compose start postgresql redis
docker compose run -u root server test-all
sentry-release:
needs:
@@ -218,27 +168,27 @@ jobs:
- build-outpost-binary
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- uses: actions/checkout@v4
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
with:
image-name: ghcr.io/goauthentik/server
- name: Get static files from docker image
run: |
docker pull ${{ steps.ev.outputs.imageMainName }}
container=$(docker container create ${{ steps.ev.outputs.imageMainName }})
docker pull ${{ steps.ev.outputs.imageMainTag }}
container=$(docker container create ${{ steps.ev.outputs.imageMainTag }})
docker cp ${container}:web/ .
- name: Create a Sentry.io release
uses: getsentry/action-release@128c5058bbbe93c8e02147fe0a9c713f166259a6 # v3
uses: getsentry/action-release@v1
continue-on-error: true
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: authentik-security-inc
SENTRY_PROJECT: authentik
with:
release: authentik@${{ steps.ev.outputs.version }}
version: authentik@${{ steps.ev.outputs.version }}
sourcemaps: "./web/dist"
url_prefix: "~/static/dist"

View File

@@ -1,197 +1,48 @@
---
name: Release - Tag new version
name: authentik-on-tag
on:
workflow_dispatch:
inputs:
version:
description: Version
required: true
type: string
release_reason:
description: Release reason
required: true
type: choice
options:
- bugfix
- feature
- security
- other
- prerelease
env:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
push:
tags:
- "version/*"
jobs:
check-inputs:
name: Check inputs validity
build:
name: Create Release from Tag
runs-on: ubuntu-latest
steps:
- id: check
- uses: actions/checkout@v4
- name: Pre-release test
run: |
echo "${{ inputs.version }}" | grep -E '^[0-9]{4}\.(0?[1-9]|1[0-2])\.[0-9]+(-rc[0-9]+)?$'
echo "major_version=${{ inputs.version }}" | grep -oE "^major_version=[0-9]{4}\.[0-9]{1,2}" >> "$GITHUB_OUTPUT"
- id: changelog-url
run: |
if [ "${{ inputs.release_reason }}" = "feature" ]; then
changelog_url="https://docs.goauthentik.io/docs/releases/${{ steps.check.outputs.major_version }}"
elif [ "${{ inputs.release_reason }}" = "prerelease" ]; then
changelog_url="https://next.goauthentik.io/docs/releases/${{ steps.check.outputs.major_version }}"
else
changelog_url="https://docs.goauthentik.io/docs/releases/${{ steps.check.outputs.major_version }}#fixed-in-$(echo -n ${{ inputs.version }} | sed 's/\.//g')"
fi
echo "changelog_url=${changelog_url}" >> "$GITHUB_OUTPUT"
outputs:
major_version: "${{ steps.check.outputs.major_version }}"
changelog_url: "${{ steps.changelog-url.outputs.changelog_url }}"
test:
name: Pre-release test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
- run: make test-docker
bump-authentik:
name: Bump authentik version
needs:
- check-inputs
- test
runs-on: ubuntu-latest
steps:
- id: app-token
name: Generate app token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
echo "PG_PASS=$(openssl rand 32 | base64)" >> .env
echo "AUTHENTIK_SECRET_KEY=$(openssl rand 32 | base64)" >> .env
docker buildx install
mkdir -p ./gen-ts-api
docker build -t testing:latest .
echo "AUTHENTIK_IMAGE=testing" >> .env
echo "AUTHENTIK_TAG=latest" >> .env
docker compose up --no-start
docker compose start postgresql redis
docker compose run -u root server test-all
- id: generate_token
uses: tibdex/github-app-token@v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- id: get-user-id
name: Get GitHub app user ID
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:
GH_TOKEN: "${{ steps.app-token.outputs.token }}"
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
with:
ref: "version-${{ needs.check-inputs.outputs.major_version }}"
token: "${{ steps.app-token.outputs.token }}"
- name: Setup authentik env
uses: ./.github/actions/setup
- name: Run migrations
run: make migrate
- name: Bump version
run: "make bump version=${{ inputs.version }}"
- name: Commit and push
run: |
# ID from https://api.github.com/users/authentik-automation[bot]
git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]'
git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com'
git commit -a -m "release: ${{ inputs.version }}" --allow-empty
git tag "version/${{ inputs.version }}" HEAD -m "version/${{ inputs.version }}"
git push --follow-tags
image-name: ghcr.io/goauthentik/server
- name: Create Release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
id: create_release
uses: actions/create-release@v1.1.4
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
with:
token: "${{ steps.app-token.outputs.token }}"
tag_name: "version/${{ inputs.version }}"
name: Release ${{ inputs.version }}
tag_name: ${{ github.ref }}
release_name: Release ${{ steps.ev.outputs.version }}
draft: true
prerelease: ${{ inputs.release_reason == 'prerelease' }}
generate_release_notes: true
body: |
See ${{ needs.check-inputs.outputs.changelog_url }}
bump-helm:
name: Bump Helm version
if: ${{ inputs.release_reason != 'prerelease' }}
needs:
- bump-authentik
runs-on: ubuntu-latest
steps:
- id: app-token
name: Generate app token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
repositories: helm
- id: get-user-id
name: Get GitHub app user ID
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: "${{ steps.app-token.outputs.token }}"
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
with:
repository: "${{ github.repository_owner }}/helm"
token: "${{ steps.app-token.outputs.token }}"
- name: Bump version
run: |
sed -i 's/^version: .*/version: ${{ inputs.version }}/' charts/authentik/Chart.yaml
sed -i 's/^appVersion: .*/appVersion: ${{ inputs.version }}/' charts/authentik/Chart.yaml
sed -i 's/upgrade to authentik .*/upgrade to authentik ${{ inputs.version }}/' charts/authentik/Chart.yaml
sed -E -i 's/[0-9]{4}\.[0-9]{1,2}\.[0-9]+$/${{ inputs.version }}/' charts/authentik/Chart.yaml
./scripts/helm-docs.sh
- name: Create pull request
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v7
with:
token: "${{ steps.app-token.outputs.token }}"
branch: bump-${{ inputs.version }}
commit-message: "charts/authentik: bump to ${{ inputs.version }}"
title: "charts/authentik: bump to ${{ inputs.version }}"
body: "charts/authentik: bump to ${{ inputs.version }}"
delete-branch: true
signoff: true
author: "${{ steps.app-token.outputs.app-slug }}[bot] <${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>"
bump-version:
name: Bump version repository
if: ${{ inputs.release_reason != 'prerelease' }}
needs:
- check-inputs
- bump-authentik
runs-on: ubuntu-latest
steps:
- id: app-token
name: Generate app token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
repositories: version
- id: get-user-id
name: Get GitHub app user ID
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: "${{ steps.app-token.outputs.token }}"
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
with:
repository: "${{ github.repository_owner }}/version"
token: "${{ steps.app-token.outputs.token }}"
- name: Bump version
if: "${{ inputs.release_reason == 'feature' }}"
run: |
changelog_url="https://docs.goauthentik.io/docs/releases/${{ needs.check-inputs.outputs.major_version }}"
jq \
--arg version "${{ inputs.version }}" \
--arg changelog "See ${changelog_url}" \
--arg changelog_url "${changelog_url}" \
'.stable.version = $version | .stable.changelog = $changelog | .stable.changelog_url = $changelog_url' version.json > version.new.json
mv version.new.json version.json
- name: Bump version
if: "${{ inputs.release_reason != 'feature' }}"
run: |
changelog_url="https://docs.goauthentik.io/docs/releases/${{ needs.check-inputs.outputs.major_version }}#fixed-in-$(echo -n ${{ inputs.version}} | sed 's/\.//g')"
jq \
--arg version "${{ inputs.version }}" \
--arg changelog "See ${changelog_url}" \
--arg changelog_url "${changelog_url}" \
'.stable.version = $version | .stable.changelog = $changelog | .stable.changelog_url = $changelog_url' version.json > version.new.json
mv version.new.json version.json
- name: Create pull request
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v7
with:
token: "${{ steps.app-token.outputs.token }}"
branch: bump-${{ inputs.version }}
commit-message: "version: bump to ${{ inputs.version }}"
title: "version: bump to ${{ inputs.version }}"
body: "version: bump to ${{ inputs.version }}"
delete-branch: true
signoff: true
author: "${{ steps.app-token.outputs.app-slug }}[bot] <${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>"
prerelease: ${{ steps.ev.outputs.prerelease == 'true' }}

View File

@@ -1,9 +1,8 @@
---
name: Repo - Mark and close stale issues
name: 'authentik-repo-stale'
on:
schedule:
- cron: "30 1 * * *"
- cron: '30 1 * * *'
workflow_dispatch:
permissions:
@@ -15,17 +14,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- id: generate_token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
uses: tibdex/github-app-token@v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/stale@v9
with:
repo-token: ${{ steps.generate_token.outputs.token }}
days-before-stale: 60
days-before-close: 7
exempt-issue-labels: pinned,security,pr_wanted,enhancement,bug/confirmed,enhancement/confirmed,question,status/reviewing
stale-issue-label: status/stale
stale-issue-label: wontfix
stale-issue-message: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you

View File

@@ -1,5 +1,4 @@
---
name: Translation - Post advice
name: authentik-translation-advice
on:
pull_request:
@@ -20,14 +19,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Find Comment
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4
uses: peter-evans/find-comment@v3
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: "github-actions[bot]"
body-includes: authentik translations instructions
- name: Create or update comment
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5
uses: peter-evans/create-or-update-comment@v4
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}

View File

@@ -1,14 +1,9 @@
---
name: Translation - Extract and compile
name: authentik-backend-translate-extract-compile
on:
schedule:
- cron: "0 0 * * *" # every day at midnight
workflow_dispatch:
pull_request:
branches:
- main
- version-*
env:
POSTGRES_DB: authentik
@@ -20,31 +15,24 @@ jobs:
runs-on: ubuntu-latest
steps:
- id: generate_token
if: ${{ github.event_name != 'pull_request' }}
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
uses: tibdex/github-app-token@v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
if: ${{ github.event_name != 'pull_request' }}
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/checkout@v4
with:
token: ${{ steps.generate_token.outputs.token }}
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
if: ${{ github.event_name == 'pull_request' }}
- name: Setup authentik env
uses: ./.github/actions/setup
- name: Generate API
run: make gen-client-ts
- name: run extract
run: |
uv run make i18n-extract
poetry run make i18n-extract
- name: run compile
run: |
uv run ak compilemessages
poetry run ak compilemessages
make web-check-compile
- name: Create Pull Request
if: ${{ github.event_name != 'pull_request' }}
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v7
uses: peter-evans/create-pull-request@v6
with:
token: ${{ steps.generate_token.outputs.token }}
branch: extract-compile-backend-translation
@@ -53,6 +41,3 @@ jobs:
body: "core, web: update translations"
delete-branch: true
signoff: true
labels: dependencies
# ID from https://api.github.com/users/authentik-automation[bot]
author: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>

View File

@@ -0,0 +1,49 @@
# Rename transifex pull requests to have a correct naming
# Also enables auto squash-merge
name: authentik-translation-transifex-rename
on:
pull_request:
types: [opened, reopened]
permissions:
# Permission to rename PR
pull-requests: write
jobs:
rename_pr:
runs-on: ubuntu-latest
if: ${{ github.event.pull_request.user.login == 'transifex-integration[bot]'}}
steps:
- id: generate_token
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Get current title
id: title
env:
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
run: |
title=$(curl -q -L \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/${GITHUB_REPOSITORY}/pulls/${{ github.event.pull_request.number }} | jq -r .title)
echo "title=${title}" >> "$GITHUB_OUTPUT"
- name: Rename
env:
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
run: |
curl -L \
-X PATCH \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/${GITHUB_REPOSITORY}/pulls/${{ github.event.pull_request.number }} \
-d "{\"title\":\"translate: ${{ steps.title.outputs.title }}\"}"
- uses: peter-evans/enable-pull-request-automerge@v3
with:
token: ${{ steps.generate_token.outputs.token }}
pull-request-number: ${{ github.event.pull_request.number }}
merge-method: squash

15
.gitignore vendored
View File

@@ -11,10 +11,6 @@ local_settings.py
db.sqlite3
media
# Node
node_modules
# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/
# in your Git repository. Update and uncomment the following line accordingly.
# <django-project-name>/staticfiles/
@@ -37,7 +33,6 @@ eggs/
lib64/
parts/
dist/
out/
sdist/
var/
wheels/
@@ -72,7 +67,7 @@ unittest.xml
# Translations
# Have to include binary mo files as they are annoying to compile at build time
# since a full postgres instance is required
# since a full postgres and redis instance are required
# *.mo
# Django stuff:
@@ -100,6 +95,9 @@ ipython_config.py
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
@@ -163,6 +161,8 @@ dmypy.json
# pyenv
# celery beat schedule file
# SageMath parsed files
# Environments
@@ -209,6 +209,3 @@ source_docs/
### Golang ###
/vendor/
### Docker ###
docker-compose.override.yml

View File

@@ -1,52 +0,0 @@
# Prettier Ignorefile
## Static Files
**/LICENSE
authentik/stages/**/*
## Build asset directories
coverage
dist
out
.docusaurus
# TODO Replace after moving website to docs
website/api/reference
## Environment
*.env
## Secrets
*.secrets
## Yarn
.yarn/**/*
## Node
node_modules
coverage
## Vendored files
vendored
*.min.js
## Configs
*.log
*.yaml
*.yml
# Templates
# TODO: Rename affected files to *.template.* or similar.
*.html
*.mdx
*.md
## Import order matters
poly.ts
src/locale-codes.ts
src/locales/
# Storybook
storybook-static/
.storybook/css-import-maps*

View File

@@ -2,7 +2,6 @@
"recommendations": [
"bashmish.es6-string-css",
"bpruitt-goddard.mermaid-markdown-syntax-highlighting",
"charliermarsh.ruff",
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"esbenp.prettier-vscode",
@@ -11,10 +10,10 @@
"Gruntfuggly.todo-tree",
"mechatroner.rainbow-csv",
"ms-python.black-formatter",
"ms-python.black-formatter",
"ms-python.debugpy",
"charliermarsh.ruff",
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.black-formatter",
"redhat.vscode-yaml",
"Tobermory.es6-string-html",
"unifiedjs.vscode-mdx",

66
.vscode/launch.json vendored
View File

@@ -2,76 +2,26 @@
"version": "0.2.0",
"configurations": [
{
"name": "Debug: Attach Server Core",
"type": "debugpy",
"name": "Python: PDB attach Server",
"type": "python",
"request": "attach",
"connect": {
"host": "localhost",
"port": 9901
"port": 6800
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "."
}
],
"justMyCode": true,
"django": true
},
{
"name": "Debug: Attach Worker",
"type": "debugpy",
"name": "Python: PDB attach Worker",
"type": "python",
"request": "attach",
"connect": {
"host": "localhost",
"port": 9901
"port": 6900
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "."
}
],
"justMyCode": true,
"django": true
},
{
"name": "Debug: Start Server Router",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cmd/server",
"cwd": "${workspaceFolder}"
},
{
"name": "Debug: Start LDAP Outpost",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cmd/ldap",
"cwd": "${workspaceFolder}"
},
{
"name": "Debug: Start Proxy Outpost",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cmd/proxy",
"cwd": "${workspaceFolder}"
},
{
"name": "Debug: Start RAC Outpost",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cmd/rac",
"cwd": "${workspaceFolder}"
},
{
"name": "Debug: Start Radius Outpost",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cmd/radius",
"cwd": "${workspaceFolder}"
}
]
}

62
.vscode/settings.json vendored
View File

@@ -1,42 +1,41 @@
{
"[css]": {
"editor.minimap.markSectionHeaderRegex": "#\\bregion\\s*(?<separator>-?)\\s*(?<label>.*)\\*/$"
},
"[makefile]": {
"editor.minimap.markSectionHeaderRegex": "^#{25}\n##\\s\\s*(?<separator>-?)\\s*(?<label>[^\n]*)\n#{25}$"
},
"[dockerfile]": {
"editor.minimap.markSectionHeaderRegex": "\\bStage\\s*\\d:(?<separator>-?)\\s*(?<label>.*)$"
},
"[jsonc]": {
"editor.minimap.markSectionHeaderRegex": "#\\bregion\\s*(?<separator>-?)\\s*(?<label>.*)$"
},
"[xml]": {
"editor.minimap.markSectionHeaderRegex": "<!--\\s*#\\bregion\\s*(?<separator>-?)\\s*(?<label>.*)\\s*-->"
},
"cSpell.words": [
"akadmin",
"asgi",
"authentik",
"authn",
"entra",
"goauthentik",
"jwks",
"kubernetes",
"oidc",
"openid",
"passwordless",
"plex",
"saml",
"scim",
"slo",
"sso",
"totp",
"traefik",
"webauthn",
],
"todo-tree.tree.showCountsInTree": true,
"todo-tree.tree.showBadges": true,
"yaml.customTags": [
"!Condition sequence",
"!Context scalar",
"!Enumerate sequence",
"!Env scalar",
"!Env sequence",
"!File scalar",
"!File sequence",
"!Find sequence",
"!FindObject sequence",
"!Format sequence",
"!If sequence",
"!Index scalar",
"!KeyOf scalar",
"!Value scalar",
"!AtIndex scalar",
"!ParseJSON scalar"
"!Context scalar",
"!Context sequence",
"!Format sequence",
"!Condition sequence",
"!Env sequence",
"!Env scalar",
"!If sequence"
],
"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"
@@ -52,9 +51,6 @@
"go.testFlags": [
"-count=1"
],
"go.testEnvVars": {
"WORKSPACE_DIR": "${workspaceFolder}"
},
"github-actions.workflows.pinned.workflows": [
".github/workflows/ci-main.yml"
]

74
.vscode/tasks.json vendored
View File

@@ -2,67 +2,85 @@
"version": "2.0.0",
"tasks": [
{
"label": "authentik/core: make",
"command": "uv",
"args": ["run", "make", "lint-fix", "lint"],
"presentation": {
"panel": "new"
},
"group": "test"
"label": "authentik[core]: format & test",
"command": "poetry",
"args": [
"run",
"make"
],
"group": "build",
},
{
"label": "authentik/core: run",
"command": "uv",
"args": ["run", "ak", "server"],
"label": "authentik[core]: run",
"command": "poetry",
"args": [
"run",
"make",
"run",
],
"group": "build",
"presentation": {
"panel": "dedicated",
"group": "running"
}
},
},
{
"label": "authentik/web: make",
"label": "authentik[web]: format",
"command": "make",
"args": ["web"],
"group": "build"
"group": "build",
},
{
"label": "authentik/web: watch",
"label": "authentik[web]: watch",
"command": "make",
"args": ["web-watch"],
"group": "build",
"presentation": {
"panel": "dedicated",
"group": "running"
}
},
},
{
"label": "authentik: install",
"command": "make",
"args": ["install", "-j4"],
"group": "build"
"args": ["install"],
"group": "build",
},
{
"label": "authentik/docs: make",
"command": "make",
"args": ["docs"],
"group": "build"
"label": "authentik: i18n-extract",
"command": "poetry",
"args": [
"run",
"make",
"i18n-extract"
],
"group": "build",
},
{
"label": "authentik/docs: watch",
"label": "authentik[website]: format",
"command": "make",
"args": ["docs-watch"],
"args": ["website"],
"group": "build",
},
{
"label": "authentik[website]: watch",
"command": "make",
"args": ["website-watch"],
"group": "build",
"presentation": {
"panel": "dedicated",
"group": "running"
}
},
},
{
"label": "authentik/api: generate",
"command": "uv",
"args": ["run", "make", "gen"],
"label": "authentik[api]: generate",
"command": "poetry",
"args": [
"run",
"make",
"gen"
],
"group": "build"
}
},
]
}

View File

@@ -1,50 +1,28 @@
# Fallback
* @goauthentik/backend @goauthentik/frontend
* @goauthentik/backend @goauthentik/frontend
# Backend
authentik/ @goauthentik/backend
blueprints/ @goauthentik/backend
cmd/ @goauthentik/backend
internal/ @goauthentik/backend
lifecycle/ @goauthentik/backend
schemas/ @goauthentik/backend
scripts/ @goauthentik/backend
tests/ @goauthentik/backend
pyproject.toml @goauthentik/backend
uv.lock @goauthentik/backend
go.mod @goauthentik/backend
go.sum @goauthentik/backend
authentik/ @goauthentik/backend
blueprints/ @goauthentik/backend
cmd/ @goauthentik/backend
internal/ @goauthentik/backend
lifecycle/ @goauthentik/backend
schemas/ @goauthentik/backend
scripts/ @goauthentik/backend
tests/ @goauthentik/backend
pyproject.toml @goauthentik/backend
poetry.lock @goauthentik/backend
go.mod @goauthentik/backend
go.sum @goauthentik/backend
# Infrastructure
.github/ @goauthentik/infrastructure
lifecycle/aws/ @goauthentik/infrastructure
Dockerfile @goauthentik/infrastructure
*Dockerfile @goauthentik/infrastructure
.dockerignore @goauthentik/infrastructure
docker-compose.yml @goauthentik/infrastructure
Makefile @goauthentik/infrastructure
.editorconfig @goauthentik/infrastructure
CODEOWNERS @goauthentik/infrastructure
# Backend packages
packages/django-channels-postgres @goauthentik/backend
packages/django-postgres-cache @goauthentik/backend
packages/django-dramatiq-postgres @goauthentik/backend
# Web packages
package.json @goauthentik/frontend
package-lock.json @goauthentik/frontend
packages/package.json @goauthentik/frontend
packages/package-lock.json @goauthentik/frontend
packages/docusaurus-config @goauthentik/frontend
packages/esbuild-plugin-live-reload @goauthentik/frontend
packages/eslint-config @goauthentik/frontend
packages/prettier-config @goauthentik/frontend
packages/tsconfig @goauthentik/frontend
.github/ @goauthentik/infrastructure
Dockerfile @goauthentik/infrastructure
*Dockerfile @goauthentik/infrastructure
.dockerignore @goauthentik/infrastructure
docker-compose.yml @goauthentik/infrastructure
# Web
web/ @goauthentik/frontend
# Locale
locale/ @goauthentik/backend @goauthentik/frontend
web/xliff/ @goauthentik/backend @goauthentik/frontend
# Docs
website/ @goauthentik/docs
CODE_OF_CONDUCT.md @goauthentik/docs
web/ @goauthentik/frontend
tests/wdio/ @goauthentik/frontend
# Docs & Website
website/ @goauthentik/docs
# Security
SECURITY.md @goauthentik/security @goauthentik/docs
website/security/ @goauthentik/security @goauthentik/docs
website/docs/security/ @goauthentik/security

View File

@@ -5,7 +5,7 @@
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socioeconomic status,
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

View File

@@ -1,4 +0,0 @@
# Contributing to authentik
Thanks for your interest in contributing! Please see our [contributing guide](https://docs.goauthentik.io/docs/developer-docs/?utm_source=github) for more information.

1
CONTRIBUTING.md Symbolic link
View File

@@ -0,0 +1 @@
website/developer-docs/index.md

View File

@@ -1,7 +1,26 @@
# syntax=docker/dockerfile:1
# Stage 1: Build webui
FROM --platform=${BUILDPLATFORM} docker.io/library/node:24-trixie-slim@sha256:45babd1b4ce0349fb12c4e24bf017b90b96d52806db32e001e3013f341bef0fe AS node-builder
# Stage 1: Build website
FROM --platform=${BUILDPLATFORM} docker.io/node:22 as website-builder
ENV NODE_ENV=production
WORKDIR /work/website
RUN --mount=type=bind,target=/work/website/package.json,src=./website/package.json \
--mount=type=bind,target=/work/website/package-lock.json,src=./website/package-lock.json \
--mount=type=cache,id=npm-website,sharing=shared,target=/root/.npm \
npm ci --include=dev
COPY ./website /work/website/
COPY ./blueprints /work/blueprints/
COPY ./schema.yml /work/
COPY ./SECURITY.md /work/
RUN npm run build-bundled
# Stage 2: Build webui
FROM --platform=${BUILDPLATFORM} docker.io/node:22 as web-builder
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
@@ -11,22 +30,25 @@ WORKDIR /work/web
RUN --mount=type=bind,target=/work/web/package.json,src=./web/package.json \
--mount=type=bind,target=/work/web/package-lock.json,src=./web/package-lock.json \
--mount=type=bind,target=/work/web/packages/sfe/package.json,src=./web/packages/sfe/package.json \
--mount=type=bind,target=/work/web/sfe/package.json,src=./web/sfe/package.json \
--mount=type=bind,target=/work/web/sfe/package-lock.json,src=./web/sfe/package-lock.json \
--mount=type=bind,target=/work/web/scripts,src=./web/scripts \
--mount=type=cache,id=npm-ak,sharing=shared,target=/root/.npm \
npm ci
--mount=type=cache,id=npm-web,sharing=shared,target=/root/.npm \
npm ci --include=dev && \
cd sfe && \
npm ci --include=dev
COPY ./package.json /work
COPY ./web /work/web/
# TODO: Update this after moving website to docs
COPY ./website /work/website/
COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
RUN npm run build && \
npm run build:sfe
cd sfe && \
npm run build
# Stage 2: Build go proxy
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.25.5-trixie@sha256:5d35fb8d28b9095d123b7d96095bbf3750ff18be0a87e5a21c9cffc4351fbf96 AS go-builder
# Stage 3: Build go proxy
FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.22-fips-bookworm AS go-builder
ARG TARGETOS
ARG TARGETARCH
@@ -50,8 +72,8 @@ RUN --mount=type=bind,target=/go/src/goauthentik.io/go.mod,src=./go.mod \
COPY ./cmd /go/src/goauthentik.io/cmd
COPY ./authentik/lib /go/src/goauthentik.io/authentik/lib
COPY ./web/static.go /go/src/goauthentik.io/web/static.go
COPY --from=node-builder /work/web/robots.txt /go/src/goauthentik.io/web/robots.txt
COPY --from=node-builder /work/web/security.txt /go/src/goauthentik.io/web/security.txt
COPY --from=web-builder /work/web/robots.txt /go/src/goauthentik.io/web/robots.txt
COPY --from=web-builder /work/web/security.txt /go/src/goauthentik.io/web/security.txt
COPY ./internal /go/src/goauthentik.io/internal
COPY ./go.mod /go/src/goauthentik.io/go.mod
COPY ./go.sum /go/src/goauthentik.io/go.sum
@@ -59,132 +81,93 @@ COPY ./go.sum /go/src/goauthentik.io/go.sum
RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
--mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \
if [ "$TARGETARCH" = "arm64" ]; then export CC=aarch64-linux-gnu-gcc && export CC_FOR_TARGET=gcc-aarch64-linux-gnu; fi && \
CGO_ENABLED=1 GOFIPS140=latest GOARM="${TARGETVARIANT#v}" \
CGO_ENABLED=1 GOEXPERIMENT="systemcrypto" GOFLAGS="-tags=requirefips" GOARM="${TARGETVARIANT#v}" \
go build -o /go/authentik ./cmd/server
# Stage 3: MaxMind GeoIP
FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.1.1@sha256:faecdca22579730ab0b7dea5aa9af350bb3c93cb9d39845c173639ead30346d2 AS geoip
# Stage 4: MaxMind GeoIP
FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.0.1 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 4: Download uv
FROM ghcr.io/astral-sh/uv:0.9.17@sha256:5cb6b54d2bc3fe2eb9a8483db958a0b9eebf9edff68adedb369df8e7b98711a2 AS uv
# Stage 5: Base python image
FROM ghcr.io/goauthentik/fips-python:3.13.9-slim-trixie-fips@sha256:700fc8c1e290bd14e5eaca50b1d8e8c748c820010559cbfb4c4f8dfbe2c4c9ff AS python-base
# Stage 5: Python dependencies
FROM ghcr.io/goauthentik/fips-python:3.12.3-slim-bookworm-fips-full AS python-deps
ENV VENV_PATH="/ak-root/.venv" \
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \
UV_COMPILE_BYTECODE=1 \
UV_LINK_MODE=copy \
UV_NATIVE_TLS=1 \
UV_PYTHON_DOWNLOADS=0
WORKDIR /ak-root/poetry
WORKDIR /ak-root/
COPY --from=uv /uv /uvx /bin/
# Stage 6: Python dependencies
FROM python-base AS python-deps
ARG TARGETARCH
ARG TARGETVARIANT
ENV VENV_PATH="/ak-root/venv" \
POETRY_VIRTUALENVS_CREATE=false \
PATH="/ak-root/venv/bin:$PATH"
RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
ENV PATH="/root/.cargo/bin:$PATH"
RUN --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/var/cache/apt \
apt-get update && \
# Required for installing pip packages
apt-get install -y --no-install-recommends \
# Build essentials
build-essential pkg-config libffi-dev git \
# cryptography
curl \
# libxml
libxslt-dev zlib1g-dev \
# postgresql
libpq-dev \
# python-kadmin-rs
clang libkrb5-dev sccache \
# xmlsec
libltdl-dev && \
curl https://sh.rustup.rs -sSf | sh -s -- -y
apt-get install -y --no-install-recommends build-essential pkg-config libpq-dev
ENV UV_NO_BINARY_PACKAGE="cryptography lxml python-kadmin-rs xmlsec" \
# https://github.com/rust-lang/rustup/issues/2949
# Fixes issues where the rust version in the build cache is older than latest
# and rustup tries to update it, which fails
RUSTUP_PERMIT_COPY_RENAME="true"
RUN --mount=type=bind,target=./pyproject.toml,src=./pyproject.toml \
--mount=type=bind,target=./poetry.lock,src=./poetry.lock \
--mount=type=cache,target=/root/.cache/pip \
--mount=type=cache,target=/root/.cache/pypoetry \
python -m venv /ak-root/venv/ && \
bash -c "source ${VENV_PATH}/bin/activate && \
pip3 install --upgrade pip && \
pip3 install poetry && \
poetry install --only=main --no-ansi --no-interaction --no-root && \
pip install --force-reinstall /wheels/*"
RUN --mount=type=bind,target=pyproject.toml,src=pyproject.toml \
--mount=type=bind,target=uv.lock,src=uv.lock \
--mount=type=bind,target=packages,src=packages \
--mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-install-project --no-dev
# Stage 6: Run
FROM ghcr.io/goauthentik/fips-python:3.12.3-slim-bookworm-fips-full AS final-image
# Stage 7: Run
FROM python-base AS final-image
ARG VERSION
ARG GIT_BUILD_HASH
ARG VERSION
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
LABEL org.opencontainers.image.authors="Authentik Security Inc." \
org.opencontainers.image.source="https://github.com/goauthentik/authentik" \
org.opencontainers.image.description="goauthentik.io Main server image, see https://goauthentik.io for more info." \
org.opencontainers.image.documentation="https://docs.goauthentik.io" \
org.opencontainers.image.licenses="https://github.com/goauthentik/authentik/blob/main/LICENSE" \
org.opencontainers.image.revision=${GIT_BUILD_HASH} \
org.opencontainers.image.source="https://github.com/goauthentik/authentik" \
org.opencontainers.image.title="authentik server image" \
org.opencontainers.image.url="https://goauthentik.io" \
org.opencontainers.image.vendor="Authentik Security Inc." \
org.opencontainers.image.version=${VERSION}
LABEL org.opencontainers.image.url https://goauthentik.io
LABEL org.opencontainers.image.description goauthentik.io Main server image, see https://goauthentik.io for more info.
LABEL org.opencontainers.image.source https://github.com/goauthentik/authentik
LABEL org.opencontainers.image.version ${VERSION}
LABEL org.opencontainers.image.revision ${GIT_BUILD_HASH}
WORKDIR /
# We cannot cache this layer otherwise we'll end up with a bigger image
RUN apt-get update && \
apt-get upgrade -y && \
# Required for runtime
apt-get install -y --no-install-recommends libpq5 libmaxminddb0 ca-certificates libkrb5-3 libkadm5clnt-mit12 libkdb5-10 libltdl7 libxslt1.1 && \
apt-get install -y --no-install-recommends libpq5 libmaxminddb0 ca-certificates && \
# Required for bootstrap & healtcheck
apt-get install -y --no-install-recommends runit && \
pip3 install --no-cache-dir --upgrade pip && \
apt-get clean && \
rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/ && \
adduser --system --no-create-home --uid 1000 --group --home /authentik authentik && \
mkdir -p /certs /data /media /blueprints && \
ln -s /media /data/media && \
mkdir -p /certs /media /blueprints && \
mkdir -p /authentik/.ssh && \
mkdir -p /ak-root && \
chown authentik:authentik /certs /data /data/media /media /authentik/.ssh /ak-root
chown authentik:authentik /certs /media /authentik/.ssh /ak-root
COPY ./authentik/ /authentik
COPY ./pyproject.toml /
COPY ./uv.lock /
COPY ./poetry.lock /
COPY ./schemas /schemas
COPY ./locale /locale
COPY ./tests /tests
COPY ./manage.py /
COPY ./blueprints /blueprints
COPY ./lifecycle/ /lifecycle
COPY ./authentik/sources/kerberos/krb5.conf /etc/krb5.conf
COPY --from=go-builder /go/authentik /bin/authentik
COPY ./packages/ /ak-root/packages
RUN ln -s /ak-root/packages /packages
COPY --from=python-deps /ak-root/.venv /ak-root/.venv
COPY --from=node-builder /work/web/dist/ /web/dist/
COPY --from=node-builder /work/web/authentik/ /web/authentik/
COPY --from=python-deps /ak-root/venv /ak-root/venv
COPY --from=web-builder /work/web/dist/ /web/dist/
COPY --from=web-builder /work/web/authentik/ /web/authentik/
COPY --from=website-builder /work/website/build/ /website/help/
COPY --from=geoip /usr/share/GeoIP /geoip
USER 1000
@@ -192,7 +175,11 @@ USER 1000
ENV TMPDIR=/dev/shm/ \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
GOFIPS=1
PATH="/ak-root/venv/bin:/lifecycle:$PATH" \
VENV_PATH="/ak-root/venv" \
POETRY_VIRTUALENVS_CREATE=false
ENV GOFIPS=1
HEALTHCHECK --interval=30s --timeout=30s --start-period=60s --retries=3 CMD [ "ak", "healthcheck" ]

316
Makefile
View File

@@ -1,39 +1,37 @@
.PHONY: gen dev-reset all clean test web docs
.PHONY: gen dev-reset all clean test web website
SHELL := /usr/bin/env bash
.SHELLFLAGS += ${SHELLFLAGS} -e -o pipefail
.SHELLFLAGS += ${SHELLFLAGS} -e
PWD = $(shell pwd)
UID = $(shell id -u)
GID = $(shell id -g)
NPM_VERSION = $(shell python -m scripts.generate_semver)
PY_SOURCES = authentik packages tests scripts lifecycle .github
NPM_VERSION = $(shell python -m scripts.npm_version)
PY_SOURCES = authentik tests scripts lifecycle .github
DOCKER_IMAGE ?= "authentik:test"
GEN_API_TS = gen-ts-api
GEN_API_PY = gen-py-api
GEN_API_GO = gen-go-api
GEN_API_TS = "gen-ts-api"
GEN_API_PY = "gen-py-api"
GEN_API_GO = "gen-go-api"
pg_user := $(shell uv run python -m authentik.lib.config postgresql.user 2>/dev/null)
pg_host := $(shell uv run python -m authentik.lib.config postgresql.host 2>/dev/null)
pg_name := $(shell uv run python -m authentik.lib.config postgresql.name 2>/dev/null)
pg_user := $(shell python -m authentik.lib.config postgresql.user 2>/dev/null)
pg_host := $(shell python -m authentik.lib.config postgresql.host 2>/dev/null)
pg_name := $(shell python -m authentik.lib.config postgresql.name 2>/dev/null)
# For macOS users, add the libxml2 installed from brew libxmlsec1 to the build path
# to prevent SAML-related tests from failing and ensure correct pip dependency compilation
# These functions are only evaluated when called in specific targets
LIBXML2_EXISTS = $(shell brew list libxml2 2> /dev/null)
KRB5_EXISTS = $(shell brew list krb5 2> /dev/null)
CODESPELL_ARGS = -D - -D .github/codespell-dictionary.txt \
-I .github/codespell-words.txt \
-S 'web/src/locales/**' \
-S 'website/developer-docs/api/reference/**' \
authentik \
internal \
cmd \
web/src \
website/src \
website/blog \
website/developer-docs \
website/docs \
website/integrations \
website/src
LIBXML2_LDFLAGS = -L$(shell brew --prefix libxml2)/lib $(LDFLAGS)
LIBXML2_CPPFLAGS = -I$(shell brew --prefix libxml2)/include $(CPPFLAGS)
LIBXML2_PKG_CONFIG = $(shell brew --prefix libxml2)/lib/pkgconfig:$(PKG_CONFIG_PATH)
KRB_PATH =
ifneq ($(KRB5_EXISTS),)
KRB_PATH = PATH="$(shell brew --prefix krb5)/sbin:$(shell brew --prefix krb5)/bin:$$PATH"
endif
all: lint-fix lint gen web test ## Lint, build, and test everything
all: lint-fix lint test gen web ## Lint, build, and test everything
HELP_WIDTH := $(shell grep -h '^[a-z][^ ]*:.*\#\#' $(MAKEFILE_LIST) 2>/dev/null | \
cut -d':' -f1 | awk '{printf "%d\n", length}' | sort -rn | head -1)
@@ -45,51 +43,42 @@ help: ## Show this help
sort
@echo ""
go-test:
GOFIPS140=latest CGO_ENABLED=1 go test -timeout 0 -v -race -cover ./...
test-go:
go test -timeout 0 -v -race -cover ./...
test-docker: ## Run all tests in a docker-compose
echo "PG_PASS=$(shell openssl rand 32 | base64)" >> .env
echo "AUTHENTIK_SECRET_KEY=$(shell openssl rand 32 | base64)" >> .env
docker compose pull -q
docker compose up --no-start
docker compose start postgresql redis
docker compose run -u root server test-all
rm -f .env
test: ## Run the server tests and produce a coverage report (locally)
$(KRB_PATH) uv run coverage run manage.py test --keepdb $(or $(filter-out $@,$(MAKECMDGOALS)),authentik)
uv run coverage html
uv run coverage report
coverage run manage.py test --keepdb authentik
coverage html
coverage report
lint-fix: lint-codespell ## Lint and automatically fix errors in the python source code. Reports spelling errors.
uv run black $(PY_SOURCES)
uv run ruff check --fix $(PY_SOURCES)
lint-codespell: ## Reports spelling errors.
uv run codespell -w
lint-fix: ## Lint and automatically fix errors in the python source code. Reports spelling errors.
black $(PY_SOURCES)
ruff check --fix $(PY_SOURCES)
codespell -w $(CODESPELL_ARGS)
lint: ## Lint the python and golang sources
uv run bandit -c pyproject.toml -r $(PY_SOURCES)
bandit -r $(PY_SOURCES) -x web/node_modules -x tests/wdio/node_modules -x website/node_modules
golangci-lint run -v
core-install:
ifneq ($(LIBXML2_EXISTS),)
# Clear cache to ensure fresh compilation
uv cache clean
# Force compilation from source for lxml and xmlsec with correct environment
LDFLAGS="$(LIBXML2_LDFLAGS)" CPPFLAGS="$(LIBXML2_CPPFLAGS)" PKG_CONFIG_PATH="$(LIBXML2_PKG_CONFIG)" uv sync --frozen --reinstall-package lxml --reinstall-package xmlsec --no-binary-package lxml --no-binary-package xmlsec
else
uv sync --frozen
endif
poetry install
migrate: ## Run the Authentik Django server's migrations
uv run python -m lifecycle.migrate
python -m lifecycle.migrate
i18n-extract: core-i18n-extract web-i18n-extract ## Extract strings that require translation into files to send to a translation service
aws-cfn:
cd lifecycle/aws && npm i && uv run npm run aws-cfn
run-server: ## Run the main authentik server process
uv run ak server
run-worker: ## Run the main authentik worker process
uv run ak worker
core-i18n-extract:
uv run ak makemessages \
ak makemessages \
--add-location file \
--no-obsolete \
--ignore web \
@@ -99,33 +88,19 @@ core-i18n-extract:
--ignore website \
-l en
install: node-install docs-install core-install ## Install all requires dependencies for `node`, `docs` and `core`
install: web-install website-install core-install ## Install all requires dependencies for `web`, `website` and `core`
dev-drop-db:
dropdb -U ${pg_user} -h ${pg_host} ${pg_name} || true
dropdb -U ${pg_user} -h ${pg_host} ${pg_name}
# Also remove the test-db if it exists
dropdb -U ${pg_user} -h ${pg_host} test_${pg_name} || true
redis-cli -n 0 flushall
dev-create-db:
createdb -U ${pg_user} -h ${pg_host} ${pg_name}
dev-reset: dev-drop-db dev-create-db migrate ## Drop and restore the Authentik PostgreSQL instance to a "fresh install" state.
update-test-mmdb: ## Update test GeoIP and ASN Databases
curl -L https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-ASN-Test.mmdb -o ${PWD}/tests/GeoLite2-ASN-Test.mmdb
curl -L https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-City-Test.mmdb -o ${PWD}/tests/GeoLite2-City-Test.mmdb
bump: ## Bump authentik version. Usage: make bump version=20xx.xx.xx
ifndef version
$(error Usage: make bump version=20xx.xx.xx )
endif
sed -i 's/^version = ".*"/version = "$(version)"/' pyproject.toml
sed -i 's/^VERSION = ".*"/VERSION = "$(version)"/' authentik/__init__.py
$(MAKE) gen-build gen-compose aws-cfn
npm version --no-git-tag-version --allow-same-version $(version)
cd ${PWD}/web && npm version --no-git-tag-version --allow-same-version $(version)
echo -n $(version) > ${PWD}/internal/constants/VERSION
#########################
## API Schema
#########################
@@ -134,158 +109,144 @@ gen-build: ## Extract the schema from the database
AUTHENTIK_DEBUG=true \
AUTHENTIK_TENANTS__ENABLED=true \
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \
uv run ak make_blueprint_schema --file blueprints/schema.json
ak make_blueprint_schema > blueprints/schema.json
AUTHENTIK_DEBUG=true \
AUTHENTIK_TENANTS__ENABLED=true \
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \
uv run ak spectacular --file schema.yml
gen-compose:
uv run scripts/generate_docker_compose.py
ak spectacular --file schema.yml
gen-changelog: ## (Release) generate the changelog based from the commits since the last tag
git log --pretty=format:" - %s" $(shell git describe --tags $(shell git rev-list --tags --max-count=1))...$(shell git branch --show-current) | sort > changelog.md
npx prettier --write changelog.md
gen-diff: ## (Release) generate the changelog diff between the current schema and the last tag
git show $(shell git describe --tags $(shell git rev-list --tags --max-count=1)):schema.yml > schema-old.yml
docker compose -f scripts/api/docker-compose.yml run --rm --user "${UID}:${GID}" diff \
--markdown \
/local/diff.md \
/local/schema-old.yml \
/local/schema.yml
rm schema-old.yml
git show $(shell git describe --tags $(shell git rev-list --tags --max-count=1)):schema.yml > old_schema.yml
docker run \
--rm -v ${PWD}:/local \
--user ${UID}:${GID} \
docker.io/openapitools/openapi-diff:2.1.0-beta.8 \
--markdown /local/diff.md \
/local/old_schema.yml /local/schema.yml
rm old_schema.yml
sed -i 's/{/&#123;/g' diff.md
sed -i 's/}/&#125;/g' diff.md
npx prettier --write diff.md
gen-clean-ts: ## Remove generated API client for TypeScript
rm -rf ${PWD}/${GEN_API_TS}/
rm -rf ${PWD}/web/node_modules/@goauthentik/api/
gen-clean-py: ## Remove generated API client for Python
rm -rf ${PWD}/${GEN_API_PY}
gen-clean-ts: ## Remove generated API client for Typescript
rm -rf ./${GEN_API_TS}/
rm -rf ./web/node_modules/@goauthentik/api/
gen-clean-go: ## Remove generated API client for Go
rm -rf ${PWD}/${GEN_API_GO}
rm -rf ./${GEN_API_GO}/
gen-clean-py: ## Remove generated API client for Python
rm -rf ./${GEN_API_PY}/
gen-clean: gen-clean-ts gen-clean-go gen-clean-py ## Remove generated API clients
gen-client-ts: gen-clean-ts ## Build and install the authentik API for Typescript into the authentik UI Application
docker compose -f scripts/api/docker-compose.yml run --rm --user "${UID}:${GID}" gen \
generate \
docker run \
--rm -v ${PWD}:/local \
--user ${UID}:${GID} \
docker.io/openapitools/openapi-generator-cli:v6.5.0 generate \
-i /local/schema.yml \
-g typescript-fetch \
-o /local/${GEN_API_TS} \
-c /local/scripts/api/ts-config.yaml \
-c /local/scripts/api-ts-config.yaml \
--additional-properties=npmVersion=${NPM_VERSION} \
--git-repo-id authentik \
--git-user-id goauthentik
cd ${PWD}/${GEN_API_TS} && npm i
cd ${PWD}/${GEN_API_TS} && npm link
cd ${PWD}/web && npm link @goauthentik/api
mkdir -p web/node_modules/@goauthentik/api
cd ./${GEN_API_TS} && npm i
\cp -rf ./${GEN_API_TS}/* web/node_modules/@goauthentik/api
gen-client-py: gen-clean-py ## Build and install the authentik API for Python
mkdir -p ${PWD}/${GEN_API_PY}
ifeq ($(wildcard ${PWD}/${GEN_API_PY}/.*),)
git clone --depth 1 https://github.com/goauthentik/client-python.git ${PWD}/${GEN_API_PY}
else
cd ${PWD}/${GEN_API_PY} && git pull
endif
cp ${PWD}/schema.yml ${PWD}/${GEN_API_PY}
make -C ${PWD}/${GEN_API_PY} build version=${NPM_VERSION}
docker run \
--rm -v ${PWD}:/local \
--user ${UID}:${GID} \
docker.io/openapitools/openapi-generator-cli:v7.4.0 generate \
-i /local/schema.yml \
-g python \
-o /local/${GEN_API_PY} \
-c /local/scripts/api-py-config.yaml \
--additional-properties=packageVersion=${NPM_VERSION} \
--git-repo-id authentik \
--git-user-id goauthentik
pip install ./${GEN_API_PY}
gen-client-go: ## Build and install the authentik API for Golang
mkdir -p ${PWD}/${GEN_API_GO}
ifeq ($(wildcard ${PWD}/${GEN_API_GO}/.*),)
git clone --depth 1 https://github.com/goauthentik/client-go.git ${PWD}/${GEN_API_GO}
else
cd ${PWD}/${GEN_API_GO} && git reset --hard
cd ${PWD}/${GEN_API_GO} && git pull
endif
cp ${PWD}/schema.yml ${PWD}/${GEN_API_GO}
make -C ${PWD}/${GEN_API_GO} build
gen-client-go: gen-clean-go ## Build and install the authentik API for Golang
mkdir -p ./${GEN_API_GO} ./${GEN_API_GO}/templates
wget https://raw.githubusercontent.com/goauthentik/client-go/main/config.yaml -O ./${GEN_API_GO}/config.yaml
wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/README.mustache -O ./${GEN_API_GO}/templates/README.mustache
wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/go.mod.mustache -O ./${GEN_API_GO}/templates/go.mod.mustache
cp schema.yml ./${GEN_API_GO}/
docker run \
--rm -v ${PWD}/${GEN_API_GO}:/local \
--user ${UID}:${GID} \
docker.io/openapitools/openapi-generator-cli:v6.5.0 generate \
-i /local/schema.yml \
-g go \
-o /local/ \
-c /local/config.yaml
go mod edit -replace goauthentik.io/api/v3=./${GEN_API_GO}
rm -rf ./${GEN_API_GO}/config.yaml ./${GEN_API_GO}/templates/
gen-dev-config: ## Generate a local development config file
uv run scripts/generate_config.py
python -m scripts.generate_config
gen: gen-build gen-client-ts
#########################
## Node.js
#########################
node-install: ## Install the necessary libraries to build Node.js packages
npm ci
npm ci --prefix web
#########################
## Web
#########################
web-build: node-install ## Build the Authentik UI
npm run --prefix web build
web-build: web-install ## Build the Authentik UI
cd web && npm run build
web: web-lint-fix web-lint web-check-compile ## Automatically fix formatting issues in the Authentik UI source code, lint the code, and compile it
web-test: ## Run tests for the Authentik UI
npm run --prefix web test
web-install: ## Install the necessary libraries to build the Authentik UI
cd web && npm ci
web-watch: ## Build and watch the Authentik UI for changes, updating automatically
npm run --prefix web watch
rm -rf web/dist/
mkdir web/dist/
touch web/dist/.gitkeep
cd web && npm run watch
web-storybook-watch: ## Build and run the storybook documentation server
npm run --prefix web storybook
cd web && npm run storybook
web-lint-fix:
npm run --prefix web prettier
cd web && npm run prettier
web-lint:
npm run --prefix web lint
npm run --prefix web lit-analyse
cd web && npm run lint
cd web && npm run lit-analyse
web-check-compile:
npm run --prefix web tsc
cd web && npm run tsc
web-i18n-extract:
npm run --prefix web extract-locales
cd web && npm run extract-locales
#########################
## Docs
## Website
#########################
docs: docs-lint-fix docs-build ## Automatically fix formatting issues in the Authentik docs source code, lint the code, and compile it
website: website-lint-fix website-build ## Automatically fix formatting issues in the Authentik website/docs source code, lint the code, and compile it
docs-install:
npm ci --prefix website
website-install:
cd website && npm ci
docs-lint-fix: lint-codespell
npm run --prefix website prettier
website-lint-fix:
cd website && npm run prettier
docs-build:
npm run --prefix website build
website-build:
cd website && npm run build
docs-watch: ## Build and watch the topics documentation
npm run --prefix website start
integrations: docs-lint-fix integrations-build ## Fix formatting issues in the integrations source code, lint the code, and compile it
integrations-build:
npm run --prefix website -w integrations build
integrations-watch: ## Build and watch the Integrations documentation
npm run --prefix website -w integrations start
docs-api-build:
npm run --prefix website -w api build
docs-api-watch: ## Build and watch the API documentation
npm run --prefix website -w api build:api
npm run --prefix website -w api start
docs-api-clean: ## Clean generated API documentation
npm run --prefix website -w api build:api:clean
website-watch: ## Build and watch the documentation website, updating automatically
cd website && npm run watch
#########################
## Docker
@@ -295,9 +256,6 @@ docker: ## Build a docker image of the current source tree
mkdir -p ${GEN_API_TS}
DOCKER_BUILDKIT=1 docker build . --progress plain --tag ${DOCKER_IMAGE}
test-docker:
BUILD=true ${PWD}/scripts/test_docker.sh
#########################
## CI
#########################
@@ -308,25 +266,17 @@ ci--meta-debug:
python -V
node --version
ci-mypy: ci--meta-debug
uv run mypy --strict $(PY_SOURCES)
ci-black: ci--meta-debug
uv run black --check $(PY_SOURCES)
black --check $(PY_SOURCES)
ci-ruff: ci--meta-debug
uv run ruff check $(PY_SOURCES)
ruff check $(PY_SOURCES)
ci-codespell: ci--meta-debug
uv run codespell -s
codespell $(CODESPELL_ARGS) -s
ci-bandit: ci--meta-debug
uv run bandit -r $(PY_SOURCES)
bandit -r $(PY_SOURCES)
ci-pending-migrations: ci--meta-debug
uv run ak makemigrations --check
ci-test: ci--meta-debug
uv run coverage run manage.py test --keepdb --randomly-seed ${CI_TEST_SEED} authentik
uv run coverage report
uv run coverage xml
ak makemigrations --check

View File

@@ -9,21 +9,19 @@
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/goauthentik/authentik/ci-outpost.yml?branch=main&label=outpost%20build&style=for-the-badge)](https://github.com/goauthentik/authentik/actions/workflows/ci-outpost.yml)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/goauthentik/authentik/ci-web.yml?branch=main&label=web%20build&style=for-the-badge)](https://github.com/goauthentik/authentik/actions/workflows/ci-web.yml)
[![Code Coverage](https://img.shields.io/codecov/c/gh/goauthentik/authentik?style=for-the-badge)](https://codecov.io/gh/goauthentik/authentik)
![Latest version](https://img.shields.io/docker/v/authentik/server?sort=semver&style=for-the-badge)
[![](https://img.shields.io/badge/Help%20translate-transifex-blue?style=for-the-badge)](https://explore.transifex.com/authentik/authentik/)
![Docker pulls](https://img.shields.io/docker/pulls/beryju/authentik.svg?style=for-the-badge)
![Latest version](https://img.shields.io/docker/v/beryju/authentik?sort=semver&style=for-the-badge)
[![](https://img.shields.io/badge/Help%20translate-transifex-blue?style=for-the-badge)](https://www.transifex.com/authentik/authentik/)
## What is authentik?
authentik is an open-source Identity Provider (IdP) for modern SSO. It supports SAML, OAuth2/OIDC, LDAP, RADIUS, and more, designed for self-hosting from small labs to large production clusters.
Our [enterprise offering](https://goauthentik.io/pricing) is available for organizations to securely replace existing IdPs such as Okta, Auth0, Entra ID, and Ping Identity for robust, large-scale identity management.
authentik is an open-source Identity Provider that emphasizes flexibility and versatility. It can be seamlessly integrated into existing environments to support new protocols. authentik is also a great solution for implementing sign-up, recovery, and other similar features in your application, saving you the hassle of dealing with them.
## Installation
- Docker Compose: recommended for small/test setups. See the [documentation](https://docs.goauthentik.io/docs/install-config/install/docker-compose/).
- Kubernetes (Helm Chart): recommended for larger setups. See the [documentation](https://docs.goauthentik.io/docs/install-config/install/kubernetes/) and the Helm chart [repository](https://github.com/goauthentik/helm).
- AWS CloudFormation: deploy on AWS using our official templates. See the [documentation](https://docs.goauthentik.io/docs/install-config/install/aws/).
- DigitalOcean Marketplace: one-click deployment via the official Marketplace app. See the [app listing](https://marketplace.digitalocean.com/apps/authentik).
For small/test setups it is recommended to use Docker Compose; refer to the [documentation](https://goauthentik.io/docs/installation/docker-compose/?utm_source=github).
For bigger setups, there is a Helm Chart [here](https://github.com/goauthentik/helm). This is documented [here](https://goauthentik.io/docs/installation/kubernetes/?utm_source=github).
## Screenshots
@@ -32,20 +30,14 @@ Our [enterprise offering](https://goauthentik.io/pricing) is available for organ
| ![](https://docs.goauthentik.io/img/screen_apps_light.jpg) | ![](https://docs.goauthentik.io/img/screen_apps_dark.jpg) |
| ![](https://docs.goauthentik.io/img/screen_admin_light.jpg) | ![](https://docs.goauthentik.io/img/screen_admin_dark.jpg) |
## Development and contributions
## Development
See the [Developer Documentation](https://docs.goauthentik.io/docs/developer-docs/) for information about setting up local build environments, testing your contributions, and our contribution process.
See [Developer Documentation](https://goauthentik.io/developer-docs/?utm_source=github)
## Security
Please see [SECURITY.md](SECURITY.md).
See [SECURITY.md](SECURITY.md)
## Adoption
## Adoption and Contributions
Using authentik? We'd love to hear your story and feature your logo. Email us at [hello@goauthentik.io](mailto:hello@goauthentik.io) or open a GitHub Issue/PR!
## License
[![MIT License](https://img.shields.io/badge/License-MIT-green?style=for-the-badge)](LICENSE)
[![CC BY-SA 4.0](https://img.shields.io/badge/License-CC%20BY--SA%204.0-lightgrey?style=for-the-badge)](website/LICENSE)
[![authentik EE License](https://img.shields.io/badge/License-EE-orange?style=for-the-badge)](authentik/enterprise/LICENSE)
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 @@ authentik takes security very seriously. We follow the rules of [responsible di
## Independent audits and pentests
We are committed to engaging in regular pentesting and security audits of authentik. Defining and adhering to a cadence of external testing ensures a stronger probability that our code base, our features, and our architecture is as secure and non-exploitable as possible. For more details about specific audits and pentests, refer to "Audits and Certificates" in our [Security documentation](https://docs.goauthentik.io/docs/security).
In May/June of 2023 [Cure53](https://cure53.de) conducted an audit and pentest. The [results](https://cure53.de/pentest-report_authentik.pdf) are published on the [Cure53 website](https://cure53.de/#publications-2023). For more details about authentik's response to the findings of the audit refer to [2023-06 Cure53 Code audit](https://goauthentik.io/docs/security/2023-06-cure53).
## What authentik classifies as a CVE
@@ -18,35 +18,14 @@ Even if the issue is not a CVE, we still greatly appreciate your help in hardeni
(.x being the latest patch release for each version)
| Version | Supported |
| ---------- | ---------- |
| 2025.8.x | ✅ |
| 2025.10.x | ✅ |
| Version | Supported |
| -------- | --------- |
| 2024.4.x | ✅ |
| 2024.6.x | ✅ |
## Reporting a Vulnerability
If you discover a potential vulnerability, please report it responsibly through one of the following channels:
- **Email**: [security@goauthentik.io](mailto:security@goauthentik.io)
- **GitHub**: Submit a private security advisory via our [repositorys advisory portal](https://github.com/goauthentik/authentik/security/advisories/new)
When submitting a report, please include as much detail as possible, such as:
- **Affected version(s)**: The version of authentik where the issue was identified.
- **Steps to reproduce**: A clear description or proof of concept to help us verify the issue.
- **Impact assessment**: How the vulnerability could be exploited and its potential effect.
- **Additional information**: Logs, configuration details (if relevant), or any suggested mitigations.
We kindly ask that you do not disclose the vulnerability publicly until we have confirmed and addressed the issue.
Our team will:
- Acknowledge receipt of your report as quickly as possible.
- Keep you updated on the investigation and resolution progress.
## Researcher Recognition
We value contributions from the security community. For each valid report, we will publish a dedicated entry on our Security Advisory page that optionally includes the reporters name (or preferred alias). Please note that while we do not currently offer monetary bounties, we are committed to giving researchers appropriate credit for their efforts in keeping authentik secure.
To report a vulnerability, send an email to [security@goauthentik.io](mailto:security@goauthentik.io). Be sure to include relevant information like which version you've found the issue in, instructions on how to reproduce the issue, and anything else that might make it easier for us to find the issue.
## Severity levels

View File

@@ -1,28 +1,20 @@
"""authentik root module"""
from functools import lru_cache
from os import environ
VERSION = "2026.2.0-rc1"
__version__ = "2024.6.5"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
@lru_cache
def authentik_version() -> str:
return VERSION
@lru_cache
def authentik_build_hash(fallback: str | None = None) -> str:
def get_build_hash(fallback: str | None = None) -> str:
"""Get build hash"""
build_hash = environ.get(ENV_GIT_HASH_KEY, fallback if fallback else "")
return fallback if build_hash == "" and fallback else build_hash
@lru_cache
def authentik_full_version() -> str:
def get_full_version() -> str:
"""Get full version, with build hash appended"""
version = authentik_version()
if (build_hash := authentik_build_hash()) != "":
return f"{version}+{build_hash}"
version = __version__
if (build_hash := get_build_hash()) != "":
version += "." + build_hash
return version

View File

@@ -0,0 +1,79 @@
"""authentik administration metrics"""
from datetime import timedelta
from django.db.models.functions import ExtractHour
from drf_spectacular.utils import extend_schema, extend_schema_field
from guardian.shortcuts import get_objects_for_user
from rest_framework.fields import IntegerField, SerializerMethodField
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from authentik.core.api.utils import PassiveSerializer
from authentik.events.models import EventAction
class CoordinateSerializer(PassiveSerializer):
"""Coordinates for diagrams"""
x_cord = IntegerField(read_only=True)
y_cord = IntegerField(read_only=True)
class LoginMetricsSerializer(PassiveSerializer):
"""Login Metrics per 1h"""
logins = SerializerMethodField()
logins_failed = SerializerMethodField()
authorizations = SerializerMethodField()
@extend_schema_field(CoordinateSerializer(many=True))
def get_logins(self, _):
"""Get successful logins per 8 hours for the last 7 days"""
user = self.context["user"]
return (
get_objects_for_user(user, "authentik_events.view_event").filter(
action=EventAction.LOGIN
)
# 3 data points per day, so 8 hour spans
.get_events_per(timedelta(days=7), ExtractHour, 7 * 3)
)
@extend_schema_field(CoordinateSerializer(many=True))
def get_logins_failed(self, _):
"""Get failed logins per 8 hours for the last 7 days"""
user = self.context["user"]
return (
get_objects_for_user(user, "authentik_events.view_event").filter(
action=EventAction.LOGIN_FAILED
)
# 3 data points per day, so 8 hour spans
.get_events_per(timedelta(days=7), ExtractHour, 7 * 3)
)
@extend_schema_field(CoordinateSerializer(many=True))
def get_authorizations(self, _):
"""Get successful authorizations per 8 hours for the last 7 days"""
user = self.context["user"]
return (
get_objects_for_user(user, "authentik_events.view_event").filter(
action=EventAction.AUTHORIZE_APPLICATION
)
# 3 data points per day, so 8 hour spans
.get_events_per(timedelta(days=7), ExtractHour, 7 * 3)
)
class AdministrationMetricsViewSet(APIView):
"""Login Metrics per 1h"""
permission_classes = [IsAuthenticated]
@extend_schema(responses={200: LoginMetricsSerializer(many=False)})
def get(self, request: Request) -> Response:
"""Login Metrics per 1h"""
serializer = LoginMetricsSerializer(True)
serializer.context["user"] = request.user
return Response(serializer.data)

View File

@@ -7,16 +7,14 @@ from sys import version as python_version
from typing import TypedDict
from cryptography.hazmat.backends.openssl.backend import backend
from django.conf import settings
from django.utils.timezone import now
from django.views.debug import SafeExceptionReporterFilter
from drf_spectacular.utils import extend_schema
from rest_framework.fields import SerializerMethodField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from authentik import authentik_full_version
from authentik import get_full_version
from authentik.core.api.utils import PassiveSerializer
from authentik.enterprise.license import LicenseKey
from authentik.lib.config import CONFIG
@@ -54,16 +52,10 @@ class SystemInfoSerializer(PassiveSerializer):
def get_http_headers(self, request: Request) -> dict[str, str]:
"""Get HTTP Request headers"""
headers = {}
raw_session = request._request.COOKIES.get(settings.SESSION_COOKIE_NAME)
for key, value in request.META.items():
if not isinstance(value, str):
continue
actual_value = value
if raw_session is not None and raw_session in actual_value:
actual_value = actual_value.replace(
raw_session, SafeExceptionReporterFilter.cleansed_substitute
)
headers[key] = actual_value
headers[key] = value
return headers
def get_http_host(self, request: Request) -> str:
@@ -78,10 +70,10 @@ class SystemInfoSerializer(PassiveSerializer):
"""Get versions"""
return {
"architecture": platform.machine(),
"authentik_version": authentik_full_version(),
"authentik_version": get_full_version(),
"environment": get_env(),
"openssl_fips_enabled": (
backend._fips_enabled if LicenseKey.get_total().status().is_valid else None
backend._fips_enabled if LicenseKey.get_total().is_valid() else None
),
"openssl_version": OPENSSL_VERSION,
"platform": platform.platform(),

View File

@@ -1,7 +1,6 @@
"""authentik administration overview"""
from django.core.cache import cache
from django_tenants.utils import get_public_schema_name
from drf_spectacular.utils import extend_schema
from packaging.version import parse
from rest_framework.fields import SerializerMethodField
@@ -10,11 +9,9 @@ from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from authentik import authentik_build_hash, authentik_version
from authentik import __version__, get_build_hash
from authentik.admin.tasks import VERSION_CACHE_KEY, VERSION_NULL, update_latest_version
from authentik.core.api.utils import PassiveSerializer
from authentik.outposts.models import Outpost
from authentik.tenants.utils import get_current_tenant
class VersionSerializer(PassiveSerializer):
@@ -25,24 +22,21 @@ class VersionSerializer(PassiveSerializer):
version_latest_valid = SerializerMethodField()
build_hash = SerializerMethodField()
outdated = SerializerMethodField()
outpost_outdated = SerializerMethodField()
def get_build_hash(self, _) -> str:
"""Get build hash, if version is not latest or released"""
return authentik_build_hash()
return get_build_hash()
def get_version_current(self, _) -> str:
"""Get current version"""
return authentik_version()
return __version__
def get_version_latest(self, _) -> str:
"""Get latest version from cache"""
if get_current_tenant().schema_name == get_public_schema_name():
return authentik_version()
version_in_cache = cache.get(VERSION_CACHE_KEY)
if not version_in_cache: # pragma: no cover
update_latest_version.send()
return authentik_version()
update_latest_version.delay()
return __version__
return version_in_cache
def get_version_latest_valid(self, _) -> bool:
@@ -53,15 +47,6 @@ class VersionSerializer(PassiveSerializer):
"""Check if we're running the latest version"""
return parse(self.get_version_current(instance)) < parse(self.get_version_latest(instance))
def get_outpost_outdated(self, _) -> bool:
"""Check if any outpost is outdated/has a version mismatch"""
any_outdated = False
for outpost in Outpost.objects.all():
for state in outpost.state:
if state.version_outdated:
any_outdated = True
return any_outdated
class VersionView(APIView):
"""Get running and latest version."""

View File

@@ -1,33 +0,0 @@
from rest_framework.permissions import IsAdminUser
from rest_framework.viewsets import ReadOnlyModelViewSet
from authentik.admin.models import VersionHistory
from authentik.core.api.utils import ModelSerializer
class VersionHistorySerializer(ModelSerializer):
"""VersionHistory Serializer"""
class Meta:
model = VersionHistory
fields = [
"id",
"timestamp",
"version",
"build",
]
class VersionHistoryViewSet(ReadOnlyModelViewSet):
"""VersionHistory Viewset"""
queryset = VersionHistory.objects.all()
serializer_class = VersionHistorySerializer
permission_classes = [IsAdminUser]
filterset_fields = [
"version",
"build",
]
search_fields = ["version", "build"]
ordering = ["-timestamp"]
pagination_class = None

View File

@@ -0,0 +1,26 @@
"""authentik administration overview"""
from django.conf import settings
from drf_spectacular.utils import extend_schema, inline_serializer
from rest_framework.fields import IntegerField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from authentik.rbac.permissions import HasPermission
from authentik.root.celery import CELERY_APP
class WorkerView(APIView):
"""Get currently connected worker count."""
permission_classes = [HasPermission("authentik_rbac.view_system_info")]
@extend_schema(responses=inline_serializer("Workers", fields={"count": IntegerField()}))
def get(self, request: Request) -> Response:
"""Get currently connected worker count."""
count = len(CELERY_APP.control.ping(timeout=0.5))
# In debug we run with `task_always_eager`, so tasks are ran on the main process
if settings.DEBUG: # pragma: no cover
count += 1
return Response({"count": count})

View File

@@ -1,13 +1,11 @@
"""authentik admin app config"""
from prometheus_client import Info
from prometheus_client import Gauge, Info
from authentik.blueprints.apps import ManagedAppConfig
from authentik.lib.config import CONFIG
from authentik.lib.utils.time import fqdn_rand
from authentik.tasks.schedules.common import ScheduleSpec
PROM_INFO = Info("authentik_version", "Currently running authentik version")
GAUGE_WORKERS = Gauge("authentik_admin_workers", "Currently connected workers")
class AuthentikAdminConfig(ManagedAppConfig):
@@ -17,31 +15,3 @@ class AuthentikAdminConfig(ManagedAppConfig):
label = "authentik_admin"
verbose_name = "authentik Admin"
default = True
@ManagedAppConfig.reconcile_global
def clear_update_notifications(self):
"""Clear update notifications on startup if the notification was for the version
we're running now."""
from packaging.version import parse
from authentik.admin.tasks import LOCAL_VERSION
from authentik.events.models import EventAction, Notification
for notification in Notification.objects.filter(event__action=EventAction.UPDATE_AVAILABLE):
if "new_version" not in notification.event.context:
continue
notification_version = notification.event.context["new_version"]
if LOCAL_VERSION >= parse(notification_version):
notification.delete()
@property
def global_schedule_specs(self) -> list[ScheduleSpec]:
from authentik.admin.tasks import update_latest_version
return [
ScheduleSpec(
actor=update_latest_version,
crontab=f"{fqdn_rand('admin_latest_version')} * * * *",
paused=CONFIG.get_bool("disable_update_check"),
),
]

View File

@@ -1,258 +0,0 @@
import mimetypes
from django.db.models import Q
from django.utils.translation import gettext as _
from drf_spectacular.utils import extend_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework.exceptions import ValidationError
from rest_framework.fields import BooleanField, CharField, ChoiceField, FileField
from rest_framework.parsers import MultiPartParser
from rest_framework.permissions import SAFE_METHODS
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from authentik.admin.files.fields import FileField as AkFileField
from authentik.admin.files.manager import get_file_manager
from authentik.admin.files.usage import FileApiUsage
from authentik.admin.files.validation import validate_upload_file_name
from authentik.api.validation import validate
from authentik.core.api.used_by import DeleteAction, UsedBySerializer
from authentik.core.api.utils import PassiveSerializer
from authentik.events.models import Event, EventAction
from authentik.lib.utils.reflection import get_apps
from authentik.rbac.permissions import HasPermission
MAX_FILE_SIZE_BYTES = 25 * 1024 * 1024 # 25MB
def get_mime_from_filename(filename: str) -> str:
mime_type, _ = mimetypes.guess_type(filename)
return mime_type or "application/octet-stream"
class FileView(APIView):
pagination_class = None
parser_classes = [MultiPartParser]
def get_permissions(self):
return [
HasPermission(
"authentik_rbac.view_media_files"
if self.request.method in SAFE_METHODS
else "authentik_rbac.manage_media_files"
)()
]
class FileListParameters(PassiveSerializer):
usage = ChoiceField(choices=list(FileApiUsage), default=FileApiUsage.MEDIA.value)
search = CharField(required=False)
manageable_only = BooleanField(required=False, default=False)
class FileListSerializer(PassiveSerializer):
name = CharField()
mime_type = CharField()
url = CharField()
@extend_schema(
parameters=[FileListParameters],
responses={200: FileListSerializer(many=True)},
)
@validate(FileListParameters, location="query")
def get(self, request: Request, query: FileListParameters) -> Response:
"""List files from storage backend."""
params = query.validated_data
try:
usage = FileApiUsage(params.get("usage", FileApiUsage.MEDIA.value))
except ValueError as exc:
raise ValidationError(
f"Invalid usage parameter provided: {params.get('usage')}"
) from exc
# Backend is source of truth - list all files from storage
manager = get_file_manager(usage)
files = manager.list_files(manageable_only=params.get("manageable_only", False))
search_query = params.get("search", "")
if search_query:
files = filter(lambda file: search_query in file.lower(), files)
files = [
FileView.FileListSerializer(
data={
"name": file,
"url": manager.file_url(file),
"mime_type": get_mime_from_filename(file),
}
)
for file in files
]
for file in files:
file.is_valid(raise_exception=True)
return Response([file.data for file in files])
class FileUploadSerializer(PassiveSerializer):
file = FileField(required=True)
name = CharField(required=False, allow_blank=True)
usage = CharField(required=False, default=FileApiUsage.MEDIA.value)
@extend_schema(
request=FileUploadSerializer,
responses={200: None},
)
@validate(FileUploadSerializer)
def post(self, request: Request, body: FileUploadSerializer) -> Response:
"""Upload file to storage backend."""
file = body.validated_data["file"]
name = body.validated_data.get("name", "").strip()
usage_value = body.validated_data.get("usage", FileApiUsage.MEDIA.value)
# Validate file size and type
if file.size > MAX_FILE_SIZE_BYTES:
raise ValidationError(
{
"file": [
_(
f"File size ({file.size}B) exceeds maximum allowed "
f"size ({MAX_FILE_SIZE_BYTES}B)."
)
]
}
)
try:
usage = FileApiUsage(usage_value)
except ValueError as exc:
raise ValidationError(f"Invalid usage parameter provided: {usage_value}") from exc
# Use original filename
if not name:
name = file.name
# Sanitize path to prevent directory traversal
validate_upload_file_name(name, ValidationError)
manager = get_file_manager(usage)
# Check if file already exists
if manager.file_exists(name):
raise ValidationError({"name": ["A file with this name already exists."]})
# Save to backend
with manager.save_file_stream(name) as f:
f.write(file.read())
Event.new(
EventAction.MODEL_CREATED,
model={
"app": "authentik_admin_files",
"model_name": "File",
"pk": name,
"name": name,
"usage": usage.value,
"mime_type": get_mime_from_filename(name),
},
).from_http(request)
return Response()
class FileDeleteParameters(PassiveSerializer):
name = CharField()
usage = ChoiceField(choices=list(FileApiUsage), default=FileApiUsage.MEDIA.value)
@extend_schema(
parameters=[FileDeleteParameters],
responses={200: None},
)
@validate(FileDeleteParameters, location="query")
def delete(self, request: Request, query: FileDeleteParameters) -> Response:
"""Delete file from storage backend."""
params = query.validated_data
validate_upload_file_name(params.get("name", ""), ValidationError)
try:
usage = FileApiUsage(params.get("usage", FileApiUsage.MEDIA.value))
except ValueError as exc:
raise ValidationError(
f"Invalid usage parameter provided: {params.get('usage')}"
) from exc
manager = get_file_manager(usage)
# Delete from backend
manager.delete_file(params.get("name"))
# Audit log for file deletion
Event.new(
EventAction.MODEL_DELETED,
model={
"app": "authentik_admin_files",
"model_name": "File",
"pk": params.get("name"),
"name": params.get("name"),
"usage": usage.value,
},
).from_http(request)
return Response()
class FileUsedByView(APIView):
pagination_class = None
def get_permissions(self):
return [
HasPermission(
"authentik_rbac.view_media_files"
if self.request.method in SAFE_METHODS
else "authentik_rbac.manage_media_files"
)()
]
class FileUsedByParameters(PassiveSerializer):
name = CharField()
@extend_schema(
parameters=[FileUsedByParameters],
responses={200: UsedBySerializer(many=True)},
)
@validate(FileUsedByParameters, location="query")
def get(self, request: Request, query: FileUsedByParameters) -> Response:
params = query.validated_data
models_and_fields = {}
for app in get_apps():
for model in app.get_models():
if model._meta.abstract:
continue
for field in model._meta.get_fields():
if isinstance(field, AkFileField):
models_and_fields.setdefault(model, []).append(field.name)
used_by = []
for model, fields in models_and_fields.items():
app = model._meta.app_label
model_name = model._meta.model_name
q = Q()
for field in fields:
q |= Q(**{field: params.get("name")})
objs = get_objects_for_user(request.user, f"{app}.view_{model_name}", model)
objs = objs.filter(q)
for obj in objs:
serializer = UsedBySerializer(
data={
"app": model._meta.app_label,
"model_name": model._meta.model_name,
"pk": str(obj.pk),
"name": str(obj),
"action": DeleteAction.LEFT_DANGLING,
}
)
serializer.is_valid()
used_by.append(serializer.data)
return Response(used_by)

View File

@@ -1,30 +0,0 @@
from pathlib import Path
from django.conf import settings
from authentik.blueprints.apps import ManagedAppConfig
from authentik.lib.config import CONFIG
class AuthentikFilesConfig(ManagedAppConfig):
name = "authentik.admin.files"
label = "authentik_admin_files"
verbose_name = "authentik Files"
default = True
@ManagedAppConfig.reconcile_global
def check_for_media_mount(self):
if settings.TEST:
return
from authentik.events.models import Event, EventAction
if (
CONFIG.get("storage.media.backend", CONFIG.get("storage.backend", "file")) == "file"
and Path("/media").exists()
):
Event.new(
EventAction.CONFIGURATION_ERROR,
message="/media has been moved to /data/media. "
"Check the release notes for migration steps.",
).save()

View File

@@ -1,162 +0,0 @@
from collections.abc import Callable, Generator, Iterator
from typing import cast
from django.core.cache import cache
from django.http.request import HttpRequest
from structlog.stdlib import get_logger
from authentik.admin.files.usage import FileUsage
CACHE_PREFIX = "goauthentik.io/admin/files"
LOGGER = get_logger()
class Backend:
"""
Base class for file storage backends.
Class attributes:
allowed_usages: List of usages that can be used with this backend
"""
allowed_usages: list[FileUsage]
def __init__(self, usage: FileUsage):
"""
Initialize backend for the given usage type.
Args:
usage: FileUsage type enum value
"""
self.usage = usage
LOGGER.debug(
"Initializing storage backend",
backend=self.__class__.__name__,
usage=usage.value,
)
def supports_file(self, name: str) -> bool:
"""
Check if this backend can handle the given file path.
Args:
name: File path to check
Returns:
True if this backend supports this file path
"""
raise NotImplementedError
def list_files(self) -> Generator[str]:
"""
List all files stored in this backend.
Yields:
Relative file paths
"""
raise NotImplementedError
def file_url(
self,
name: str,
request: HttpRequest | None = None,
use_cache: bool = True,
) -> str:
"""
Get URL for accessing the file.
Args:
file_path: Relative file path
request: Optional Django HttpRequest for fully qualifed URL building
use_cache: whether to retrieve the URL from cache
Returns:
URL to access the file (may be relative or absolute depending on backend)
"""
raise NotImplementedError
class ManageableBackend(Backend):
"""
Base class for manageable file storage backends.
Class attributes:
name: Canonical name of the storage backend, for use in configuration.
"""
name: str
@property
def manageable(self) -> bool:
"""
Whether this backend can actually be used for management.
Used only for management check, not for created the backend
"""
raise NotImplementedError
def save_file(self, name: str, content: bytes) -> None:
"""
Save file content to storage.
Args:
file_path: Relative file path
content: File content as bytes
"""
raise NotImplementedError
def save_file_stream(self, name: str) -> Iterator:
"""
Context manager for streaming file writes.
Args:
file_path: Relative file path
Returns:
Context manager that yields a writable file-like object
FileUsage:
with backend.save_file_stream("output.csv") as f:
f.write(b"data...")
"""
raise NotImplementedError
def delete_file(self, name: str) -> None:
"""
Delete file from storage.
Args:
file_path: Relative file path
"""
raise NotImplementedError
def file_exists(self, name: str) -> bool:
"""
Check if a file exists.
Args:
file_path: Relative file path
Returns:
True if file exists, False otherwise
"""
raise NotImplementedError
def _cache_get_or_set(
self,
name: str,
request: HttpRequest | None,
default: Callable[[str, HttpRequest | None], str],
timeout: int,
) -> str:
timeout_ignore = 60
timeout = int(timeout * 0.67)
if timeout < timeout_ignore:
timeout = 0
request_key = "None"
if request is not None:
request_key = f"{request.build_absolute_uri('/')}"
cache_key = f"{CACHE_PREFIX}/{self.name}/{self.usage}/{request_key}/{name}"
return cast(str, cache.get_or_set(cache_key, lambda: default(name, request), timeout))

View File

@@ -1,126 +0,0 @@
import os
from collections.abc import Generator, Iterator
from contextlib import contextmanager
from datetime import timedelta
from hashlib import sha256
from pathlib import Path
import jwt
from django.conf import settings
from django.db import connection
from django.http.request import HttpRequest
from django.utils.timezone import now
from authentik.admin.files.backends.base import ManageableBackend
from authentik.admin.files.usage import FileUsage
from authentik.lib.config import CONFIG
from authentik.lib.utils.time import timedelta_from_string
class FileBackend(ManageableBackend):
"""Local filesystem backend for file storage.
Stores files in a local directory structure:
- Path: {base_dir}/{usage}/{schema}/{filename}
- Supports full file management (upload, delete, list)
- Used when storage.backend=file (default)
"""
name = "file"
allowed_usages = list(FileUsage) # All usages
@property
def _base_dir(self) -> Path:
return Path(
CONFIG.get(
f"storage.{self.usage.value}.{self.name}.path",
CONFIG.get(f"storage.{self.name}.path", "./data"),
)
)
@property
def base_path(self) -> Path:
"""Path structure: {base_dir}/{usage}/{schema}"""
return self._base_dir / self.usage.value / connection.schema_name
@property
def manageable(self) -> bool:
return (
self.base_path.exists()
and (self._base_dir.is_mount() or (self._base_dir / self.usage.value).is_mount())
or (settings.DEBUG or settings.TEST)
)
def supports_file(self, name: str) -> bool:
"""We support all files"""
return True
def list_files(self) -> Generator[str]:
"""List all files returning relative paths from base_path."""
for root, _, files in os.walk(self.base_path):
for file in files:
full_path = Path(root) / file
rel_path = full_path.relative_to(self.base_path)
yield str(rel_path)
def file_url(
self,
name: str,
request: HttpRequest | None = None,
use_cache: bool = True,
) -> str:
"""Get URL for accessing the file."""
expires_in = timedelta_from_string(
CONFIG.get(
f"storage.{self.usage.value}.{self.name}.url_expiry",
CONFIG.get(f"storage.{self.name}.url_expiry", "minutes=15"),
)
)
def _file_url(name: str, request: HttpRequest | None) -> str:
prefix = CONFIG.get("web.path", "/")[:-1]
path = f"{self.usage.value}/{connection.schema_name}/{name}"
token = jwt.encode(
payload={
"path": path,
"exp": now() + expires_in,
"nbf": now() - timedelta(seconds=15),
},
key=sha256(f"{settings.SECRET_KEY}:{self.usage}".encode()).hexdigest(),
algorithm="HS256",
)
url = f"{prefix}/files/{path}?token={token}"
if request is None:
return url
return request.build_absolute_uri(url)
if use_cache:
timeout = int(expires_in.total_seconds())
return self._cache_get_or_set(name, request, _file_url, timeout)
else:
return _file_url(name, request)
def save_file(self, name: str, content: bytes) -> None:
"""Save file to local filesystem."""
path = self.base_path / Path(name)
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w+b") as f:
f.write(content)
@contextmanager
def save_file_stream(self, name: str) -> Iterator:
"""Context manager for streaming file writes to local filesystem."""
path = self.base_path / Path(name)
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "wb") as f:
yield f
def delete_file(self, name: str) -> None:
"""Delete file from local filesystem."""
path = self.base_path / Path(name)
path.unlink(missing_ok=True)
def file_exists(self, name: str) -> bool:
"""Check if a file exists."""
path = self.base_path / Path(name)
return path.exists()

View File

@@ -1,48 +0,0 @@
from collections.abc import Generator
from django.http.request import HttpRequest
from authentik.admin.files.backends.base import Backend
from authentik.admin.files.usage import FileUsage
EXTERNAL_URL_SCHEMES = ["http:", "https://"]
FONT_AWESOME_SCHEME = "fa://"
class PassthroughBackend(Backend):
"""Passthrough backend for external URLs and special schemes.
Handles external resources that aren't stored in authentik:
- Font Awesome icons (fa://...)
- HTTP/HTTPS URLs (http://..., https://...)
Files that are "managed" by this backend are just passed through as-is.
No upload, delete, or listing operations are supported.
Only accessible through resolve_file_url when an external URL is detected.
"""
allowed_usages = [FileUsage.MEDIA]
def supports_file(self, name: str) -> bool:
"""Check if file path is an external URL or Font Awesome icon."""
if name.startswith(FONT_AWESOME_SCHEME):
return True
for scheme in EXTERNAL_URL_SCHEMES:
if name.startswith(scheme):
return True
return False
def list_files(self) -> Generator[str]:
"""External files cannot be listed."""
yield from []
def file_url(
self,
name: str,
request: HttpRequest | None = None,
use_cache: bool = True,
) -> str:
"""Return the URL as-is for passthrough files."""
return name

View File

@@ -1,226 +0,0 @@
from collections.abc import Generator, Iterator
from contextlib import contextmanager
from tempfile import SpooledTemporaryFile
from urllib.parse import urlsplit
import boto3
from botocore.config import Config
from botocore.exceptions import ClientError
from django.db import connection
from django.http.request import HttpRequest
from authentik.admin.files.backends.base import ManageableBackend
from authentik.admin.files.usage import FileUsage
from authentik.lib.config import CONFIG
from authentik.lib.utils.time import timedelta_from_string
class S3Backend(ManageableBackend):
"""S3-compatible object storage backend.
Stores files in s3-compatible storage:
- Key prefix: {usage}/{schema}/{filename}
- Supports full file management (upload, delete, list)
- Generates presigned URLs for file access
- Used when storage.backend=s3
"""
allowed_usages = list(FileUsage) # All usages
name = "s3"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._config = {}
self._session = None
def _get_config(self, key: str, default: str | None) -> tuple[str | None, bool]:
unset = object()
current = self._config.get(key, unset)
refreshed = CONFIG.refresh(
f"storage.{self.usage.value}.{self.name}.{key}",
CONFIG.refresh(f"storage.{self.name}.{key}", default),
)
if current is unset:
current = refreshed
self._config[key] = refreshed
return (refreshed, current != refreshed)
@property
def base_path(self) -> str:
"""S3 key prefix: {usage}/{schema}/"""
return f"{self.usage.value}/{connection.schema_name}"
@property
def bucket_name(self) -> str:
return CONFIG.get(
f"storage.{self.usage.value}.{self.name}.bucket_name",
CONFIG.get(f"storage.{self.name}.bucket_name"),
)
@property
def session(self) -> boto3.Session:
"""Create boto3 session with configured credentials."""
session_profile, session_profile_r = self._get_config("session_profile", None)
if session_profile is not None:
if session_profile_r or self._session is None:
self._session = boto3.Session(profile_name=session_profile)
return self._session
else:
return self._session
else:
access_key, access_key_r = self._get_config("access_key", None)
secret_key, secret_key_r = self._get_config("secret_key", None)
session_token, session_token_r = self._get_config("session_token", None)
if access_key_r or secret_key_r or session_token_r or self._session is None:
self._session = boto3.Session(
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
aws_session_token=session_token,
)
return self._session
else:
return self._session
@property
def client(self):
"""Create S3 client with configured endpoint and region."""
endpoint_url = CONFIG.get(
f"storage.{self.usage.value}.{self.name}.endpoint",
CONFIG.get(f"storage.{self.name}.endpoint", None),
)
use_ssl = CONFIG.get(
f"storage.{self.usage.value}.{self.name}.use_ssl",
CONFIG.get(f"storage.{self.name}.use_ssl", True),
)
region_name = CONFIG.get(
f"storage.{self.usage.value}.{self.name}.region",
CONFIG.get(f"storage.{self.name}.region", None),
)
addressing_style = CONFIG.get(
f"storage.{self.usage.value}.{self.name}.addressing_style",
CONFIG.get(f"storage.{self.name}.addressing_style", "auto"),
)
return self.session.client(
"s3",
endpoint_url=endpoint_url,
use_ssl=use_ssl,
region_name=region_name,
config=Config(signature_version="s3v4", s3={"addressing_style": addressing_style}),
)
@property
def manageable(self) -> bool:
return True
def supports_file(self, name: str) -> bool:
"""We support all files"""
return True
def list_files(self) -> Generator[str]:
"""List all files returning relative paths from base_path."""
paginator = self.client.get_paginator("list_objects_v2")
pages = paginator.paginate(Bucket=self.bucket_name, Prefix=f"{self.base_path}/")
for page in pages:
for obj in page.get("Contents", []):
key = obj["Key"]
# Remove base path prefix to get relative path
rel_path = key.removeprefix(f"{self.base_path}/")
if rel_path: # Skip if it's just the directory itself
yield rel_path
def file_url(
self,
name: str,
request: HttpRequest | None = None,
use_cache: bool = True,
) -> str:
"""Generate presigned URL for file access."""
use_https = CONFIG.get_bool(
f"storage.{self.usage.value}.{self.name}.secure_urls",
CONFIG.get_bool(f"storage.{self.name}.secure_urls", True),
)
expires_in = int(
timedelta_from_string(
CONFIG.get(
f"storage.{self.usage.value}.{self.name}.url_expiry",
CONFIG.get(f"storage.{self.name}.url_expiry", "minutes=15"),
)
).total_seconds()
)
def _file_url(name: str, request: HttpRequest | None) -> str:
params = {
"Bucket": self.bucket_name,
"Key": f"{self.base_path}/{name}",
}
url = self.client.generate_presigned_url(
"get_object",
Params=params,
ExpiresIn=expires_in,
HttpMethod="GET",
)
# Support custom domain for S3-compatible storage (so not AWS)
# Well, can't you do custom domains on AWS as well?
custom_domain = CONFIG.get(
f"storage.{self.usage.value}.{self.name}.custom_domain",
CONFIG.get(f"storage.{self.name}.custom_domain", None),
)
if custom_domain:
parsed = urlsplit(url)
scheme = "https" if use_https else "http"
url = f"{scheme}://{custom_domain}{parsed.path}?{parsed.query}"
return url
if use_cache:
return self._cache_get_or_set(name, request, _file_url, expires_in)
else:
return _file_url(name, request)
def save_file(self, name: str, content: bytes) -> None:
"""Save file to S3."""
self.client.put_object(
Bucket=self.bucket_name,
Key=f"{self.base_path}/{name}",
Body=content,
ACL="private",
)
@contextmanager
def save_file_stream(self, name: str) -> Iterator:
"""Context manager for streaming file writes to S3."""
# Keep files in memory up to 5 MB
with SpooledTemporaryFile(max_size=5 * 1024 * 1024, suffix=".S3File") as file:
yield file
file.seek(0)
self.client.upload_fileobj(
Fileobj=file,
Bucket=self.bucket_name,
Key=f"{self.base_path}/{name}",
ExtraArgs={
"ACL": "private",
},
)
def delete_file(self, name: str) -> None:
"""Delete file from S3."""
self.client.delete_object(
Bucket=self.bucket_name,
Key=f"{self.base_path}/{name}",
)
def file_exists(self, name: str) -> bool:
"""Check if a file exists in S3."""
try:
self.client.head_object(
Bucket=self.bucket_name,
Key=f"{self.base_path}/{name}",
)
return True
except ClientError:
return False

View File

@@ -1,58 +0,0 @@
from collections.abc import Generator
from pathlib import Path
from django.http.request import HttpRequest
from authentik.admin.files.backends.base import Backend
from authentik.admin.files.usage import FileUsage
from authentik.lib.config import CONFIG
STATIC_ASSETS_BASE_DIR = Path("web/dist")
STATIC_ASSETS_DIRS = [Path(p) for p in ("assets/icons", "assets/images")]
STATIC_ASSETS_SOURCES_DIR = Path("web/authentik/sources")
STATIC_FILE_EXTENSIONS = [".svg", ".png", ".jpg", ".jpeg"]
STATIC_PATH_PREFIX = "/static"
class StaticBackend(Backend):
"""Read-only backend for static files from web/dist/assets.
- Used for serving built-in static assets like icons and images.
- Files cannot be uploaded or deleted through this backend.
- Only accessible through resolve_file_url when a static path is detected.
"""
allowed_usages = [FileUsage.MEDIA]
def supports_file(self, name: str) -> bool:
"""Check if file path is a static path."""
return name.startswith(STATIC_PATH_PREFIX)
def list_files(self) -> Generator[str]:
"""List all static files."""
# List built-in source icons
if STATIC_ASSETS_SOURCES_DIR.exists():
for file_path in STATIC_ASSETS_SOURCES_DIR.iterdir():
if file_path.is_file() and (file_path.suffix in STATIC_FILE_EXTENSIONS):
yield f"{STATIC_PATH_PREFIX}/authentik/sources/{file_path.name}"
# List other static assets
for dir in STATIC_ASSETS_DIRS:
dist_dir = STATIC_ASSETS_BASE_DIR / dir
if dist_dir.exists():
for file_path in dist_dir.rglob("*"):
if file_path.is_file() and (file_path.suffix in STATIC_FILE_EXTENSIONS):
yield f"{STATIC_PATH_PREFIX}/dist/{dir}/{file_path.name}"
def file_url(
self,
name: str,
request: HttpRequest | None = None,
use_cache: bool = True,
) -> str:
"""Get URL for static file."""
prefix = CONFIG.get("web.path", "/")[:-1]
url = f"{prefix}{name}"
if request is None:
return url
return request.build_absolute_uri(url)

View File

@@ -1,167 +0,0 @@
from pathlib import Path
from django.test import TestCase
from authentik.admin.files.backends.file import FileBackend
from authentik.admin.files.tests.utils import FileTestFileBackendMixin
from authentik.admin.files.usage import FileUsage
from authentik.lib.config import CONFIG
class TestFileBackend(FileTestFileBackendMixin, TestCase):
"""Test FileBackend class"""
def setUp(self):
"""Set up test fixtures"""
super().setUp()
self.backend = FileBackend(FileUsage.MEDIA)
def test_allowed_usages(self):
"""Test that FileBackend supports all usage types"""
self.assertEqual(self.backend.allowed_usages, list(FileUsage))
def test_base_path(self):
"""Test base_path property constructs correct path"""
base_path = self.backend.base_path
expected = Path(self.media_backend_path) / "media" / "public"
self.assertEqual(base_path, expected)
def test_base_path_reports_usage(self):
"""Test base_path with reports usage"""
backend = FileBackend(FileUsage.REPORTS)
base_path = backend.base_path
expected = Path(self.reports_backend_path) / "reports" / "public"
self.assertEqual(base_path, expected)
def test_list_files_empty_directory(self):
"""Test list_files returns empty when directory is empty"""
# Create the directory but keep it empty
self.backend.base_path.mkdir(parents=True, exist_ok=True)
files = list(self.backend.list_files())
self.assertEqual(files, [])
def test_list_files_with_files(self):
"""Test list_files returns all files in directory"""
base_path = self.backend.base_path
base_path.mkdir(parents=True, exist_ok=True)
# Create some test files
(base_path / "file1.txt").write_text("content1")
(base_path / "file2.png").write_text("content2")
(base_path / "subdir").mkdir()
(base_path / "subdir" / "file3.csv").write_text("content3")
files = sorted(list(self.backend.list_files()))
expected = sorted(["file1.txt", "file2.png", "subdir/file3.csv"])
self.assertEqual(files, expected)
def test_list_files_nonexistent_directory(self):
"""Test list_files returns empty when directory doesn't exist"""
files = list(self.backend.list_files())
self.assertEqual(files, [])
def test_save_file(self):
content = b"test file content"
file_name = "test.txt"
self.backend.save_file(file_name, content)
# Verify file was created
file_path = self.backend.base_path / file_name
self.assertTrue(file_path.exists())
self.assertEqual(file_path.read_bytes(), content)
def test_save_file_creates_subdirectories(self):
"""Test save_file creates parent directories as needed"""
content = b"nested file content"
file_name = "subdir1/subdir2/nested.txt"
self.backend.save_file(file_name, content)
# Verify file and directories were created
file_path = self.backend.base_path / file_name
self.assertTrue(file_path.exists())
self.assertEqual(file_path.read_bytes(), content)
def test_save_file_stream(self):
"""Test save_file_stream context manager writes file correctly"""
content = b"streamed content"
file_name = "stream_test.txt"
with self.backend.save_file_stream(file_name) as f:
f.write(content)
# Verify file was created
file_path = self.backend.base_path / file_name
self.assertTrue(file_path.exists())
self.assertEqual(file_path.read_bytes(), content)
def test_save_file_stream_creates_subdirectories(self):
"""Test save_file_stream creates parent directories as needed"""
content = b"nested stream content"
file_name = "dir1/dir2/stream.bin"
with self.backend.save_file_stream(file_name) as f:
f.write(content)
# Verify file and directories were created
file_path = self.backend.base_path / file_name
self.assertTrue(file_path.exists())
self.assertEqual(file_path.read_bytes(), content)
def test_delete_file(self):
"""Test delete_file removes existing file"""
file_name = "to_delete.txt"
# Create file first
self.backend.save_file(file_name, b"content")
file_path = self.backend.base_path / file_name
self.assertTrue(file_path.exists())
# Delete it
self.backend.delete_file(file_name)
self.assertFalse(file_path.exists())
def test_delete_file_nonexistent(self):
"""Test delete_file handles nonexistent file gracefully"""
file_name = "does_not_exist.txt"
self.backend.delete_file(file_name)
def test_file_url(self):
"""Test file_url generates correct URL"""
file_name = "icon.png"
url = self.backend.file_url(file_name).split("?")[0]
expected = "/files/media/public/icon.png"
self.assertEqual(url, expected)
@CONFIG.patch("web.path", "/authentik/")
def test_file_url_with_prefix(self):
"""Test file_url with web path prefix"""
file_name = "logo.svg"
url = self.backend.file_url(file_name).split("?")[0]
expected = "/authentik/files/media/public/logo.svg"
self.assertEqual(url, expected)
def test_file_url_nested_path(self):
"""Test file_url with nested file path"""
file_name = "path/to/file.png"
url = self.backend.file_url(file_name).split("?")[0]
expected = "/files/media/public/path/to/file.png"
self.assertEqual(url, expected)
def test_file_exists_true(self):
"""Test file_exists returns True for existing file"""
file_name = "exists.txt"
self.backend.base_path.mkdir(parents=True, exist_ok=True)
(self.backend.base_path / file_name).touch()
self.assertTrue(self.backend.file_exists(file_name))
def test_file_exists_false(self):
"""Test file_exists returns False for nonexistent file"""
self.assertFalse(self.backend.file_exists("does_not_exist.txt"))

View File

@@ -1,67 +0,0 @@
"""Test passthrough backend"""
from django.test import TestCase
from authentik.admin.files.backends.passthrough import PassthroughBackend
from authentik.admin.files.usage import FileUsage
class TestPassthroughBackend(TestCase):
"""Test PassthroughBackend class"""
def setUp(self):
"""Set up test fixtures"""
self.backend = PassthroughBackend(FileUsage.MEDIA)
def test_allowed_usages(self):
"""Test that PassthroughBackend only supports MEDIA usage"""
self.assertEqual(self.backend.allowed_usages, [FileUsage.MEDIA])
def test_supports_file_path_font_awesome(self):
"""Test supports_file_path returns True for Font Awesome icons"""
self.assertTrue(self.backend.supports_file("fa://user"))
self.assertTrue(self.backend.supports_file("fa://home"))
self.assertTrue(self.backend.supports_file("fa://shield"))
def test_supports_file_path_http(self):
"""Test supports_file_path returns True for HTTP URLs"""
self.assertTrue(self.backend.supports_file("http://example.com/icon.png"))
self.assertTrue(self.backend.supports_file("http://cdn.example.com/logo.svg"))
def test_supports_file_path_https(self):
"""Test supports_file_path returns True for HTTPS URLs"""
self.assertTrue(self.backend.supports_file("https://example.com/icon.png"))
self.assertTrue(self.backend.supports_file("https://cdn.example.com/logo.svg"))
def test_supports_file_path_false(self):
"""Test supports_file_path returns False for regular paths"""
self.assertFalse(self.backend.supports_file("icon.png"))
self.assertFalse(self.backend.supports_file("/static/icon.png"))
self.assertFalse(self.backend.supports_file("media/logo.svg"))
self.assertFalse(self.backend.supports_file(""))
def test_supports_file_path_invalid_scheme(self):
"""Test supports_file_path returns False for invalid schemes"""
self.assertFalse(self.backend.supports_file("ftp://example.com/file.png"))
self.assertFalse(self.backend.supports_file("file:///path/to/file.png"))
self.assertFalse(self.backend.supports_file("data:image/png;base64,abc123"))
def test_list_files(self):
"""Test list_files returns empty generator"""
files = list(self.backend.list_files())
self.assertEqual(files, [])
def test_file_url(self):
"""Test file_url returns the URL as-is"""
url = "https://example.com/icon.png"
self.assertEqual(self.backend.file_url(url), url)
def test_file_url_font_awesome(self):
"""Test file_url returns Font Awesome URL as-is"""
url = "fa://user"
self.assertEqual(self.backend.file_url(url), url)
def test_file_url_http(self):
"""Test file_url returns HTTP URL as-is"""
url = "http://cdn.example.com/logo.svg"
self.assertEqual(self.backend.file_url(url), url)

View File

@@ -1,109 +0,0 @@
from django.test import TestCase
from authentik.admin.files.tests.utils import FileTestS3BackendMixin
from authentik.admin.files.usage import FileUsage
from authentik.lib.config import CONFIG
class TestS3Backend(FileTestS3BackendMixin, TestCase):
"""Test S3 backend functionality"""
def setUp(self):
super().setUp()
def test_base_path(self):
"""Test base_path property generates correct S3 key prefix"""
expected = "media/public"
self.assertEqual(self.media_s3_backend.base_path, expected)
def test_supports_file_path_s3(self):
"""Test supports_file_path returns True for s3 backend"""
self.assertTrue(self.media_s3_backend.supports_file("path/to/any-file.png"))
self.assertTrue(self.media_s3_backend.supports_file("any-file.png"))
def test_list_files(self):
"""Test list_files returns relative paths"""
self.media_s3_backend.client.put_object(
Bucket=self.media_s3_bucket_name,
Key="media/public/file1.png",
Body=b"test content",
ACL="private",
)
self.media_s3_backend.client.put_object(
Bucket=self.media_s3_bucket_name,
Key="media/other/file1.png",
Body=b"test content",
ACL="private",
)
files = list(self.media_s3_backend.list_files())
self.assertEqual(len(files), 1)
self.assertIn("file1.png", files)
def test_list_files_empty(self):
"""Test list_files with no files"""
files = list(self.media_s3_backend.list_files())
self.assertEqual(len(files), 0)
def test_save_file(self):
"""Test save_file uploads to S3"""
content = b"test file content"
self.media_s3_backend.save_file("test.png", content)
def test_save_file_stream(self):
"""Test save_file_stream uploads to S3 using context manager"""
with self.media_s3_backend.save_file_stream("test.csv") as f:
f.write(b"header1,header2\n")
f.write(b"value1,value2\n")
def test_delete_file(self):
"""Test delete_file removes from S3"""
self.media_s3_backend.client.put_object(
Bucket=self.media_s3_bucket_name,
Key="media/public/test.png",
Body=b"test content",
ACL="private",
)
self.media_s3_backend.delete_file("test.png")
@CONFIG.patch("storage.s3.secure_urls", True)
@CONFIG.patch("storage.s3.custom_domain", None)
def test_file_url_basic(self):
"""Test file_url generates presigned URL with AWS signature format"""
url = self.media_s3_backend.file_url("test.png")
self.assertIn("X-Amz-Algorithm=AWS4-HMAC-SHA256", url)
self.assertIn("X-Amz-Signature=", url)
self.assertIn("test.png", url)
@CONFIG.patch("storage.s3.bucket_name", "test-bucket")
def test_file_exists_true(self):
"""Test file_exists returns True for existing file"""
self.media_s3_backend.client.put_object(
Bucket=self.media_s3_bucket_name,
Key="media/public/test.png",
Body=b"test content",
ACL="private",
)
exists = self.media_s3_backend.file_exists("test.png")
self.assertTrue(exists)
@CONFIG.patch("storage.s3.bucket_name", "test-bucket")
def test_file_exists_false(self):
"""Test file_exists returns False for non-existent file"""
exists = self.media_s3_backend.file_exists("nonexistent.png")
self.assertFalse(exists)
def test_allowed_usages(self):
"""Test that S3Backend supports all usage types"""
self.assertEqual(self.media_s3_backend.allowed_usages, list(FileUsage))
def test_reports_usage(self):
"""Test S3Backend with REPORTS usage"""
self.assertEqual(self.reports_s3_backend.usage, FileUsage.REPORTS)
self.assertEqual(self.reports_s3_backend.base_path, "reports/public")

View File

@@ -1,42 +0,0 @@
from django.test import TestCase
from authentik.admin.files.backends.static import StaticBackend
from authentik.admin.files.usage import FileUsage
class TestStaticBackend(TestCase):
"""Test Static backend functionality"""
def setUp(self):
"""Set up test fixtures"""
self.usage = FileUsage.MEDIA
self.backend = StaticBackend(self.usage)
def test_init(self):
"""Test StaticBackend initialization"""
self.assertEqual(self.backend.usage, self.usage)
def test_allowed_usages(self):
"""Test that StaticBackend only supports MEDIA usage"""
self.assertEqual(self.backend.allowed_usages, [FileUsage.MEDIA])
def test_supports_file_path_static_prefix(self):
"""Test supports_file_path returns True for /static prefix"""
self.assertTrue(self.backend.supports_file("/static/assets/icons/test.svg"))
self.assertTrue(self.backend.supports_file("/static/authentik/sources/icon.png"))
def test_supports_file_path_not_static(self):
"""Test supports_file_path returns False for non-static paths"""
self.assertFalse(self.backend.supports_file("web/dist/assets/icons/test.svg"))
self.assertFalse(self.backend.supports_file("web/dist/assets/images/logo.png"))
self.assertFalse(self.backend.supports_file("media/public/test.png"))
self.assertFalse(self.backend.supports_file("/media/test.svg"))
self.assertFalse(self.backend.supports_file("test.jpg"))
def test_list_files(self):
"""Test list_files includes expected files"""
files = list(self.backend.list_files())
self.assertIn("/static/authentik/sources/ldap.png", files)
self.assertIn("/static/authentik/sources/openidconnect.svg", files)
self.assertIn("/static/authentik/sources/saml.png", files)

View File

@@ -1,7 +0,0 @@
from django.db import models
from authentik.admin.files.validation import validate_file_name
class FileField(models.TextField):
default_validators = [validate_file_name]

View File

@@ -1,142 +0,0 @@
from collections.abc import Generator, Iterator
from django.core.exceptions import ImproperlyConfigured
from django.http.request import HttpRequest
from rest_framework.request import Request
from structlog.stdlib import get_logger
from authentik.admin.files.backends.base import ManageableBackend
from authentik.admin.files.backends.file import FileBackend
from authentik.admin.files.backends.passthrough import PassthroughBackend
from authentik.admin.files.backends.s3 import S3Backend
from authentik.admin.files.backends.static import StaticBackend
from authentik.admin.files.usage import FileUsage
from authentik.lib.config import CONFIG
LOGGER = get_logger()
_FILE_BACKENDS = [
StaticBackend,
PassthroughBackend,
FileBackend,
S3Backend,
]
class FileManager:
def __init__(self, usage: FileUsage) -> None:
management_backend_name = CONFIG.get(
f"storage.{usage.value}.backend",
CONFIG.get("storage.backend", "file"),
)
self.management_backend = None
for backend in _FILE_BACKENDS:
if issubclass(backend, ManageableBackend) and backend.name == management_backend_name:
self.management_backend = backend(usage)
if self.management_backend is None:
LOGGER.warning(
f"Storage backend configuration for {usage.value} is "
f"invalid: {management_backend_name}"
)
self.backends = []
for backend in _FILE_BACKENDS:
if usage not in backend.allowed_usages:
continue
if isinstance(self.management_backend, backend):
self.backends.append(self.management_backend)
elif not issubclass(backend, ManageableBackend):
self.backends.append(backend(usage))
@property
def manageable(self) -> bool:
"""
Whether this file manager is able to manage files.
"""
return self.management_backend is not None and self.management_backend.manageable
def list_files(self, manageable_only: bool = False) -> Generator[str]:
"""
List available files.
"""
for backend in self.backends:
if manageable_only and not isinstance(backend, ManageableBackend):
continue
yield from backend.list_files()
def file_url(
self,
name: str | None,
request: HttpRequest | Request | None = None,
use_cache: bool = True,
) -> str:
"""
Get URL for accessing the file.
"""
if not name:
return ""
if isinstance(request, Request):
request = request._request
for backend in self.backends:
if backend.supports_file(name):
return backend.file_url(name, request)
LOGGER.warning(f"Could not find file backend for file: {name}")
return ""
def _check_manageable(self) -> None:
if not self.manageable:
raise ImproperlyConfigured("No file management backend configured.")
def save_file(self, file_path: str, content: bytes) -> None:
"""
Save file contents to storage.
"""
self._check_manageable()
assert self.management_backend is not None # nosec
return self.management_backend.save_file(file_path, content)
def save_file_stream(self, file_path: str) -> Iterator:
"""
Context manager for streaming file writes.
Args:
file_path: Relative file path
Returns:
Context manager that yields a writable file-like object
Usage:
with manager.save_file_stream("output.csv") as f:
f.write(b"data...")
"""
self._check_manageable()
assert self.management_backend is not None # nosec
return self.management_backend.save_file_stream(file_path)
def delete_file(self, file_path: str) -> None:
"""
Delete file from storage.
"""
self._check_manageable()
assert self.management_backend is not None # nosec
return self.management_backend.delete_file(file_path)
def file_exists(self, file_path: str) -> bool:
"""
Check if a file exists.
"""
self._check_manageable()
assert self.management_backend is not None # nosec
return self.management_backend.file_exists(file_path)
MANAGERS = {usage: FileManager(usage) for usage in list(FileUsage)}
def get_file_manager(usage: FileUsage) -> FileManager:
return MANAGERS[usage]

View File

@@ -1 +0,0 @@
"""authentik files tests"""

View File

@@ -1,229 +0,0 @@
"""test file api"""
from io import BytesIO
from django.test import TestCase
from django.urls import reverse
from authentik.admin.files.api import get_mime_from_filename
from authentik.admin.files.manager import FileManager
from authentik.admin.files.tests.utils import FileTestFileBackendMixin
from authentik.admin.files.usage import FileUsage
from authentik.core.tests.utils import create_test_admin_user
from authentik.events.models import Event, EventAction
class TestFileAPI(FileTestFileBackendMixin, TestCase):
"""test file api"""
def setUp(self) -> None:
super().setUp()
self.user = create_test_admin_user()
self.client.force_login(self.user)
def test_upload_creates_event(self):
"""Test that uploading a file creates a FILE_UPLOADED event"""
manager = FileManager(FileUsage.MEDIA)
file_content = b"test file content"
file_name = "test-upload.png"
# Upload file
response = self.client.post(
reverse("authentik_api:files"),
{
"file": BytesIO(file_content),
"name": file_name,
"usage": FileUsage.MEDIA.value,
},
format="multipart",
)
self.assertEqual(response.status_code, 200)
# Verify event was created
event = Event.objects.filter(action=EventAction.MODEL_CREATED).first()
self.assertIsNotNone(event)
assert event is not None # nosec
self.assertEqual(event.context["model"]["name"], file_name)
self.assertEqual(event.context["model"]["usage"], FileUsage.MEDIA.value)
self.assertEqual(event.context["model"]["mime_type"], "image/png")
# Verify user is captured
self.assertEqual(event.user["username"], self.user.username)
self.assertEqual(event.user["pk"], self.user.pk)
manager.delete_file(file_name)
def test_delete_creates_event(self):
"""Test that deleting a file creates an event"""
manager = FileManager(FileUsage.MEDIA)
file_name = "test-delete.png"
manager.save_file(file_name, b"test content")
# Delete file
response = self.client.delete(
reverse(
"authentik_api:files",
query={
"name": file_name,
"usage": FileUsage.MEDIA.value,
},
)
)
self.assertEqual(response.status_code, 200)
# Verify event was created
event = Event.objects.filter(action=EventAction.MODEL_DELETED).first()
self.assertIsNotNone(event)
assert event is not None # nosec
self.assertEqual(event.context["model"]["name"], file_name)
self.assertEqual(event.context["model"]["usage"], FileUsage.MEDIA.value)
# Verify user is captured
self.assertEqual(event.user["username"], self.user.username)
self.assertEqual(event.user["pk"], self.user.pk)
def test_list_files_basic(self):
"""Test listing files with default parameters"""
response = self.client.get(reverse("authentik_api:files"))
self.assertEqual(response.status_code, 200)
self.assertIn(
{
"name": "/static/authentik/sources/ldap.png",
"url": "/static/authentik/sources/ldap.png",
"mime_type": "image/png",
},
response.data,
)
def test_list_files_invalid_usage(self):
"""Test listing files with invalid usage parameter"""
response = self.client.get(
reverse(
"authentik_api:files",
query={
"usage": "invalid",
},
)
)
self.assertEqual(response.status_code, 400)
self.assertIn("not a valid choice", str(response.data))
def test_list_files_with_search(self):
"""Test listing files with search query"""
response = self.client.get(
reverse(
"authentik_api:files",
query={
"search": "ldap.png",
},
)
)
self.assertEqual(response.status_code, 200)
self.assertIn(
{
"name": "/static/authentik/sources/ldap.png",
"url": "/static/authentik/sources/ldap.png",
"mime_type": "image/png",
},
response.data,
)
def test_list_files_with_manageable_only(self):
"""Test listing files with omit parameter"""
response = self.client.get(
reverse(
"authentik_api:files",
query={
"manageableOnly": "true",
},
)
)
self.assertEqual(response.status_code, 200)
self.assertNotIn(
{
"name": "/static/dist/assets/images/flow_background.jpg",
"mime_type": "image/jpeg",
},
response.data,
)
def test_upload_file_with_custom_path(self):
"""Test uploading file with custom path"""
manager = FileManager(FileUsage.MEDIA)
file_name = "custom/test"
file_content = b"test content"
response = self.client.post(
reverse("authentik_api:files"),
{
"file": BytesIO(file_content),
"name": file_name,
"usage": FileUsage.MEDIA.value,
},
format="multipart",
)
self.assertEqual(response.status_code, 200)
self.assertTrue(manager.file_exists(file_name))
manager.delete_file(file_name)
def test_upload_file_duplicate(self):
"""Test uploading file that already exists"""
manager = FileManager(FileUsage.MEDIA)
file_name = "test-file.png"
file_content = b"test content"
manager.save_file(file_name, file_content)
response = self.client.post(
reverse("authentik_api:files"),
{
"file": BytesIO(file_content),
"name": file_name,
},
format="multipart",
)
self.assertEqual(response.status_code, 400)
self.assertIn("already exists", str(response.data))
manager.delete_file(file_name)
def test_delete_without_name_parameter(self):
"""Test delete without name parameter"""
response = self.client.delete(reverse("authentik_api:files"))
self.assertEqual(response.status_code, 400)
self.assertIn("field is required", str(response.data))
class TestGetMimeFromFilename(TestCase):
"""Test get_mime_from_filename function"""
def test_image_png(self):
"""Test PNG image MIME type"""
self.assertEqual(get_mime_from_filename("test.png"), "image/png")
def test_image_jpeg(self):
"""Test JPEG image MIME type"""
self.assertEqual(get_mime_from_filename("test.jpg"), "image/jpeg")
def test_image_svg(self):
"""Test SVG image MIME type"""
self.assertEqual(get_mime_from_filename("test.svg"), "image/svg+xml")
def test_text_plain(self):
"""Test text file MIME type"""
self.assertEqual(get_mime_from_filename("test.txt"), "text/plain")
def test_unknown_extension(self):
"""Test unknown extension returns octet-stream"""
self.assertEqual(get_mime_from_filename("test.unknown"), "application/octet-stream")
def test_no_extension(self):
"""Test no extension returns octet-stream"""
self.assertEqual(get_mime_from_filename("test"), "application/octet-stream")

View File

@@ -1,99 +0,0 @@
"""Test file service layer"""
from django.http import HttpRequest
from django.test import TestCase
from authentik.admin.files.manager import FileManager
from authentik.admin.files.tests.utils import FileTestFileBackendMixin, FileTestS3BackendMixin
from authentik.admin.files.usage import FileUsage
from authentik.lib.config import CONFIG
class TestResolveFileUrlBasic(TestCase):
def test_resolve_empty_path(self):
"""Test resolving empty file path"""
manager = FileManager(FileUsage.MEDIA)
result = manager.file_url("")
self.assertEqual(result, "")
def test_resolve_none_path(self):
"""Test resolving None file path"""
manager = FileManager(FileUsage.MEDIA)
result = manager.file_url(None)
self.assertEqual(result, "")
def test_resolve_font_awesome(self):
"""Test resolving Font Awesome icon"""
manager = FileManager(FileUsage.MEDIA)
result = manager.file_url("fa://fa-check")
self.assertEqual(result, "fa://fa-check")
def test_resolve_http_url(self):
"""Test resolving HTTP URL"""
manager = FileManager(FileUsage.MEDIA)
result = manager.file_url("http://example.com/icon.png")
self.assertEqual(result, "http://example.com/icon.png")
def test_resolve_https_url(self):
"""Test resolving HTTPS URL"""
manager = FileManager(FileUsage.MEDIA)
result = manager.file_url("https://example.com/icon.png")
self.assertEqual(result, "https://example.com/icon.png")
def test_resolve_static_path(self):
"""Test resolving static file path"""
manager = FileManager(FileUsage.MEDIA)
result = manager.file_url("/static/authentik/sources/icon.svg")
self.assertEqual(result, "/static/authentik/sources/icon.svg")
class TestResolveFileUrlFileBackend(FileTestFileBackendMixin, TestCase):
def test_resolve_storage_file(self):
"""Test resolving uploaded storage file"""
manager = FileManager(FileUsage.MEDIA)
result = manager.file_url("test.png").split("?")[0]
self.assertEqual(result, "/files/media/public/test.png")
def test_resolve_full_static_with_request(self):
"""Test resolving static file with request builds absolute URI"""
mock_request = HttpRequest()
mock_request.META = {
"HTTP_HOST": "example.com",
"SERVER_NAME": "example.com",
}
manager = FileManager(FileUsage.MEDIA)
result = manager.file_url("/static/icon.svg", mock_request)
self.assertEqual(result, "http://example.com/static/icon.svg")
def test_resolve_full_file_backend_with_request(self):
"""Test resolving FileBackend file with request"""
mock_request = HttpRequest()
mock_request.META = {
"HTTP_HOST": "example.com",
"SERVER_NAME": "example.com",
}
manager = FileManager(FileUsage.MEDIA)
result = manager.file_url("test.png", mock_request).split("?")[0]
self.assertEqual(result, "http://example.com/files/media/public/test.png")
class TestResolveFileUrlS3Backend(FileTestS3BackendMixin, TestCase):
@CONFIG.patch("storage.media.s3.custom_domain", "s3.test:8080/test")
@CONFIG.patch("storage.media.s3.secure_urls", False)
def test_resolve_full_s3_backend(self):
"""Test resolving S3Backend returns presigned URL as-is"""
mock_request = HttpRequest()
mock_request.META = {
"HTTP_HOST": "example.com",
"SERVER_NAME": "example.com",
}
manager = FileManager(FileUsage.MEDIA)
result = manager.file_url("test.png", mock_request)
# S3 URLs should be returned as-is (already absolute)
self.assertTrue(result.startswith("http://s3.test:8080/test"))

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