// The forge's tools act as an account of their own, not as the site administrator (novox/hq ADR 0365 §2, // issue 469). // // The administrator's token reached everything the forge holds, so the tools' boundary rested on their own guards // (guard.ts): a protection edit, say, the forge itself would have taken. Now the tools' token is minted for a plain // account, `mesh-tools`, which is no administrator and writes to the mesh's repositories through one team of the // mesh's organisation: // // - **the account**: made by the module with the administrator's login (that login sets the forge up and does // nothing else), never an administrator — one found made an administrator is made a plain account again. Its // password is random, sent once and kept nowhere: the account is reached only by the tokens the administrator // mints for it (gitea lets an administrator mint for another account: `reqSelfOrAdmin`). // - **the team**: `mesh-tools` in the organisation `novox`, on every repository of it, writing code (pushes to a // branch, commit statuses, merges), issues (notes, comments, labels) and pull requests. No administration of a // repository, so the forge refuses the account a protection edit, a repository's settings and its deletion; // no repository made in the organisation, since its maker would be that repository's administrator. // // Every step is idempotent (GiteaAdmin's, client.ts), and run with the administrator's login before each mint of the tools' token, so a team // or a membership lost behind the module's back is made again at the next mint. /** The account the tools act as. */ export const TOOLS_ACCOUNT = "mesh-tools"; /** The organisation whose repositories are the mesh's, and the team through which the tools' account writes there. */ export const TOOLS_ORG = "novox"; export const TOOLS_TEAM = "mesh-tools"; /** What the team grants: write on code, issues and pull requests of every repository of the organisation, and no more. */ export const TOOLS_TEAM_UNITS = { permission: "write", units_map: { "repo.code": "write", "repo.issues": "write", "repo.pulls": "write" }, includes_all_repositories: true, can_create_org_repo: false, } as const; /** One call to the forge's API with the administrator's login: a path under /api/v1, the status and body answered. */ export type AdminCall = (method: string, path: string, body?: unknown) => Promise<{ status: number; body: any }>; /** What setting the account up needs of the forge's admin API: GiteaAdmin's own (client.ts), shared with the * package provisioner, so a create race (422) and a user made by hand are handled once. */ export interface ToolsForge { ensureUser(username: string, password: string, email: string, plain?: boolean): Promise; ensureTeamWith(org: string, team: string, units: Record): Promise; addUserToTeam(teamId: number, username: string): Promise; } /** Make the tools' account, plain, a member of its team on every repository of the organisation. Its password is * set afresh each time and kept nowhere: the account is reached only by the tokens the administrator mints. */ export async function setUpToolsAccount(forge: ToolsForge, account = TOOLS_ACCOUNT, password: () => string): Promise { await forge.ensureUser(account, password(), `${account}@noreply.localhost`, true); const team = await forge.ensureTeamWith(TOOLS_ORG, TOOLS_TEAM, { name: TOOLS_TEAM, ...TOOLS_TEAM_UNITS }); await forge.addUserToTeam(team, account); }