Files
2026-07-27 12:42:18 +05:30

80 lines
3.1 KiB
TypeScript

import { test, expect } from "bun:test";
import { createContext } from "@wrnexus/core";
import { createJwtKeyring, signJwt, verifyJwt, jwtAuth, JwtError } from "../src/index.ts";
const SECRET = "test-secret-key";
test("sign + verify round-trip preserves claims", async () => {
const token = await signJwt({ sub: "u1", role: "admin" }, SECRET);
const claims = await verifyJwt(token, SECRET);
expect(claims.sub).toBe("u1");
expect(claims.role).toBe("admin");
expect(typeof claims.iat).toBe("number");
});
test("tampering or wrong secret fails verification", async () => {
const token = await signJwt({ sub: "u1" }, SECRET);
await expect(verifyJwt(token, "other-secret")).rejects.toThrow(JwtError);
const tampered = token.slice(0, -2) + (token.endsWith("a") ? "bb" : "aa");
await expect(verifyJwt(tampered, SECRET)).rejects.toThrow();
});
test("rejects a signed token whose header declares another algorithm", async () => {
const token = await signJwt({ sub: "u1" }, SECRET);
const [, body] = token.split(".");
const header = btoa(JSON.stringify({ alg: "none", typ: "JWT" }))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
await expect(verifyJwt(`${header}.${body}.invalid`, SECRET)).rejects.toThrow(
"Unsupported token header",
);
});
test("expiry is enforced", async () => {
const token = await signJwt({ sub: "u1" }, SECRET, { expiresIn: 100, now: 1000 });
expect((await verifyJwt(token, SECRET, { now: 1050 })).sub).toBe("u1"); // still valid
await expect(verifyJwt(token, SECRET, { now: 1200 })).rejects.toThrow("expired");
});
function ctxWith(auth?: string) {
const url = new URL("http://x/api/me");
return createContext(new Request(url, { headers: auth ? { authorization: auth } : {} }), url);
}
test("jwtAuth sets ctx.user for a valid bearer token", async () => {
const token = await signJwt({ sub: "u9" }, SECRET);
const ctx = ctxWith(`Bearer ${token}`);
const res = await jwtAuth({ secret: SECRET })(ctx, () => new Response("ok"));
expect(res.status).toBe(200);
expect((ctx.user as { sub: string }).sub).toBe("u9");
});
test("jwtAuth 401s a missing/invalid token when required", async () => {
const missing = await jwtAuth({ secret: SECRET })(ctxWith(), () => new Response("ok"));
expect(missing.status).toBe(401);
const bad = await jwtAuth({ secret: SECRET })(ctxWith("Bearer nope"), () => new Response("ok"));
expect(bad.status).toBe(401);
});
test("jwtAuth optional mode passes through anonymously", async () => {
const ctx = ctxWith();
const res = await jwtAuth({ secret: SECRET, required: false })(ctx, () => new Response("ok"));
expect(res.status).toBe(200);
expect(ctx.user).toBeUndefined();
});
test("JWT keyrings reject duplicates and return defensive copies", () => {
expect(() =>
createJwtKeyring([
{ id: "one", secret: "secret-one" },
{ id: "one", secret: "secret-two" },
]),
).toThrow("DUPLICATE");
const keyring = createJwtKeyring([{ id: "one", secret: "secret-one", active: true }]);
const key = keyring.active();
key.secret = "changed";
expect(keyring.active().secret).toBe("secret-one");
});