/** * Phase 2 adversarial — deeper authorization escalation paths. * * Complements the foundational 403/401 checks in 05-admin/authorization.spec.ts * with cross-user and banned-user scenarios that span multiple resources. */ import { test, expect } from '../../fixtures/test'; const BASE = process.env.E2E_FRONTEND_URL ?? 'http://localhost:3101'; test.describe('Adversarial — deep authorization', () => { test('user A cannot delete user B\'s comment via /api/v1/comment/{id}', async ({ api, guest }) => { const a = await guest('CommentA'); const b = await guest('CommentB'); // We need an upload first; without a multipart helper here we use a placeholder: // post a comment on a non-existent upload to force the path to return 404 / 403 / 401. // The real intent is verified once an upload helper feeds this test a real upload_id. const fakeId = '00000000-0000-0000-0000-000000000000'; const res = await fetch(`${BASE}/api/v1/comment/${fakeId}`, { method: 'DELETE', headers: { Authorization: `Bearer ${b.jwt}` }, }); // Acceptable: 403 (not your comment), 404 (no such comment), 401. expect([401, 403, 404]).toContain(res.status); void a; void api; }); test('banned user cannot toggle a like', async ({ api, host, guest }) => { const target = await guest('BannedLike'); await api.banUser(host.jwt, target.userId, false); const res = await fetch(`${BASE}/api/v1/upload/00000000-0000-0000-0000-000000000000/like`, { method: 'POST', headers: { Authorization: `Bearer ${target.jwt}` }, }); expect([403, 404]).toContain(res.status); }); test('banned user cannot post a comment', async ({ api, host, guest }) => { const target = await guest('BannedComment'); await api.banUser(host.jwt, target.userId, false); const res = await fetch(`${BASE}/api/v1/upload/00000000-0000-0000-0000-000000000000/comments`, { method: 'POST', headers: { Authorization: `Bearer ${target.jwt}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ body: 'should be rejected' }), }); expect([403, 404]).toContain(res.status); }); test('banned user can still read the feed (read-only access preserved)', async ({ api, host, guest }) => { const target = await guest('BannedRead'); await api.banUser(host.jwt, target.userId, false); const res = await fetch(`${BASE}/api/v1/feed`, { headers: { Authorization: `Bearer ${target.jwt}` }, }); // The journey docs explicitly state banned users keep read access. expect(res.status).toBe(200); }); test('host cannot delete another host\'s session via /api/v1/session', async ({ api, host, guest }) => { const otherHost = await guest('OtherHost'); // Promote so they have a host JWT to play with. await api.setRole(host.jwt, otherHost.userId, 'host'); // The /session DELETE endpoint deletes the caller's own session by token hash. // A host cannot pass another host's token here (no way to authenticate as them), // so this is structurally safe — we assert by trying to delete with the wrong // Authorization header and checking that only the caller's session is gone. await api.logout(host.jwt); const stillWorks = await fetch(`${BASE}/api/v1/me/context`, { headers: { Authorization: `Bearer ${otherHost.jwt}` }, }); expect(stillWorks.status).toBe(200); }); test('promote endpoint cannot be used to make oneself admin', async ({ host }) => { const res = await fetch(`${BASE}/api/v1/host/users/${'00000000-0000-0000-0000-000000000000'}/role`, { method: 'PATCH', headers: { Authorization: `Bearer ${host.jwt}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ role: 'admin' }), }); // 400 (invalid role for host-callable endpoint) or 403/404. expect([400, 403, 404]).toContain(res.status); // Critically, NOT 200/204. expect([200, 204]).not.toContain(res.status); }); });