96 lines
3.4 KiB
TypeScript
96 lines
3.4 KiB
TypeScript
import { test, expect } from "bun:test";
|
|
import {
|
|
google,
|
|
github,
|
|
discord,
|
|
defineProvider,
|
|
startAuth,
|
|
exchangeCode,
|
|
completeAuth,
|
|
randomToken,
|
|
} 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");
|
|
});
|