fix(oauth): accept application/x-www-form-urlencoded on /oauth/token
All checks were successful
Deploy to Production / deploy (push) Successful in 1m24s
All checks were successful
Deploy to Production / deploy (push) Successful in 1m24s
Sovereign-audit traced "Authorization with the MCP server failed" past discovery, DCR, /authorize → redirect → code, and into POST /oauth/token, which Fastify rejected with 415 before our handler ever ran. RFC 6749 §3.2 makes form-urlencoded the mandatory wire format for the token endpoint, and every DCR-emitting client (Claude Desktop, Cursor, OpenAI Codex, …) posts it that way. Fastify ships no built-in parser for that media type so the route 415'd from the framework's content- type layer — invisible to a code review of the route handler. Adds a small URLSearchParams-based parser next to the existing JSON one, parses the form body into a plain object so the route's zod schema picks it up unchanged. No new dependency.
This commit is contained in:
parent
0c6d738a6b
commit
44cebc9fd8
@ -53,6 +53,29 @@ app.addContentTypeParser(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// RFC 6749 §3.2 makes application/x-www-form-urlencoded the mandatory wire
|
||||||
|
// format for the OAuth token endpoint, and most DCR-emitting clients
|
||||||
|
// (Claude Desktop included) post it that way without negotiating. Fastify
|
||||||
|
// has no built-in parser for it, so without this every POST /oauth/token
|
||||||
|
// hit 415 before reaching our handler. Parsed into a plain object so the
|
||||||
|
// existing zod schemas don't need to change.
|
||||||
|
app.addContentTypeParser(
|
||||||
|
'application/x-www-form-urlencoded',
|
||||||
|
{ parseAs: 'string' },
|
||||||
|
(_req, body, done) => {
|
||||||
|
const text = body as string;
|
||||||
|
if (!text) return done(null, {});
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams(text);
|
||||||
|
const out: Record<string, string> = {};
|
||||||
|
for (const [k, v] of params) out[k] = v;
|
||||||
|
done(null, out);
|
||||||
|
} catch (err) {
|
||||||
|
done(err as Error, undefined);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
await app.register(cors, {
|
await app.register(cors, {
|
||||||
origin: [config.NEXT_PUBLIC_APP_URL],
|
origin: [config.NEXT_PUBLIC_APP_URL],
|
||||||
credentials: true,
|
credentials: true,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user