diff --git a/api/auth.py b/api/auth.py index fc397760..4746d642 100644 --- a/api/auth.py +++ b/api/auth.py @@ -24,7 +24,7 @@ PUBLIC_PATHS = frozenset({ }) COOKIE_NAME = 'hermes_session' -SESSION_TTL = 86400 # 24 hours +SESSION_TTL = 86400 * 30 # 30 days _SESSIONS_FILE = STATE_DIR / '.sessions.json' @@ -229,7 +229,12 @@ def check_auth(handler, parsed) -> bool: handler.wfile.write(b'{"error":"Authentication required"}') else: handler.send_response(302) - handler.send_header('Location', '/login') + # Pass the original path as ?next= so login.js redirects back after auth. + import urllib.parse as _urlparse + _next = _urlparse.quote(parsed.path or '/', safe='/:@!$&\'()*+,;=') + if parsed.query: + _next += '?' + parsed.query + handler.send_header('Location', '/login?next=' + _urlparse.quote(_next, safe='/:@!$&\'()*+,;=?')) handler.end_headers() return False diff --git a/static/login.js b/static/login.js index bba29b50..72c47a5b 100644 --- a/static/login.js +++ b/static/login.js @@ -66,4 +66,45 @@ document.addEventListener('DOMContentLoaded', function () { doLogin(e); } }); + + // On page load, probe the server so we can distinguish "can't reach server" + // (Tailscale off, wrong network) from "session expired / need to log in". + // Uses /health — a public endpoint, no auth required. + // If unreachable, retries every 3 s and auto-reloads once the server is back. + (function checkConnectivity() { + var retryTimer = null; + + function setFormDisabled(disabled) { + if (input) input.disabled = disabled; + var btn = form.querySelector('button'); + if (btn) btn.disabled = disabled; + } + + function probe() { + fetch('health', { method: 'GET', credentials: 'omit' }) + .then(function (r) { + if (r.ok) { + // Server is reachable — if we were in retry mode, reload so the + // page reflects the correct auth state (expired session, etc.). + if (retryTimer !== null) { + clearTimeout(retryTimer); + retryTimer = null; + window.location.reload(); + } + } else { + showErr(connFailed + ' (server error ' + r.status + ')'); + } + }) + .catch(function () { + showErr('Cannot reach server — check your VPN / Tailscale connection.'); + setFormDisabled(true); + // Keep retrying so the page auto-recovers once the network is back. + if (retryTimer === null) { + retryTimer = setInterval(probe, 3000); + } + }); + } + + probe(); + })(); });