diff --git a/bliss-cli/bliss b/bliss-cli/bliss index 2fe9bc5..17c6b18 100755 --- a/bliss-cli/bliss +++ b/bliss-cli/bliss @@ -68,6 +68,14 @@ const commands = { out(c.BASE); } }, + // Force a fresh login with BLISS_USER / BLISS_PASS and report the result. + // Unlike normal commands (which reuse a cached cookie and only re-auth on a + // 401 the server never sends), this always hits /login, so it's the way to + // (re)authenticate after changing creds or clearing a stale anonymous cookie. + async login() { + await c.login(); // throws with a clear message if the server rejects creds + out("logged in as '" + (process.env.BLISS_USER || "?") + "' -> " + c.BASE); + }, // ---- reads (JSON via /plumbing) ---- async structures() { out(await c.getJSON("/plumbing/structures")); diff --git a/bliss-cli/client.js b/bliss-cli/client.js index a82c14f..c99b47a 100644 --- a/bliss-cli/client.js +++ b/bliss-cli/client.js @@ -79,10 +79,21 @@ async function login() { headers: { "content-type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ username: USER, password: PASS }).toString(), }); + // The server issues an (anonymous) connect.sid on EVERY response — including a + // failed login, which re-renders the form as a 200. So the presence of a + // cookie says nothing about success. The real signal is the redirect: a good + // login 302s to `next`/"/", a rejected one stays 2xx. Trust the status, not + // the cookie, or we'd cache an anonymous session and silently stay logged out. const cookie = extractCookie(res); - if (!cookie) { - // A 200 back from /login means the login form re-rendered => bad creds. - throw new Error("login failed for user '" + USER + "' (check BLISS_PASS)"); + const redirected = res.status >= 300 && res.status < 400; + if (!redirected || !cookie) { + throw new Error( + "login failed for user '" + + USER + + "' — server rejected the credentials (HTTP " + + res.status + + "). Check BLISS_USER / BLISS_PASS.", + ); } saveCookie(cookie); return cookie; @@ -168,6 +179,7 @@ async function getText(urlPath) { module.exports = { BASE, setTarget, + login, request, getJSON, getText,