BREAKING CHANGE(opsserver): Require authentication for OpsServer endpoints, split handlers into authenticated view/admin routers, and make identity required on many TypedRequest interfaces

This commit is contained in:
2026-03-03 21:39:20 +00:00
parent baab152fd3
commit ed3964e892
27 changed files with 326 additions and 227 deletions

View File

@@ -4,27 +4,44 @@ import { TypedRequest } from '@api.global/typedrequest';
import * as interfaces from '../ts_interfaces/index.js';
let testDcRouter: DcRouter;
let adminIdentity: interfaces.data.IIdentity;
tap.test('should start DCRouter with OpsServer', async () => {
testDcRouter = new DcRouter({
// Minimal config for testing
cacheConfig: { enabled: false },
});
await testDcRouter.start();
expect(testDcRouter.opsServer).toBeInstanceOf(Object);
});
tap.test('should login as admin', async () => {
const loginRequest = new TypedRequest<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
'http://localhost:3000/typedrequest',
'adminLoginWithUsernameAndPassword'
);
const response = await loginRequest.fire({
username: 'admin',
password: 'admin',
});
expect(response).toHaveProperty('identity');
adminIdentity = response.identity;
});
tap.test('should respond to health status request', async () => {
const healthRequest = new TypedRequest<interfaces.requests.IReq_GetHealthStatus>(
'http://localhost:3000/typedrequest',
'getHealthStatus'
);
const response = await healthRequest.fire({
detailed: false
identity: adminIdentity,
detailed: false,
});
expect(response).toHaveProperty('health');
expect(response.health.healthy).toBeTrue();
expect(response.health.services).toHaveProperty('OpsServer');
@@ -35,11 +52,12 @@ tap.test('should respond to server statistics request', async () => {
'http://localhost:3000/typedrequest',
'getServerStatistics'
);
const response = await statsRequest.fire({
includeHistory: false
identity: adminIdentity,
includeHistory: false,
});
expect(response).toHaveProperty('stats');
expect(response.stats).toHaveProperty('uptime');
expect(response.stats).toHaveProperty('cpuUsage');
@@ -51,9 +69,11 @@ tap.test('should respond to configuration request', async () => {
'http://localhost:3000/typedrequest',
'getConfiguration'
);
const response = await configRequest.fire({});
const response = await configRequest.fire({
identity: adminIdentity,
});
expect(response).toHaveProperty('config');
expect(response.config).toHaveProperty('system');
expect(response.config).toHaveProperty('smartProxy');
@@ -70,19 +90,34 @@ tap.test('should handle log retrieval request', async () => {
'http://localhost:3000/typedrequest',
'getRecentLogs'
);
const response = await logsRequest.fire({
limit: 10
identity: adminIdentity,
limit: 10,
});
expect(response).toHaveProperty('logs');
expect(response).toHaveProperty('total');
expect(response).toHaveProperty('hasMore');
expect(response.logs).toBeArray();
});
tap.test('should reject unauthenticated requests', async () => {
const healthRequest = new TypedRequest<interfaces.requests.IReq_GetHealthStatus>(
'http://localhost:3000/typedrequest',
'getHealthStatus'
);
try {
await healthRequest.fire({} as any);
expect(true).toBeFalse(); // Should not reach here
} catch (error) {
expect(error).toBeTruthy();
}
});
tap.test('should stop DCRouter', async () => {
await testDcRouter.stop();
});
export default tap.start();
export default tap.start();

View File

@@ -82,28 +82,31 @@ tap.test('should reject verify identity with invalid JWT', async () => {
}
});
tap.test('should allow access to public endpoints without auth', async () => {
tap.test('should reject protected endpoints without auth', async () => {
const healthRequest = new TypedRequest<interfaces.requests.IReq_GetHealthStatus>(
'http://localhost:3000/typedrequest',
'getHealthStatus'
);
// No identity provided
const response = await healthRequest.fire({});
expect(response).toHaveProperty('health');
expect(response.health.healthy).toBeTrue();
console.log('Public endpoint accessible without auth');
try {
// No identity provided — should be rejected
await healthRequest.fire({} as any);
expect(true).toBeFalse(); // Should not reach here
} catch (error) {
expect(error).toBeTruthy();
console.log('Protected endpoint correctly rejects unauthenticated request');
}
});
tap.test('should allow read-only config access', async () => {
tap.test('should allow authenticated access to protected endpoints', async () => {
const configRequest = new TypedRequest<interfaces.requests.IReq_GetConfiguration>(
'http://localhost:3000/typedrequest',
'getConfiguration'
);
// Config is read-only and doesn't require auth
const response = await configRequest.fire({});
const response = await configRequest.fire({
identity: adminIdentity,
});
expect(response).toHaveProperty('config');
expect(response.config).toHaveProperty('system');
@@ -114,7 +117,7 @@ tap.test('should allow read-only config access', async () => {
expect(response.config).toHaveProperty('cache');
expect(response.config).toHaveProperty('radius');
expect(response.config).toHaveProperty('remoteIngress');
console.log('Configuration read successfully');
console.log('Authenticated access to config successful');
});
tap.test('should stop DCRouter', async () => {