// Which team of the forge's npm owner a consumer of `npm-package-registry` joins, and the per-consumer // half of the provisioner around it — pure of the forge's address, so it is tested with a stand-in. // // **Two teams, by what the consumer said it needs** (novox/hq issue 624). A consumer that publishes — // the build agent, whose builds publish the SDK and every TypeScript module's packages — joins `packages`: // code read on the org's repositories and write on its packages. A consumer that contributes // `{"access": "read"}` joins `readers`: code read and package read, and no write. The lab is the first: it // clones branches that are not yet reviewed and runs their code beside its credential, and code that is // not reviewed must not be able to publish the mesh's packages with it. /** The npm registry's owner: the gitea org every consumer's packages live under (ADR 0076). */ export const ORG = "novox"; /** The team whose members read and write the org's packages. */ export const PACKAGE_TEAM = "packages"; /** The team whose members read the org's code and packages, and write nothing. */ export const READ_TEAM = "readers"; /** The team a consumer joins, from what it contributed; an access it may not ask for is refused. */ export function teamFor(values: Readonly> | undefined): { team: string; packageWrite: boolean } { const access = values?.access; if (access === undefined || access === "write") return { team: PACKAGE_TEAM, packageWrite: true }; if (access === "read") return { team: READ_TEAM, packageWrite: false }; throw new Error(`npm-package-registry: access ${JSON.stringify(access)} is not "read" or "write"`); } /** What the adapter asks of the forge's admin API (client.ts GiteaAdmin). */ export interface Forge { ensureOrg(org: string): Promise; ensureTeam(org: string, team: string, packageWrite: boolean): Promise; ensureUser(username: string, password: string, email: string): Promise; addUserToTeam(teamId: number, username: string): Promise; removeUserFromTeam(teamId: number, username: string): Promise; holdsTeamMember(org: string, team: string, username: string, password: string): Promise; } export interface Consumer { readonly as: string; readonly password: string; readonly values: Readonly>; } /** * grant makes the consumer's user, with exactly the mesh's password, a member of its one team. A consumer * that asks for read only is also taken out of the package team, so one that asked for write before and * read now loses the write at once. */ export async function grant(forge: Forge, p: Consumer): Promise { const { team, packageWrite } = teamFor(p.values); await forge.ensureOrg(ORG); const teamId = await forge.ensureTeam(ORG, team, packageWrite); // The address is gitea's own convention for one that is not real: its email validation requires a // dotted domain, so `@localhost` was refused at create, while `@noreply.localhost` is the shape gitea // itself gives hidden addresses. await forge.ensureUser(p.as, p.password, `${p.as}@noreply.localhost`); await forge.addUserToTeam(teamId, p.as); if (!packageWrite) { await forge.removeUserFromTeam(await forge.ensureTeam(ORG, PACKAGE_TEAM, true), p.as); } } /** holds is whether the forge still holds the consumer as the mesh gave it: its login, in its team. */ export async function holds(forge: Forge, p: Consumer): Promise { return forge.holdsTeamMember(ORG, teamFor(p.values).team, p.as, p.password); }