Vectrum
Sign In
35 lines · 1.6 KB · TypeScript
"use server";

import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { approve, deniedRedirect, disconnectApp, parseAuthorizeRequest } from "@/lib/oauth";
import { grantableRepoIds, viewerOrThrow } from "./guard";
import type { FormState } from "./types";

/**
 * The consent form on /oauth/authorize. The request is checked again here, since the parameters
 * come back from the browser. Either way, the user ends up back at the application.
 */
export async function authorizeAction(params: Record<string, string>, _prev: FormState, formData: FormData): Promise<FormState> {
  const viewer = await viewerOrThrow();
  const parsed = await parseAuthorizeRequest(params);
  if ("fatal" in parsed) return { error: parsed.fatal };
  if ("redirect" in parsed) redirect(parsed.redirect);
  const { request } = parsed;
  if (formData.get("decision") !== "allow") redirect(await deniedRedirect(request));

  // The user may narrow what the application asked for, but not widen it.
  const permission = request.permission === "write" && formData.get("permission") !== "read" ? "write" : "read";
  let repoIds: string[] | null = null;
  if (formData.get("scope") === "selected") {
    repoIds = await grantableRepoIds(viewer.id, formData.getAll("repos").map(String), permission);
    if (repoIds.length === 0) return { error: "Select at least one repository." };
  }
  redirect(await approve(request, viewer.id, { permission, repoIds }));
}

export async function disconnectAppAction(grantId: string) {
  const viewer = await viewerOrThrow();
  await disconnectApp(viewer.id, String(grantId));
  revalidatePath("/settings/applications");
}