From 6f9744df9123a47c846cc60c35e69053cc7f324c Mon Sep 17 00:00:00 2001 From: DIodide Date: Sat, 22 Aug 2026 16:09:39 -0400 Subject: [PATCH] Route Google's OAuth callback through the real code exchange MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SDK's callback handler treats any already-connected server as "auth accepted" and returns success WITHOUT exchanging the authorization code — and Google's anonymous connect leaves the gcal connection "ready", so every consent round-trip silently discarded the code and saved no tokens ("one step left" forever). When a consent URL is handed out, the connection is now flipped to the authenticating state, live and persisted via the server row's auth_url (so a fresh isolate at callback time restores the same way), which routes the callback into the code-exchange branch. Breadcrumb logging on the callback route and the consent probe for observability. Claude-Session: https://claude.ai/code/session_01MKLJUWk6biNAKXupHTTWn5 --- app/src/server/index.ts | 11 ++++++-- app/src/server/pi.ts | 57 +++++++++++++++++++++++++++++++---------- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/app/src/server/index.ts b/app/src/server/index.ts index d325a44..a0a6ab7 100644 --- a/app/src/server/index.ts +++ b/app/src/server/index.ts @@ -15,12 +15,19 @@ export default { // signed-in user's desk DO, which initiated the flow and holds the state. if (url.pathname === GCAL_CALLBACK_PATH) { const session = await getSession(request, env); - if (!session) return Response.redirect(`${url.origin}/`, 302); + if (!session) { + console.warn("oauth callback arrived without a session — bouncing home"); + return Response.redirect(`${url.origin}/`, 302); + } const desk = await getAgentByName( env.Pi, `${userPrefix(session.netid)}desk` ); - return desk.fetch(request); + const response = await desk.fetch(request); + console.log( + `oauth callback for ${session.netid}: desk answered ${response.status} → ${response.headers.get("location") ?? "(no redirect)"}` + ); + return response; } // Every agent route belongs to exactly one signed-in user: instance diff --git a/app/src/server/pi.ts b/app/src/server/pi.ts index 6db6b81..d14f914 100644 --- a/app/src/server/pi.ts +++ b/app/src/server/pi.ts @@ -267,21 +267,52 @@ export class Pi extends Think { const manager = this.mcp as unknown as { mcpConnections?: Record< string, - { options?: { transport?: { authProvider?: { authUrl?: string } } } } + { + connectionState?: string; + options?: { transport?: { authProvider?: { authUrl?: string } } }; + } >; + getServersFromStorage?: () => Array<{ + id: string; + name: string; + server_url: string; + client_id: string | null; + auth_url: string | null; + callback_url: string; + server_options: string | null; + }>; + saveServerToStorage?: (server: { + id: string; + name: string; + server_url: string; + client_id: string | null; + auth_url: string | null; + callback_url: string; + server_options: string | null; + }) => void; }; - const live = - manager.mcpConnections?.gcal?.options?.transport?.authProvider?.authUrl; - if (live) return live; - // Fallback: re-registering an existing server redeems a stored auth URL. - const retry = await this.addMcpServer("Google Calendar", GCAL_MCP_URL, { - id: "gcal", - callbackHost: this.appOrigin(), - callbackPath: GCAL_CALLBACK_PATH.slice(1), - transport: { type: "streamable-http" }, - }); - if (retry.state === "authenticating") return retry.authUrl; - return this.getMcpServers().servers.gcal?.auth_url ?? null; + const url = + manager.mcpConnections?.gcal?.options?.transport?.authProvider?.authUrl ?? + this.getMcpServers().servers.gcal?.auth_url ?? + null; + if (!url) { + console.warn("gcal: probe ran but no consent URL was produced"); + return null; + } + // The anonymous connect left the connection "ready", and the SDK's + // callback handler short-circuits ready connections as "auth accepted" + // WITHOUT exchanging the authorization code — so consent silently did + // nothing. Flip the connection into the authenticating state (live and + // persisted, so a fresh isolate at callback time restores the same way) + // to route the callback through the real code exchange. + const conn = manager.mcpConnections?.gcal; + if (conn) conn.connectionState = "authenticating"; + const row = manager + .getServersFromStorage?.() + .find((server) => server.id === "gcal"); + if (row) manager.saveServerToStorage?.({ ...row, auth_url: url }); + console.log("gcal: consent pending, connection marked authenticating"); + return url; } /** The per-user desk instance is the token authority for Google OAuth. */