203 lines · 8.4 KB
import { peekAuthToken, storeAuthToken } from "../auth-tokens";
import { first } from "../db";
import { containersEnabled } from "./container-config";
import { containersAllowedFor } from "./container-meter";
import { credentialFromRequest, loadUserById, toViewer } from "../auth";
import { randomToken } from "../crypto";
import { notFoundError, unauthorized } from "../errors";
import { assertVisibleRevision } from "../git/reachable";
import { listRefs, resolveRevision } from "../git/refs";
import { directUpdateViolation, loadProtections, protectionFor } from "../protection";
import { hasPermission, loadRepoById, repoPermission } from "../repos";
import { credentialAllows, parseTokenPermissions, scopedRepoPermission } from "../token-scope";
import type { TokenGrant } from "../token-scope";
import type { ResolvedRevision } from "../git/refs";
import type { Permission, RepoRow, Viewer } from "~/lib/types";

export const EDITOR_TOKEN_PREFIX = "gitfor_edt";
const EDITOR_TOKEN_KIND = "editor-session";
const EDITOR_TOKEN_TTL_SECONDS = 60 * 60;
const UNLIMITED_ATTEMPTS = Number.MAX_SAFE_INTEGER;

/** `patId` ties a session minted from a personal access token to that token, so revoking it ends the session. */
type SessionPayload = { repoId: string; ref: string; ceiling: Permission; canOpenPull: boolean; patId: string | null };

export type MintGrant = { ceiling: Permission; canOpenPull: boolean; patId: string | null };

const PERMISSION_ORDER: Permission[] = ["none", "read", "write", "admin"];

function isPermission(value: unknown): value is Permission {
  return typeof value === "string" && PERMISSION_ORDER.some((permission) => permission === value);
}

function capPermission(actual: Permission, ceiling: Permission): Permission {
  return PERMISSION_ORDER.indexOf(actual) <= PERMISSION_ORDER.indexOf(ceiling) ? actual : ceiling;
}

export type EditorSession = {
  viewer: Viewer;
  repo: RepoRow;
  permission: Permission;