Skip to content
File

Blob: tests/e2e/oidc-mock.ts

typescript100 lines
1import { OIDCMockProvider, type OIDCMockProviderConfig } from "@mongodb-js/oidc-mock-provider";
2import type { IncomingMessage, ServerResponse } from "node:http";
3 
4export const TEST_OIDC_CLIENT_ID = "bland-e2e";
5export const TEST_OIDC_CLIENT_SECRET = "bland-e2e-tessera-secret";
6 
7export const E2E_BASELINE_TESSERA_SUB = "e2e-baseline-sub";
8export const E2E_BASELINE_EMAIL = "e2e@bland.test";
9export const E2E_BASELINE_NAME = "E2E Test User";
10 
11export interface MockIdentity {
12 sub: string;
13 email: string;
14 email_verified?: boolean;
15 name?: string;
16}
17 
18export interface MockOidcServer {
19 issuer: string;
20 close(): Promise<void>;
21}
22 
23const BASELINE_IDENTITY: MockIdentity = {
24 sub: E2E_BASELINE_TESSERA_SUB,
25 email: E2E_BASELINE_EMAIL,
26 email_verified: true,
27 name: E2E_BASELINE_NAME,
28};
29 
30// In-process slot consulted by `getTokenPayload`. Per-spec helpers POST
31// `/__test/identity` to set the next identity, then trigger the OIDC flow.
32// The slot is single-shot: it resets to the baseline after one consumption,
33// keeping the default sign-in path safe for specs that don't override.
34let nextIdentity: MockIdentity | null = null;
35 
36function selectIdentity(): MockIdentity {
37 if (nextIdentity) {
38 const id = nextIdentity;
39 nextIdentity = null;
40 return id;
41 }
42 return BASELINE_IDENTITY;
43}
44 
45function handleControlRoute(url: string, req: IncomingMessage, res: ServerResponse): void {
46 if (!url.includes("/__test/")) return;
47 const parsed = new URL(url);
48 if (parsed.pathname !== "/__test/identity") return;
49 // The upstream mock provider forces application/x-www-form-urlencoded for
50 // POST bodies before calling override handlers, so the control endpoint
51 // accepts identity fields via query string instead.
52 if (req.method !== "GET") {
53 res.statusCode = 405;
54 res.end(JSON.stringify({ error: "method_not_allowed" }));
55 return;
56 }
57 const sub = parsed.searchParams.get("sub");
58 const email = parsed.searchParams.get("email");
59 const name = parsed.searchParams.get("name") ?? undefined;
60 const emailVerified = parsed.searchParams.get("email_verified") !== "false";
61 if (!sub || !email) {
62 res.statusCode = 400;
63 res.end(JSON.stringify({ error: "invalid_body" }));
64 return;
65 }
66 nextIdentity = { sub, email, email_verified: emailVerified, name };
67 res.statusCode = 200;
68 res.end(JSON.stringify({ ok: true }));
69}
70 
71export async function startMockOidcProvider(options: { port?: number } = {}): Promise<MockOidcServer> {
72 const config: OIDCMockProviderConfig = {
73 port: options.port,
74 hostname: "127.0.0.1",
75 getTokenPayload: () => {
76 const identity = selectIdentity();
77 return {
78 expires_in: 3600,
79 payload: { sub: identity.sub, scope: "openid email profile" },
80 customIdTokenPayload: {
81 sub: identity.sub,
82 email: identity.email,
83 email_verified: identity.email_verified !== false,
84 name: identity.name,
85 },
86 };
87 },
88 overrideRequestHandler: handleControlRoute,
89 };
90 const provider = await OIDCMockProvider.create(config);
91 return {
92 issuer: provider.issuer,
93 close: () => provider.close(),
94 };
95}
96 
97export function resetMockIdentity(): void {
98 nextIdentity = null;
99}