import { batch, first, run, stmt } from "./db";
import { randomId, randomToken, sha256Hex } from "./crypto";
import { PAT_PREFIX } from "./auth";
import { badRequest } from "./errors";
import { assertBelowCap, CAPS } from "./limits";
import { requireRepoAccess } from "./repos";
import { DEPLOY_TOKEN_PREFIX, parseTokenPermissions } from "./token-scope";
import type { TokenPermissions } from "./token-scope";
import type { RepoRow, Viewer } from "~/lib/types";
const VISIBLE_TOKEN_CHARS = 6;
const DAY_MS = 24 * 60 * 60 * 1000;
/** Only the SHA-256 of a token is stored; the plaintext is returned once to the caller. */
async function mint(prefix: string): Promise<{ token: string; hash: string; visible: string }> {
const token = randomToken(prefix);
return { token, hash: await sha256Hex(token), visible: token.slice(0, prefix.length + 1 + VISIBLE_TOKEN_CHARS) };
}
export function tokenExpiry(days: number): number | null {
return days > 0 ? Date.now() + days * DAY_MS : null;
}
export type FineGrainedTokenInput = {
name: string;
expiresInDays: number;
allRepos: boolean;
repos: string[];
permissions: TokenPermissions;
};
/** Callers must have authenticated the viewer; each selected repo must be readable by them (checked here). */
export async function createFineGrainedToken(
viewer: Viewer,
input: FineGrainedTokenInput,
): Promise<{ id: string; token: string; repoIds: string[] }> {
const count = await first<{ count: number }>(
"SELECT COUNT(*) AS count FROM access_tokens WHERE user_id = ?",
viewer.id,
);