291 lines · 12.4 KB
import { first } from "./db";
import { accountDb, repoDb } from "./scoped-db";
import { locate } from "./scoped-db/locators";
import { embeddedAccountId } from "./account-scope";
import { isScratchRepo } from "./sites/config";
import { sha256Hex } from "./crypto";
import { badRequest, forbidden } from "./errors";
import { credentialFromRequest, loadActiveUserById, PAT_PREFIX, toViewer, viewerFromSessionCookie } from "./auth";
import { viewerFromGrant } from "./usercontent-grant";
import { hasPermission, repoHiddenFrom, repoPermission, repoTakenDown } from "./repos";
import type { Permission, RepoRow, Viewer } from "~/lib/types";

export const DEPLOY_TOKEN_PREFIX = "gitfor_dpl";
export const TOKEN_AREAS = ["contents", "issues", "pulls", "actions", "admin"] as const;
export type TokenArea = (typeof TOKEN_AREAS)[number];
export type AreaAccess = "read" | "write";
export type TokenPermissions = Partial<Record<TokenArea, AreaAccess>>;

/** What a bearer credential is allowed to do beyond the identity it represents. */
export type TokenGrant =
  | { kind: "fine"; tokenId: string; allRepos: boolean; permissions: TokenPermissions }
  | { kind: "deploy"; tokenId: string; repoId: string; permission: AreaAccess };

export type RequestCredential = { viewer: Viewer | null; grant: TokenGrant | null };

const TOUCH_INTERVAL_MS = 60 * 1000;
const PERMISSION_ORDER: Permission[] = ["none", "read", "write", "admin"];

type PatRow = {
  id: string;
  user_id: string;
  expires_at: number | null;
  last_used_at: number | null;
  permissions: string | null;
  all_repos: number;
  audience: string | null;
};

type DeployRow = {
  id: string;