diff --git a/modules/gitea/client.ts b/modules/gitea/client.ts index 17384a31..60372f6f 100644 --- a/modules/gitea/client.ts +++ b/modules/gitea/client.ts @@ -9,6 +9,7 @@ import { ConfiguredToken, MintedToken, type TokenSource } from "./token.js"; import { MAIN_BRANCH, mainRuleBody, protectionBody, type BranchProtection, type ProtectionWanted } from "./protection.js"; import { Refused, checkSentPath, ruleCovers, pathSegment, pathSegments } from "./guard.js"; import { isCommit } from "./checks.js"; +import type { ForgeTeam } from "./teamrights.js"; /** A repository's path on the forge, its owner and name each one segment (guard.ts pathSegment): uniform for every * call, whether the names came from a caller or from the forge's own answer. */ @@ -930,8 +931,6 @@ interface AdminResponse { } /** A repository as the site administrator sees it: what saying why an account cannot read it needs (readall.ts). */ -import type { ForgeTeam } from "./teamrights.js"; - export interface ForgeRepository { full_name: string; owner: string; diff --git a/modules/gitea/teamrights.ts b/modules/gitea/teamrights.ts index 7904f96d..27cf9bdc 100644 --- a/modules/gitea/teamrights.ts +++ b/modules/gitea/teamrights.ts @@ -24,6 +24,7 @@ export interface ForgeTeam { export interface TeamForge { teams(org: string): Promise; teamMembers(teamId: number): Promise; + accountExists(user: string): Promise; everyRepository(): Promise<{ full_name: string; owner: string; name: string }[]>; repositoryPermission(owner: string, repo: string, user: string): Promise; } @@ -74,6 +75,7 @@ export interface UserRights { * in and its effective right on every repository (or on those named), as the forge says it. Only reads. */ export async function readTeamRights(forge: TeamForge, org: string, opts: { team?: string; user?: string; repositories?: string[]; list?: boolean } = {}): Promise<{ organisation: string; teams: TeamRights[]; user?: UserRights }> { + if (opts.user && !(await forge.accountExists(opts.user))) throw new Error(`no forge user ${opts.user}`); const all = await forge.teams(org); const named = opts.team ? all.filter((t) => t.name === opts.team) : all; if (opts.team && !named.length) throw new Error(`no team "${opts.team}" in ${org}: it holds ${all.map((t) => t.name).join(", ") || "none"}`); diff --git a/modules/gitea/test/team-rights.test.ts b/modules/gitea/test/team-rights.test.ts index 1a54463b..65e79be5 100644 --- a/modules/gitea/test/team-rights.test.ts +++ b/modules/gitea/test/team-rights.test.ts @@ -42,6 +42,8 @@ const server = createServer((req, res) => { if (path === "/orgs/novox/teams") return send(200, teams); const m = path.match(/^\/teams\/(\d+)\/members$/); if (m) return send(200, (members[m[1]] ?? []).map(user)); + const u = path.match(/^\/users\/([^/]+)$/); + if (u) return send(u[1] === "nobody" ? 404 : 200, u[1] === "nobody" ? { message: "not found" } : user(u[1])); if (path === "/repos/search") return send(200, { ok: true, data: repos }); const r = path.match(/^\/repos\/([^/]+)\/([^/]+)$/); if (r) { @@ -94,3 +96,7 @@ test("the tools' own token is refused the teams, which is why the administrator' const res = await fetch(`${url}/api/v1/orgs/novox/teams`, { headers: { Authorization: "token tools-token" } }); assert.equal(res.status, 403); }); + +test("a user the forge does not know is refused, not answered as writing nowhere", async () => { + await assert.rejects(run({ user: "nobody" }), /no forge user nobody/); +});