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<string, string>();
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;