import { describe, expect, it } from "vitest"; import { base64UrlDecode, base64UrlEncode, bearerToken, DEVICE_TOKEN_TTL_SEC, mintDeviceToken, verifyDeviceToken, } from "./device-token"; const SECRET = "test-secret-at-least-32-characters-long"; const SID = "ab".repeat(16); const ORIGIN = "https://your-app.example"; function future(): number { return Math.floor(Date.now() / 1000) + DEVICE_TOKEN_TTL_SEC; } describe("base64url", () => { it("round-trips bytes without padding", () => { const bytes = new Uint8Array([0, 1, 250, 251, 252, 253, 254, 255]); const encoded = base64UrlEncode(bytes); expect(encoded).not.toContain("="); expect(encoded).not.toContain("+"); expect(encoded).not.toContain("/"); expect([...base64UrlDecode(encoded)]).toEqual([...bytes]); }); }); describe("device token", () => { it("mints and verifies a token carrying the session id and origin", async () => { const token = await mintDeviceToken(SECRET, { sid: SID, origin: ORIGIN, expiresAt: future() }); const result = await verifyDeviceToken(SECRET, token); expect(result.ok).toBe(true); if (!result.ok) return; expect(result.claims.sid).toBe(SID); expect(result.claims.origin).toBe(ORIGIN); }); it("rejects a token signed with another secret", async () => { const token = await mintDeviceToken(SECRET, { sid: SID, origin: ORIGIN, expiresAt: future() }); const result = await verifyDeviceToken("a-different-secret-value-32-chars!!", token); expect(result).toEqual({ ok: false, reason: "token signature is invalid" }); }); it("rejects a tampered session id", async () => { const token = await mintDeviceToken(SECRET, { sid: SID, origin: ORIGIN, expiresAt: future() }); const [version, payload, signature] = token.split("."); const forgedPayload = base64UrlEncode( new TextEncoder().encode(JSON.stringify({ s: "cd".repeat(16), o: ORIGIN, e: future() })), ); expect(forgedPayload).not.toBe(payload); const result = await verifyDeviceToken(SECRET, `${version}.${forgedPayload}.${signature}`); expect(result.ok).toBe(false); }); it("rejects an expired token", async () => { const past = Math.floor(Date.now() / 1000) - 1; const token = await mintDeviceToken(SECRET, { sid: SID, origin: ORIGIN, expiresAt: past }); const result = await verifyDeviceToken(SECRET, token); expect(result).toEqual({ ok: false, reason: "token has expired" }); }); it("rejects malformed tokens and unknown versions", async () => { expect((await verifyDeviceToken(SECRET, "nope")).ok).toBe(false); expect((await verifyDeviceToken(SECRET, "v2.abc.def")).ok).toBe(false); expect((await verifyDeviceToken(SECRET, "v1.!!!.???")).ok).toBe(false); }); it("refuses to work when no secret is configured", async () => { const token = await mintDeviceToken(SECRET, { sid: SID, origin: ORIGIN, expiresAt: future() }); expect((await verifyDeviceToken("", token)).ok).toBe(false); await expect( mintDeviceToken("", { sid: SID, origin: ORIGIN, expiresAt: future() }), ).rejects.toThrow(/not configured/); }); }); describe("bearerToken", () => { it("accepts a bare token or a Bearer prefix", () => { expect(bearerToken("abc")).toBe("abc"); expect(bearerToken("Bearer abc")).toBe("abc"); expect(bearerToken("bearer abc")).toBe("abc"); expect(bearerToken(null)).toBeNull(); expect(bearerToken(" ")).toBeNull(); }); });