Add GET /api/session, an unguarded probe that reports whether the request
carries valid credentials. It returns 200 with {"authenticated":bool} and never
sends WWW-Authenticate, so the static public pages can check login state without
triggering the browser's Basic-auth dialog. To keep it from being a
password-guessing oracle that bypasses the /upload limiter, a request with no
credentials is answered cheaply as false, while wrong credentials go through the
same failed-attempt limiter (429 once drained).
static/session.js, loaded by every page template, calls it and, only when
authenticated, builds a fixed toolbar: Upload / Manage links, an "Edit this"
link on photo pages (via <body data-slug>, deep-linking to /manage?edit=<slug>,
which scrolls to and focuses that row), a signed-in indicator, and a log-out
control. Logged-out visitors see nothing and get no dialog.
Log out is best-effort: HTTP Basic has no reliable sign-out (the browser caches
credentials until the window closes), so it overwrites the cached credential and
reloads, which works in some browsers but not all. A dependable logout would
need cookie sessions.
66 lines
2.3 KiB
JavaScript
66 lines
2.3 KiB
JavaScript
// Shows a management toolbar + signed-in indicator on every page when the
|
|
// browser holds valid credentials. The public gallery is static and carries no
|
|
// markup for this; the bar is built here only after /api/session confirms the
|
|
// viewer is authenticated, so logged-out visitors see nothing and get no login
|
|
// dialog.
|
|
//
|
|
// A page may set <body data-slug="..."> (the photo pages do) to get an
|
|
// "Edit this" link straight to that post in /manage.
|
|
(function () {
|
|
fetch("/api/session", { headers: { Accept: "application/json" }, cache: "no-store" })
|
|
.then((r) => (r.ok ? r.json() : { authenticated: false }))
|
|
.then((s) => {
|
|
if (s && s.authenticated) buildBar();
|
|
})
|
|
.catch(() => {});
|
|
|
|
function el(tag, cls, text) {
|
|
const e = document.createElement(tag);
|
|
if (cls) e.className = cls;
|
|
if (text != null) e.textContent = text;
|
|
return e;
|
|
}
|
|
|
|
function buildBar() {
|
|
document.body.classList.add("authed");
|
|
|
|
const bar = el("div", "session-bar");
|
|
const path = location.pathname.replace(/\/$/, "");
|
|
|
|
const link = (href, label) => {
|
|
const a = el("a", null, label);
|
|
a.href = href;
|
|
bar.appendChild(a);
|
|
};
|
|
|
|
if (path !== "/upload") link("/upload", "Upload");
|
|
if (path !== "/manage") link("/manage", "Manage");
|
|
|
|
const slug = document.body.dataset.slug;
|
|
if (slug) link("/manage?edit=" + encodeURIComponent(slug), "Edit this");
|
|
|
|
bar.appendChild(el("span", "session-dot", "● signed in"));
|
|
|
|
// Best-effort logout. HTTP Basic has no real sign-out — the browser caches
|
|
// credentials and clears them reliably only when the window closes — so
|
|
// this overwrites the cached credential with a throwaway one and reloads.
|
|
// It works in some browsers and not others; a dependable logout would need
|
|
// cookie sessions.
|
|
const out = el("a", "session-out", "log out");
|
|
out.href = "#";
|
|
out.title = "Best-effort — HTTP Basic auth has no reliable logout";
|
|
out.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
fetch("/api/session", {
|
|
headers: { Authorization: "Basic " + btoa("logout:" + Date.now()) },
|
|
cache: "no-store",
|
|
})
|
|
.catch(() => {})
|
|
.finally(() => setTimeout(() => location.reload(), 150));
|
|
});
|
|
bar.appendChild(out);
|
|
|
|
document.body.appendChild(bar);
|
|
}
|
|
})();
|