Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: a request that finds the login ended reloads the page to the login

The gateway answered every non-page request without a session with a login
redirect, which fetch and EventSource cannot follow across origins, so polls,
forms and streams failed with errors that said nothing about the session. It
now answers anything that does not ask for text/html with a 401 and a
WWW-Authenticate header, and the swarm UI reloads on that 401, from a wrapped
fetch (one place for every call) and from a probe after a stream dies. A second
reload within ten seconds is skipped so a page that keeps answering 401 cannot
loop. The proxy test and the docs follow.
This commit is contained in:
iris 2026-10-28 18:14:58 +01:00 • committed by mara
commit 4d5a65a185
2 changed files with 187 additions and 9 deletions

View file

@ -252,11 +252,24 @@ in
proxy_buffering off;
'';
};
# A refused request gets the login page only when it asks for a page.
# The swarm UI calls `/api/` with `fetch` and `EventSource`, and a
# cross-origin redirect makes both fail with an error that says nothing
# about why, so after a session ends every poll, form and stream broke
# until a reload. Anything that does not ask for `text/html` gets a plain
# 401 with a `WWW-Authenticate` header, which the UI reads as "log in
# again" and answers with a reload. The header is also how the UI tells
# this 401 from one the controller sends for its own reasons.
"@swarm_api_unauthorized".extraConfig = ''
if ($http_authorization) {
add_header WWW-Authenticate 'Bearer realm="swarm"' always;
return 401;
}
return 302 ${loginRedirect};
if ($http_accept ~* "text/html") {
return 302 ${loginRedirect};
}
add_header WWW-Authenticate 'Bearer realm="swarm"' always;
return 401;
'';
# ⚠️ THE ONE PROXIED LOCATION ON THIS VHOST WITH NO `auth_request`,
# and that is deliberate rather than an omission.

View file

@ -457,9 +457,12 @@ let
}
{
# `swarmctl --swarm` reads a 401 as "log in again"; a 302 would hand it
# the login page instead. The `/` clause is the control: the browser
# route still redirects, so a pass means the two routes differ.
name = "the controller API answers a refused bearer caller 401 and a browser the login";
# the login page instead. The swarm UI's own `fetch` and `EventSource`
# need the same 401, because a redirect to another origin makes both fail
# with an error that says nothing about the session; only a request that
# asks for `text/html` gets the login. The `/` clause is the control: the
# page route still redirects, so a pass means the two routes differ.
name = "the controller API answers a refused bearer caller or fetch 401 and a page request the login";
ok =
let
l = allLocal.services.nginx.virtualHosts."t.local".locations;
@ -469,8 +472,9 @@ let
lib.hasInfix "error_page 401 = @swarm_api_unauthorized;" api
&& !(lib.hasInfix "=302" api)
&& lib.hasInfix ''proxy_set_header Authorization "";'' api
&& lib.hasInfix "if ($http_authorization) {\n return 401;\n}" refused
&& lib.hasInfix "return 302 https://auth.t.local/?rd=$target_url;" refused
&& lib.hasInfix "if ($http_authorization) {\n add_header WWW-Authenticate 'Bearer realm=\"swarm\"' always;\n return 401;\n}" refused
&& lib.hasInfix "if ($http_accept ~* \"text/html\") {\n return 302 https://auth.t.local/?rd=$target_url;\n}" refused
&& lib.hasInfix "}\nadd_header WWW-Authenticate 'Bearer realm=\"swarm\"' always;\nreturn 401;" refused
&& lib.hasInfix "error_page 401 =302 https://auth.t.local/" l."/".extraConfig;
}
{