Fix: Allow workspace Viewers with collection Admin membership to edit templates

Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-13 16:55:03 +00:00
parent 6354a9c5f3
commit 188e362533
4 changed files with 60 additions and 101 deletions
+1 -1
View File
@@ -57,7 +57,7 @@ describe("policies/team", () => {
const permissions = new Map<UserRole, boolean>([
[UserRole.Admin, true],
[UserRole.Member, true],
[UserRole.Viewer, false],
[UserRole.Viewer, true],
[UserRole.Guest, false],
]);
for (const [role, permission] of permissions.entries()) {
-1
View File
@@ -15,7 +15,6 @@ allow(User, "readTemplate", Team, (actor, team) =>
and(
//
!actor.isGuest,
!actor.isViewer,
isTeamModel(actor, team)
)
);
@@ -1,9 +1,12 @@
import {
buildAdmin,
buildUser,
buildViewer,
buildTemplate,
buildCollection,
} from "@server/test/factories";
import { UserMembership } from "@server/models";
import { CollectionPermission } from "@shared/types";
import { getTestServer } from "@server/test/support";
const server = getTestServer();
@@ -159,6 +162,62 @@ describe("#templates.update", () => {
expect(body.data.data).toEqual(data);
});
it("should allow workspace viewer with collection admin membership to update template", async () => {
const viewer = await buildViewer();
const collection = await buildCollection({
teamId: viewer.teamId,
permission: null,
});
await UserMembership.create({
createdById: viewer.id,
collectionId: collection.id,
userId: viewer.id,
permission: CollectionPermission.Admin,
});
const template = await buildTemplate({
teamId: viewer.teamId,
collectionId: collection.id,
});
const res = await server.post("/api/templates.update", {
body: {
token: viewer.getJwtToken(),
id: template.id,
title: "Updated by collection manager",
},
});
const body = await res.json();
expect(res.status).toEqual(200);
expect(body.data.title).toEqual("Updated by collection manager");
expect(body.policies[0].abilities.update).toEqual(true);
});
it("should not allow workspace viewer without collection admin to update template", async () => {
const viewer = await buildViewer();
const collection = await buildCollection({
teamId: viewer.teamId,
permission: CollectionPermission.ReadWrite,
});
const template = await buildTemplate({
teamId: viewer.teamId,
collectionId: collection.id,
});
const res = await server.post("/api/templates.update", {
body: {
token: viewer.getJwtToken(),
id: template.id,
title: "Should fail",
},
});
expect(res.status).toEqual(403);
});
it("should allow admin to move template to another accessible collection", async () => {
const admin = await buildAdmin();
const template = await buildTemplate({
@@ -1,99 +0,0 @@
import { UserMembership } from "@server/models";
import {
buildViewer,
buildCollection,
} from "@server/test/factories";
import { CollectionPermission } from "@shared/types";
import { getTestServer } from "@server/test/support";
const server = getTestServer();
describe("viewer with collection admin can edit templates", () => {
it("should return update:true policy after viewer creates a template in their managed collection", async () => {
// Create a workspace viewer
const viewer = await buildViewer();
// Create a collection with permission: null (private/restricted)
const collection = await buildCollection({
teamId: viewer.teamId,
permission: null,
});
// Give the viewer Admin (Manager) collection membership
await UserMembership.create({
createdById: viewer.id,
collectionId: collection.id,
userId: viewer.id,
permission: CollectionPermission.Admin,
});
// Create a template via the API (as the viewer)
const createRes = await server.post("/api/templates.create", {
body: {
token: viewer.getJwtToken(),
collectionId: collection.id,
title: "My Template",
},
});
const createBody = await createRes.json();
console.log("Create Status:", createRes.status);
console.log("Create Policies:", JSON.stringify(createBody.policies, null, 2));
expect(createRes.status).toEqual(200);
const templateId = createBody.data.id;
const policy = createBody.policies?.find((p: {id: string}) => p.id === templateId);
console.log("Template policy after create:", JSON.stringify(policy, null, 2));
// The template should be editable by the creator (viewer + collection manager)
expect(policy?.abilities?.update).toBeTruthy();
});
it("should allow viewer to create a template via templates.create if they have collection admin", async () => {
const viewer = await buildViewer();
const collection = await buildCollection({
teamId: viewer.teamId,
permission: null,
});
await UserMembership.create({
createdById: viewer.id,
collectionId: collection.id,
userId: viewer.id,
permission: CollectionPermission.Admin,
});
const createRes = await server.post("/api/templates.create", {
body: {
token: viewer.getJwtToken(),
collectionId: collection.id,
title: "Viewer Template",
},
});
console.log("Status:", createRes.status);
expect(createRes.status).toEqual(200);
});
it("should NOT allow viewer to create a template without collection admin", async () => {
const viewer = await buildViewer();
const collection = await buildCollection({
teamId: viewer.teamId,
permission: CollectionPermission.ReadWrite, // public collection
});
const createRes = await server.post("/api/templates.create", {
body: {
token: viewer.getJwtToken(),
collectionId: collection.id,
title: "Viewer Template",
},
});
console.log("Status:", createRes.status);
expect(createRes.status).toEqual(403);
});
});