forked from EduCraft/curriculum-project-hub
feat(admin): gate project surfaces behind permission grants for members
The org admin SPA was org-admin only: every project route used requireOrgRole, so a plain MEMBER could not reach the projects they held a project grant on, and an org OWNER/ADMIN could mutate any project without holding the project's `manage` grant. That contradicts ADR-0004 (spec `Permission.lean`): org role is not a project authorization root, and the only out-of-role override is platform-admin force-release (`RequiresAdmin`), not org admin. Add `requireProjectPermission` (guards.ts): resolve any org member, bind the project to their org, then check the PermissionGrant authorizer. `allowOrgAdminOversight=true` lets OWNER/ADMIN through for *read* oversight only; mutations pinned to `collaborator.manage` (grant/revoke team-access) pass `allowOrgAdminOversight=false`, so an org admin still needs the project MANAGE grant to mutate access. The project detail GET now also returns `actorIsOrgAdmin` and `actorCanManageProject` so the SPA can render mutation controls only for entitled actors. Add a member-facing project surface: - `GET /api/org/:orgSlug/my-projects` + `listMyProjects` resolve the actor's principals and return the projects with a READ+ grant. - The SPA routes members (non-admin) to the projects page instead of the admin overview, renders a member project shell on project routes, shows a `我的项目` list for members and the full folder explorer for admins. - The project detail page gates rename/archive/bind/sessions behind org admin and the grant/revoke UI behind `actorCanManageProject`. - The denied panel now points members at their authorized projects. Update admin-members-teams integration test: seed the owner with a MANAGE grant on the test project so the org-owner flow still passes the new project-level gate on team-access grant/revoke.
This commit is contained in:
@@ -129,6 +129,18 @@ describe("admin members + teams API", () => {
|
||||
workspaceDir: "/tmp/p1",
|
||||
},
|
||||
});
|
||||
// Team-access grant/revoke is gated to project MANAGE (no org-admin bypass).
|
||||
// Seed the owner with a MANAGE grant on p1 so the org-owner flow still works.
|
||||
await prisma.permissionGrant.create({
|
||||
data: {
|
||||
resourceType: "PROJECT",
|
||||
resourceId: "p1",
|
||||
principalType: "USER",
|
||||
principalId: "ou_owner",
|
||||
role: "MANAGE",
|
||||
createdByUserId: "u-owner",
|
||||
},
|
||||
});
|
||||
|
||||
const app = await buildApp();
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user