File size: 1,367 Bytes
67d18ac | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | /**
* JWT age inspection for the stored start-plan token.
*
* Informational only: the token payload carries `iat` but no `exp`, and the
* billing gateway does NOT reject by age — an 8-day-old JWT still serves
* `billing/balance` (verified live). Re-login is only needed when the gateway
* actually returns 401 / 3012. The age is surfaced so operators can see how
* fresh the credential is.
*/
export interface JwtInfo {
/** Unix seconds when the token was issued. */
iat: number;
/** Token age in hours at the time of inspection. */
ageHours: number;
userId?: string;
}
/** Decode a zcode-plan JWT payload without verifying the signature. */
export function inspectJwt(jwt: string): JwtInfo | null {
const parts = jwt.split(".");
if (parts.length !== 3) return null;
try {
const b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
const pad = b64.length % 4 === 0 ? "" : "=".repeat(4 - (b64.length % 4));
const payload = JSON.parse(Buffer.from(b64 + pad, "base64").toString("utf8")) as {
iat?: number;
user_id?: string;
sub?: string;
};
if (typeof payload.iat !== "number") return null;
return {
iat: payload.iat,
ageHours: Math.max(0, (Date.now() / 1000 - payload.iat) / 3600),
...(payload.user_id ? { userId: payload.user_id } : {}),
};
} catch {
return null;
}
}
|