fix(admin): let project MANAGE holders list teams for grants

GET /teams was org-admin only while team-access mutations require project
MANAGE, so members with MANAGE saw an empty grant picker. Open the
read-only team list to any org member and load it in the project page
whenever the actor can manage the project.
This commit is contained in:
2026-07-14 23:55:16 +08:00
parent 4e2699d0a5
commit 4ad0259193
2 changed files with 18 additions and 15 deletions
@@ -45,22 +45,19 @@
const [p, a] = await Promise.all([api.project(slug, projectId), api.teamAccess(slug, projectId)]); const [p, a] = await Promise.all([api.project(slug, projectId), api.teamAccess(slug, projectId)]);
proj = p; proj = p;
access = a.access; access = a.access;
// Org-admin-only oversight sub-resources: sessions/teams/explorer are // Team list is needed for grant UI whenever the actor has project MANAGE
// not available to member-level project MANAGE holders. // (org admin or member). Sessions/explorer stay org-admin oversight only.
if (p.actorIsOrgAdmin) { const needTeams = p.actorIsOrgAdmin === true || p.actorCanManageProject === true;
const [s, t, e] = await Promise.all([ const [s, t, e] = await Promise.all([
api.sessions(slug, projectId), p.actorIsOrgAdmin ? api.sessions(slug, projectId) : Promise.resolve({ sessions: [] as SessionSummary[] }),
api.teams(slug), needTeams ? api.teams(slug) : Promise.resolve({ teams: [] as TeamRow[] }),
api.explorer(slug), p.actorIsOrgAdmin ? api.explorer(slug) : Promise.resolve(null as ExplorerData | null),
]); ]);
sessions = s.sessions; sessions = s.sessions;
teams = t.teams; teams = t.teams;
explorer = e; explorer = e;
if (p.actorIsOrgAdmin) {
moveFolder = p.folderId ?? ''; moveFolder = p.folderId ?? '';
} else {
sessions = [];
teams = [];
explorer = null;
} }
} catch (err) { } catch (err) {
error = err instanceof Error ? err.message : String(err); error = err instanceof Error ? err.message : String(err);
+7 -1
View File
@@ -18,10 +18,16 @@ export async function registerTeamsRoutes(
): Promise<void> { ): Promise<void> {
const guardDeps: GuardDeps = { prisma: config.prisma, sessionSecret: config.sessionSecret }; const guardDeps: GuardDeps = { prisma: config.prisma, sessionSecret: config.sessionSecret };
// Read-only listing is open to any active org member so project MANAGE
// holders can pick a team when granting TEAM→PROJECT access (ADR-0004).
// Mutations below stay org-admin only.
app.get("/api/org/:orgSlug/teams", async (request, reply) => { app.get("/api/org/:orgSlug/teams", async (request, reply) => {
try { try {
const { orgSlug } = request.params as { orgSlug: string }; const { orgSlug } = request.params as { orgSlug: string };
const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); const auth = await requireOrgRole(request, reply, guardDeps, {
orgSlug,
roles: ["OWNER", "ADMIN", "MEMBER"],
});
if (auth === null) return; if (auth === null) return;
return { teams: await listOrgTeams(config.prisma, auth.organization.id) }; return { teams: await listOrgTeams(config.prisma, auth.organization.id) };
} catch (err) { } catch (err) {