From 4e5ebcf2b7c8ddc938586c624f163d8fa2a353d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 12:59:13 -0700 Subject: [PATCH 01/39] Setup test host --- hosts/spore/services/web/virtual-hosts.nix | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hosts/spore/services/web/virtual-hosts.nix b/hosts/spore/services/web/virtual-hosts.nix index 0ebcab1f..fa983039 100644 --- a/hosts/spore/services/web/virtual-hosts.nix +++ b/hosts/spore/services/web/virtual-hosts.nix @@ -69,5 +69,13 @@ enableAutheliaAuth = true; locations."/".proxyPass = "http://127.0.0.1:8082"; }; + "test.zx.dev" = { + forceSSL = true; + useACMEHost = "zx.dev"; + locations."= /".extraConfig = '' + default_type text/html; + return 200 'Test

Test page

'; + ''; + }; }; } From 0515d91eb952ac11d437cce8559fcf67c2672773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 13:05:05 -0700 Subject: [PATCH 02/39] Enable pocket-id service --- hosts/spore/services/default.nix | 1 + hosts/spore/services/pocket-id.nix | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 hosts/spore/services/pocket-id.nix diff --git a/hosts/spore/services/default.nix b/hosts/spore/services/default.nix index 27c46ef7..ddcb2594 100644 --- a/hosts/spore/services/default.nix +++ b/hosts/spore/services/default.nix @@ -8,6 +8,7 @@ ./db.nix ./homepage-dashboard.nix ./mastodon.nix + ./pocket-id.nix ./web ./infrastructure ]; diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix new file mode 100644 index 00000000..7be0c508 --- /dev/null +++ b/hosts/spore/services/pocket-id.nix @@ -0,0 +1,29 @@ +{ + config, + pkgs, + ... +}: let + appHost = "id.zx.dev"; +in { + services.pocket-id = { + enable = true; + settings = { + APP_URL = "https://${appHost}"; + TRUST_PROXY = true; + }; + }; + services.nginx = { + enable = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + + virtualHosts.${appHost} = { + enableACME = true; + forceSSL = true; + locations."/" = { + proxyPass = "http://127.0.0.1:1411"; + proxyWebsockets = true; + }; + }; + }; +} From b59bb32dbff29d581a83247036d05e94e48aa172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 13:18:41 -0700 Subject: [PATCH 03/39] Use pocketid postgres database --- hosts/spore/services/pocket-id.nix | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 7be0c508..01f61e46 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -10,8 +10,22 @@ in { settings = { APP_URL = "https://${appHost}"; TRUST_PROXY = true; + DB_PROVIDER = "postgres"; + DB_CONNECTION_STRING = "host=/run/postgresql user=pocketid dbname=pocketid"; + KEYS_STORAGE = "database"; }; }; + + services.postgresql = { + ensureDatabases = ["pocketid"]; + ensureUsers = [ + { + name = "pocketid"; + ensureDBOwnership = true; + } + ]; + }; + services.nginx = { enable = true; recommendedProxySettings = true; From 54c10e9415d2300821a6966511a76a29202d3d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 13:19:09 -0700 Subject: [PATCH 04/39] Ensure start-ordering --- hosts/spore/services/pocket-id.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 01f61e46..fa3e3b48 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -26,6 +26,11 @@ in { ]; }; + systemd.services.pocket-id = { + after = ["postgresql.service" "network-online.target"]; + requires = ["postgresql.service"]; + }; + services.nginx = { enable = true; recommendedProxySettings = true; From 4ca055f52b6cc79f29bdb15ee452a9727578a2e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 13:19:24 -0700 Subject: [PATCH 05/39] Supply stable encryption key --- hosts/spore/secrets/pocket-id-encryption-key.age | 7 +++++++ hosts/spore/services/pocket-id.nix | 8 ++++++++ lib/secrets/spore.nix | 1 + 3 files changed, 16 insertions(+) create mode 100644 hosts/spore/secrets/pocket-id-encryption-key.age diff --git a/hosts/spore/secrets/pocket-id-encryption-key.age b/hosts/spore/secrets/pocket-id-encryption-key.age new file mode 100644 index 00000000..471b8cd7 --- /dev/null +++ b/hosts/spore/secrets/pocket-id-encryption-key.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 stFZUQ aFqQX0Rpg18dsE3tTTcg3mxbyaskZwmeHmwRXTTAizc +/Gwz5TQ7ladAcB3ED82VVIWndbImy2g3tTjF6HeWPnU +-> ssh-ed25519 3EWhnQ vskXoOXSQeBFLf7AV7ojly2EVcElbuclX0fTvLk6Og8 +eyaqaKa7Bq5n/+0xVqsBwyx5Y5OeWOHDJEY7MIasHCU +--- EFeE0j6r+vRDo3hf7AcB7ZtmXmwRK7wigxu5ucLPyhA +L%_,RrK_#='fI Date: Tue, 14 Oct 2025 14:02:39 -0700 Subject: [PATCH 06/39] Use oauth2-proxy for single shared SSO --- hosts/spore/secrets/oauth2-proxy-env.age | Bin 0 -> 454 bytes hosts/spore/services/pocket-id.nix | 42 ++++++++++++++++++++--- lib/secrets/spore.nix | 1 + 3 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 hosts/spore/secrets/oauth2-proxy-env.age diff --git a/hosts/spore/secrets/oauth2-proxy-env.age b/hosts/spore/secrets/oauth2-proxy-env.age new file mode 100644 index 0000000000000000000000000000000000000000..54e7dbbe06f3e24bbf0c62c81d93f1adc602ab87 GIT binary patch literal 454 zcmYdHPt{G$OD?J`D9Oyv)5|YP*Do{V(zR14F3!+RO))YxHMCSHE^&(r4OFObuS`kK z2`UaS56><(%JX*f)iyD#NO3Q9*RL}3a*r}dsqziYu<-ROPv-LS_D?f4$#pI=@JI_a zcL_Jk@ys+b&+!a#GRu7W{$p-)jsMv!4%K~PGFV{uY)m|>oQn~8T} zrBiy4zIK*nP;O9pa$1^KAlHV4;-8PN)HyB7!0^6FLWe7E!lMP>EmBXu2~X1P4xE1G zq3lzy!XV=XbJ8zgIbAnl<_q3J3ZAnDy3`9|E$RdX>YskXixlkzv0TG*&j*{FIbSVQgZKC>%yfM-mGRh p`Q@0>ZhamNcPs8aGoL=r^1L!XyZiJfgIc4+?Ir?C_!TE{0065+wOarH literal 0 HcmV?d00001 diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 5e9d6780..6adbcf14 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -3,8 +3,15 @@ pkgs, ... }: let - appHost = "id.zx.dev"; + issuerHost = "id.zx.dev"; + authHost = "auth2.zx.dev"; # TODO: Conflict with Authelia in { + age.secrets.oauth2-proxy-env = { + file = ./../secrets/oauth2-proxy-env.age; + mode = "440"; + owner = "oauth2-proxy"; + group = "oauth2-proxy"; + }; age.secrets.pocket-id-encryption-key = { file = ./../secrets/pocket-id-encryption-key.age; mode = "440"; @@ -15,7 +22,7 @@ in { services.pocket-id = { enable = true; settings = { - APP_URL = "https://${appHost}"; + APP_URL = "https://${issuerHost}"; TRUST_PROXY = true; DB_PROVIDER = "postgres"; DB_CONNECTION_STRING = "host=/run/postgresql user=pocketid dbname=pocketid"; @@ -39,18 +46,45 @@ in { requires = ["postgresql.service"]; }; + services.oauth2-proxy = { + enable = true; + provider = "oidc"; + oidcIssuerUrl = "https://${issuerHost}"; + keyFile = config.age.secrets.oauth2-proxy-env.path; + reverseProxy = true; + setXauthrequest = true; + clientID = "shared-sso"; + redirectURL = "https://${authHost}/oauth2/callback"; + cookie.domain = ".zx.dev"; + email.domains = [".zx.dev"]; + }; + services.nginx = { enable = true; recommendedProxySettings = true; recommendedTlsSettings = true; - virtualHosts.${appHost} = { - enableACME = true; + virtualHosts.${issuerHost} = { forceSSL = true; + useACMEHost = "zx.dev"; locations."/" = { proxyPass = "http://127.0.0.1:1411"; proxyWebsockets = true; }; }; + + virtualHosts.${authHost} = { + forceSSL = true; + useACMEHost = "zx.dev"; + locations."/oauth2/" = { + proxyPass = "http://127.0.0.1:4180"; + proxyWebsockets = true; + }; + locations."/" = { + extraConfig = '' + return 200 'Auth

oauth2-proxy at ${authHost}

'; + ''; + }; + }; }; } diff --git a/lib/secrets/spore.nix b/lib/secrets/spore.nix index 365067d2..a5eac7c0 100644 --- a/lib/secrets/spore.nix +++ b/lib/secrets/spore.nix @@ -9,6 +9,7 @@ in { "hosts/spore/secrets/mastodon-vapid-public-key.age".publicKeys = keys; "hosts/spore/secrets/mastodon-vapid-private-key.age".publicKeys = keys; "hosts/spore/secrets/notifier-smtp-password.age".publicKeys = keys; + "hosts/spore/secrets/oauth2-proxy-env.age".publicKeys = keys; "hosts/spore/secrets/pocket-id-encryption-key.age".publicKeys = keys; "hosts/spore/secrets/restic-env.age".publicKeys = keys; "hosts/spore/secrets/restic-password.age".publicKeys = keys; From ad6a33ffb56e3b318b989cd230b621ae34c31fde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 15:23:32 -0700 Subject: [PATCH 07/39] Move test vhost --- hosts/spore/services/pocket-id.nix | 9 +++++++++ hosts/spore/services/web/virtual-hosts.nix | 8 -------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 6adbcf14..85f49ac2 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -86,5 +86,14 @@ in { ''; }; }; + + virtualHosts."test.zx.dev" = { + forceSSL = true; + useACMEHost = "zx.dev"; + locations."= /".extraConfig = '' + default_type text/html; + return 200 'Test

Test page

'; + ''; + }; }; } diff --git a/hosts/spore/services/web/virtual-hosts.nix b/hosts/spore/services/web/virtual-hosts.nix index fa983039..0ebcab1f 100644 --- a/hosts/spore/services/web/virtual-hosts.nix +++ b/hosts/spore/services/web/virtual-hosts.nix @@ -69,13 +69,5 @@ enableAutheliaAuth = true; locations."/".proxyPass = "http://127.0.0.1:8082"; }; - "test.zx.dev" = { - forceSSL = true; - useACMEHost = "zx.dev"; - locations."= /".extraConfig = '' - default_type text/html; - return 200 'Test

Test page

'; - ''; - }; }; } From 390a556c488fbd50607661aaa8e246a1208087cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 15:25:18 -0700 Subject: [PATCH 08/39] Use oauth2-proxy in test page --- hosts/spore/services/pocket-id.nix | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 85f49ac2..04a471b0 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -90,7 +90,24 @@ in { virtualHosts."test.zx.dev" = { forceSSL = true; useACMEHost = "zx.dev"; + locations."=/oauth2/auth" = { + proxyPass = "http://127.0.0.1:4180"; + extraConfig = '' + proxy_set_header X-Original-URI $request_uri; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Scheme $scheme; + proxy_set_header X-Forwarded-Host $host; + ''; + }; locations."= /".extraConfig = '' + auth_request /oauth2/auth; + error_page 401 = https://${authHost}/oauth2/start?rd=$scheme://$http_host$request_uri; + + auth_request_set $auth_user $upstream_http_x_auth_request_user; + auth_request_set $auth_email $upstream_http_x_auth_request_email; + add_header X-Auth-User $auth_user always; + add_header X-Auth-Email $auth_email always; + default_type text/html; return 200 'Test

Test page

'; ''; From c78f861fcc0449536cfa21fdbeee30f2ca15d760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 15:53:30 -0700 Subject: [PATCH 09/39] Update `authHost` --- hosts/spore/services/pocket-id.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 04a471b0..7ee23abb 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -4,7 +4,7 @@ ... }: let issuerHost = "id.zx.dev"; - authHost = "auth2.zx.dev"; # TODO: Conflict with Authelia + authHost = "oauth.zx.dev"; # TODO: Conflict with Authelia in { age.secrets.oauth2-proxy-env = { file = ./../secrets/oauth2-proxy-env.age; From d31ebfef465510aa5df7c173c0e2a50122129726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 16:10:38 -0700 Subject: [PATCH 10/39] Set content type --- hosts/spore/services/pocket-id.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 7ee23abb..e0b207c2 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -82,6 +82,8 @@ in { }; locations."/" = { extraConfig = '' + default_type text/html; + add_header Content-Type "text/html; charset=utf-8"; return 200 'Auth

oauth2-proxy at ${authHost}

'; ''; }; From de9d60fe30a969e691627c1d538fa029dc1aee6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 16:17:18 -0700 Subject: [PATCH 11/39] Set headers in oauth2 Per: https://oauth2-proxy.github.io/oauth2-proxy/configuration/integration#configuring-for-use-with-the-nginx-auth_request-directive --- hosts/spore/services/pocket-id.nix | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index e0b207c2..8c7b0f67 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -78,7 +78,16 @@ in { useACMEHost = "zx.dev"; locations."/oauth2/" = { proxyPass = "http://127.0.0.1:4180"; - proxyWebsockets = true; + extraConfig = '' + proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri; + ''; + }; + locations."/oauth2/auth" = { + proxyPass = "http://127.0.0.1:4180"; + extraConfig = '' + proxy_set_header Content-Length ""; + proxy_pass_request_body off; + ''; }; locations."/" = { extraConfig = '' From a9c33eefee234f65dbe309fd374e725735ccf0d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 16:20:25 -0700 Subject: [PATCH 12/39] Update error_page directive --- hosts/spore/services/pocket-id.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 8c7b0f67..b0396e94 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -112,7 +112,7 @@ in { }; locations."= /".extraConfig = '' auth_request /oauth2/auth; - error_page 401 = https://${authHost}/oauth2/start?rd=$scheme://$http_host$request_uri; + error_page 401 =403 https://${authHost}/oauth2/start?rd=$scheme://$http_host$request_uri; auth_request_set $auth_user $upstream_http_x_auth_request_user; auth_request_set $auth_email $upstream_http_x_auth_request_email; From 4d13fac48ce3d2d5f62408fc3b81d958e7df67f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 16:22:46 -0700 Subject: [PATCH 13/39] Use named location for redirect --- hosts/spore/services/pocket-id.nix | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index b0396e94..edbaee93 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -110,9 +110,14 @@ in { proxy_set_header X-Forwarded-Host $host; ''; }; + locations."@oauth2_redirect" = { + extraConfig = '' + return 302 https://${authHost}/oauth2/start?rd=$scheme://$http_host$request_uri; + ''; + }; locations."= /".extraConfig = '' auth_request /oauth2/auth; - error_page 401 =403 https://${authHost}/oauth2/start?rd=$scheme://$http_host$request_uri; + error_page 401 =403 @oauth2_redirect; auth_request_set $auth_user $upstream_http_x_auth_request_user; auth_request_set $auth_email $upstream_http_x_auth_request_email; From 6c91865d5b842e58c31df15b8e0161609e3e1ceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 16:43:38 -0700 Subject: [PATCH 14/39] Update `email.domains` --- hosts/spore/services/pocket-id.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index edbaee93..3a81dabd 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -56,7 +56,7 @@ in { clientID = "shared-sso"; redirectURL = "https://${authHost}/oauth2/callback"; cookie.domain = ".zx.dev"; - email.domains = [".zx.dev"]; + email.domains = ["zx.dev"]; }; services.nginx = { From 1861fe162c5e0513cc36962a652c5e6e12602f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 16:50:41 -0700 Subject: [PATCH 15/39] Update test page HTML --- hosts/spore/services/pocket-id.nix | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 3a81dabd..d5b6d009 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -119,13 +119,16 @@ in { auth_request /oauth2/auth; error_page 401 =403 @oauth2_redirect; - auth_request_set $auth_user $upstream_http_x_auth_request_user; auth_request_set $auth_email $upstream_http_x_auth_request_email; - add_header X-Auth-User $auth_user always; add_header X-Auth-Email $auth_email always; default_type text/html; - return 200 'Test

Test page

'; + return 200 'Protected + +

✅ Authenticated via Pocket ID

+

Hello $auth_email from test.zx.dev.

+

Sign out

+ '; ''; }; }; From 9208affb5622a37975ee986f9cab1a6791309614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 16:56:54 -0700 Subject: [PATCH 16/39] Do not rewrite 403 --- hosts/spore/services/pocket-id.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index d5b6d009..435e94a7 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -117,7 +117,7 @@ in { }; locations."= /".extraConfig = '' auth_request /oauth2/auth; - error_page 401 =403 @oauth2_redirect; + error_page 401 = @oauth2_redirect; auth_request_set $auth_email $upstream_http_x_auth_request_email; add_header X-Auth-Email $auth_email always; From 9d395f1efe53ceb73d7e5bc07f85979f0830f5e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 17:03:04 -0700 Subject: [PATCH 17/39] Set `nginx.domain` --- hosts/spore/services/pocket-id.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 435e94a7..f9c97813 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -55,6 +55,7 @@ in { setXauthrequest = true; clientID = "shared-sso"; redirectURL = "https://${authHost}/oauth2/callback"; + nginx.domain = authHost; cookie.domain = ".zx.dev"; email.domains = ["zx.dev"]; }; From da576dcc0cde48bf031ec065c569f0ccd87a7e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 17:07:18 -0700 Subject: [PATCH 18/39] Use `whitelist-domain` --- hosts/spore/services/pocket-id.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index f9c97813..7f374c83 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -58,6 +58,9 @@ in { nginx.domain = authHost; cookie.domain = ".zx.dev"; email.domains = ["zx.dev"]; + extraConfig = { + whitelist-domain = ".zx.dev"; + }; }; services.nginx = { From 5621b568ad47c0d6a12ae35627ccb5ac61d141d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 17:09:10 -0700 Subject: [PATCH 19/39] Auth any email domain --- hosts/spore/services/pocket-id.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 7f374c83..69c01b37 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -57,7 +57,7 @@ in { redirectURL = "https://${authHost}/oauth2/callback"; nginx.domain = authHost; cookie.domain = ".zx.dev"; - email.domains = ["zx.dev"]; + email.domains = ["*"]; extraConfig = { whitelist-domain = ".zx.dev"; }; From fda872c371b90ee447815a90bf63201c6f9de0b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 17:21:48 -0700 Subject: [PATCH 20/39] Try allowing unverified emails --- hosts/spore/services/pocket-id.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 69c01b37..6a1f8a4c 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -60,6 +60,7 @@ in { email.domains = ["*"]; extraConfig = { whitelist-domain = ".zx.dev"; + insecure-oidc-allow-unverified-email = true; }; }; From cc10c195ac64628f13ee699eca2daac847460800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 17:27:09 -0700 Subject: [PATCH 21/39] Remove exact-match --- hosts/spore/services/pocket-id.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 6a1f8a4c..94eee731 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -120,7 +120,7 @@ in { return 302 https://${authHost}/oauth2/start?rd=$scheme://$http_host$request_uri; ''; }; - locations."= /".extraConfig = '' + locations."/".extraConfig = '' auth_request /oauth2/auth; error_page 401 = @oauth2_redirect; From 51a75d2c96b172c84994d04e9e2ac2f231a9cd0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 17:31:46 -0700 Subject: [PATCH 22/39] Fix typo --- hosts/spore/services/pocket-id.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 94eee731..de086b1e 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -106,7 +106,7 @@ in { virtualHosts."test.zx.dev" = { forceSSL = true; useACMEHost = "zx.dev"; - locations."=/oauth2/auth" = { + locations."= /oauth2/auth" = { proxyPass = "http://127.0.0.1:4180"; extraConfig = '' proxy_set_header X-Original-URI $request_uri; From 51e0c18e6e1f491e092355f5b101867ffc709567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 17:37:37 -0700 Subject: [PATCH 23/39] Use `X-Forwarded-Proto` --- hosts/spore/services/pocket-id.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index de086b1e..dc07b12c 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -111,7 +111,7 @@ in { extraConfig = '' proxy_set_header X-Original-URI $request_uri; proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Scheme $scheme; + proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; ''; }; From 5e291911a4aaf9aefa6526ae52c8def5e101ed08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Tue, 14 Oct 2025 17:38:39 -0700 Subject: [PATCH 24/39] Set subrequest with no body --- hosts/spore/services/pocket-id.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index dc07b12c..f88cfede 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -113,6 +113,8 @@ in { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; + proxy_set_header Content-Length ""; + proxy_pass_request_body off; ''; }; locations."@oauth2_redirect" = { From 589fe9fac59c9619ec05e5f65007eff026a71616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Wed, 15 Oct 2025 09:23:39 -0700 Subject: [PATCH 25/39] Try removing `return 200 ...` --- hosts/spore/services/pocket-id.nix | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index f88cfede..6089793f 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -122,21 +122,16 @@ in { return 302 https://${authHost}/oauth2/start?rd=$scheme://$http_host$request_uri; ''; }; - locations."/".extraConfig = '' - auth_request /oauth2/auth; - error_page 401 = @oauth2_redirect; - - auth_request_set $auth_email $upstream_http_x_auth_request_email; - add_header X-Auth-Email $auth_email always; + locations."/" = { + proxyPass = "http://127.0.0.1"; + extraConfig = '' + auth_request /oauth2/auth; + error_page 401 = @oauth2_redirect; - default_type text/html; - return 200 'Protected - -

✅ Authenticated via Pocket ID

-

Hello $auth_email from test.zx.dev.

-

Sign out

- '; - ''; + auth_request_set $auth_email $upstream_http_x_auth_request_email; + add_header X-Auth-Email $auth_email always; + ''; + }; }; }; } From 71234320e797cd1b2eed38c526063fd265dba42a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Wed, 15 Oct 2025 09:28:38 -0700 Subject: [PATCH 26/39] Try internal named location --- hosts/spore/services/pocket-id.nix | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 6089793f..7ae0689b 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -122,16 +122,25 @@ in { return 302 https://${authHost}/oauth2/start?rd=$scheme://$http_host$request_uri; ''; }; - locations."/" = { - proxyPass = "http://127.0.0.1"; - extraConfig = '' - auth_request /oauth2/auth; - error_page 401 = @oauth2_redirect; + locations."= /__ok".extraConfig = '' + internal; + default_type text/html; + return 200 'Protected + +

✅ Authenticated via Pocket ID

+

Hello $upstream_http_x_auth_request_email from test.zx.dev.

+

Sign out

+ '; + ''; + locations."/".extraConfig = '' + auth_request /oauth2/auth; + error_page 401 = @oauth2_redirect; - auth_request_set $auth_email $upstream_http_x_auth_request_email; - add_header X-Auth-Email $auth_email always; - ''; - }; + auth_request_set $auth_email $upstream_http_x_auth_request_email; + add_header X-Auth-Email $auth_email always; + + try_files /__ok =404; + ''; }; }; } From ba30907e2201953e782ff0b7575d308555833e19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Wed, 15 Oct 2025 09:46:33 -0700 Subject: [PATCH 27/39] Use `rewrite` directive --- hosts/spore/services/pocket-id.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 7ae0689b..3b58b5fb 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -139,7 +139,7 @@ in { auth_request_set $auth_email $upstream_http_x_auth_request_email; add_header X-Auth-Email $auth_email always; - try_files /__ok =404; + rewrite ^ /__ok last; ''; }; }; From ba531da324df7c7be1998531d3f4cd4151cd81b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Wed, 15 Oct 2025 09:56:21 -0700 Subject: [PATCH 28/39] Use `root` with static site from store --- hosts/spore/services/pocket-id.nix | 55 +++++++++++++++++++----------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 3b58b5fb..6a67e0be 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -122,25 +122,42 @@ in { return 302 https://${authHost}/oauth2/start?rd=$scheme://$http_host$request_uri; ''; }; - locations."= /__ok".extraConfig = '' - internal; - default_type text/html; - return 200 'Protected - -

✅ Authenticated via Pocket ID

-

Hello $upstream_http_x_auth_request_email from test.zx.dev.

-

Sign out

- '; - ''; - locations."/".extraConfig = '' - auth_request /oauth2/auth; - error_page 401 = @oauth2_redirect; - - auth_request_set $auth_email $upstream_http_x_auth_request_email; - add_header X-Auth-Email $auth_email always; - - rewrite ^ /__ok last; - ''; + locations."/" = { + extraConfig = '' + auth_request /oauth2/auth; + error_page 401 = @oauth2_redirect; + ''; + root = "/var/www/test"; + index = "index.html"; + }; }; }; + + systemd.tmpfiles.rules = [ + "d /var/www 0755 root root -" + "d /var/www/test 0755 root root -" + ]; + systemd.mounts = let + testSite = pkgs.writeTextDir "index.html" '' + + + Protected + +

✅ Authenticated via Pocket ID

+ + ''; + in [ + { + what = "${testSite}"; + where = "/var/www/test"; + type = "none"; + options = "bind,ro"; + wantedBy = ["multi-user.target"]; + } + ]; + systemd.services.nginx = { + after = ["var-www-test.mount"]; + requires = ["var-www-test.mount"]; + serviceConfig.RequiresMountsFor = ["/var/www/test"]; + }; } From e93b803a3c456389dc6ae959351f05c2a8de18f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Wed, 15 Oct 2025 12:05:33 -0700 Subject: [PATCH 29/39] Extract config into `rc.web.auth` module --- hosts/spore/services/pocket-id.nix | 111 ++-------------- modules/nixos/default.nix | 1 + modules/nixos/web/auth.nix | 204 +++++++++++++++++++++++++++++ modules/nixos/web/default.nix | 5 + 4 files changed, 223 insertions(+), 98 deletions(-) create mode 100644 modules/nixos/web/auth.nix create mode 100644 modules/nixos/web/default.nix diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 6a67e0be..6c579497 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -2,10 +2,7 @@ config, pkgs, ... -}: let - issuerHost = "id.zx.dev"; - authHost = "oauth.zx.dev"; # TODO: Conflict with Authelia -in { +}: { age.secrets.oauth2-proxy-env = { file = ./../secrets/oauth2-proxy-env.age; mode = "440"; @@ -19,109 +16,27 @@ in { group = config.services.pocket-id.group; }; - services.pocket-id = { + rc.web.auth = { enable = true; - settings = { - APP_URL = "https://${issuerHost}"; - TRUST_PROXY = true; - DB_PROVIDER = "postgres"; - DB_CONNECTION_STRING = "host=/run/postgresql user=pocketid dbname=pocketid"; - KEYS_STORAGE = "database"; - ENCRYPTION_KEY_FILE = config.age.secrets.pocket-id-encryption-key.path; - }; - }; - - services.postgresql = { - ensureDatabases = ["pocketid"]; - ensureUsers = [ - { - name = "pocketid"; - ensureDBOwnership = true; - } - ]; - }; - - systemd.services.pocket-id = { - after = ["postgresql.service" "network-online.target"]; - requires = ["postgresql.service"]; - }; - - services.oauth2-proxy = { - enable = true; - provider = "oidc"; - oidcIssuerUrl = "https://${issuerHost}"; - keyFile = config.age.secrets.oauth2-proxy-env.path; - reverseProxy = true; - setXauthrequest = true; - clientID = "shared-sso"; - redirectURL = "https://${authHost}/oauth2/callback"; - nginx.domain = authHost; - cookie.domain = ".zx.dev"; - email.domains = ["*"]; - extraConfig = { - whitelist-domain = ".zx.dev"; - insecure-oidc-allow-unverified-email = true; - }; - }; - - services.nginx = { - enable = true; - recommendedProxySettings = true; - recommendedTlsSettings = true; - - virtualHosts.${issuerHost} = { - forceSSL = true; + issuer = { + host = "id.zx.dev"; useACMEHost = "zx.dev"; - locations."/" = { - proxyPass = "http://127.0.0.1:1411"; - proxyWebsockets = true; - }; + encryptionKeyFile = config.age.secrets.pocket-id-encryption-key.path; }; - - virtualHosts.${authHost} = { - forceSSL = true; + authProxy = { + host = "oauth.zx.dev"; # TODO: Conflict with Authelia + domain = ".zx.dev"; + clientID = "shared-sso"; useACMEHost = "zx.dev"; - locations."/oauth2/" = { - proxyPass = "http://127.0.0.1:4180"; - extraConfig = '' - proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri; - ''; - }; - locations."/oauth2/auth" = { - proxyPass = "http://127.0.0.1:4180"; - extraConfig = '' - proxy_set_header Content-Length ""; - proxy_pass_request_body off; - ''; - }; - locations."/" = { - extraConfig = '' - default_type text/html; - add_header Content-Type "text/html; charset=utf-8"; - return 200 'Auth

oauth2-proxy at ${authHost}

'; - ''; - }; + keyFile = config.age.secrets.oauth2-proxy-env.path; }; + }; + services.nginx = { virtualHosts."test.zx.dev" = { forceSSL = true; useACMEHost = "zx.dev"; - locations."= /oauth2/auth" = { - proxyPass = "http://127.0.0.1:4180"; - extraConfig = '' - proxy_set_header X-Original-URI $request_uri; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Host $host; - proxy_set_header Content-Length ""; - proxy_pass_request_body off; - ''; - }; - locations."@oauth2_redirect" = { - extraConfig = '' - return 302 https://${authHost}/oauth2/start?rd=$scheme://$http_host$request_uri; - ''; - }; + requireAuth = true; locations."/" = { extraConfig = '' auth_request /oauth2/auth; diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 2db159c7..79beb510 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -1,6 +1,7 @@ # NixOS-specific configuration modules { imports = [ + ./web ./users.nix ./ssh.nix ./sudo.nix diff --git a/modules/nixos/web/auth.nix b/modules/nixos/web/auth.nix new file mode 100644 index 00000000..ce76e875 --- /dev/null +++ b/modules/nixos/web/auth.nix @@ -0,0 +1,204 @@ +{ + config, + lib, + ... +}: let + inherit (lib) mkIf mkOption; + + cfg = config.rc.web.auth; + + # TODO: Source from web-servers/nginx/vhost-options.nix + useACMEHost = mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + A host of an existing Let's Encrypt certificate to use. + ''; + }; +in { + options = { + rc.web.auth = { + enable = lib.mkEnableOption "Web authentication stack"; + + issuer = { + inherit useACMEHost; + + host = lib.mkOption { + type = lib.types.str; + description = '' + The OAuth issuer host. + ''; + example = "id.example.org"; + }; + + encryptionKeyFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + Key used to encrypt OAuth issuer data, including the private keys. + ''; + example = "/run/keys/oauth-issuer-encryption-key"; + }; + }; + + authProxy = { + inherit useACMEHost; + + host = lib.mkOption { + type = lib.types.str; + description = '' + The host where OAuth proxy is accessed for shared SSO. + ''; + example = "auth.example.org"; + }; + + domain = lib.mkOption { + type = lib.types.str; + description = '' + The domain in which proxy redirection occurs and cookies are scoped. + ''; + example = ".example.org"; + }; + + clientID = lib.mkOption { + type = lib.types.nullOr lib.types.str; + description = '' + The client ID for the OAuth proxy. + ''; + example = "123456.apps.googleusercontent.com"; + }; + + keyFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + oauth2-proxy allows passing sensitive configuration via environment variables. + Make a file that contains lines like + OAUTH2_PROXY_CLIENT_SECRET=asdfasdfasdf.apps.googleuserscontent.com + and specify the path here. + ''; + example = "/run/keys/oauth2-proxy"; + }; + }; + }; + + services.nginx.virtualHosts = let + requireAuthOption = {config, ...}: { + options = { + requireAuth = lib.mkEnableOption "Require authentication to access host."; + }; + config = lib.mkIf config.requireAuth { + locations."= /oauth2/auth" = { + proxyPass = "http://127.0.0.1:4180"; + extraConfig = '' + proxy_set_header X-Original-URI $request_uri; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header Content-Length ""; + proxy_pass_request_body off; + ''; + }; + locations."@oauth2_redirect" = { + extraConfig = '' + return 302 https://${cfg.authProxy.host}/oauth2/start?rd=$scheme://$http_host$request_uri; + ''; + }; + }; + }; + in + lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule [requireAuthOption]); + }; + }; + + config = mkIf cfg.enable { + # TODO: Add assertion for services.nginx.virtualHosts..requireAuth + # assertions = ... + + services.pocket-id = { + enable = true; + settings = { + APP_URL = "https://${cfg.issuer.host}"; + TRUST_PROXY = true; + DB_PROVIDER = "postgres"; + DB_CONNECTION_STRING = "host=/run/postgresql user=pocketid dbname=pocketid"; + KEYS_STORAGE = "database"; + ENCRYPTION_KEY_FILE = cfg.issuer.encryptionKeyFile; + }; + }; + + services.postgresql = { + ensureDatabases = ["pocketid"]; + ensureUsers = [ + { + name = "pocketid"; + ensureDBOwnership = true; + } + ]; + }; + + systemd.services.pocket-id = { + after = ["postgresql.service" "network-online.target"]; + requires = ["postgresql.service"]; + }; + + services.oauth2-proxy = { + enable = true; + provider = "oidc"; + oidcIssuerUrl = "https://${cfg.issuer.host}"; + keyFile = cfg.authProxy.keyFile; + reverseProxy = true; + setXauthrequest = true; + clientID = cfg.authProxy.clientID; + redirectURL = "https://${cfg.authProxy.host}/oauth2/callback"; + nginx.domain = cfg.authProxy.host; + cookie.domain = cfg.authProxy.domain; + email.domains = ["*"]; + extraConfig = { + whitelist-domain = cfg.authProxy.domain; + insecure-oidc-allow-unverified-email = true; + }; + }; + + services.nginx = { + enable = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + + virtualHosts.${cfg.issuer.host} = { + forceSSL = true; + useACMEHost = cfg.issuer.useACMEHost; + locations."/" = { + proxyPass = "http://127.0.0.1:1411"; + proxyWebsockets = true; + }; + }; + + virtualHosts.${cfg.authProxy.host} = { + forceSSL = true; + useACMEHost = cfg.authProxy.useACMEHost; + locations."/oauth2/" = { + proxyPass = "http://127.0.0.1:4180"; + extraConfig = '' + proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri; + ''; + }; + locations."/oauth2/auth" = { + proxyPass = "http://127.0.0.1:4180"; + extraConfig = '' + proxy_set_header Content-Length ""; + proxy_pass_request_body off; + ''; + }; + locations."/" = { + extraConfig = '' + default_type text/html; + add_header Content-Type "text/html; charset=utf-8"; + return 200 'Auth

oauth2-proxy at ${cfg.authProxy.host}

'; + ''; + }; + }; + }; + }; +} diff --git a/modules/nixos/web/default.nix b/modules/nixos/web/default.nix new file mode 100644 index 00000000..12f5c325 --- /dev/null +++ b/modules/nixos/web/default.nix @@ -0,0 +1,5 @@ +{ + imports = [ + ./auth.nix + ]; +} From d75579d9086dd95369b6c12ecbc5513af9231e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Wed, 15 Oct 2025 13:49:41 -0700 Subject: [PATCH 30/39] Wrap module logic in `mkMerge` --- modules/nixos/web/auth.nix | 158 +++++++++++++++++++------------------ 1 file changed, 80 insertions(+), 78 deletions(-) diff --git a/modules/nixos/web/auth.nix b/modules/nixos/web/auth.nix index ce76e875..208a0db4 100644 --- a/modules/nixos/web/auth.nix +++ b/modules/nixos/web/auth.nix @@ -112,93 +112,95 @@ in { }; }; - config = mkIf cfg.enable { - # TODO: Add assertion for services.nginx.virtualHosts..requireAuth - # assertions = ... - - services.pocket-id = { - enable = true; - settings = { - APP_URL = "https://${cfg.issuer.host}"; - TRUST_PROXY = true; - DB_PROVIDER = "postgres"; - DB_CONNECTION_STRING = "host=/run/postgresql user=pocketid dbname=pocketid"; - KEYS_STORAGE = "database"; - ENCRYPTION_KEY_FILE = cfg.issuer.encryptionKeyFile; + config = lib.mkMerge [ + (mkIf cfg.enable { + # TODO: Add assertion for services.nginx.virtualHosts..requireAuth + # assertions = ... + + services.pocket-id = { + enable = true; + settings = { + APP_URL = "https://${cfg.issuer.host}"; + TRUST_PROXY = true; + DB_PROVIDER = "postgres"; + DB_CONNECTION_STRING = "host=/run/postgresql user=pocketid dbname=pocketid"; + KEYS_STORAGE = "database"; + ENCRYPTION_KEY_FILE = cfg.issuer.encryptionKeyFile; + }; }; - }; - - services.postgresql = { - ensureDatabases = ["pocketid"]; - ensureUsers = [ - { - name = "pocketid"; - ensureDBOwnership = true; - } - ]; - }; - systemd.services.pocket-id = { - after = ["postgresql.service" "network-online.target"]; - requires = ["postgresql.service"]; - }; + services.postgresql = { + ensureDatabases = ["pocketid"]; + ensureUsers = [ + { + name = "pocketid"; + ensureDBOwnership = true; + } + ]; + }; - services.oauth2-proxy = { - enable = true; - provider = "oidc"; - oidcIssuerUrl = "https://${cfg.issuer.host}"; - keyFile = cfg.authProxy.keyFile; - reverseProxy = true; - setXauthrequest = true; - clientID = cfg.authProxy.clientID; - redirectURL = "https://${cfg.authProxy.host}/oauth2/callback"; - nginx.domain = cfg.authProxy.host; - cookie.domain = cfg.authProxy.domain; - email.domains = ["*"]; - extraConfig = { - whitelist-domain = cfg.authProxy.domain; - insecure-oidc-allow-unverified-email = true; + systemd.services.pocket-id = { + after = ["postgresql.service" "network-online.target"]; + requires = ["postgresql.service"]; }; - }; - services.nginx = { - enable = true; - recommendedProxySettings = true; - recommendedTlsSettings = true; - - virtualHosts.${cfg.issuer.host} = { - forceSSL = true; - useACMEHost = cfg.issuer.useACMEHost; - locations."/" = { - proxyPass = "http://127.0.0.1:1411"; - proxyWebsockets = true; + services.oauth2-proxy = { + enable = true; + provider = "oidc"; + oidcIssuerUrl = "https://${cfg.issuer.host}"; + keyFile = cfg.authProxy.keyFile; + reverseProxy = true; + setXauthrequest = true; + clientID = cfg.authProxy.clientID; + redirectURL = "https://${cfg.authProxy.host}/oauth2/callback"; + nginx.domain = cfg.authProxy.host; + cookie.domain = cfg.authProxy.domain; + email.domains = ["*"]; + extraConfig = { + whitelist-domain = cfg.authProxy.domain; + insecure-oidc-allow-unverified-email = true; }; }; - virtualHosts.${cfg.authProxy.host} = { - forceSSL = true; - useACMEHost = cfg.authProxy.useACMEHost; - locations."/oauth2/" = { - proxyPass = "http://127.0.0.1:4180"; - extraConfig = '' - proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri; - ''; - }; - locations."/oauth2/auth" = { - proxyPass = "http://127.0.0.1:4180"; - extraConfig = '' - proxy_set_header Content-Length ""; - proxy_pass_request_body off; - ''; + services.nginx = { + enable = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + + virtualHosts.${cfg.issuer.host} = { + forceSSL = true; + useACMEHost = cfg.issuer.useACMEHost; + locations."/" = { + proxyPass = "http://127.0.0.1:1411"; + proxyWebsockets = true; + }; }; - locations."/" = { - extraConfig = '' - default_type text/html; - add_header Content-Type "text/html; charset=utf-8"; - return 200 'Auth

oauth2-proxy at ${cfg.authProxy.host}

'; - ''; + + virtualHosts.${cfg.authProxy.host} = { + forceSSL = true; + useACMEHost = cfg.authProxy.useACMEHost; + locations."/oauth2/" = { + proxyPass = "http://127.0.0.1:4180"; + extraConfig = '' + proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri; + ''; + }; + locations."/oauth2/auth" = { + proxyPass = "http://127.0.0.1:4180"; + extraConfig = '' + proxy_set_header Content-Length ""; + proxy_pass_request_body off; + ''; + }; + locations."/" = { + extraConfig = '' + default_type text/html; + add_header Content-Type "text/html; charset=utf-8"; + return 200 'Auth

oauth2-proxy at ${cfg.authProxy.host}

'; + ''; + }; }; }; - }; - }; + }) + ]; } From 8e4ca3566daadfad2da351d13b0bbd5afb69a3db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Wed, 15 Oct 2025 14:24:45 -0700 Subject: [PATCH 31/39] Warn when `requireAuth` set with auth not enabled --- modules/nixos/web/auth.nix | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/nixos/web/auth.nix b/modules/nixos/web/auth.nix index 208a0db4..2f300b32 100644 --- a/modules/nixos/web/auth.nix +++ b/modules/nixos/web/auth.nix @@ -114,9 +114,6 @@ in { config = lib.mkMerge [ (mkIf cfg.enable { - # TODO: Add assertion for services.nginx.virtualHosts..requireAuth - # assertions = ... - services.pocket-id = { enable = true; settings = { @@ -202,5 +199,15 @@ in { }; }; }) + (let + vhosts = config.services.nginx.virtualHosts; + vhostsRequiringAuth = mapNames (lib.filter (set: set.value.requireAuth == true) (lib.attrsToList vhosts)); + mapNames = e: toString (lib.map (set: set.name) e); + in + mkIf (!cfg.enable && vhostsRequiringAuth != "") { + warnings = [ + "The following nginx hosts have requireAuth, but config.rc.web.auth is not enabled: ${vhostsRequiringAuth}" + ]; + }) ]; } From c0208556c1855b1c14f4b9a0d44385bfd445b1b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Wed, 15 Oct 2025 14:34:08 -0700 Subject: [PATCH 32/39] Move auth_request into vhost option --- hosts/spore/services/pocket-id.nix | 4 ---- modules/nixos/web/auth.nix | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 6c579497..00bd2eb7 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -38,10 +38,6 @@ useACMEHost = "zx.dev"; requireAuth = true; locations."/" = { - extraConfig = '' - auth_request /oauth2/auth; - error_page 401 = @oauth2_redirect; - ''; root = "/var/www/test"; index = "index.html"; }; diff --git a/modules/nixos/web/auth.nix b/modules/nixos/web/auth.nix index 2f300b32..82354061 100644 --- a/modules/nixos/web/auth.nix +++ b/modules/nixos/web/auth.nix @@ -104,6 +104,10 @@ in { return 302 https://${cfg.authProxy.host}/oauth2/start?rd=$scheme://$http_host$request_uri; ''; }; + locations."/".extraConfig = '' + auth_request /oauth2/auth; + error_page 401 = @oauth2_redirect; + ''; }; }; in From b7b7ea5c676aaf7e538d80c9beb7c38bb5c3ed7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Wed, 15 Oct 2025 14:34:59 -0700 Subject: [PATCH 33/39] Switch to `rc.web.auth` for hosts --- hosts/spore/services/web/virtual-hosts.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hosts/spore/services/web/virtual-hosts.nix b/hosts/spore/services/web/virtual-hosts.nix index 0ebcab1f..b4d517a1 100644 --- a/hosts/spore/services/web/virtual-hosts.nix +++ b/hosts/spore/services/web/virtual-hosts.nix @@ -43,7 +43,7 @@ "torrents.zx.dev" = { forceSSL = true; useACMEHost = "zx.dev"; - enableAutheliaAuth = true; + requireAuth = true; locations."/".proxyPass = "http://glyph.rove-duck.ts.net:9091"; locations."~ (/transmission)?/rpc".proxyPass = "http://glyph.rove-duck.ts.net:9091"; }; @@ -60,13 +60,13 @@ "files.zx.dev" = { forceSSL = true; useACMEHost = "zx.dev"; - enableAutheliaAuth = true; + requireAuth = true; locations."/".proxyPass = "http://glyph.rove-duck.ts.net:8080"; }; "home.zx.dev" = { forceSSL = true; useACMEHost = "zx.dev"; - enableAutheliaAuth = true; + requireAuth = true; locations."/".proxyPass = "http://127.0.0.1:8082"; }; }; From 80efe57b0b345463b4dbe53b0d8d9a0085917395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Wed, 15 Oct 2025 14:40:53 -0700 Subject: [PATCH 34/39] Remove test vhost --- hosts/spore/services/pocket-id.nix | 40 ------------------------------ 1 file changed, 40 deletions(-) diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/pocket-id.nix index 00bd2eb7..38028543 100644 --- a/hosts/spore/services/pocket-id.nix +++ b/hosts/spore/services/pocket-id.nix @@ -31,44 +31,4 @@ keyFile = config.age.secrets.oauth2-proxy-env.path; }; }; - - services.nginx = { - virtualHosts."test.zx.dev" = { - forceSSL = true; - useACMEHost = "zx.dev"; - requireAuth = true; - locations."/" = { - root = "/var/www/test"; - index = "index.html"; - }; - }; - }; - - systemd.tmpfiles.rules = [ - "d /var/www 0755 root root -" - "d /var/www/test 0755 root root -" - ]; - systemd.mounts = let - testSite = pkgs.writeTextDir "index.html" '' - - - Protected - -

✅ Authenticated via Pocket ID

- - ''; - in [ - { - what = "${testSite}"; - where = "/var/www/test"; - type = "none"; - options = "bind,ro"; - wantedBy = ["multi-user.target"]; - } - ]; - systemd.services.nginx = { - after = ["var-www-test.mount"]; - requires = ["var-www-test.mount"]; - serviceConfig.RequiresMountsFor = ["/var/www/test"]; - }; } From 6e1f16802fa422be36335a35008cee48ffbd3b83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Wed, 15 Oct 2025 14:50:29 -0700 Subject: [PATCH 35/39] Move module to `services/web/auth.nix` --- hosts/spore/services/default.nix | 1 - hosts/spore/services/{pocket-id.nix => web/auth.nix} | 0 hosts/spore/services/web/default.nix | 1 + 3 files changed, 1 insertion(+), 1 deletion(-) rename hosts/spore/services/{pocket-id.nix => web/auth.nix} (100%) diff --git a/hosts/spore/services/default.nix b/hosts/spore/services/default.nix index ddcb2594..27c46ef7 100644 --- a/hosts/spore/services/default.nix +++ b/hosts/spore/services/default.nix @@ -8,7 +8,6 @@ ./db.nix ./homepage-dashboard.nix ./mastodon.nix - ./pocket-id.nix ./web ./infrastructure ]; diff --git a/hosts/spore/services/pocket-id.nix b/hosts/spore/services/web/auth.nix similarity index 100% rename from hosts/spore/services/pocket-id.nix rename to hosts/spore/services/web/auth.nix diff --git a/hosts/spore/services/web/default.nix b/hosts/spore/services/web/default.nix index 26523608..38bd102b 100644 --- a/hosts/spore/services/web/default.nix +++ b/hosts/spore/services/web/default.nix @@ -1,6 +1,7 @@ # Web services and nginx configuration { imports = [ + ./auth.nix ./nginx-options.nix ./ssl-acme.nix ./nginx-config.nix From 4c9a18a592a007a1464087fc8bd50dc75c181dbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Wed, 15 Oct 2025 14:50:46 -0700 Subject: [PATCH 36/39] Update paths --- hosts/spore/services/web/auth.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hosts/spore/services/web/auth.nix b/hosts/spore/services/web/auth.nix index 38028543..aba2e2d9 100644 --- a/hosts/spore/services/web/auth.nix +++ b/hosts/spore/services/web/auth.nix @@ -4,13 +4,13 @@ ... }: { age.secrets.oauth2-proxy-env = { - file = ./../secrets/oauth2-proxy-env.age; + file = ./../../secrets/oauth2-proxy-env.age; mode = "440"; owner = "oauth2-proxy"; group = "oauth2-proxy"; }; age.secrets.pocket-id-encryption-key = { - file = ./../secrets/pocket-id-encryption-key.age; + file = ./../../secrets/pocket-id-encryption-key.age; mode = "440"; owner = config.services.pocket-id.user; group = config.services.pocket-id.group; From 335ab9882965d9739153309dc1b21a2b7ef0c950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Wed, 15 Oct 2025 14:52:25 -0700 Subject: [PATCH 37/39] Remove Authelia --- hosts/spore/secrets/jwt-secret.age | Bin 781 -> 0 bytes hosts/spore/secrets/session-secret.age | 13 -- .../spore/secrets/storage-encryption-key.age | Bin 781 -> 0 bytes hosts/spore/services/authelia.nix | 145 ------------------ hosts/spore/services/default.nix | 1 - hosts/spore/services/web/default.nix | 1 - hosts/spore/services/web/nginx-options.nix | 124 --------------- hosts/spore/services/web/virtual-hosts.nix | 7 - justfile | 2 +- lib/secrets/spore.nix | 3 - 10 files changed, 1 insertion(+), 295 deletions(-) delete mode 100644 hosts/spore/secrets/jwt-secret.age delete mode 100644 hosts/spore/secrets/session-secret.age delete mode 100644 hosts/spore/secrets/storage-encryption-key.age delete mode 100644 hosts/spore/services/authelia.nix delete mode 100644 hosts/spore/services/web/nginx-options.nix diff --git a/hosts/spore/secrets/jwt-secret.age b/hosts/spore/secrets/jwt-secret.age deleted file mode 100644 index 40234e89730cfae8edc9ba8c1c8c1ca75fdbda76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 781 zcmZ9{OKZ~r003YGy%ZcwVLCwwj42XolQd1O!Zb~~^p)n-v`rZ5mUq)8Y1$-BK|xPG z4o(q#UOb3+5cHs+Anf2l1P=}r(Sw2qw;}jIVFy7y?-zW0^M;nx9L0~lf#o^}T;E8A za3VPa^!sK~Q;|#t&H=vQgFX&))JEEE_)eQ^mV$JtX?C%OHEKb4&B}w#9utxF^j(CJ z71eWC)=wLp3DJd=L_{H)0mCrvYc!)Zql80tFeM-(*MmK)+v`D9M-T<9Opq4KGous@ z53^*)FPLp{qn0i-O@{+&xE=EjOba5OlND&e(sJ1fmZ2avE+QPE2cuT6WyjP=bQ7#* zjdYcECBg<v4dG zT?=E}jT&L)Bhmm$y{gmes8Sdxd|p*u2Jdrb+5v+S19psDnd12f0>cPLxI`n!lB8aZ z0~&SOB0OY@Ib#SefEle^tJ`%Nub6Pzf+4FCC6dV`U_>sWcgbuX{QqiGWeH^w3DSBC z783eCp~LemMmBY^hq;NXkG?+H=gvHS`bWR?;O@ot#M;vNUwheOM<=Jx?)g;Xy{!PD ze;ivLn>exfdHc0uVZ8JFb*X;j%f0U(-^_hzF3!DQ;cnT53qQ7=2By?K_PHmkOU}!+ z>qpNV8tY!ZNlZ@t-Z?q`8U49@``wEzS62>iTix$~yYuqQ`e0(y=2yq3SHI1k@^6em b%j@^AyxsNp*~yf>JiX)2$@KxiKa~CfVnGu+ diff --git a/hosts/spore/secrets/session-secret.age b/hosts/spore/secrets/session-secret.age deleted file mode 100644 index e8af21b6..00000000 --- a/hosts/spore/secrets/session-secret.age +++ /dev/null @@ -1,13 +0,0 @@ -age-encryption.org/v1 --> ssh-ed25519 rSr+rA BWdzZruTjk0r53+3pf/1BYQOiyRl1iz6CvU4OYjS2j0 -2VbYsxaGN7Xq86CyOp/mWFyIJczq+iFvDZP4+vYBY/A --> ssh-ed25519 KYfd6A iUFqZ6D1bkZBwfxCnS1c0GF+Nb0vP9HPETog9FlsjWA -SQDCQih5zJjUFXciEYRd1k6G7Q/3zrMUoTcTJI7EJa0 --> ssh-ed25519 3EWhnQ xjTNgb7vaDgERaevBfBJx+SrDkb4YRibLl+Q718fE20 -rDKqVg5mNhIc3PoMDyaV9HqL6AHdcfEJb+68WIUSC/s --> ssh-ed25519 stFZUQ Q9YL41TlmX8EAMnGuRC2GlJtiE9E5VM3hJLu2tSiyiA -pd/5ooXcByM3M7KkooZCYwXV7YrAc8gBLC6XhgNcjtE --> ssh-ed25519 CiBwDg ukW5B8fW8my53/K2O+47vOJgL11yBqZkPcdf3qIjD1Q -lzKjiPrp0+pnmjklXez46ZU/1o4a8XWgmHTP/Y+tPgY ---- 6qsMeU9IG5whSVCeV36FoRUVsw0uzayARz6fd7hpS2Y -`O5G8vk֛qݱPFQkn ]\]|"(x++?N^z= gАT5 o4Ͷs%̡kv| #iDFGႫģqO?ʪh'@B3!u$d%[ ɘQ5Xu- \ No newline at end of file diff --git a/hosts/spore/secrets/storage-encryption-key.age b/hosts/spore/secrets/storage-encryption-key.age deleted file mode 100644 index d5307be3e5405c3af6293bef01d29d678fde9bca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 781 zcmZ9_JBZT&002;N5DY5#!NG6j&=W#U+9YklfskJ>pX)VE)21yrBxycv+NNpJ^mBtC zI2;bAt8yZO_#MPSxf2wG!$A-a!OtchDmthj4t~-3<~Kauu@Y9t?2Won!|xpPdk!2Y zfy8kz4BdoPPvLly1bf9E)T2N>ZMs=mAubo%YJn>>y;@c0)^AePuBRz*z|j=sckAM>&l=sW>I0y9PmdX1B& z>mxqL42%?7jD__Z%4u$=0HSm;!U@G;w2+f6jCLdrRdY;55h{M)rtDkW Date: Wed, 15 Oct 2025 14:53:05 -0700 Subject: [PATCH 38/39] Remove TODO I don't think I really care enough about this to bother changing it. --- hosts/spore/services/web/auth.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/spore/services/web/auth.nix b/hosts/spore/services/web/auth.nix index aba2e2d9..6860cd3b 100644 --- a/hosts/spore/services/web/auth.nix +++ b/hosts/spore/services/web/auth.nix @@ -24,7 +24,7 @@ encryptionKeyFile = config.age.secrets.pocket-id-encryption-key.path; }; authProxy = { - host = "oauth.zx.dev"; # TODO: Conflict with Authelia + host = "oauth.zx.dev"; domain = ".zx.dev"; clientID = "shared-sso"; useACMEHost = "zx.dev"; From 418172cd4a2481682e7e2b5eee1f919412f1a530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=BF=20corey=20=28they/them=29?= Date: Wed, 15 Oct 2025 14:56:24 -0700 Subject: [PATCH 39/39] Depend on `network-online.target` Fixes: ``` evaluation warning: pocket-id.service is ordered after 'network-online.target' but doesn't depend on it ``` See also: https://github.com/NixOS/nixpkgs/pull/282795 --- modules/nixos/web/auth.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/nixos/web/auth.nix b/modules/nixos/web/auth.nix index 82354061..aeafc043 100644 --- a/modules/nixos/web/auth.nix +++ b/modules/nixos/web/auth.nix @@ -141,6 +141,7 @@ in { }; systemd.services.pocket-id = { + wants = ["network-online.target"]; after = ["postgresql.service" "network-online.target"]; requires = ["postgresql.service"]; };