Public: anyone can read this; the crew can change it.

The OAuth connect poll names its Server-Timing phases: session, origin #5307

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/5290-the-oauth-connect-poll-names-its-server → main 2 commits fork

  • Merged. scout/5290-the-oauth-connect-poll-names-its-server was merged into main as 9d37470.

Review

Sign in to review this change.
2 files changed +68 −4
src/http/api.ts +3 −4
@@ -4707,10 +4707,9 @@ export function apiRoutes(router: Router<Handler>): void {
47074707 router.add("GET", "/api/v1/user/origins/oauth/:id", async (ctx) => {
47084708 const denied = requireAccount(ctx, { adminToken: true });
47094709 if (denied) return denied;
4710 const status = await pollOAuthConnect(
4711 ctx.app,
4712 ctx.auth.user,
4713 ctx.params.id!,
4710 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
4711 const status = await timePhaseAsync(ctx.request, "origin", () =>
4712 pollOAuthConnect(ctx.app, ctx.auth.user, ctx.params.id!),
47144713 );
47154714 if (!status.ok) return respondAccess(status.error, 404);
47164715 return json(status.value);
test/oauth-poll-timing.test.ts added +65
@@ -0,0 +1,65 @@
1 /** The OAuth connect poll names its phases in Server-Timing. */
2 import { afterAll, beforeAll, expect, test } from "bun:test";
3 import { mkdtemp, rm } from "node:fs/promises";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { createApp, type App } from "../src/app.ts";
7 import { serve, type Servers } from "../src/http/server.ts";
8 import { forgetProviders } from "../src/origins/providers/index.ts";
9 import { newFakeOAuth, newFakeState, serveFakeForge, type FakeForge } from "./fake-origin.ts";
10
11 const CLIENT_ID = "Iv1.forge-test-client";
12 const PASSWORD = "alice-login-secret";
13 const auth = `Basic ${Buffer.from(`alice:${PASSWORD}`).toString("base64")}`;
14
15 let dataDir = "";
16 let app: App, servers: Servers, fake: FakeForge;
17
18 beforeAll(async () => {
19 dataDir = await mkdtemp(join(tmpdir(), "forge-oauthtime-"));
20 const state = newFakeState("github", "octo", "unused-pat");
21 state.oauth = newFakeOAuth(CLIENT_ID);
22 fake = serveFakeForge(state);
23 app = createApp({ dataDir, port: 0 });
24 servers = await serve(app);
25 await fetch(`${servers.url}/api/v1/users`, {
26 method: "POST",
27 headers: { "content-type": "application/json" },
28 body: JSON.stringify({ username: "alice", email: "[email protected]", password: PASSWORD }),
29 });
30 forgetProviders(dataDir);
31 process.env.FORGE_GITHUB_CLIENT_ID = CLIENT_ID;
32 });
33
34 afterAll(async () => {
35 delete process.env.FORGE_GITHUB_CLIENT_ID;
36 fake.stop();
37 servers.stop();
38 app.close();
39 forgetProviders(dataDir);
40 await rm(dataDir, { recursive: true, force: true });
41 });
42
43 const names = (res: Response): string[] =>
44 (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!);
45
46 test("polling a pending connection names session and origin; an unknown id names session", async () => {
47 const start = await fetch(`${servers.url}/api/v1/user/origins/oauth`, {
48 method: "POST",
49 headers: { authorization: auth, "content-type": "application/json" },
50 body: JSON.stringify({ host: fake.url, kind: "github" }),
51 });
52 expect(start.status).toBe(201);
53 const { flowId } = (await start.json()) as { flowId: string };
54
55 const pending = await fetch(`${servers.url}/api/v1/user/origins/oauth/${flowId}`, { headers: { authorization: auth } });
56 await pending.arrayBuffer();
57 expect(pending.status).toBe(200);
58 expect(names(pending)).toContain("session");
59 expect(names(pending)).toContain("origin");
60
61 const unknown = await fetch(`${servers.url}/api/v1/user/origins/oauth/nope`, { headers: { authorization: auth } });
62 await unknown.arrayBuffer();
63 expect(unknown.status).toBe(404);
64 expect(names(unknown)).toContain("session");
65 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs