import { assertAccountActive } from "./account-disable";
import { captureServerEvent } from "./analytics";
import { accountDb, repoDb, stmt } from "./scoped-db";
import { forgetLocator, registerLocator } from "./scoped-db/locators";
import { accountToken } from "./account-scope";
import { notifySecurityEvent } from "./account-security";
import { randomId, sha256Hex, toHex } from "./crypto";
import { PAT_PREFIX } from "./auth";
import { badRequest } from "./errors";
import { assertBelowCap, CAPS } from "./limits";
import { recordDeployToken } from "./deploy-token-index";
import { requireRepoAccess } from "./repos";
import { assertAreaDependencies, 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. The token embeds the id
* of the account or repository that stores it, so lookups skip the locator.
*/
async function mint(prefix: string, scopeId: string): Promise<{ token: string; hash: string; visible: string }> {
const token = accountToken(prefix, scopeId, toHex(crypto.getRandomValues(new Uint8Array(20))));
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;
}
/** Expiry for a token minted by another token: never later than the parent's own expiry. */
export function cappedTokenExpiry(days: number, notAfter: number | null | undefined): number | null {
const requested = tokenExpiry(days);
if (notAfter === null || notAfter === undefined) return requested;
return requested === null ? notAfter : Math.min(requested, notAfter);
}
export type TokenLineageInput = {