Vectrum
Sign In
49 lines · 2.2 KB · TypeScript
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;