manual improvement 1
This commit is contained in:
@@ -32,7 +32,28 @@ export function decodeJwtPayload(token: string | undefined): JwtPayload | null {
|
||||
}
|
||||
}
|
||||
|
||||
/** Compact serialization part counts: a JWS is 3, a JWE is 5 (header.key.iv.ciphertext.tag). */
|
||||
const JWE_PART_COUNT = 5;
|
||||
|
||||
export function isTokenAlive(token: string | undefined): boolean {
|
||||
if (!token) return false;
|
||||
|
||||
const payload = decodeJwtPayload(token);
|
||||
return typeof payload?.exp === 'number' && payload.exp * 1000 > Date.now();
|
||||
if (payload) return typeof payload.exp === 'number' && payload.exp * 1000 > Date.now();
|
||||
|
||||
/*
|
||||
* The server issues an **encrypted** access token (JWE — `JwtService._createSecurityTokenAsync`
|
||||
* pairs SigningCredentials with EncryptingCredentials), so its claims, `exp` included, are
|
||||
* ciphertext that no client-side decode will ever read. Treating that as "not alive" is what
|
||||
* made every real session look logged-out to this gate: the middleware bounced private routes
|
||||
* to /login, and an authenticated hit on '/' fell through the guest-front-door branch and got
|
||||
* rewritten to the marketing page — the "app just shows a loading skeleton at /fa" symptom.
|
||||
*
|
||||
* A well-formed but unreadable token is therefore treated as alive. That is sound here and only
|
||||
* here: this is a routing/UX hint, never a security boundary (the API is the sole authority on
|
||||
* the token), and the access cookie's own 15-minute `maxAge` bounds how long the optimism can
|
||||
* last — an expired cookie is simply absent, which is still a clean `false`. A stale token that
|
||||
* slips through gets a 401 on the first call, which the fetch layer's silent refresh handles.
|
||||
*/
|
||||
return token.split('.').length === JWE_PART_COUNT;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user