123 lines · 5.7 KB
import { env } from "cloudflare:workers";
import { first, run } from "../db";
import { repoDb, stmt } from "../scoped-db";
import { forgetLocator, locate, registerLocator } from "../scoped-db/locators";
import { randomToken, sha256Hex } from "../crypto";
import { credentialFromRequest } from "../auth";
import { publicUrl } from "../events/emit";
import { allows, restrictPermissions } from "./permissions";
import type { TokenPermissions } from "./permissions";
import { isUntrustedRun } from "./jobs";
import type { JobExecution } from "./jobs";
import type { Permission } from "~/lib/types";

export const JOB_TOKEN_PREFIX = "gitfor_job";
const TOKEN_GRACE_MS = 10 * 60 * 1000;
export const JOB_TOKEN_MARKER_TTL_MS = 24 * 60 * 60 * 1000;

export type JobTokenRow = {
  token_hash: string;
  job_id: string;
  repo_id: string;
  permissions: string;
  expires_at: number;
};

export type JobTokenAccess = { jobId: string; repoId: string; permissions: TokenPermissions };

/** Mints the job's `GITHUB_TOKEN` equivalent; it is revoked when the job completes and expires after its timeout. */
export async function mintJobToken(execution: JobExecution): Promise<string> {
  const token = randomToken(JOB_TOKEN_PREFIX);
  const forked = isUntrustedRun(execution);
  const permissions = forked ? restrictPermissions(execution.spec.permissions) : execution.spec.permissions;
  const hash = await sha256Hex(token);
  await repoDb(execution.repo.id).run(
    "INSERT INTO job_tokens (token_hash, job_id, repo_id, permissions, expires_at) VALUES (?, ?, ?, ?, ?)",
    hash,
    execution.job.id,
    execution.repo.id,
    JSON.stringify(permissions),
    Date.now() + execution.spec.timeoutMinutes * 60 * 1000 + TOKEN_GRACE_MS,