feat: centralize application framework primitives
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { afterEach, expect, test } from "bun:test";
|
||||
import { createContext, withContextHeaders } from "@wrnexus/core";
|
||||
import type { OAuthProvider } from "@wrnexus/oauth";
|
||||
import { createAuthEngine } from "../src/engine.ts";
|
||||
import { createAuthHttpHandlers } from "../src/http/index.ts";
|
||||
import { MemoryAuthStore } from "../src/stores/memory.ts";
|
||||
|
||||
const realFetch = globalThis.fetch;
|
||||
afterEach(() => {
|
||||
globalThis.fetch = realFetch;
|
||||
});
|
||||
|
||||
test("configured OAuth owns PKCE state, callback, and session establishment", async () => {
|
||||
const provider: OAuthProvider = {
|
||||
name: "example",
|
||||
authorizeUrl: "https://identity.example/authorize",
|
||||
tokenUrl: "https://identity.example/token",
|
||||
userInfoUrl: "https://identity.example/user",
|
||||
scopes: ["openid", "email"],
|
||||
clientId: "client",
|
||||
clientSecret: "secret",
|
||||
mapProfile: (raw) => ({
|
||||
id: String(raw.id),
|
||||
email: String(raw.email),
|
||||
raw,
|
||||
}),
|
||||
};
|
||||
const engine = createAuthEngine({
|
||||
store: new MemoryAuthStore(),
|
||||
secret: "oauth-http-secret-that-is-longer-than-thirty-two-characters",
|
||||
});
|
||||
const handlers = createAuthHttpHandlers({
|
||||
engine,
|
||||
baseUrl: "https://app.example",
|
||||
oauth: { example: provider },
|
||||
});
|
||||
const startRequest = new Request(
|
||||
"https://app.example/api/auth/oauth/example?returnTo=%2Fdashboard",
|
||||
);
|
||||
const startCtx = createContext(startRequest, new URL(startRequest.url));
|
||||
startCtx.params = { provider: "example" };
|
||||
const start = await handlers.startOAuth(startCtx);
|
||||
expect(start.status).toBe(302);
|
||||
const location = new URL(start.headers.get("location")!);
|
||||
expect(location.searchParams.get("code_challenge_method")).toBe("S256");
|
||||
const issued = withContextHeaders(startCtx, start).headers.get("set-cookie")!;
|
||||
|
||||
const replies = [
|
||||
Response.json({ access_token: "access", token_type: "Bearer" }),
|
||||
Response.json({ id: "provider-user", email: "oauth@example.test" }),
|
||||
];
|
||||
const mock: typeof fetch = Object.assign(async () => replies.shift()!, {
|
||||
preconnect: () => undefined,
|
||||
});
|
||||
globalThis.fetch = mock;
|
||||
|
||||
const callbackRequest = new Request(
|
||||
`https://app.example/api/auth/oauth/example/callback?code=code&state=${encodeURIComponent(location.searchParams.get("state")!)}`,
|
||||
{ headers: { cookie: issued.split(";", 1)[0]! } },
|
||||
);
|
||||
const callbackCtx = createContext(callbackRequest, new URL(callbackRequest.url));
|
||||
callbackCtx.params = { provider: "example" };
|
||||
const callback = await handlers.completeOAuth(callbackCtx);
|
||||
expect(callback.status).toBe(303);
|
||||
expect(callback.headers.get("location")).toBe("https://app.example/dashboard");
|
||||
expect(callbackCtx.user).toMatchObject({ id: expect.any(String) });
|
||||
});
|
||||
Reference in New Issue
Block a user