From a248c6bee14a30295826377428ae1932be6a0163 Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 1 Jun 2026 23:33:16 +0200 Subject: [PATCH] feat(gateway): custom 401 page explaining how to add users When HTTP Basic auth is enabled and credentials are absent or rejected, nginx serves a Catppuccin-styled 401 page that tells the operator which hivectl command to run to create a user. Uses error_page 401 =401 so the browser still receives a 401 status (login dialog fires on first visit) while getting a human-readable body when the dialog is dismissed. The exact-match location (= /__hive_auth_unauthorized) beats location / in nginx's prefix ordering so the internal subrequest does not loop back through auth_basic. --- nix/modules/hive-gateway.nix | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/nix/modules/hive-gateway.nix b/nix/modules/hive-gateway.nix index 9925aeb9..edae09a8 100644 --- a/nix/modules/hive-gateway.nix +++ b/nix/modules/hive-gateway.nix @@ -59,6 +59,32 @@ let EOF + cat > $out/unauthorized.html <<'EOF' + + + + + unauthorized ◆ hyperhive + + + +

◆ unauthorized

+

This hive is protected by HTTP Basic auth. Valid credentials are required.

+

Operator: add a user with hivectl gateway create-user:

+
hivectl gateway create-user \
+      --file /etc/hyperhive/gateway.htpasswd \
+      <username> --password-stdin
+

Then reload your browser and enter the credentials when prompted.

+ + + EOF ''; in { @@ -586,9 +612,30 @@ in ${lib.optionalString (cfg.auth.enable && cfg.auth.htpasswdFile != null) '' auth_basic "${cfg.auth.realm}"; auth_basic_user_file /run/gateway-auth/${builtins.baseNameOf cfg.auth.htpasswdFile}; + # Serve a custom page when credentials are missing or wrong. + # `=401` forces the final status to remain 401 so browsers + # still present the login dialog on first visit; users who + # dismiss the dialog see a page explaining how to add users + # with `hivectl gateway create-user`. + # The exact-match location below beats `location /` in nginx's + # prefix ordering, so the internal subrequest does not loop back + # through auth_basic. + error_page 401 =401 /__hive_auth_unauthorized; ''} ''; }; + } + // lib.optionalAttrs (cfg.auth.enable && cfg.auth.htpasswdFile != null) { + # Internal-only target for the 401 error_page above. + # `internal` prevents direct client access; `alias` serves + # the pre-built HTML from the Nix store. + "= /__hive_auth_unauthorized" = { + extraConfig = '' + internal; + alias ${agentErrorPagesDir}/unauthorized.html; + default_type text/html; + ''; + }; }; # Per-agent location blocks, generated at runtime by # hive-c0re and written to /var/lib/hyperhive/gateway/agents.conf