1{
2 lib,
3 stdenvNoCC,
4 callPackages,
5 cacert,
6 fetchFromGitHub,
7 buildGoModule,
8 runCommand,
9 bash,
10 chromedriver,
11 openapi-generator-cli,
12 nodejs,
13 python312,
14 makeWrapper,
15}:
16
17let
18 version = "2025.6.4";
19
20 src = fetchFromGitHub {
21 owner = "goauthentik";
22 repo = "authentik";
23 rev = "version/${version}";
24 hash = "sha256-bs/ThY3YixwBObahcS7BrOWj0gsaUXI664ldUQlJul8=";
25 };
26
27 meta = {
28 description = "Authentication glue you need";
29 changelog = "https://github.com/goauthentik/authentik/releases/tag/version%2F${version}";
30 homepage = "https://goauthentik.io/";
31 license = lib.licenses.mit;
32 platforms = [
33 "aarch64-linux"
34 "x86_64-linux"
35 ];
36 maintainers = with lib.maintainers; [
37 jvanbruegge
38 risson
39 ];
40 };
41
42 # prefetch-npm-deps does not save all dependencies even though the lockfile is fine
43 website-deps = stdenvNoCC.mkDerivation {
44 pname = "authentik-website-deps";
45 inherit src version meta;
46
47 sourceRoot = "${src.name}/website";
48
49 outputHash =
50 {
51 "aarch64-linux" = "sha256-+UObt/FhHkEzZUR24ND6phSDqvuzaOuUiyoW0dolsiY=";
52 "x86_64-linux" = "sha256-1qlJf4mVYM5znF3Ifi7CpGMfinUsb/YoXGeM6QLYMis=";
53 }
54 .${stdenvNoCC.hostPlatform.system} or (throw "authentik-website-deps: unsupported hust platform");
55
56 outputHashMode = "recursive";
57
58 nativeBuildInputs = [
59 nodejs
60 cacert
61 ];
62
63 buildPhase = ''
64 npm ci --cache ./cache
65
66 rm -r ./cache node_modules/.package-lock.json
67 '';
68
69 installPhase = ''
70 mv node_modules $out
71 '';
72
73 dontPatchShebangs = true;
74 };
75
76 website = stdenvNoCC.mkDerivation {
77 pname = "authentik-website";
78 inherit src version meta;
79
80 nativeBuildInputs = [ nodejs ];
81
82 postPatch = ''
83 substituteInPlace package.json --replace-fail 'cross-env ' ""
84 substituteInPlace ../packages/docusaurus-config/lib/common.js \
85 --replace-fail 'title: "authentik",' 'title: "authentik", future: { experimental_faster : { rspackBundler: false }},'
86 '';
87
88 sourceRoot = "${src.name}/website";
89
90 buildPhase = ''
91 runHook preBuild
92
93 cp -r ${website-deps} node_modules
94 chmod -R +w node_modules
95
96 pushd node_modules/.bin
97 patchShebangs $(readlink docusaurus)
98 popd
99 npm run build:schema
100 npm run build:api
101 npm run build:docusaurus
102
103 runHook postBuild
104 '';
105
106 installPhase = ''
107 mkdir $out
108 cp -r build $out/help
109 '';
110 };
111
112 clientapi = stdenvNoCC.mkDerivation {
113 pname = "authentik-client-api";
114 inherit version src meta;
115
116 postPatch = ''
117 rm Makefile
118
119 substituteInPlace ./scripts/api-ts-config.yaml \
120 --replace-fail '/local' "$(pwd)/"
121 '';
122
123 nativeBuildInputs = [ openapi-generator-cli ];
124 buildPhase = ''
125 runHook preBuild
126 openapi-generator-cli generate -i ./schema.yml \
127 -g typescript-fetch -o $out \
128 -c ./scripts/api-ts-config.yaml \
129 --additional-properties=npmVersion="$(${lib.getExe' nodejs "npm"} --version)" \
130 --git-repo-id authentik --git-user-id goauthentik
131 runHook postBuild
132 '';
133 };
134
135 # prefetch-npm-deps does not save all dependencies even though the lockfile is fine
136 webui-deps = stdenvNoCC.mkDerivation {
137 pname = "authentik-webui-deps";
138 inherit src version meta;
139
140 sourceRoot = "${src.name}/web";
141
142 outputHash =
143 {
144 "aarch64-linux" = "sha256-e4v7f3+/e++CI9xa9G2/47y8Dga+vluyUtBXGyaWFcI=";
145 "x86_64-linux" = "sha256-m0vYUevOz6pof8XqiZHXzgPhkQLUUFOdblmfOjHJUJc=";
146 }
147 .${stdenvNoCC.hostPlatform.system} or (throw "authentik-webui-deps: unsupported hust platform");
148 outputHashMode = "recursive";
149
150 nativeBuildInputs = [
151 nodejs
152 cacert
153 ];
154
155 postPatch = ''
156 substituteInPlace packages/core/version/node.js \
157 --replace-fail 'import PackageJSON from "../../../../package.json" with { type: "json" };' "" \
158 --replace-fail '(PackageJSON.version);' '"${version}";'
159 '';
160
161 buildPhase = ''
162 npm ci --cache ./cache
163
164 rm -r node_modules/chromedriver node_modules/.bin/chromedriver
165
166 rm -r ./cache node_modules/.package-lock.json
167 rm node_modules/@goauthentik/{core,web-sfe,esbuild-plugin-live-reload}
168 cp -r packages/core node_modules/@goauthentik/
169 cp -r packages/sfe node_modules/@goauthentik/web-sfe
170 '';
171
172 installPhase = ''
173 mv node_modules $out
174 '';
175
176 dontPatchShebangs = true;
177 };
178
179 webui = stdenvNoCC.mkDerivation {
180 pname = "authentik-webui";
181 inherit version meta;
182
183 src = runCommand "authentik-webui-source" { } ''
184 mkdir $out
185 cp -r ${src}/web $out/
186 ln -s ${src}/package.json $out/
187 chmod -R +w $out/web
188 ln -s ${src}/website $out/web/
189 cp -r ${webui-deps} $out/web/node_modules
190 chmod -R +w $out/web/node_modules
191 ln -s ${clientapi} $out/web/node_modules/@goauthentik/api
192 '';
193
194 nativeBuildInputs = [
195 nodejs
196 ];
197
198 postPatch = ''
199 cd web
200
201 substituteInPlace packages/core/version/node.js \
202 --replace-fail 'import PackageJSON from "../../../../package.json" with { type: "json" };' "" \
203 --replace-fail '(PackageJSON.version);' '"${version}";'
204 '';
205
206 buildPhase = ''
207 runHook preBuild
208
209 pushd node_modules/.bin
210 patchShebangs $(readlink rollup)
211 patchShebangs $(readlink wireit)
212 patchShebangs $(readlink lit-localize)
213 popd
214
215 npm run build
216
217 runHook postBuild
218 '';
219
220 CHROMEDRIVER_FILEPATH = lib.getExe chromedriver;
221
222 installPhase = ''
223 runHook preInstall
224 mkdir $out
225 cp -r dist $out/dist
226 cp -r authentik $out/authentik
227 runHook postInstall
228 '';
229
230 NODE_ENV = "production";
231 NODE_OPTIONS = "--openssl-legacy-provider";
232
233 npmInstallFlags = [
234 "--include=dev"
235 "--ignore-scripts"
236 ];
237 };
238
239 python = python312.override {
240 self = python;
241 packageOverrides = final: prev: {
242 # https://github.com/goauthentik/authentik/pull/14709
243 django = final.django_5_1;
244
245 # Running authentik currently requires a custom version.
246 # Look in `pyproject.toml` for changes to the rev in the `[tool.uv.sources]` section.
247 # See https://github.com/goauthentik/authentik/pull/14057 for latest version bump.
248 djangorestframework = prev.buildPythonPackage {
249 pname = "djangorestframework";
250 version = "3.16.0";
251 format = "setuptools";
252
253 src = fetchFromGitHub {
254 owner = "authentik-community";
255 repo = "django-rest-framework";
256 rev = "896722bab969fabc74a08b827da59409cf9f1a4e";
257 hash = "sha256-YrEDEU3qtw/iyQM3CoB8wYx57zuPNXiJx6ZjrIwnCNU=";
258 };
259
260 propagatedBuildInputs = with final; [
261 django
262 pytz
263 ];
264
265 nativeCheckInputs = with final; [
266 pytest-django
267 pytest7CheckHook
268
269 # optional tests
270 coreapi
271 django-guardian
272 inflection
273 pyyaml
274 uritemplate
275 ];
276
277 disabledTests = [
278 "test_ignore_validation_for_unchanged_fields"
279 "test_invalid_inputs"
280 "test_shell_code_example_rendering"
281 "test_unique_together_condition"
282 "test_unique_together_with_source"
283 ];
284
285 pythonImportsCheck = [ "rest_framework" ];
286 };
287
288 tenant-schemas-celery = prev.tenant-schemas-celery.overrideAttrs (_: rec {
289 version = "3.0.0";
290
291 src = fetchFromGitHub {
292 owner = "maciej-gol";
293 repo = "tenant-schemas-celery";
294 tag = version;
295 hash = "sha256-rGLrP8rE9SACMDVpNeBU85U/Sb2lNhsgEgHJhAsdKNM=";
296 };
297 });
298
299 authentik-django = prev.buildPythonPackage {
300 pname = "authentik-django";
301 inherit version src meta;
302 pyproject = true;
303
304 postPatch = ''
305 rm lifecycle/system_migrations/tenant_files.py
306 substituteInPlace authentik/root/settings.py \
307 --replace-fail 'Path(__file__).absolute().parent.parent.parent' "Path(\"$out\")"
308 substituteInPlace authentik/lib/default.yml \
309 --replace-fail '/blueprints' "$out/blueprints" \
310 --replace-fail './media' '/var/lib/authentik/media'
311 substituteInPlace pyproject.toml \
312 --replace-fail 'djangorestframework-guardian' 'djangorestframework-guardian2'
313 substituteInPlace authentik/stages/email/utils.py \
314 --replace-fail 'web/' '${webui}/'
315 '';
316
317 nativeBuildInputs = [
318 prev.hatchling
319 prev.pythonRelaxDepsHook
320 ];
321
322 pythonRemoveDeps = [ "dumb-init" ];
323
324 pythonRelaxDeps = true;
325
326 propagatedBuildInputs =
327 with final;
328 [
329 argon2-cffi
330 celery
331 channels
332 channels-redis
333 cryptography
334 dacite
335 deepmerge
336 defusedxml
337 django
338 django-countries
339 django-cte
340 django-filter
341 django-guardian
342 django-model-utils
343 django-pglock
344 django-prometheus
345 django-redis
346 django-storages
347 django-tenants
348 djangorestframework
349 djangorestframework-guardian2
350 docker
351 drf-orjson-renderer
352 drf-spectacular
353 duo-client
354 fido2
355 flower
356 geoip2
357 geopy
358 google-api-python-client
359 gunicorn
360 gssapi
361 jsonpatch
362 jwcrypto
363 kubernetes
364 ldap3
365 lxml
366 msgraph-sdk
367 opencontainers
368 packaging
369 paramiko
370 psycopg
371 pydantic
372 pydantic-scim
373 pyjwt
374 pyrad
375 python-kadmin-rs
376 pyyaml
377 requests-oauthlib
378 scim2-filter-parser
379 sentry-sdk
380 service-identity
381 setproctitle
382 structlog
383 swagger-spec-validator
384 tenant-schemas-celery
385 twilio
386 ua-parser
387 unidecode
388 urllib3
389 uvicorn
390 watchdog
391 webauthn
392 wsproto
393 xmlsec
394 zxcvbn
395 ]
396 ++ django-storages.optional-dependencies.s3
397 ++ opencontainers.optional-dependencies.reggie
398 ++ psycopg.optional-dependencies.c
399 ++ psycopg.optional-dependencies.pool
400 ++ uvicorn.optional-dependencies.standard;
401
402 postInstall = ''
403 mkdir -p $out/web $out/website
404 cp -r lifecycle manage.py $out/${prev.python.sitePackages}/
405 cp -r blueprints $out/
406 cp -r ${webui}/dist ${webui}/authentik $out/web/
407 cp -r ${website} $out/website/help
408 ln -s $out/${prev.python.sitePackages}/authentik $out/authentik
409 ln -s $out/${prev.python.sitePackages}/lifecycle $out/lifecycle
410 '';
411 };
412 };
413 };
414
415 inherit (python.pkgs) authentik-django;
416
417 proxy = buildGoModule {
418 pname = "authentik-proxy";
419 inherit version src meta;
420
421 postPatch = ''
422 substituteInPlace internal/gounicorn/gounicorn.go \
423 --replace-fail './lifecycle' "${authentik-django}/lifecycle"
424 substituteInPlace web/static.go \
425 --replace-fail './web' "${authentik-django}/web"
426 substituteInPlace internal/web/static.go \
427 --replace-fail './web' "${authentik-django}/web"
428 '';
429
430 env.CGO_ENABLED = 0;
431
432 vendorHash = "sha256-7oX7e7Ni5I6zblEQIeXjYOt4+QNSjH4Rpn7B5Cr5LMc=";
433
434 postInstall = ''
435 mv $out/bin/server $out/bin/authentik
436 '';
437
438 subPackages = [ "cmd/server" ];
439 };
440
441in
442stdenvNoCC.mkDerivation {
443 pname = "authentik";
444 inherit src version;
445
446 buildInputs = [ bash ];
447
448 postPatch = ''
449 rm Makefile
450 patchShebangs lifecycle/ak
451
452 # This causes issues in systemd services
453 substituteInPlace lifecycle/ak \
454 --replace-fail 'printf' '>&2 printf' \
455 --replace-fail '>/dev/stderr' ""
456 '';
457
458 installPhase = ''
459 runHook preInstall
460 mkdir -p $out/bin
461 cp -r lifecycle/ak $out/bin/
462
463 wrapProgram $out/bin/ak \
464 --prefix PATH : ${
465 lib.makeBinPath [
466 (python.withPackages (ps: [ ps.authentik-django ]))
467 proxy
468 ]
469 } \
470 --set TMPDIR /dev/shm \
471 --set PYTHONDONTWRITEBYTECODE 1 \
472 --set PYTHONUNBUFFERED 1
473 runHook postInstall
474 '';
475
476 passthru = {
477 inherit proxy;
478 outposts = callPackages ./outposts.nix {
479 inherit (proxy) vendorHash;
480 };
481 };
482
483 nativeBuildInputs = [ makeWrapper ];
484
485 meta = meta // {
486 mainProgram = "ak";
487 };
488}