auth: fix HTTP login loop (Secure=false default, gate dashboard flash)
This commit is contained in:
parent
ed3c080976
commit
2923f350f8
@ -1,28 +1,44 @@
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Auth gate: runs before the main app IIFE bootstraps. If the user is not
|
// Auth gate: must complete before the main app IIFE renders. While the
|
||||||
// authenticated, we redirect to /login.html (or /setup.html when the backend
|
// gate is in flight, `<html data-auth-pending>` hides the UI via CSS so the
|
||||||
// indicates the initial setup is still required) and abort further init.
|
// dashboard never flashes for an unauthenticated user.
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
(async function authGate() {
|
document.documentElement.dataset.authPending = '1';
|
||||||
|
|
||||||
|
window.__authReady = (async function authGate() {
|
||||||
try {
|
try {
|
||||||
const r = await fetch('/api/auth/me', { credentials: 'same-origin' });
|
const r = await fetch('/api/auth/me', { credentials: 'same-origin' });
|
||||||
if (r.status === 401) {
|
if (r.status === 401) {
|
||||||
const setup = await fetch('/api/auth/setup-required').then(function (x) { return x.json(); }).catch(function () { return { setup_required: false }; });
|
const setup = await fetch('/api/auth/setup-required').then(function (x) { return x.json(); }).catch(function () { return { setup_required: false }; });
|
||||||
window.location.replace(setup.setup_required ? '/setup.html' : '/login.html');
|
window.location.replace(setup.setup_required ? '/setup.html' : '/login.html');
|
||||||
return;
|
return false;
|
||||||
|
}
|
||||||
|
if (!r.ok) {
|
||||||
|
window.location.replace('/login.html');
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (!r.ok) return;
|
|
||||||
const me = await r.json();
|
const me = await r.json();
|
||||||
window.__clearviewUser = me;
|
window.__clearviewUser = me;
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', function () { applyAuthUi(me); }, { once: true });
|
||||||
|
} else {
|
||||||
|
applyAuthUi(me);
|
||||||
|
}
|
||||||
|
delete document.documentElement.dataset.authPending;
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
window.location.replace('/login.html');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
function applyAuthUi(me) {
|
||||||
renderUserBadge(me);
|
renderUserBadge(me);
|
||||||
if (me.role !== 'admin') {
|
if (me.role !== 'admin') {
|
||||||
const usersLink = document.querySelector('[data-route="users"]');
|
const usersLink = document.querySelector('[data-route="users"]');
|
||||||
if (usersLink) usersLink.style.display = 'none';
|
if (usersLink) usersLink.style.display = 'none';
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
window.location.replace('/login.html');
|
|
||||||
}
|
}
|
||||||
})();
|
|
||||||
|
|
||||||
function renderUserBadge(me) {
|
function renderUserBadge(me) {
|
||||||
const slot = document.getElementById('userBadge');
|
const slot = document.getElementById('userBadge');
|
||||||
|
|||||||
@ -753,6 +753,11 @@ strong {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hide the SPA until the auth gate resolves — prevents the unauthenticated
|
||||||
|
dashboard flash before the redirect kicks in. Login/setup pages don't load
|
||||||
|
app.js, so they are unaffected. */
|
||||||
|
html[data-auth-pending] body { visibility: hidden; }
|
||||||
|
|
||||||
/* === Auth (login / setup) pages and header badge ============================== */
|
/* === Auth (login / setup) pages and header badge ============================== */
|
||||||
.auth-page {
|
.auth-page {
|
||||||
display: flex; align-items: center; justify-content: center;
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
|||||||
@ -39,5 +39,7 @@ SCAN_MAX_ITEMS_PER_LIST = _int_env("SCAN_MAX_ITEMS_PER_LIST", 10000)
|
|||||||
|
|
||||||
# Auth cookie settings (override via env)
|
# Auth cookie settings (override via env)
|
||||||
COOKIE_NAME = "clearview_session"
|
COOKIE_NAME = "clearview_session"
|
||||||
COOKIE_SECURE = os.environ.get("COOKIE_SECURE", "true").lower() != "false"
|
# Local-only HTTP deployment: default to non-Secure cookies. Set
|
||||||
|
# COOKIE_SECURE=true if the stack ever sits behind HTTPS.
|
||||||
|
COOKIE_SECURE = os.environ.get("COOKIE_SECURE", "false").lower() == "true"
|
||||||
COOKIE_SAMESITE = "lax"
|
COOKIE_SAMESITE = "lax"
|
||||||
|
|||||||
@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
This file documents changes on the develop branch of this project.
|
This file documents changes on the develop branch of this project.
|
||||||
|
|
||||||
|
## 2026-05-28 — Authentication: fix login loop on HTTP-only deployment
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `COOKIE_SECURE` now defaults to `false` (the stack runs HTTP-only locally; with `Secure` set, browsers silently drop the session cookie and the user enters a redirect loop). Set `COOKIE_SECURE=true` if the stack is ever fronted by HTTPS.
|
||||||
|
- SPA boot now hides the dashboard until `/api/auth/me` resolves (via `html[data-auth-pending]` + body visibility), eliminating the brief dashboard flash that appeared before the redirect.
|
||||||
|
|
||||||
## 2026-05-28 — Authentication: align login/setup/modal styling with site light theme
|
## 2026-05-28 — Authentication: align login/setup/modal styling with site light theme
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user