Always revalidate the app's files, tell open pages when a newer version exists, and log why a session is refused
build-container / build (push) Successful in 1m58s
build-container / build (push) Successful in 1m58s
An open page kept running the old version after a deploy (the single page never reloads), so the new People screens did not appear. Static files are now sent with no-cache + ETag (304 when unchanged), icons are cached for a week, /api/config reports a build stamp, and the app shows "A new version is available - Reload" when it comes back into view or every 10 minutes. The service worker cache name is bumped. The server also logs, at most once a minute per reason, why it refused a session cookie (unknown account, turned off, signed out everywhere, bad signature, expired) without printing the cookie. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2d17ee9939
commit
12a1e50a0e
6 files changed
+57
-9
No files matched your search
@@ -42,18 +42,31 @@ const isHttps = req => (req.headers['x-forwarded-proto'] || '') === 'https' || (
|
||||
const safeEq = (a, b) => a.length === b.length && crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
|
||||
|
||||
/** Session cookie: uid.epoch.expiry.signature. (Older single-user cookies, expiry.signature, count as the admin.) */
|
||||
const rejectedAt = new Map();
|
||||
const whyRejected = (req, why) => { // explain a refused session in the log (at most once a minute per reason), without printing the cookie
|
||||
const k = why, last = rejectedAt.get(k) || 0; if (Date.now() - last < 60e3) return; rejectedAt.set(k, Date.now());
|
||||
console.warn(`[auth] session refused: ${why} (${req.method} ${req.url.split('?')[0]})`);
|
||||
};
|
||||
function currentUser(req) {
|
||||
const parts = String(cookies(req).hub_session || '').split('.');
|
||||
const raw = cookies(req).hub_session;
|
||||
if (!raw) return null;
|
||||
const parts = String(raw).split('.');
|
||||
let user = null, exp;
|
||||
if (parts.length === 4) {
|
||||
const [uid, epoch, e, sig] = parts; exp = Number(e);
|
||||
const u = users.get(uid);
|
||||
if (u && !u.disabled && String(u.epoch) === epoch && safeEq(sig, mac(`s${uid}.${epoch}.${e}`))) user = u;
|
||||
if (!u) whyRejected(req, 'unknown account ' + uid);
|
||||
else if (u.disabled) whyRejected(req, 'account is turned off');
|
||||
else if (String(u.epoch) !== epoch) whyRejected(req, `signed out everywhere (cookie epoch ${epoch}, account epoch ${u.epoch})`);
|
||||
else if (!safeEq(sig, mac(`s${uid}.${epoch}.${e}`))) whyRejected(req, 'bad signature (the cookie secret changed, or the cookie was altered)');
|
||||
else user = u;
|
||||
} else if (parts.length === 2) {
|
||||
const [e, sig] = parts; exp = Number(e);
|
||||
if (safeEq(sig, mac('s' + e))) user = users.firstAdmin();
|
||||
}
|
||||
return user && exp > Date.now() ? user : null;
|
||||
if (safeEq(sig, mac('s' + e))) { user = users.firstAdmin(); if (!user) whyRejected(req, 'old-style cookie but no admin account exists'); }
|
||||
else whyRejected(req, 'bad signature on an old-style cookie (the cookie secret changed?)');
|
||||
} else whyRejected(req, 'cookie is not in a known format');
|
||||
if (user && !(exp > Date.now())) { whyRejected(req, 'session expired'); return null; }
|
||||
return user;
|
||||
}
|
||||
const isAuthed = req => !!currentUser(req);
|
||||
|
||||
|
||||
Reference in new issue
Block a user