import { test, expect } from "bun:test"; import { google, github, discord, defineProvider, startAuth, exchangeCode, completeAuth, randomToken, discoverOidc, validateOidcClaims, } from "../src/index.ts"; const CREDS = { clientId: "cid", clientSecret: "secret" }; test("presets have the right endpoints + scopes", () => { expect(google(CREDS).authorizeUrl).toContain("accounts.google.com"); expect(github(CREDS).scopes).toContain("user:email"); expect(discord(CREDS).userInfoUrl).toContain("discord.com/api/users/@me"); }); test("startAuth builds an authorize URL with PKCE + state", async () => { const { url, state, verifier } = await startAuth(google(CREDS), { redirectUri: "https://app.test/cb", }); const u = new URL(url); expect(u.origin + u.pathname).toBe("https://accounts.google.com/o/oauth2/v2/auth"); expect(u.searchParams.get("client_id")).toBe("cid"); expect(u.searchParams.get("redirect_uri")).toBe("https://app.test/cb"); expect(u.searchParams.get("response_type")).toBe("code"); expect(u.searchParams.get("scope")).toBe("openid email profile"); expect(u.searchParams.get("state")).toBe(state); expect(u.searchParams.get("code_challenge")).toBeTruthy(); expect(u.searchParams.get("code_challenge_method")).toBe("S256"); expect(u.searchParams.get("access_type")).toBe("offline"); // provider default param expect(verifier.length).toBeGreaterThan(20); }); test("randomToken is URL-safe and unique", () => { const a = randomToken(); const b = randomToken(); expect(a).not.toBe(b); expect(a).toMatch(/^[A-Za-z0-9_-]+$/); }); test("exchangeCode posts the code + PKCE verifier and parses tokens", async () => { let captured: { url: string; body: string } | null = null; const fakeFetch = (async (url: string, init: RequestInit) => { captured = { url: String(url), body: String(init.body) }; return new Response(JSON.stringify({ access_token: "tok", token_type: "Bearer" }), { headers: { "content-type": "application/json" }, }); }) as unknown as typeof fetch; const tokens = await exchangeCode(github(CREDS), { code: "abc", redirectUri: "https://app.test/cb", verifier: "ver123", fetch: fakeFetch, }); expect(tokens.access_token).toBe("tok"); expect(captured!.url).toBe("https://github.com/login/oauth/access_token"); expect(captured!.body).toContain("code=abc"); expect(captured!.body).toContain("code_verifier=ver123"); expect(captured!.body).toContain("grant_type=authorization_code"); }); test("completeAuth maps the provider profile (custom provider)", async () => { const provider = defineProvider({ name: "acme", authorizeUrl: "https://acme.test/authorize", tokenUrl: "https://acme.test/token", userInfoUrl: "https://acme.test/me", scopes: ["email"], clientId: "cid", clientSecret: "secret", mapProfile: (raw) => ({ id: String(raw.user_id), email: raw.mail as string, name: "n", raw }), }); const fakeFetch = (async (url: string) => { if (String(url).endsWith("/token")) return new Response(JSON.stringify({ access_token: "t" }), { headers: { "content-type": "application/json" }, }); return new Response(JSON.stringify({ user_id: 99, mail: "x@acme.test" }), { headers: { "content-type": "application/json" }, }); }) as unknown as typeof fetch; const { profile } = await completeAuth(provider, { code: "c", redirectUri: "r", fetch: fakeFetch, }); expect(profile.id).toBe("99"); expect(profile.email).toBe("x@acme.test"); }); test("OIDC discovery enforces issuer and secure required endpoints", async () => { const valid = await discoverOidc("https://issuer.example/", (async () => Response.json({ issuer: "https://issuer.example", authorization_endpoint: "https://issuer.example/authorize", token_endpoint: "https://issuer.example/token", jwks_uri: "https://keys.example/jwks", })) as unknown as typeof fetch); expect(valid.jwks_uri).toBe("https://keys.example/jwks"); await expect( discoverOidc("https://issuer.example", (async () => Response.json({ issuer: "https://attacker.example", authorization_endpoint: "https://issuer.example/authorize", token_endpoint: "http://issuer.example/token", jwks_uri: "https://issuer.example/jwks", })) as unknown as typeof fetch), ).rejects.toThrow("issuer mismatch"); }); test("OIDC claim conformance enforces nonce, subject, and authorized party", () => { const valid = { sub: "user-1", iss: "https://issuer.example", aud: ["client-1", "api"], azp: "client-1", exp: 200, iat: 100, nonce: "nonce-1", }; expect(() => validateOidcClaims(valid, { clientId: "client-1", nonce: "nonce-1" })).not.toThrow(); expect(() => validateOidcClaims({ ...valid, nonce: "wrong" }, { clientId: "client-1", nonce: "nonce-1" }), ).toThrow("nonce"); expect(() => validateOidcClaims({ ...valid, azp: "other" }, { clientId: "client-1" })).toThrow( "authorized party", ); });