Add SSH-managed node registry with connection testing and reauth.
Register hosts under Containers/VMs/Docker with encrypted key storage, and require re-authentication for sensitive account changes.
This commit is contained in:
@@ -288,17 +288,28 @@ describe('deleteUser', () => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('sends DELETE by id and returns users', async () => {
|
||||
it('sends DELETE by id with reauth credentials and returns users', async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ users: [] }),
|
||||
})
|
||||
|
||||
await deleteUser('u2', fetchMock)
|
||||
await deleteUser(
|
||||
'u2',
|
||||
{ current_password: 'correct horse battery staple extra' },
|
||||
fetchMock,
|
||||
)
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'http://localhost:8080/api/v1/users?id=u2',
|
||||
expect.objectContaining({ method: 'DELETE', credentials: 'include' }),
|
||||
expect.objectContaining({
|
||||
method: 'DELETE',
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({
|
||||
current_password: 'correct horse battery staple extra',
|
||||
totp_code: '',
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -309,9 +320,9 @@ describe('deleteUser', () => {
|
||||
json: async () => ({ error: 'cannot delete the last administrator' }),
|
||||
})
|
||||
|
||||
await expect(deleteUser('u1', fetchMock)).rejects.toThrow(
|
||||
'cannot delete the last administrator',
|
||||
)
|
||||
await expect(
|
||||
deleteUser('u1', { current_password: 'x' }, fetchMock),
|
||||
).rejects.toThrow('cannot delete the last administrator')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -463,3 +474,86 @@ describe('saveNetwork', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('nodes API', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('fetches nodes filtered by kind', async () => {
|
||||
const { fetchNodes } = await import('./client')
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ nodes: [] }),
|
||||
})
|
||||
|
||||
await fetchNodes('container', fetchMock)
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'http://localhost:8080/api/v1/nodes?kind=container',
|
||||
expect.objectContaining({ credentials: 'include' }),
|
||||
)
|
||||
})
|
||||
|
||||
it('creates a node with generate options', async () => {
|
||||
const { createNode } = await import('./client')
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
node: {
|
||||
id: '11111111-2222-4333-8444-555555555555',
|
||||
kind: 'container',
|
||||
name: 'ct1',
|
||||
host_ip: '10.0.0.1',
|
||||
username: 'clustercanvas',
|
||||
group_name: 'Administrators',
|
||||
public_key: 'ssh-ed25519 AAAA',
|
||||
key_algo: 'ed25519',
|
||||
created_at: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
await createNode(
|
||||
{
|
||||
kind: 'container',
|
||||
name: 'ct1',
|
||||
host_ip: '10.0.0.1',
|
||||
username: 'clustercanvas',
|
||||
group_name: 'Administrators',
|
||||
generate: { algorithm: 'ed25519', kdf_rounds: 100 },
|
||||
},
|
||||
fetchMock,
|
||||
)
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'http://localhost:8080/api/v1/nodes',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('posts to the SSH test endpoint', async () => {
|
||||
const { testNodeSSH } = await import('./client')
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ ok: false, message: 'connection refused' }),
|
||||
})
|
||||
|
||||
const result = await testNodeSSH(
|
||||
'11111111-2222-4333-8444-555555555555',
|
||||
fetchMock,
|
||||
)
|
||||
|
||||
expect(result).toEqual({ ok: false, message: 'connection refused' })
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'http://localhost:8080/api/v1/nodes/11111111-2222-4333-8444-555555555555/test-ssh',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user