mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-03 14:37:06 +00:00
fix(core): keep MCP OAuth login waiting after stray callback requests (#58342)
Co-authored-by: Reynold Hu <88558425+reynold-hu@users.noreply.github.com>
This commit is contained in:
parent
e49157b34c
commit
309522f0ff
2 changed files with 41 additions and 1 deletions
|
|
@ -373,8 +373,17 @@ export const authorize = (input: {
|
|||
}
|
||||
const error = url.searchParams.get("error_description") ?? url.searchParams.get("error")
|
||||
if (error) return fail(error, "authorization_error")
|
||||
if (url.searchParams.get("state") !== state) return fail("OAuth state mismatch", "state_mismatch")
|
||||
const returned = url.searchParams.get("state")
|
||||
const value = url.searchParams.get("code")
|
||||
// Browsers can reach the callback without any OAuth parameters (preflights, prefetches, reloads). Only a
|
||||
// redirect from the authorization server may end the login, so keep waiting for it.
|
||||
if (returned === null && !value) {
|
||||
response
|
||||
.writeHead(400, { "Content-Type": "text/html" })
|
||||
.end(OauthCallbackPage.error("Missing authorization code", { provider: input.name }))
|
||||
return
|
||||
}
|
||||
if (returned !== state) return fail("OAuth state mismatch", "state_mismatch")
|
||||
if (!value) return fail("Missing authorization code", "missing_code")
|
||||
// The page waits for the token exchange so the browser never reports success for a rejected code.
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -160,6 +160,37 @@ describe("MCP OAuth", () => {
|
|||
expect(tokenRequests[0]?.get("resource")).toBe(server.url.href)
|
||||
})
|
||||
|
||||
test("keeps waiting for the redirect after callback requests without OAuth parameters", async () => {
|
||||
const server = Bun.serve({
|
||||
port: 0,
|
||||
async fetch(request) {
|
||||
if (request.method !== "POST" || new URL(request.url).pathname !== "/token")
|
||||
return new Response(null, { status: 404 })
|
||||
return Response.json({ access_token: "access", token_type: "Bearer", refresh_token: "refresh", expires_in: 3600 })
|
||||
},
|
||||
})
|
||||
|
||||
const credential = await Effect.runPromise(
|
||||
Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
const { authorization, url: authorizationUrl } = yield* start(server, { client_id: "client" })
|
||||
const redirectValue = authorizationUrl.searchParams.get("redirect_uri")
|
||||
const state = authorizationUrl.searchParams.get("state")
|
||||
if (!redirectValue || !state) throw new Error("Missing OAuth redirect parameters")
|
||||
const callback = new URL(redirectValue)
|
||||
expect((yield* Effect.promise(() => fetch(callback, { method: "OPTIONS" }))).status).toBe(400)
|
||||
expect((yield* Effect.promise(() => fetch(callback))).status).toBe(400)
|
||||
callback.searchParams.set("code", "accepted")
|
||||
callback.searchParams.set("state", state)
|
||||
expect((yield* Effect.promise(() => fetch(callback))).status).toBe(200)
|
||||
return yield* authorization.callback
|
||||
}),
|
||||
),
|
||||
).finally(() => server.stop(true))
|
||||
|
||||
expect(credential.access).toBe("access")
|
||||
})
|
||||
|
||||
test("reports a rejected token exchange on the callback page", async () => {
|
||||
const server = Bun.serve({
|
||||
port: 0,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue