38 lines
1.2 KiB
TypeScript
38 lines
1.2 KiB
TypeScript
const FIVE_MINUTES_MS = 5 * 60 * 1000;
|
|
const ONE_HOUR_MS = 60 * 60 * 1000;
|
|
|
|
function decodeBase64Url(input: string): string {
|
|
const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
|
|
return Buffer.from(padded, "base64").toString("utf8");
|
|
}
|
|
|
|
/** Decode JWT payload and extract expiry as epoch ms (with 5min safety margin). Returns fallback of now+1h on decode failure. */
|
|
export function decodeJwtExpiry(token: string): number {
|
|
const fallback = Date.now() + ONE_HOUR_MS;
|
|
|
|
try {
|
|
const payload = token.split(".")[1];
|
|
if (!payload) {
|
|
return fallback;
|
|
}
|
|
|
|
const decodedPayload = decodeBase64Url(payload);
|
|
const parsed = JSON.parse(decodedPayload) as { exp?: unknown };
|
|
|
|
if (typeof parsed.exp !== "number" || !Number.isFinite(parsed.exp)) {
|
|
return fallback;
|
|
}
|
|
|
|
const expiryMs = parsed.exp * 1000 - FIVE_MINUTES_MS;
|
|
return Number.isFinite(expiryMs) ? expiryMs : fallback;
|
|
} catch {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
/** Returns true if the token is expired (with optional buffer). */
|
|
export function isJwtExpired(token: string, bufferMs = 0): boolean {
|
|
return decodeJwtExpiry(token) <= Date.now() + bufferMs;
|
|
}
|