import { corsJson, preflight } from "@/lib/cors"; import { exchangeCode, OAuthError, refreshGrant } from "@/lib/oauth"; /** Token request parameters: a form body as OAuth specifies, or JSON, which some clients send. */ async function readParams(request: Request) { const params = new Map(); if ((request.headers.get("content-type") ?? "").includes("application/json")) { const body: unknown = await request.json().catch(() => null); if (body && typeof body === "object") for (const [k, v] of Object.entries(body)) if (typeof v === "string") params.set(k, v); } else { const form = await request.formData().catch(() => null); form?.forEach((v, k) => typeof v === "string" && params.set(k, v)); } // Clients are public, but one may still send its id as the Basic auth username. const header = request.headers.get("authorization"); if (header?.startsWith("Basic ") && !params.has("client_id")) { const decoded = Buffer.from(header.slice(6), "base64").toString("utf8"); const colon = decoded.indexOf(":"); params.set("client_id", colon === -1 ? decoded : decoded.slice(0, colon)); } return (key: string) => params.get(key) ?? ""; } export async function POST(request: Request) { const param = await readParams(request); const headers = { Pragma: "no-cache" }; try { const clientId = param("client_id"); if (!clientId) throw new OAuthError("invalid_client", "client_id is required.", 401); const resource = param("resource") || undefined; switch (param("grant_type")) { case "authorization_code": return corsJson( await exchangeCode({ code: param("code"), clientId, redirectUri: param("redirect_uri"), codeVerifier: param("code_verifier"), resource }), 200, headers, ); case "refresh_token": return corsJson(await refreshGrant({ refreshToken: param("refresh_token"), clientId, resource }), 200, headers); default: throw new OAuthError("unsupported_grant_type", "grant_type must be authorization_code or refresh_token."); } } catch (e) { if (e instanceof OAuthError) return corsJson({ error: e.code, error_description: e.message }, e.status, headers); throw e; } } export const OPTIONS = preflight;