70 lines
2.7 KiB
TypeScript
70 lines
2.7 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { decodeTokenExp, getSessionExpMs } from "@/lib/session/token";
|
|
|
|
/** Build a JWT-shaped string with the given payload object. */
|
|
function makeToken(payload: Record<string, unknown>): string {
|
|
const header = btoa(JSON.stringify({ alg: "HS256", typ: "JWT" }));
|
|
const body = btoa(JSON.stringify(payload));
|
|
return `${header}.${body}.signature`;
|
|
}
|
|
|
|
describe("decodeTokenExp", () => {
|
|
it("returns the numeric exp claim from a well-formed token", () => {
|
|
const exp = 1_800_000_000;
|
|
expect(decodeTokenExp(makeToken({ sub: "1", exp }))).toBe(exp);
|
|
});
|
|
|
|
it("returns null when the token has no payload segment", () => {
|
|
expect(decodeTokenExp("header")).toBeNull();
|
|
expect(decodeTokenExp("")).toBeNull();
|
|
});
|
|
|
|
it("returns null when the payload is not valid base64 JSON", () => {
|
|
expect(decodeTokenExp("header.not-json.signature")).toBeNull();
|
|
});
|
|
|
|
it("returns null when exp is missing or not a finite number", () => {
|
|
expect(decodeTokenExp(makeToken({ sub: "1" }))).toBeNull();
|
|
expect(decodeTokenExp(makeToken({ exp: "soon" }))).toBeNull();
|
|
expect(decodeTokenExp(makeToken({ exp: null }))).toBeNull();
|
|
expect(decodeTokenExp(makeToken({ exp: Number.NaN }))).toBeNull();
|
|
});
|
|
|
|
it("handles URL-safe base64 (JWT padding) in the payload", () => {
|
|
// A payload whose base64 would contain - and _ after URL-safe encoding.
|
|
const payload = { exp: 1_800_000_000, data: "a+b/c=d" };
|
|
const body = btoa(JSON.stringify(payload)).replace(/\+/g, "-").replace(/\//g, "_");
|
|
const token = `header.${body}.signature`;
|
|
expect(decodeTokenExp(token)).toBe(1_800_000_000);
|
|
});
|
|
});
|
|
|
|
describe("getSessionExpMs", () => {
|
|
const exp = 1_800_000_000;
|
|
const token = makeToken({ sub: "1", exp });
|
|
|
|
it("returns the exp in milliseconds from the cookie header", () => {
|
|
const headers = { get: (name: string) => (name === "cookie" ? `payload-token=${token}` : null) };
|
|
expect(getSessionExpMs(headers)).toBe(exp * 1000);
|
|
});
|
|
|
|
it("returns null when there is no cookie header", () => {
|
|
const headers = { get: () => null };
|
|
expect(getSessionExpMs(headers)).toBeNull();
|
|
});
|
|
|
|
it("returns null when the payload-token cookie is absent", () => {
|
|
const headers = { get: () => "other=value" };
|
|
expect(getSessionExpMs(headers)).toBeNull();
|
|
});
|
|
|
|
it("parses the token among other cookies", () => {
|
|
const headers = { get: () => `foo=1; payload-token=${token}; bar=2` };
|
|
expect(getSessionExpMs(headers)).toBe(exp * 1000);
|
|
});
|
|
|
|
it("returns null when the token is malformed", () => {
|
|
const headers = { get: () => "payload-token=not-a-jwt" };
|
|
expect(getSessionExpMs(headers)).toBeNull();
|
|
});
|
|
});
|