diff options
Diffstat (limited to 'src/api/dashboard/dashboard.html')
| -rw-r--r-- | src/api/dashboard/dashboard.html | 55 |
1 files changed, 44 insertions, 11 deletions
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html index d6f034b..d32fa7a 100644 --- a/src/api/dashboard/dashboard.html +++ b/src/api/dashboard/dashboard.html @@ -2625,8 +2625,9 @@ function unapplySubscriptions() { // Tokens: this page's, from logging in on this node (the browser's password for it, the // proxy's, or the session the login page began), and a narrower one, reading the member // list only, to send to others. -// Each refreshed half way through its life. +// Each refreshed half way through its life (`tokenAt`: when the page got it). let token = null, narrowToken = null, tokenWhy = "not logged in yet", tokenTimer = null; +let tokenAt = 0, obtaining = false; const READ_MEMBERS = [{ path: ["_continuum", "members"], rights: ["read"] }]; // A request that changes something, with this page's token: the session's cookie lets // in only those that read (`login::bearer`). @@ -2640,19 +2641,31 @@ async function tokenCall(url, bearer, body) { const response = await fetch(url, { method: "POST", headers, body: JSON.stringify(body || {}) }); const answer = await response.json().catch(() => ({})); - if (!response.ok) throw new Error(response.status + " " + (answer.error || response.statusText)); + if (!response.ok) throw refused(response.status, answer.error || response.statusText); return answer; } -// How the page got in: by its session ("session"), which logging out ends, or by what -// it can't end (`login` of `POST /auth/login`): the browser's password ("htpasswd"), the -// proxy's ("header"). +// An error with the status it came with (`status`): 400, 401 and 403 are the login's +// end, others pass. +function refused(status, why) { + const error = new Error(status + " " + why); + error.status = status; + return error; +} +// How the page got in: by its session ("session"), which logging out ends, or through +// the proxy, which names the user ("header"), which it can't end. Never by a password +// the browser keeps (basic authentication): the login page takes passwords. let loggedIn = null; const CANT_LOG_OUT = { - htpasswd: "This page got in by the password the browser keeps for this site, which a " + - "page can't make it forget: close the browser (all its windows) to log out.", header: "This page got in by your proxy, which names you to the node: log out of the " + "proxy (its own logout, or closing the browser).", }; +// Through the proxy, if it names the user (`POST /auth/login`); refused otherwise, a +// password the browser keeps included. +async function throughProxy() { + const answer = await tokenCall("../auth/login"); + if (answer.login !== "header") throw refused(401, "log in on the login page"); + return answer; +} function showWho() { const who = document.getElementById("who"); who.hidden = !token; @@ -2698,7 +2711,8 @@ async function logOut() { token = narrowToken = null; // In still, by what the page can't end: said so; else to the login page. try { - token = await tokenCall("../auth/login"); + token = await throughProxy(); + tokenAt = Date.now(); loggedIn = token.login; showWho(); say("Logged out of this session", el("p", {}, CANT_LOG_OUT[loggedIn] || @@ -2855,21 +2869,25 @@ async function keyRequest(id, line) { } } async function obtainTokens() { + if (obtaining) return; + obtaining = true; clearTimeout(tokenTimer); try { // Refreshed as the session (`POST /auth/session`), whose cookie lets the page in; - // the first: by that cookie, else by the browser's password or the proxy's. + // the first: by that cookie, else through the proxy. if (token && token.expiresAt > Date.now() + 60000) { token = await tokenCall("../auth/session", token.token); } else { try { token = await tokenCall("../auth/session"); loggedIn = "session"; - } catch (_) { - token = await tokenCall("../auth/login"); + } catch (error) { + if (!(error.status >= 400 && error.status < 500)) throw error; + token = await throughProxy(); loggedIn = token.login; } } + tokenAt = Date.now(); showWho(); narrowToken = await tokenCall("../auth/refresh", token.token, { limit: READ_MEMBERS }); tokenWhy = null; @@ -2884,9 +2902,24 @@ async function obtainTokens() { token = narrowToken = null; tokenWhy = "no token: " + error.message; showWho(); + // The login ended (or none): to the login page; else (the node away) again soon. + if ([400, 401, 403].includes(error.status)) { + location.assign("../login"); + return; + } tokenTimer = setTimeout(obtainTokens, 30000); + } finally { + obtaining = false; } } +// A timer fires late where the computer slept or the tab was in the background: whether +// the token is half way through its life is looked at again when the page shows, and +// every 30 s, refreshed while it still can be. +function refreshIfDue() { + if (token && Date.now() - tokenAt >= (token.expiresAt - tokenAt) / 2) obtainTokens(); +} +document.addEventListener("visibilitychange", () => { if (!document.hidden) refreshIfDue(); }); +setInterval(refreshIfDue, 30000); // Follows `id`'s own links, again after a while if it fails (2 s, doubling to a minute). // Not this node itself (a subscription handed over from another node's page, where it // was another): its links and messages are the page's own already, and read twice |
