This commit is contained in:
2026-09-25 10:14:15 +05:30
parent 3d404665ab
commit 174e7dd890
2 changed files with 115 additions and 4 deletions

View File

@@ -82,10 +82,25 @@ export async function login(email: string, password: string): Promise<SessionUse
}
// The token rides on the envelope, not on `details` — it is not a fact about
// the user, it is what proves a later request is theirs. Absent against a
// Fiesta that does not issue one yet, which is why it is spread in rather
// than assigned: `exactOptionalPropertyTypes` refuses an explicit undefined.
const session = { ...toSessionUser(envelope.details), ...(envelope.token ? { token: envelope.token } : {}) };
// the user, it is what proves a later request is theirs.
//
// Refused when absent, rather than stored and hoped for. `attachWebSession`
// on the server logs a minting failure and returns the user record anyway,
// which was correct while WEB_AUTH_REQUIRED was off and is a broken console
// now that it defaults on: the sign-in succeeds, the shell renders, and every
// request after it goes out with no `authorization` header and comes back
// 401 with nothing on screen to say why.
//
// Failing here names the problem at the moment it happens, to the person best
// placed to report it, instead of scattering 401s across every page.
if (!envelope.token) {
throw new Error(
'Signed in, but the server did not issue a session. Nothing would load — ' +
'tell your administrator the API could not mint a session token.',
);
}
const session = { ...toSessionUser(envelope.details), token: envelope.token };
persist(session);
return session;
}
@@ -208,6 +223,24 @@ export function restore(): SessionUser | null {
// A stored blob is only as trustworthy as the tab it came from; a shape
// check keeps a corrupted value from crashing the shell on boot.
if (typeof parsed?.userid !== 'number' || typeof parsed?.role !== 'string') return null;
// A session without a token is not a session.
//
// This used to be restored happily, and the result was the worst state the
// console can be in: signed in by every visible measure — name in the
// corner, nav rendered, pages mounted — and unable to make a single
// authenticated request, because `authHeader()` had nothing to send. Every
// call came back 401 and nothing on screen explained why. A PUT to
// `users/update` on 2026-09-25 went out with no `authorization` header at
// all, which is what sent us looking.
//
// It happens whenever login could not mint one: `attachWebSession` logs the
// failure and returns the user record anyway, which was right while
// WEB_AUTH_REQUIRED was off and is a broken console now that it defaults on.
// A stored session predating the token also lands here.
//
// Returning null sends them to the sign-in screen, which is a state people
// know what to do with.
if (typeof parsed.token !== 'string' || parsed.token.trim() === '') return null;
return parsed;
} catch {
return null;