diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 812242db..122d4349 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,14 @@ ## 2026-09-07 +- **Configured OpenID Connect (OIDC) authentication for Paperless-ngx**: + - Added secret management for `paperless-oidc-secret.age` via `secrets.paperless-oidc-secret` owned by `paperless:paperless` (0440). + - Configured `systemd.services.paperless-oidc-secret-secret` to run before and be required by all Paperless units, dynamically generating `/var/private/paperless-env` with `PAPERLESS_SOCIALACCOUNT_PROVIDERS` configured for Pocket ID (`auth.masu.rs`) using `client_id = "e6ff7fce-8e67-4c66-8f32-5ec3db4740a9"`, `oauth_pkce_enabled = true`, and email authentication linking (`email_authentication = true`, `verified_email = true`) in the native `APPS` schema. + - Added `services.paperless.environmentFile = "${config.secretsDirectory}/paperless-env"` to securely pass the OIDC configuration to Paperless services without exposing secrets in the Nix store. + - Configured Paperless settings to enable `allauth.socialaccount.providers.openid_connect` in `PAPERLESS_APPS` and enabled `PAPERLESS_REDIRECT_LOGIN_TO_SSO`. + - Configured reverse proxy trust settings (`PAPERLESS_USE_X_FORWARD_HOST`, `PAPERLESS_PROXY_SSL_HEADER`, and `PAPERLESS_TRUSTED_PROXIES`) to ensure callback URIs preserve the `https://` scheme behind Caddy. + - Fixed `systemd.services.paperless-secret` to order before and be required by `paperless-scheduler.service` instead of non-existent `paperless.service`. + - **Configured real client IP forwarding for Jellyfin reverse proxy**: - Added a `map` handler to Jellyfin's Caddy route in `platforms/nixos/modules/nmasur/presets/services/jellyfin.nix` to resolve `{client_ip}` using Cloudflare's `CF-Connecting-IP` header when available, falling back to `{http.request.remote.host}` for direct local LAN connections. - Configured `reverse_proxy.headers.request.set` to forward `X-Real-IP`, `X-Forwarded-For`, and `X-Forwarded-Proto` with the resolved client IP and request scheme to Jellyfin. diff --git a/docs/oidc-services.md b/docs/oidc-services.md index af6b0aca..9662649e 100644 --- a/docs/oidc-services.md +++ b/docs/oidc-services.md @@ -183,24 +183,28 @@ All client secrets should be encrypted with `agenix` under the respective servic ```nix services.paperless.settings = { PAPERLESS_APPS = "allauth.socialaccount.providers.openid_connect"; - PAPERLESS_SOCIALACCOUNT_PROVIDERS = builtins.toJSON { - openid_connect = { - SERVERS = [ - { - id = "pocket-id"; - name = "Pocket ID"; - server_url = "https://auth.masu.rs"; - token_auth_method = "client_secret_basic"; - APP = { - client_id = "paperless"; - secret = "..."; # or via environmentFile - }; - } - ]; - }; - }; - PAPERLESS_REDIRECT_LOGIN_TO_SSO = "true"; # Optional: bypass local login + PAPERLESS_REDIRECT_LOGIN_TO_SSO = true; }; + # Configured via environmentFile to pass the client secret, enable PKCE, and link by email: + # PAPERLESS_SOCIALACCOUNT_PROVIDERS = builtins.toJSON { + # openid_connect = { + # APPS = [ + # { + # provider_id = "pocket-id"; + # name = "Pocket ID"; + # client_id = "e6ff7fce-8e67-4c66-8f32-5ec3db4740a9"; + # secret = "..."; + # settings = { + # server_url = "https://auth.masu.rs"; + # token_auth_method = "client_secret_basic"; + # oauth_pkce_enabled = true; + # email_authentication = true; + # verified_email = true; + # }; + # } + # ]; + # }; + # }; ``` ### 6. Mealie (`cooking.masu.rs`) diff --git a/platforms/nixos/modules/nmasur/presets/services/paperless/paperless-oidc-secret.age b/platforms/nixos/modules/nmasur/presets/services/paperless/paperless-oidc-secret.age new file mode 100644 index 00000000..f64d20ef --- /dev/null +++ b/platforms/nixos/modules/nmasur/presets/services/paperless/paperless-oidc-secret.age @@ -0,0 +1,17 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IE1nSGFPdyBCa2J1 +QjFGRjdZa0RHcjd0V0JXL3lUNzRGczNHanIzb3BtaE93NlNvcGhnCnNOM3JBc0t3 +VENnWVp1b09Sd1JwcDk1Q0pQM3BBMlJmWHh5TTJHakIvcGsKLT4gc3NoLWVkMjU1 +MTkgWXlTVU1RIDljQUIrenlGZHMxSHlReG80VVhaSXhsbFZ0L2dtcCtWbTRvNE1i +WEdOeVkKUk9HK25Fci9TYWRSS1htSU9BNHlqbHpGT3c5bkI4b3RUL09QK1BYTE1Y +VQotPiBzc2gtZWQyNTUxOSBuanZYNUEgNXk5bzRhR1BoTjlMeHVRR2UyUEZJN0Y1 +ZXAyU3BjeFJvRHhER0ptaVlsVQo0K3p6bXBwcTVIdGlTajBucWV5dzZKM2JyWWNB +R2s2UnhTMFVPRGwrVWhRCi0+IHNzaC1lZDI1NTE5IENxSU9VQSBaL3NTdHBjVFRX +ODNrcDVmZGFxNVRwbGkrcC9heFN5bmVCZ0xId1NESUhFCm5aQS9xZmhkSWRwMFl1 +b3FPdUhPZU5YdVQvTEQ3eVdRODBhVzJSWjdyek0KLT4gc3NoLWVkMjU1MTkgejFP +Y1p3IHdDR3YwYTFyWlZQZWFDZ1cvWk00bjdRdW16c1M4eDlDaHl4Y3lkbVFGVFEK +dndhaG5pUTM3aU04WHZUSHJDdUdkZ0VWRXRaL2t6cUJoZTR1bTh6NlBFawotLS0g +NXRDeDFZL0RvaURVK2k3R0NVZGdKR0diZ2RwZjQxTFhuNk82SW9GMGVaRQqYDG6J ++/POhVn84NGh8ganDIzNUqQF7uBIxdUMiRoFa0kU4eJd9V4vmjsjtr/lbQ9O+Xfq +WJJ8Hjwyzwopx1Phrw== +-----END AGE ENCRYPTED FILE----- diff --git a/platforms/nixos/modules/nmasur/presets/services/paperless/paperless.nix b/platforms/nixos/modules/nmasur/presets/services/paperless/paperless.nix index 11b022cd..5f6ccf76 100644 --- a/platforms/nixos/modules/nmasur/presets/services/paperless/paperless.nix +++ b/platforms/nixos/modules/nmasur/presets/services/paperless/paperless.nix @@ -1,6 +1,11 @@ # Paperless-ngx is a document scanning and management solution. -{ config, lib, ... }: +{ + config, + pkgs, + lib, + ... +}: let inherit (config.nmasur.settings) hostnames username; @@ -17,12 +22,26 @@ in enable = true; mediaDir = "/data/generic/paperless"; passwordFile = config.secrets.paperless.dest; + environmentFile = "${config.secretsDirectory}/paperless-env"; configureTika = true; # Enable processing of emails settings = { PAPERLESS_OCR_USER_ARGS = builtins.toJSON { invalidate_digital_signatures = true; }; PAPERLESS_URL = "https://${hostnames.paperless}"; PAPERLESS_DATE_ORDER = "MDY"; # Check document for US-formatted dates + # OIDC Authentication with Pocket ID + PAPERLESS_APPS = "allauth.socialaccount.providers.openid_connect"; + PAPERLESS_REDIRECT_LOGIN_TO_SSO = true; + PAPERLESS_USE_X_FORWARD_HOST = true; + PAPERLESS_PROXY_SSL_HEADER = [ + "HTTP_X_FORWARDED_PROTO" + "https" + ]; + PAPERLESS_TRUSTED_PROXIES = [ + "127.0.0.1" + "::1" + ]; + # Enable if changing the path name in Caddy # PAPERLESS_FORCE_SCRIPT_NAME = "/paperless"; # PAPERLESS_STATIC_URL = "/paperless/static/"; @@ -61,8 +80,60 @@ in permissions = "0440"; }; systemd.services.paperless-secret = { - requiredBy = [ "paperless.service" ]; - before = [ "paperless.service" ]; + requiredBy = [ "paperless-scheduler.service" ]; + before = [ "paperless-scheduler.service" ]; + }; + + secrets.paperless-oidc-secret = { + source = ./paperless-oidc-secret.age; + dest = "${config.secretsDirectory}/paperless-oidc-secret"; + owner = "paperless"; + group = "paperless"; + permissions = "0440"; + }; + systemd.services.paperless-oidc-secret-secret = { + requiredBy = [ + "paperless-secret-key.service" + "paperless-scheduler.service" + "paperless-task-queue.service" + "paperless-consumer.service" + "paperless-web.service" + ]; + before = [ + "paperless-secret-key.service" + "paperless-scheduler.service" + "paperless-task-queue.service" + "paperless-consumer.service" + "paperless-web.service" + ]; + postStart = '' + SECRET="$(tr -d '\r\n' < '${config.secrets.paperless-oidc-secret.dest}')" + JSON=$(${pkgs.jq}/bin/jq -nc \ + --arg secret "$SECRET" \ + --arg auth "https://${hostnames.auth}" \ + '{ + openid_connect: { + APPS: [ + { + provider_id: "pocket-id", + name: "Pocket ID", + client_id: "e6ff7fce-8e67-4c66-8f32-5ec3db4740a9", + secret: $secret, + settings: { + server_url: $auth, + token_auth_method: "client_secret_basic", + oauth_pkce_enabled: true, + email_authentication: true, + verified_email: true + } + } + ] + } + }') + echo "PAPERLESS_SOCIALACCOUNT_PROVIDERS='$JSON'" > '${config.secretsDirectory}/paperless-env' + chown paperless:paperless '${config.secretsDirectory}/paperless-env' + chmod 0440 '${config.secretsDirectory}/paperless-env' + ''; }; # Fix paperless shared permissions