67 lines
3.2 KiB
TypeScript
67 lines
3.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { betterAuth } from "better-auth";
|
|
import { memoryAdapter } from "better-auth/adapters/memory";
|
|
import { genericOAuth } from "better-auth/plugins";
|
|
|
|
const origin = "https://guide.test";
|
|
|
|
function createReplica(database: Record<string, Record<string, unknown>[]>) {
|
|
return betterAuth({
|
|
baseURL: origin,
|
|
secret: "a-shared-test-secret-with-enough-entropy-123",
|
|
database: memoryAdapter(database),
|
|
rateLimit: { enabled: false },
|
|
plugins: [genericOAuth({ config: [{
|
|
providerId: "sudloh",
|
|
clientId: "test-client",
|
|
clientSecret: "test-secret",
|
|
authorizationUrl: "https://account.test/authorize",
|
|
tokenUrl: "https://account.test/token",
|
|
getToken: async () => ({ accessToken: "test-access-token", accessTokenExpiresAt: new Date(Date.now() + 3600_000) }),
|
|
getUserInfo: async () => ({ id: "test-subject", email: "[email protected]",
|
|
emailVerified: true, name: "Test User" }),
|
|
}] })],
|
|
});
|
|
}
|
|
|
|
describe("Sudloh OAuth callback", () => {
|
|
it("completes across replicas, creates the Guide session, and consumes state once", async () => {
|
|
const database = { user: [], session: [], account: [], verification: [] };
|
|
const first = createReplica(database);
|
|
const second = createReplica(database);
|
|
const start = await first.handler(new Request(`${origin}/api/auth/sign-in/social`, {
|
|
method: "POST", headers: { Origin: origin, "Content-Type": "application/json" },
|
|
body: JSON.stringify({ provider: "sudloh", callbackURL: "/profile" }),
|
|
}));
|
|
expect(start.status).toBe(200);
|
|
const authorization = new URL((await start.json()).url);
|
|
expect(authorization.searchParams.get("code_challenge_method")).toBe("S256");
|
|
expect(authorization.searchParams.get("code_challenge")).toBeTruthy();
|
|
const stateCookie = start.headers.get("set-cookie")?.split(";", 1)[0];
|
|
expect(stateCookie).toBeTruthy();
|
|
const callbackURL = new URL(`${origin}/api/auth/callback/sudloh`);
|
|
callbackURL.searchParams.set("code", "test-code");
|
|
callbackURL.searchParams.set("state", authorization.searchParams.get("state")!);
|
|
const callback = await second.handler(new Request(callbackURL, {
|
|
headers: { Cookie: stateCookie! },
|
|
}));
|
|
expect(callback.status).toBe(302);
|
|
expect(callback.headers.get("location")).toBe("/profile");
|
|
const sessionCookie = callback.headers.getSetCookie()
|
|
.find((cookie) => cookie.includes("session_token="))?.split(";", 1)[0];
|
|
const session = await second.api.getSession({ headers: new Headers({ Cookie: sessionCookie! }) });
|
|
expect(session?.user.email).toBe("[email protected]");
|
|
const replay = await first.handler(new Request(callbackURL, {
|
|
headers: { Cookie: stateCookie! },
|
|
}));
|
|
expect(replay.headers.get("location")).toContain("state_mismatch");
|
|
});
|
|
|
|
it("rejects a callback without state before exchanging a code", async () => {
|
|
const auth = createReplica({ user: [], session: [], account: [], verification: [] });
|
|
const response = await auth.handler(new Request(`${origin}/api/auth/callback/sudloh?code=test-code`));
|
|
expect(response.status).toBe(302);
|
|
expect(response.headers.get("location")).toContain("state_not_found");
|
|
});
|
|
});
|