Files
jump-host/nodejs/test/unit/access.test.js
T
wmantly 4fb4e77007 Dashboard: list hosts you can reach; bump @simpleworkjs/ldap to 1.0.1
- New GET /api/user/hosts (auth-only): all hosts for admins, group-filtered
  list for everyone else.
- accessibleHosts() accepts a pre-resolved user.groups, so the web UI's
  OIDC session skips a redundant LDAP getGroups(dn) call.
- Dashboard shows a "Hosts you can reach" / "All hosts" table.
- @simpleworkjs/ldap 1.0.1 fixes addSshKey's ObjectClassViolationError on
  accounts predating the ldapPublicKey objectClass -- was aborting key
  injection (and the SSH connection) on affected accounts.
- Bump to 1.5.0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-26 23:11:23 -04:00

96 lines
3.7 KiB
JavaScript

'use strict';
const { test } = require('node:test');
const assert = require('node:assert');
const { accessibleHosts, allHosts, clearCache } = require('../../utils/access');
function stubLdap(groups) {
return { getGroups: async () => groups };
}
function stubFetch(byGroup) {
return async (url) => {
const cn = decodeURIComponent(url.split('group=')[1]);
return { ok: true, json: async () => ({ results: byGroup[cn] || [] }) };
};
}
test('unions hosts across groups, dedupes, drops non-hosts', async () => {
clearCache();
const user = { uid: 'alice', dn: 'uid=alice,ou=people,dc=x' };
const fetchImpl = stubFetch({
host_web01_access: [
{ id: '1', kind: 'host', slug: 'host_web01' },
{ id: '9', kind: 'service', slug: 'app_gitea' }, // dropped: not a host
],
host_db_access: [
{ id: '1', kind: 'host', slug: 'host_web01' }, // dupe by id
{ id: '2', kind: 'host', slug: 'host_db' },
],
});
const hosts = await accessibleHosts(user, { fetchImpl, ldap: stubLdap(['host_web01_access', 'host_db_access']) });
assert.deepStrictEqual(hosts.map((h) => h.id).sort(), ['1', '2']);
});
test('a failing group query does not sink the rest', async () => {
clearCache();
const user = { uid: 'bob', dn: 'uid=bob,ou=people,dc=x' };
const fetchImpl = async (url) => {
if (url.includes('bad')) return { ok: false, status: 500 };
return { ok: true, json: async () => ({ results: [{ id: '3', kind: 'host', slug: 'host_ok' }] }) };
};
const hosts = await accessibleHosts(user, { fetchImpl, ldap: stubLdap(['bad_access', 'good_access']) });
assert.deepStrictEqual(hosts.map((h) => h.id), ['3']);
});
test('caches per uid', async () => {
clearCache();
let calls = 0;
const user = { uid: 'cara', dn: 'd' };
const fetchImpl = async () => { calls++; return { ok: true, json: async () => ({ results: [] }) }; };
const ldap = { getGroups: async () => ['g1'] };
await accessibleHosts(user, { fetchImpl, ldap });
await accessibleHosts(user, { fetchImpl, ldap });
assert.strictEqual(calls, 1);
});
test('accepts pre-resolved groups (web UI/OIDC session) without calling ldap.getGroups', async () => {
clearCache();
let ldapCalled = false;
const user = { uid: 'erin', groups: ['host_web01_access'] };
const fetchImpl = stubFetch({
host_web01_access: [{ id: '5', kind: 'host', slug: 'host_web01' }],
});
const ldap = { getGroups: async () => { ldapCalled = true; return []; } };
const hosts = await accessibleHosts(user, { fetchImpl, ldap });
assert.deepStrictEqual(hosts.map((h) => h.id), ['5']);
assert.strictEqual(ldapCalled, false);
});
test('allHosts fetches the whole host inventory with no group filter', async () => {
const fetchImpl = async (url) => {
assert.ok(!url.includes('group='), 'must not filter by group');
assert.ok(url.includes('kind=host'));
return { ok: true, json: async () => ({ results: [
{ id: '1', kind: 'host', slug: 'host_a' },
{ id: '2', kind: 'host', slug: 'host_b' },
] }) };
};
const hosts = await allHosts({ fetchImpl });
assert.deepStrictEqual(hosts.map((h) => h.id).sort(), ['1', '2']);
});
test('a bare-array response (envelope drift) is treated as a failed group, not silently []', async () => {
clearCache();
const user = { uid: 'dave', dn: 'd' };
// drift shape: a bare array instead of { results: [...] }. The shared client
// throws DirectoryEnvelopeViolation; access.js must catch + continue, so a
// good group alongside still yields its hosts.
const fetchImpl = async (url) => {
if (url.includes('drift')) return { ok: true, json: async () => [{ id: '7', kind: 'host' }] };
return { ok: true, json: async () => ({ results: [{ id: '8', kind: 'host' }] }) };
};
const hosts = await accessibleHosts(user, { fetchImpl, ldap: stubLdap(['drift_access', 'good_access']) });
assert.deepStrictEqual(hosts.map((h) => h.id), ['8']);
});