import { chunks, placeholders } from "./db";
import { accountDb, stmt, type ScopedValue } from "./scoped-db";
import { forgetLocator } from "./scoped-db/locators";
import { revokeDeployTokensMintedBy } from "./deploy-token-index";
/**
* Revokes personal access tokens of one account with everything they minted: REST children
* (`access_tokens.parent_token_id`), MCP delegations (`mcp_token_agents.parent_token_id`), their refresh tokens,
* and deploy tokens they created. D1 did this in the `access_tokens_revoke_mcp` trigger; the trigger reached into
* `deploy_tokens`, which now lives in each repository's RepoState, so the cascade runs here. Returns the ids
* removed (roots included).
*/
export async function revokeAccessTokens(userId: string, rootIds: string[]): Promise<string[]> {
if (rootIds.length === 0) return [];
const db = accountDb(userId);
const edges = await db.all<{ child: string; parent: string }>(
`SELECT id AS child, parent_token_id AS parent FROM access_tokens WHERE parent_token_id IS NOT NULL
UNION SELECT token_id, parent_token_id FROM mcp_token_agents WHERE parent_token_id IS NOT NULL`,
);
const ids = new Set(rootIds);
let grew = true;
while (grew) {
grew = false;
for (const edge of edges) {
if (ids.has(edge.parent) && !ids.has(edge.child)) {
ids.add(edge.child);
grew = true;
}
}
}
const allIds = [...ids];
const removed: { id: string; token_hash: string }[] = [];
for (const group of chunks(allIds)) {
removed.push(
...(await db.all<{ id: string; token_hash: string }>(
`SELECT id, token_hash FROM access_tokens WHERE user_id = ? AND id IN (${placeholders(group.length)})`,
userId,
...group,
)),
);