feat: guest role for clients and outsiders
A project member can be a GUEST. In a private project a guest only sees the tickets they take part in (opened, assigned or followed): every other ticket answers 404, also in my work, realtime changes, blockers and linked tickets, and project-wide views (activity, statistics, dashboard, releases, sprints, epics, trash) answer 403. Guests open tickets without assigning or planning them, and comment, attach, follow and vote on their own; they change nothing else. In a public project they see what anyone does. The web gives them only Tablero, Lista and Miembros and loads none of the project-wide data. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1755,4 +1755,101 @@ describe('task-stream (e2e)', () => {
|
||||
(await rest('token-ana', 'PUT', `/issues/${id}/presence`, {})).body,
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('lets a guest see and talk about only their own tickets', async () => {
|
||||
const key = 'G' + Date.now().toString(36).slice(-5).toUpperCase();
|
||||
await rest('token-ana', 'POST', '/projects', { key, name: key });
|
||||
await eventually(
|
||||
() => rest('token-ana', 'GET', `/projects/${key}`),
|
||||
(r) => r.status === 200,
|
||||
);
|
||||
// Quinn, a client, joins as a guest.
|
||||
expect((await rest('token-qa', 'GET', '/users/me')).status).toBe(200);
|
||||
expect(
|
||||
(
|
||||
await rest('token-ana', 'POST', `/projects/${key}/members`, {
|
||||
userId: '103',
|
||||
role: 'GUEST',
|
||||
})
|
||||
).status,
|
||||
).toBe(201);
|
||||
await eventually(
|
||||
() => rest('token-qa', 'GET', `/projects/${key}`),
|
||||
(r) => r.status === 200,
|
||||
);
|
||||
const internal = (
|
||||
await rest('token-ana', 'POST', `/projects/${key}/issues`, {
|
||||
type: 'TASK',
|
||||
title: 'Solo del equipo',
|
||||
})
|
||||
).body['key'];
|
||||
|
||||
// A guest opens a ticket, but does not plan it.
|
||||
expect(
|
||||
(
|
||||
await rest('token-qa', 'POST', `/projects/${key}/issues`, {
|
||||
type: 'BUG',
|
||||
title: 'Con asignación',
|
||||
assigneeId: '101',
|
||||
})
|
||||
).status,
|
||||
).toBe(403);
|
||||
const mine = (
|
||||
await rest('token-qa', 'POST', `/projects/${key}/issues`, {
|
||||
type: 'BUG',
|
||||
title: 'No puedo pagar',
|
||||
})
|
||||
).body['key'];
|
||||
await eventually(
|
||||
() => rest('token-qa', 'GET', `/issues/${mine}`),
|
||||
(r) => r.status === 200,
|
||||
);
|
||||
|
||||
const list = await eventually(
|
||||
() => rest('token-ana', 'GET', `/projects/${key}/issues`),
|
||||
(r) => r.body?.['total'] === 2,
|
||||
);
|
||||
expect(list.body['total']).toBe(2);
|
||||
const guestList = await rest('token-qa', 'GET', `/projects/${key}/issues`);
|
||||
expect(guestList.body['items'].map((i: { key: string }) => i.key)).toEqual([
|
||||
mine,
|
||||
]);
|
||||
expect((await rest('token-qa', 'GET', `/issues/${internal}`)).status).toBe(
|
||||
404,
|
||||
);
|
||||
expect(
|
||||
(await rest('token-qa', 'PUT', `/issues/${internal}/watch`)).status,
|
||||
).toBe(404);
|
||||
// Project-wide views are not theirs.
|
||||
expect(
|
||||
(await rest('token-qa', 'GET', `/projects/${key}/activity`)).status,
|
||||
).toBe(403);
|
||||
expect(
|
||||
(await rest('token-qa', 'GET', `/projects/${key}/stats`)).status,
|
||||
).toBe(403);
|
||||
|
||||
// They talk on their ticket, but do not move it.
|
||||
expect(
|
||||
(
|
||||
await rest('token-qa', 'POST', `/issues/${mine}/comments`, {
|
||||
body: '¿Hay novedades?',
|
||||
})
|
||||
).status,
|
||||
).toBe(201);
|
||||
expect(
|
||||
(
|
||||
await rest('token-qa', 'PUT', `/issues/${mine}/status`, {
|
||||
status: 'TODO',
|
||||
})
|
||||
).status,
|
||||
).toBe(403);
|
||||
// Once the team adds them to a ticket, they see it.
|
||||
await rest('token-ana', 'PUT', `/issues/${internal}/assignee`, {
|
||||
assigneeId: '103',
|
||||
});
|
||||
await eventually(
|
||||
() => rest('token-qa', 'GET', `/issues/${internal}`),
|
||||
(r) => r.status === 200,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11060,7 +11060,8 @@
|
||||
"OWNER",
|
||||
"ADMIN",
|
||||
"MEMBER",
|
||||
"VIEWER"
|
||||
"VIEWER",
|
||||
"GUEST"
|
||||
]
|
||||
},
|
||||
"ProjectMemberDto": {
|
||||
|
||||
@@ -1213,6 +1213,7 @@ input ProjectMemberInput {
|
||||
|
||||
enum ProjectRole {
|
||||
ADMIN
|
||||
GUEST
|
||||
MEMBER
|
||||
OWNER
|
||||
VIEWER
|
||||
|
||||
Reference in New Issue
Block a user