feat(gateway): add node capability negotiation foundation

This commit is contained in:
William Valentin
2026-02-16 12:14:25 -08:00
parent de0c1f41b3
commit d9f7807ab2
17 changed files with 675 additions and 7 deletions
+53 -1
View File
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { authenticateRequest } from './auth.js';
import { authenticateRequest, authorizeNodeMethod } from './auth.js';
import type { IncomingMessage } from 'http';
function mockRequest(headers: Record<string, string> = {}): IncomingMessage {
@@ -127,3 +127,55 @@ describe('authenticateRequest', () => {
});
});
});
describe('authorizeNodeMethod', () => {
it('allows non-node methods', () => {
const result = authorizeNodeMethod({ enabled: false, method: 'system.health' });
expect(result.authenticated).toBe(true);
});
it('blocks node methods when node RPC is disabled', () => {
const result = authorizeNodeMethod({ enabled: false, method: 'node.capabilities.get' });
expect(result.authenticated).toBe(false);
expect(result.error).toContain('disabled');
});
it('allows node.register without prior registration', () => {
const result = authorizeNodeMethod({ enabled: true, method: 'node.register' });
expect(result.authenticated).toBe(true);
});
it('requires role for scoped node methods', () => {
const result = authorizeNodeMethod({ enabled: true, method: 'node.capabilities.get' });
expect(result.authenticated).toBe(false);
expect(result.error).toContain('not registered');
});
it('enforces allowed role list and method scopes', () => {
const deniedRole = authorizeNodeMethod({
enabled: true,
method: 'node.capabilities.get',
nodeRole: 'observer',
allowedRoles: ['companion'],
});
expect(deniedRole.authenticated).toBe(false);
const deniedMethod = authorizeNodeMethod({
enabled: true,
method: 'node.admin.reset',
nodeRole: 'companion',
allowedRoles: ['companion'],
roleScopes: { companion: ['node.capabilities.get'] },
});
expect(deniedMethod.authenticated).toBe(false);
const allowed = authorizeNodeMethod({
enabled: true,
method: 'node.capabilities.get',
nodeRole: 'companion',
allowedRoles: ['companion'],
roleScopes: { companion: ['node.capabilities.get'] },
});
expect(allowed.authenticated).toBe(true);
});
});