release: WRNexusJS 0.8.0
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-02 23:18:51 +05:30
parent 87507edf59
commit 586a6db8ff
625 changed files with 243608 additions and 11210 deletions
+68
View File
@@ -0,0 +1,68 @@
import { describe, expect, test } from "bun:test";
import {
clearJwtCookie,
createAccessToken,
createRefreshToken,
createTokenPair,
extractBearerToken,
hasScopes,
jwtCookie,
readJwtCookie,
tokenScopes,
verifyAccessToken,
verifyRefreshToken,
} from "../src/index.ts";
const secret = "a-long-jwt-package-helper-test-secret";
describe("JWT helper kit", () => {
test("creates and verifies typed access and refresh tokens", async () => {
const access = await createAccessToken("user-1", secret, {
scopes: ["profile:read", "profile:write"],
expiresIn: 60,
now: 100,
});
const refresh = await createRefreshToken("user-1", secret, {
family: "family-1",
expiresIn: 600,
now: 100,
});
const accessClaims = await verifyAccessToken(access, secret, { now: 120 });
const refreshClaims = await verifyRefreshToken(refresh, secret, { now: 120 });
expect(hasScopes(accessClaims, ["profile:read", "profile:write"])).toBe(true);
expect(tokenScopes(accessClaims)).toEqual(["profile:read", "profile:write"]);
expect(refreshClaims.family).toBe("family-1");
});
test("creates complete access and refresh pairs", async () => {
const pair = await createTokenPair("user-1", {
accessSecret: secret,
accessExpiresIn: 60,
refreshExpiresIn: 600,
scopes: ["profile:read"],
family: "family-2",
accessOptions: { now: 100 },
refreshOptions: { now: 100 },
});
expect((await verifyAccessToken(pair.accessToken, secret, { now: 120 })).type).toBe("access");
expect((await verifyRefreshToken(pair.refreshToken, secret, { now: 120 })).family).toBe(
"family-2",
);
});
test("extracts bearer and cookie tokens from supported sources", () => {
expect(extractBearerToken("Bearer abc")).toBe("abc");
expect(extractBearerToken(new Headers({ authorization: "bearer xyz" }))).toBe("xyz");
expect(readJwtCookie("other=x; __Host-wrn_token=abc%20123")).toBe("abc 123");
});
test("enforces secure cookie invariants", () => {
expect(jwtCookie("token")).toContain("__Host-wrn_token=token");
expect(clearJwtCookie()).toContain("Max-Age=0");
expect(() => jwtCookie("token", { path: "/auth" })).toThrow("__Host-");
expect(() => jwtCookie("token", { name: "bad;name" })).toThrow("cookie name");
expect(() => jwtCookie("token", { name: "token", sameSite: "None", secure: false })).toThrow(
"SameSite=None",
);
});
});
+97
View File
@@ -0,0 +1,97 @@
import { expect, test } from "bun:test";
import { createRemoteJwks, verifyJwtWithJwks } from "../src/index.ts";
function base64url(value: Uint8Array): string {
let binary = "";
for (const byte of value) binary += String.fromCharCode(byte);
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
async function rsaKey(kid: string) {
const pair = (await crypto.subtle.generateKey(
{
name: "RSASSA-PKCS1-v1_5",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true,
["sign", "verify"],
)) as CryptoKeyPair;
const jwk = await crypto.subtle.exportKey("jwk", pair.publicKey);
return { pair, jwk: { ...jwk, kid, alg: "RS256", use: "sig" } };
}
async function sign(privateKey: CryptoKey, kid: string, claims: Record<string, unknown>) {
const encoder = new TextEncoder();
const header = base64url(encoder.encode(JSON.stringify({ alg: "RS256", typ: "JWT", kid })));
const payload = base64url(encoder.encode(JSON.stringify(claims)));
const input = `${header}.${payload}`;
const signature = await crypto.subtle.sign(
"RSASSA-PKCS1-v1_5",
privateKey,
encoder.encode(input),
);
return `${input}.${base64url(new Uint8Array(signature))}`;
}
test("remote JWKS verifies RS256 claims and refreshes immediately for key rotation", async () => {
const first = await rsaKey("key-1");
const second = await rsaKey("key-2");
const documents = [{ keys: [first.jwk] }, { keys: [second.jwk] }];
let fetches = 0;
const jwks = createRemoteJwks("https://issuer.example/jwks", {
now: () => 0,
cacheTtlMs: 60_000,
fetch: (async () =>
Response.json(
documents[Math.min(fetches++, documents.length - 1)],
)) as unknown as typeof fetch,
});
const tokenOne = await sign(first.pair.privateKey, "key-1", {
sub: "user-1",
iss: "https://issuer.example",
aud: "client-1",
iat: 100,
exp: 200,
});
expect(
(
await verifyJwtWithJwks(tokenOne, jwks, {
issuer: "https://issuer.example",
audience: "client-1",
now: 150,
})
).sub,
).toBe("user-1");
expect(fetches).toBe(1);
const tokenTwo = await sign(second.pair.privateKey, "key-2", {
sub: "user-2",
iss: "https://issuer.example",
aud: "client-1",
exp: 200,
});
expect((await verifyJwtWithJwks(tokenTwo, jwks, { now: 150 })).sub).toBe("user-2");
expect(fetches).toBe(2);
expect(jwks.stats()).toMatchObject({ fetches: 2, hits: 2, keys: 1 });
await expect(verifyJwtWithJwks(tokenOne, jwks, { now: 150 })).rejects.toThrow("Unknown key");
});
test("remote JWKS rejects insecure, oversized, and incompatible key sets", async () => {
expect(() => createRemoteJwks("http://issuer.example/jwks")).toThrow("HTTPS");
const oversized = createRemoteJwks("https://issuer.example/jwks", {
maxBytes: 10,
fetch: (async () => Response.json({ keys: [] })) as unknown as typeof fetch,
});
await expect(oversized.refresh()).rejects.toThrow("too large");
const incompatible = createRemoteJwks("https://issuer.example/jwks", {
fetch: (async () =>
Response.json({
keys: [{ kid: "ec", kty: "EC", alg: "ES256" }],
})) as unknown as typeof fetch,
});
await incompatible.refresh();
await expect(incompatible.resolve("ec", "RS256")).rejects.toThrow("Unknown key");
});