/** * ICD Test Runner — AuthZ Tier * Permission boundary tests: cross-user isolation, role enforcement. */ (function () { 'use strict'; var T = window.ICD; if (!T) return; if (!window.sw || !window.sw.testing) return; sw.testing.suite('icd/authz', async function (s) { if (!T.fixtures.ready) { s.test('setup: fixtures required', async function (t) { t.skip('Provision test fixtures first'); }); return; } var testUser = T.getFixtureUser('-user'); var teamAdmin = T.getFixtureUser('-tadmin'); var testAdmin = T.getFixtureUser('-admin2'); if (!testUser || !testUser.token) { s.test('setup: fixture user token', async function (t) { t.skip('Test user not provisioned or login failed (no token)'); }); return; } // ── Regular user MUST be denied admin routes ── var adminGets = [ '/admin/stats', '/admin/users', '/admin/settings', '/admin/configs', '/admin/models', '/admin/personas', '/admin/teams', '/admin/groups', '/admin/packages', '/admin/providers/health', '/admin/routing/policies', '/admin/permissions', '/admin/roles', '/admin/vault/status', '/admin/storage/status', '/admin/audit', '/admin/pricing', '/admin/tasks', '/admin/projects', '/admin/channels/archived', '/admin/extensions' ]; for (var ai = 0; ai < adminGets.length; ai++) { (function (path) { s.test('admin-deny: user → GET ' + path + ' (expect 403)', async function (t) { var d = await T.authFetch(testUser.token, 'GET', path); T.assert(d._status === 403 || d._status === 401, 'expected 403/401 for non-admin, got ' + d._status); }); })(adminGets[ai]); } // ── Regular user MUST NOT be able to create admin resources ── s.test('admin-deny: user → POST /admin/users (expect 403)', async function (t) { var d = await T.authFetch(testUser.token, 'POST', '/admin/users', { username: 'should-not-exist', password: 'x', email: 'no@no.no', role: 'user' }); T.assert(d._status === 403 || d._status === 401, 'expected 403/401, got ' + d._status); }); s.test('admin-deny: user → POST /admin/teams (expect 403)', async function (t) { var d = await T.authFetch(testUser.token, 'POST', '/admin/teams', { name: 'should-not-exist' }); T.assert(d._status === 403 || d._status === 401, 'expected 403/401, got ' + d._status); }); s.test('admin-deny: user → POST /admin/groups (expect 403)', async function (t) { var d = await T.authFetch(testUser.token, 'POST', '/admin/groups', { name: 'should-not-exist', scope: 'global' }); T.assert(d._status === 403 || d._status === 401, 'expected 403/401, got ' + d._status); }); s.test('admin-deny: user → POST /admin/packages/install (expect 403)', async function (t) { var d = await T.authFetch(testUser.token, 'POST', '/admin/packages/install', {}); T.assert(d._status === 403 || d._status === 401 || d._status === 400, 'expected 403/401/400, got ' + d._status); }); s.test('admin-deny: user → PUT /admin/users/:id/role (expect 403)', async function (t) { var d = await T.authFetch(testUser.token, 'PUT', '/admin/users/' + testUser.id + '/role', { role: 'admin' }); T.assert(d._status === 403 || d._status === 401, 'CRITICAL: non-admin was able to escalate role! got ' + d._status); }); s.test('admin-deny: user → POST /admin/users/:id/reset-password (expect 403)', async function (t) { var d = await T.authFetch(testUser.token, 'POST', '/admin/users/' + testUser.id + '/reset-password', { new_password: 'hacked' }); T.assert(d._status === 403 || d._status === 401, 'CRITICAL: non-admin was able to reset passwords! got ' + d._status); }); // ── Regular user SHOULD be able to access their own stuff ── s.test('user-allow: user → GET /profile (expect 200)', async function (t) { var d = await T.authFetch(testUser.token, 'GET', '/profile'); T.assertStatus(d, 200, 'profile'); T.assert(d.username === testUser.username, 'username mismatch'); }); s.test('user-allow: user → GET /channels (expect 200)', async function (t) { var d = await T.authFetch(testUser.token, 'GET', '/channels'); T.assertStatus(d, 200, 'channels'); }); s.test('user-allow: user → GET /models/enabled (expect 200)', async function (t) { var d = await T.authFetch(testUser.token, 'GET', '/models/enabled'); T.assertStatus(d, 200, 'models'); }); s.test('user-allow: user → GET /surfaces (expect 200)', async function (t) { var d = await T.authFetch(testUser.token, 'GET', '/surfaces'); T.assertStatus(d, 200, 'surfaces'); }); s.test('user-allow: user → GET /notifications/unread-count (expect 200)', async function (t) { var d = await T.authFetch(testUser.token, 'GET', '/notifications/unread-count'); T.assertStatus(d, 200, 'unread-count'); }); s.test('user-allow: user → GET /teams/mine (expect 200)', async function (t) { var d = await T.authFetch(testUser.token, 'GET', '/teams/mine'); T.assertStatus(d, 200, 'teams/mine'); }); s.test('user-allow: user → GET /groups/mine (expect 200)', async function (t) { var d = await T.authFetch(testUser.token, 'GET', '/groups/mine'); T.assertStatus(d, 200, 'groups/mine'); }); // ── Permission-gated operations ── s.test('perm-gate: user → POST /workflows (expect 403, no workflow.create)', async function (t) { var d = await T.authFetch(testUser.token, 'POST', '/workflows', { name: 'should-not-exist', slug: 'should-not-exist', entry_mode: 'team_only' }); T.assert(d._status === 403 || d._status === 401, 'expected 403 (no workflow.create), got ' + d._status); }); // ── Team admin can manage their team but NOT platform admin ── if (teamAdmin && teamAdmin.token && T.fixtures.team) { s.test('team-scope: teamAdmin → GET /teams/:id/members (expect 200)', async function (t) { var d = await T.authFetch(teamAdmin.token, 'GET', '/teams/' + T.fixtures.team.id + '/members'); T.assertStatus(d, 200, 'team-members'); }); s.test('team-scope: teamAdmin → GET /teams/:id/personas (expect 200)', async function (t) { var d = await T.authFetch(teamAdmin.token, 'GET', '/teams/' + T.fixtures.team.id + '/personas'); T.assertStatus(d, 200, 'team-personas'); }); s.test('team-scope: teamAdmin → GET /teams/:id/providers (expect 200)', async function (t) { var d = await T.authFetch(teamAdmin.token, 'GET', '/teams/' + T.fixtures.team.id + '/providers'); T.assertStatus(d, 200, 'team-providers'); }); s.test('team-scope: teamAdmin → GET /admin/stats (expect 403)', async function (t) { var d = await T.authFetch(teamAdmin.token, 'GET', '/admin/stats'); T.assert(d._status === 403 || d._status === 401, 'team admin should not have platform admin, got ' + d._status); }); s.test('team-scope: teamAdmin → PUT /admin/users/:id/role (expect 403)', async function (t) { var d = await T.authFetch(teamAdmin.token, 'PUT', '/admin/users/' + teamAdmin.id + '/role', { role: 'admin' }); T.assert(d._status === 403 || d._status === 401, 'CRITICAL: team admin was able to escalate to platform admin! got ' + d._status); }); } // ── Second admin CAN access admin routes ── if (testAdmin && testAdmin.token) { s.test('admin-allow: admin2 → GET /admin/stats (expect 200)', async function (t) { var d = await T.authFetch(testAdmin.token, 'GET', '/admin/stats'); T.assertStatus(d, 200, 'admin-stats'); }); s.test('admin-allow: admin2 → GET /admin/users (expect 200)', async function (t) { var d = await T.authFetch(testAdmin.token, 'GET', '/admin/users'); T.assertStatus(d, 200, 'admin-users'); }); } // ── Expired/invalid token ── s.test('token: bogus token → GET /profile (expect 401)', async function (t) { var d = await T.authFetch('this-is-not-a-valid-token', 'GET', '/profile'); T.assert(d._status === 401, 'expected 401 for bogus token, got ' + d._status); }); s.test('token: empty token → GET /profile (expect 401)', async function (t) { var d = await T.authFetch('', 'GET', '/profile'); T.assert(d._status === 401, 'expected 401 for empty token, got ' + d._status); }); // ── Cross-user resource isolation ── var userChannel = null; s.test('isolation: admin creates private channel', async function (t) { var cleanup = []; try { var d = await T.apiPost('/channels', { title: 'authz-private-' + Date.now(), type: 'direct' }); T.assertHasKey(d, 'id', 'channel'); userChannel = d.id; cleanup.push(function () { return T.safeDelete('/channels/' + userChannel); }); } finally { for (var i = 0; i < cleanup.length; i++) { try { await cleanup[i](); } catch (e) { /* ok */ } } } }); if (testUser.token) { s.test('isolation: user → GET /channels/:id (expect 403/404)', async function (t) { if (!userChannel) { t.skip('parent channel not created'); return; } var d = await T.authFetch(testUser.token, 'GET', '/channels/' + userChannel); T.assert(d._status === 403 || d._status === 404, 'user should not see another user\'s channel, got ' + d._status); }); s.test('isolation: user → DELETE /channels/:id (expect 403/404)', async function (t) { if (!userChannel) { t.skip('parent channel not created'); return; } var d = await T.authFetch(testUser.token, 'DELETE', '/channels/' + userChannel); T.assert(d._status === 403 || d._status === 404, 'user should not delete another user\'s channel, got ' + d._status); }); s.test('isolation: user → GET /channels/:id/path (expect 403/404)', async function (t) { if (!userChannel) { t.skip('parent channel not created'); return; } var d = await T.authFetch(testUser.token, 'GET', '/channels/' + userChannel + '/path'); T.assert(d._status === 403 || d._status === 404, 'user should not read another user\'s messages, got ' + d._status); }); } }); })();