Merge PR #1419 from bsgdigital: login session TTL + redirect-back + connectivity probe

This commit is contained in:
nesquena-hermes
2026-05-01 21:26:35 +00:00
2 changed files with 48 additions and 2 deletions
+7 -2
View File
@@ -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
+41
View File
@@ -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();
})();
});