From ed3964e892b24da316daec7ff295080718973d88 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Tue, 3 Mar 2026 21:39:20 +0000 Subject: [PATCH] BREAKING CHANGE(opsserver): Require authentication for OpsServer endpoints, split handlers into authenticated view/admin routers, and make identity required on many TypedRequest interfaces --- changelog.md | 11 ++++ package.json | 4 +- pnpm-lock.yaml | 51 +++++++++------ test/test.opsserver-api.ts | 63 ++++++++++++++----- test/test.protected-endpoint.ts | 25 ++++---- ts/00_commitinfo_data.ts | 2 +- ts/opsserver/classes.opsserver.ts | 35 ++++++++--- ts/opsserver/handlers/api-token.handler.ts | 38 +++-------- ts/opsserver/handlers/certificate.handler.ts | 22 ++++--- ts/opsserver/handlers/config.handler.ts | 9 ++- ts/opsserver/handlers/email-ops.handler.ts | 17 ++--- ts/opsserver/handlers/logs.handler.ts | 10 +-- ts/opsserver/handlers/radius.handler.ts | 54 ++++++++-------- .../handlers/remoteingress.handler.ts | 28 +++++---- ts/opsserver/handlers/security.handler.ts | 19 +++--- ts/opsserver/handlers/stats.handler.ts | 21 +++---- ts/opsserver/helpers/guards.ts | 18 +++--- ts_interfaces/requests/api-tokens.ts | 10 +-- ts_interfaces/requests/certificate.ts | 12 ++-- ts_interfaces/requests/config.ts | 2 +- ts_interfaces/requests/email-ops.ts | 6 +- ts_interfaces/requests/logs.ts | 4 +- ts_interfaces/requests/radius.ts | 24 +++---- ts_interfaces/requests/remoteingress.ts | 14 ++--- ts_interfaces/requests/stats.ts | 37 ++++++++--- ts_web/00_commitinfo_data.ts | 2 +- ts_web/appstate.ts | 15 +++-- 27 files changed, 326 insertions(+), 227 deletions(-) diff --git a/changelog.md b/changelog.md index 7624df6..8221f5e 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,16 @@ # Changelog +## 2026-03-03 - 11.0.0 - BREAKING CHANGE(opsserver) +Require authentication for OpsServer endpoints, split handlers into authenticated view/admin routers, and make identity required on many TypedRequest interfaces + +- Added viewRouter and adminRouter to OpsServer and wired middleware to enforce identity/admin checks (requireValidIdentity, requireAdminIdentity). +- Moved handlers to appropriate routers (viewRouter for read endpoints, adminRouter for write/admin endpoints) instead of registering on the unauthenticated main typedrouter. +- Made identity a required field on numerous ts_interfaces request types (breaking change to request typings). +- Refactored ApiTokenHandler to register directly on adminRouter and use dataArg.identity.userId (no per-handler admin checks needed thanks to middleware). +- Updated tests: added admin login to obtain identity, adjusted protected endpoint tests to expect rejection when unauthenticated, and adapted other tests to pass identity where required. +- Added IReq_GetNetworkStats request/response typings to ts_interfaces/requests/stats.ts. +- Bumped dependencies: @api.global/typedrequest ^3.3.0 and @api.global/typedserver ^8.4.2. + ## 2026-03-03 - 10.1.9 - fix(deps) bump @push.rocks/smartproxy to ^25.9.1 diff --git a/package.json b/package.json index 766fba6..1574211 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,9 @@ "@types/node": "^25.3.3" }, "dependencies": { - "@api.global/typedrequest": "^3.2.7", + "@api.global/typedrequest": "^3.3.0", "@api.global/typedrequest-interfaces": "^3.0.19", - "@api.global/typedserver": "^8.4.0", + "@api.global/typedserver": "^8.4.2", "@api.global/typedsocket": "^4.1.2", "@apiclient.xyz/cloudflare": "^7.1.0", "@design.estate/dees-catalog": "^3.43.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45d4bd5..ff15df4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,14 +9,14 @@ importers: .: dependencies: '@api.global/typedrequest': - specifier: ^3.2.7 - version: 3.2.7 + specifier: ^3.3.0 + version: 3.3.0 '@api.global/typedrequest-interfaces': specifier: ^3.0.19 version: 3.0.19 '@api.global/typedserver': - specifier: ^8.4.0 - version: 8.4.0(@tiptap/pm@2.27.2) + specifier: ^8.4.2 + version: 8.4.2(@tiptap/pm@2.27.2) '@api.global/typedsocket': specifier: ^4.1.2 version: 4.1.2(@push.rocks/smartserve@2.0.1) @@ -138,14 +138,14 @@ packages: '@api.global/typedrequest-interfaces@3.0.19': resolution: {integrity: sha512-uuHUXJeOy/inWSDrwD0Cwax2rovpxYllDhM2RWh+6mVpQuNmZ3uw6IVg6dA2G1rOe24Ebs+Y9SzEogo+jYN7vw==} - '@api.global/typedrequest@3.2.7': - resolution: {integrity: sha512-9CC8EojPDraKlwWK3ZjM8/wJ9jguY/kc+pCgcd61epHFXTIKC8jYts3vKPmEkBPno5Ejn3JZgqp/GRzplCC51w==} + '@api.global/typedrequest@3.3.0': + resolution: {integrity: sha512-Jwobqla+9k2IBG0duwrCFtc6GU6wsvHS3f0gJJsxTrpapylBW1YSF7NnGHPGs7F9hbATsO6IoUBpR2ScoKyGJA==} '@api.global/typedserver@3.0.80': resolution: {integrity: sha512-dcp0oXsjBL+XdFg1wUUP08uJQid5bQ0Yv3V3Y3lnI2QCbat0FU+Tsb0TZRnZ4+P150Vj/ITBqJUgDzFsF34grA==} - '@api.global/typedserver@8.4.0': - resolution: {integrity: sha512-qOa5jUwiuHEoY1ZuLZiuU1unPl5JNd99Vv+hNAwgEIgAZd4TTy/mjdTp7lyviBzkGf2pROeCmAbDJJF8YQFCSA==} + '@api.global/typedserver@8.4.2': + resolution: {integrity: sha512-eESOcWvrbqkshR4s4OeTX1AK74bNCeGgiRebKgjxIzJ+b0+rkPQyn2DOaMtyXjFZRNgRHyytLm5Iqj5fdazeqw==} '@api.global/typedsocket@3.1.1': resolution: {integrity: sha512-Wkz3NlhmfdZMKqXXI2c2dMtGGmSmhdOegZiziL+9b2mqPYdc7Gd8AZRdEOKvbSoIvc9G22/5BEadIWHrfq66TA==} @@ -1141,6 +1141,9 @@ packages: '@push.rocks/webrequest@4.0.2': resolution: {integrity: sha512-rowzty+Q2papFBcnNYPcy+8CQJukSn/FGfQG8ap0bUgQUsx882u8kEyLM0Q+GlGHS5OiZ+Z0z5TZqLKlk3XHxA==} + '@push.rocks/webrequest@4.0.5': + resolution: {integrity: sha512-wVSCaXqJ9Vh+rbwVz0wDl46dYz4rnwwSrm5vbVXKbuH6oKTPF0YRoujeJPqRltIn64RVGdLeY9/6ix+ZCrzhsg==} + '@push.rocks/websetup@3.0.19': resolution: {integrity: sha512-iKJDwXdMmQdu5siOIgziPRxM51lN1AU9HOr+yMteu1YMDkZT7HKCyisDAr4gC9WZ9a7FzsG8zgthm4dMeA8NTw==} @@ -4294,7 +4297,7 @@ snapshots: '@api.global/typedrequest-interfaces@3.0.19': {} - '@api.global/typedrequest@3.2.7': + '@api.global/typedrequest@3.3.0': dependencies: '@api.global/typedrequest-interfaces': 3.0.19 '@push.rocks/isounique': 1.0.5 @@ -4303,12 +4306,12 @@ snapshots: '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartguard': 3.1.0 '@push.rocks/smartpromise': 4.2.3 - '@push.rocks/webrequest': 4.0.2 + '@push.rocks/webrequest': 4.0.5 '@push.rocks/webstream': 1.0.10 '@api.global/typedserver@3.0.80(@push.rocks/smartserve@2.0.1)': dependencies: - '@api.global/typedrequest': 3.2.7 + '@api.global/typedrequest': 3.3.0 '@api.global/typedrequest-interfaces': 3.0.19 '@api.global/typedsocket': 3.1.1(@push.rocks/smartserve@2.0.1) '@cloudflare/workers-types': 4.20260210.0 @@ -4354,9 +4357,9 @@ snapshots: - utf-8-validate - vue - '@api.global/typedserver@8.4.0(@tiptap/pm@2.27.2)': + '@api.global/typedserver@8.4.2(@tiptap/pm@2.27.2)': dependencies: - '@api.global/typedrequest': 3.2.7 + '@api.global/typedrequest': 3.3.0 '@api.global/typedrequest-interfaces': 3.0.19 '@api.global/typedsocket': 4.1.2(@push.rocks/smartserve@2.0.1) '@cloudflare/workers-types': 4.20260303.0 @@ -4402,7 +4405,7 @@ snapshots: '@api.global/typedsocket@3.1.1(@push.rocks/smartserve@2.0.1)': dependencies: - '@api.global/typedrequest': 3.2.7 + '@api.global/typedrequest': 3.3.0 '@api.global/typedrequest-interfaces': 3.0.19 '@push.rocks/isohash': 2.0.1 '@push.rocks/smartjson': 5.2.0 @@ -4422,7 +4425,7 @@ snapshots: '@api.global/typedsocket@4.1.2(@push.rocks/smartserve@2.0.1)': dependencies: - '@api.global/typedrequest': 3.2.7 + '@api.global/typedrequest': 3.3.0 '@api.global/typedrequest-interfaces': 3.0.19 '@push.rocks/isohash': 2.0.1 '@push.rocks/smartdelay': 3.0.5 @@ -4997,14 +5000,14 @@ snapshots: '@design.estate/dees-comms@1.0.30': dependencies: - '@api.global/typedrequest': 3.2.7 + '@api.global/typedrequest': 3.3.0 '@api.global/typedrequest-interfaces': 3.0.19 '@push.rocks/smartdelay': 3.0.5 broadcast-channel: 7.3.0 '@design.estate/dees-domtools@2.3.8': dependencies: - '@api.global/typedrequest': 3.2.7 + '@api.global/typedrequest': 3.3.0 '@design.estate/dees-comms': 1.0.30 '@push.rocks/lik': 6.2.2 '@push.rocks/smartdelay': 3.0.5 @@ -5290,7 +5293,7 @@ snapshots: '@git.zone/tswatch@3.2.0(@tiptap/pm@2.27.2)': dependencies: - '@api.global/typedserver': 8.4.0(@tiptap/pm@2.27.2) + '@api.global/typedserver': 8.4.2(@tiptap/pm@2.27.2) '@git.zone/tsbundle': 2.9.0 '@git.zone/tsrun': 2.0.1 '@push.rocks/early': 4.0.4 @@ -5765,7 +5768,7 @@ snapshots: '@push.rocks/qenv@6.1.3': dependencies: - '@api.global/typedrequest': 3.2.7 + '@api.global/typedrequest': 3.3.0 '@configvault.io/interfaces': 1.0.17 '@push.rocks/smartfile': 11.2.7 '@push.rocks/smartlog': 3.2.1 @@ -6438,7 +6441,7 @@ snapshots: '@push.rocks/smartserve@2.0.1': dependencies: - '@api.global/typedrequest': 3.2.7 + '@api.global/typedrequest': 3.3.0 '@cfworker/json-schema': 4.1.1 '@push.rocks/lik': 6.2.2 '@push.rocks/smartenv': 6.0.0 @@ -6650,6 +6653,14 @@ snapshots: '@push.rocks/smartpromise': 4.2.3 '@push.rocks/webstore': 2.0.20 + '@push.rocks/webrequest@4.0.5': + dependencies: + '@push.rocks/smartdelay': 3.0.5 + '@push.rocks/smartenv': 6.0.0 + '@push.rocks/smartjson': 6.0.0 + '@push.rocks/smartpromise': 4.2.3 + '@push.rocks/webstore': 2.0.20 + '@push.rocks/websetup@3.0.19': dependencies: '@pushrocks/smartdelay': 3.0.1 diff --git a/test/test.opsserver-api.ts b/test/test.opsserver-api.ts index a73e528..b2fd0a3 100644 --- a/test/test.opsserver-api.ts +++ b/test/test.opsserver-api.ts @@ -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( + '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( '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( + '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(); \ No newline at end of file +export default tap.start(); diff --git a/test/test.protected-endpoint.ts b/test/test.protected-endpoint.ts index 65c23e9..ffe9ab1 100644 --- a/test/test.protected-endpoint.ts +++ b/test/test.protected-endpoint.ts @@ -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( '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( '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 () => { diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 94c6eba..9690776 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/dcrouter', - version: '10.1.9', + version: '11.0.0', description: 'A multifaceted routing service handling mail and SMS delivery functions.' } diff --git a/ts/opsserver/classes.opsserver.ts b/ts/opsserver/classes.opsserver.ts index cbb48e7..5cba64b 100644 --- a/ts/opsserver/classes.opsserver.ts +++ b/ts/opsserver/classes.opsserver.ts @@ -2,14 +2,20 @@ import type DcRouter from '../classes.dcrouter.js'; import * as plugins from '../plugins.js'; import * as paths from '../paths.js'; import * as handlers from './handlers/index.js'; +import * as interfaces from '../../ts_interfaces/index.js'; +import { requireValidIdentity, requireAdminIdentity } from './helpers/guards.js'; export class OpsServer { public dcRouterRef: DcRouter; public server: plugins.typedserver.utilityservers.UtilityWebsiteServer; - - // TypedRouter for OpsServer-specific handlers + + // Main TypedRouter — unauthenticated endpoints (login/logout/verify) and own-auth handlers public typedrouter = new plugins.typedrequest.TypedRouter(); - + + // Auth-enforced routers — middleware validates identity before any handler runs + public viewRouter = new plugins.typedrequest.TypedRouter<{ request: { identity: interfaces.data.IIdentity } }>(); + public adminRouter = new plugins.typedrequest.TypedRouter<{ request: { identity: interfaces.data.IIdentity } }>(); + // Handler instances public adminHandler: handlers.AdminHandler; private configHandler: handlers.ConfigHandler; @@ -25,7 +31,7 @@ export class OpsServer { constructor(dcRouterRefArg: DcRouter) { this.dcRouterRef = dcRouterRefArg; - + // Add our typedrouter to the dcRouter's main typedrouter this.dcRouterRef.typedrouter.addTypedRouter(this.typedrouter); } @@ -51,10 +57,25 @@ export class OpsServer { * Set up all TypedRequest handlers */ private async setupHandlers(): Promise { - // Instantiate all handlers - they self-register with the typedrouter + // AdminHandler must be initialized first (JWT setup needed for guards) this.adminHandler = new handlers.AdminHandler(this); - await this.adminHandler.initialize(); // JWT needs async initialization - + await this.adminHandler.initialize(); + + // viewRouter middleware: requires valid identity (any logged-in user) + this.viewRouter.addMiddleware(async (typedRequest) => { + await requireValidIdentity(this.adminHandler, typedRequest.request); + }); + + // adminRouter middleware: requires admin identity + this.adminRouter.addMiddleware(async (typedRequest) => { + await requireAdminIdentity(this.adminHandler, typedRequest.request); + }); + + // Connect auth routers to the main typedrouter + this.typedrouter.addTypedRouter(this.viewRouter); + this.typedrouter.addTypedRouter(this.adminRouter); + + // Instantiate all handlers — they self-register with the appropriate router this.configHandler = new handlers.ConfigHandler(this); this.logsHandler = new handlers.LogsHandler(this); this.securityHandler = new handlers.SecurityHandler(this); diff --git a/ts/opsserver/handlers/api-token.handler.ts b/ts/opsserver/handlers/api-token.handler.ts index 2f3537a..3cccc9a 100644 --- a/ts/opsserver/handlers/api-token.handler.ts +++ b/ts/opsserver/handlers/api-token.handler.ts @@ -3,34 +3,20 @@ import type { OpsServer } from '../classes.opsserver.js'; import * as interfaces from '../../../ts_interfaces/index.js'; export class ApiTokenHandler { - public typedrouter = new plugins.typedrequest.TypedRouter(); - constructor(private opsServerRef: OpsServer) { - this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.registerHandlers(); } - /** - * Token management requires admin JWT only (tokens cannot manage tokens). - */ - private async requireAdmin(identity?: interfaces.data.IIdentity): Promise { - if (!identity?.jwt) { - throw new plugins.typedrequest.TypedResponseError('unauthorized'); - } - const isAdmin = await this.opsServerRef.adminHandler.adminIdentityGuard.exec({ identity }); - if (!isAdmin) { - throw new plugins.typedrequest.TypedResponseError('admin access required'); - } - return identity.userId; - } - private registerHandlers(): void { + // All token management endpoints register directly on adminRouter + // (middleware enforces admin JWT check, so no per-handler requireAdmin needed) + const router = this.opsServerRef.adminRouter; + // Create API token - this.typedrouter.addTypedHandler( + router.addTypedHandler( new plugins.typedrequest.TypedHandler( 'createApiToken', async (dataArg) => { - const userId = await this.requireAdmin(dataArg.identity); const manager = this.opsServerRef.dcRouterRef.apiTokenManager; if (!manager) { return { success: false, message: 'Token management not initialized' }; @@ -39,7 +25,7 @@ export class ApiTokenHandler { dataArg.name, dataArg.scopes, dataArg.expiresInDays ?? null, - userId, + dataArg.identity.userId, ); return { success: true, tokenId: result.id, tokenValue: result.rawToken }; }, @@ -47,11 +33,10 @@ export class ApiTokenHandler { ); // List API tokens - this.typedrouter.addTypedHandler( + router.addTypedHandler( new plugins.typedrequest.TypedHandler( 'listApiTokens', async (dataArg) => { - await this.requireAdmin(dataArg.identity); const manager = this.opsServerRef.dcRouterRef.apiTokenManager; if (!manager) { return { tokens: [] }; @@ -62,11 +47,10 @@ export class ApiTokenHandler { ); // Revoke API token - this.typedrouter.addTypedHandler( + router.addTypedHandler( new plugins.typedrequest.TypedHandler( 'revokeApiToken', async (dataArg) => { - await this.requireAdmin(dataArg.identity); const manager = this.opsServerRef.dcRouterRef.apiTokenManager; if (!manager) { return { success: false, message: 'Token management not initialized' }; @@ -78,11 +62,10 @@ export class ApiTokenHandler { ); // Roll API token - this.typedrouter.addTypedHandler( + router.addTypedHandler( new plugins.typedrequest.TypedHandler( 'rollApiToken', async (dataArg) => { - await this.requireAdmin(dataArg.identity); const manager = this.opsServerRef.dcRouterRef.apiTokenManager; if (!manager) { return { success: false, message: 'Token management not initialized' }; @@ -97,11 +80,10 @@ export class ApiTokenHandler { ); // Toggle API token - this.typedrouter.addTypedHandler( + router.addTypedHandler( new plugins.typedrequest.TypedHandler( 'toggleApiToken', async (dataArg) => { - await this.requireAdmin(dataArg.identity); const manager = this.opsServerRef.dcRouterRef.apiTokenManager; if (!manager) { return { success: false, message: 'Token management not initialized' }; diff --git a/ts/opsserver/handlers/certificate.handler.ts b/ts/opsserver/handlers/certificate.handler.ts index aceca95..e58c498 100644 --- a/ts/opsserver/handlers/certificate.handler.ts +++ b/ts/opsserver/handlers/certificate.handler.ts @@ -3,16 +3,18 @@ import type { OpsServer } from '../classes.opsserver.js'; import * as interfaces from '../../../ts_interfaces/index.js'; export class CertificateHandler { - public typedrouter = new plugins.typedrequest.TypedRouter(); - constructor(private opsServerRef: OpsServer) { - this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.registerHandlers(); } private registerHandlers(): void { + const viewRouter = this.opsServerRef.viewRouter; + const adminRouter = this.opsServerRef.adminRouter; + + // ---- Read endpoints (viewRouter — valid identity required via middleware) ---- + // Get Certificate Overview - this.typedrouter.addTypedHandler( + viewRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getCertificateOverview', async (dataArg) => { @@ -23,8 +25,10 @@ export class CertificateHandler { ) ); + // ---- Write endpoints (adminRouter — admin identity required via middleware) ---- + // Legacy route-based reprovision (backward compat) - this.typedrouter.addTypedHandler( + adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'reprovisionCertificate', async (dataArg) => { @@ -34,7 +38,7 @@ export class CertificateHandler { ); // Domain-based reprovision (preferred) - this.typedrouter.addTypedHandler( + adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'reprovisionCertificateDomain', async (dataArg) => { @@ -44,7 +48,7 @@ export class CertificateHandler { ); // Delete certificate - this.typedrouter.addTypedHandler( + adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'deleteCertificate', async (dataArg) => { @@ -54,7 +58,7 @@ export class CertificateHandler { ); // Export certificate - this.typedrouter.addTypedHandler( + adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'exportCertificate', async (dataArg) => { @@ -64,7 +68,7 @@ export class CertificateHandler { ); // Import certificate - this.typedrouter.addTypedHandler( + adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'importCertificate', async (dataArg) => { diff --git a/ts/opsserver/handlers/config.handler.ts b/ts/opsserver/handlers/config.handler.ts index 7829a2f..157df14 100644 --- a/ts/opsserver/handlers/config.handler.ts +++ b/ts/opsserver/handlers/config.handler.ts @@ -4,17 +4,16 @@ import type { OpsServer } from '../classes.opsserver.js'; import * as interfaces from '../../../ts_interfaces/index.js'; export class ConfigHandler { - public typedrouter = new plugins.typedrequest.TypedRouter(); - constructor(private opsServerRef: OpsServer) { - // Add this handler's router to the parent - this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.registerHandlers(); } private registerHandlers(): void { + // Config endpoint registers directly on viewRouter (valid identity required via middleware) + const router = this.opsServerRef.viewRouter; + // Get Configuration Handler (read-only) - this.typedrouter.addTypedHandler( + router.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getConfiguration', async (dataArg, toolsArg) => { diff --git a/ts/opsserver/handlers/email-ops.handler.ts b/ts/opsserver/handlers/email-ops.handler.ts index 48ada85..c9c99c2 100644 --- a/ts/opsserver/handlers/email-ops.handler.ts +++ b/ts/opsserver/handlers/email-ops.handler.ts @@ -3,17 +3,18 @@ import type { OpsServer } from '../classes.opsserver.js'; import * as interfaces from '../../../ts_interfaces/index.js'; export class EmailOpsHandler { - public typedrouter = new plugins.typedrequest.TypedRouter(); - constructor(private opsServerRef: OpsServer) { - // Add this handler's router to the parent - this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.registerHandlers(); } private registerHandlers(): void { + const viewRouter = this.opsServerRef.viewRouter; + const adminRouter = this.opsServerRef.adminRouter; + + // ---- Read endpoints (viewRouter — valid identity required via middleware) ---- + // Get All Emails Handler - this.typedrouter.addTypedHandler( + viewRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getAllEmails', async (dataArg) => { @@ -24,7 +25,7 @@ export class EmailOpsHandler { ); // Get Email Detail Handler - this.typedrouter.addTypedHandler( + viewRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getEmailDetail', async (dataArg) => { @@ -34,8 +35,10 @@ export class EmailOpsHandler { ) ); + // ---- Write endpoints (adminRouter) ---- + // Resend Failed Email Handler - this.typedrouter.addTypedHandler( + adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'resendEmail', async (dataArg) => { diff --git a/ts/opsserver/handlers/logs.handler.ts b/ts/opsserver/handlers/logs.handler.ts index 7c86e71..011f9e5 100644 --- a/ts/opsserver/handlers/logs.handler.ts +++ b/ts/opsserver/handlers/logs.handler.ts @@ -10,12 +10,9 @@ let logPushDestinationInstalled = false; let currentOpsServerRef: OpsServer | null = null; export class LogsHandler { - public typedrouter = new plugins.typedrequest.TypedRouter(); private activeStreamStops: Set<() => void> = new Set(); constructor(private opsServerRef: OpsServer) { - // Add this handler's router to the parent - this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.registerHandlers(); this.setupLogPushDestination(); } @@ -35,8 +32,11 @@ export class LogsHandler { } private registerHandlers(): void { + // All log endpoints register directly on viewRouter (valid identity required via middleware) + const router = this.opsServerRef.viewRouter; + // Get Recent Logs Handler - this.typedrouter.addTypedHandler( + router.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getRecentLogs', async (dataArg, toolsArg) => { @@ -59,7 +59,7 @@ export class LogsHandler { ); // Get Log Stream Handler - this.typedrouter.addTypedHandler( + router.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getLogStream', async (dataArg, toolsArg) => { diff --git a/ts/opsserver/handlers/radius.handler.ts b/ts/opsserver/handlers/radius.handler.ts index c3e3c96..7c5eb85 100644 --- a/ts/opsserver/handlers/radius.handler.ts +++ b/ts/opsserver/handlers/radius.handler.ts @@ -3,21 +3,19 @@ import type { OpsServer } from '../classes.opsserver.js'; import * as interfaces from '../../../ts_interfaces/index.js'; export class RadiusHandler { - public typedrouter = new plugins.typedrequest.TypedRouter(); - constructor(private opsServerRef: OpsServer) { - // Add this handler's router to the parent - this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.registerHandlers(); } private registerHandlers(): void { + const viewRouter = this.opsServerRef.viewRouter; + const adminRouter = this.opsServerRef.adminRouter; // ======================================================================== // RADIUS Client Management // ======================================================================== - // Get all RADIUS clients - this.typedrouter.addTypedHandler( + // Get all RADIUS clients (read) + viewRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getRadiusClients', async (dataArg, toolsArg) => { @@ -40,8 +38,8 @@ export class RadiusHandler { ) ); - // Add or update a RADIUS client - this.typedrouter.addTypedHandler( + // Add or update a RADIUS client (write) + adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'setRadiusClient', async (dataArg, toolsArg) => { @@ -61,8 +59,8 @@ export class RadiusHandler { ) ); - // Remove a RADIUS client - this.typedrouter.addTypedHandler( + // Remove a RADIUS client (write) + adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'removeRadiusClient', async (dataArg, toolsArg) => { @@ -85,8 +83,8 @@ export class RadiusHandler { // VLAN Mapping Management // ======================================================================== - // Get all VLAN mappings - this.typedrouter.addTypedHandler( + // Get all VLAN mappings (read) + viewRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getVlanMappings', async (dataArg, toolsArg) => { @@ -121,8 +119,8 @@ export class RadiusHandler { ) ); - // Add or update a VLAN mapping - this.typedrouter.addTypedHandler( + // Add or update a VLAN mapping (write) + adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'setVlanMapping', async (dataArg, toolsArg) => { @@ -153,8 +151,8 @@ export class RadiusHandler { ) ); - // Remove a VLAN mapping - this.typedrouter.addTypedHandler( + // Remove a VLAN mapping (write) + adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'removeVlanMapping', async (dataArg, toolsArg) => { @@ -174,8 +172,8 @@ export class RadiusHandler { ) ); - // Update VLAN configuration - this.typedrouter.addTypedHandler( + // Update VLAN configuration (write) + adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'updateVlanConfig', async (dataArg, toolsArg) => { @@ -206,8 +204,8 @@ export class RadiusHandler { ) ); - // Test VLAN assignment - this.typedrouter.addTypedHandler( + // Test VLAN assignment (read) + viewRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'testVlanAssignment', async (dataArg, toolsArg) => { @@ -240,8 +238,8 @@ export class RadiusHandler { // Accounting / Session Management // ======================================================================== - // Get active sessions - this.typedrouter.addTypedHandler( + // Get active sessions (read) + viewRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getRadiusSessions', async (dataArg, toolsArg) => { @@ -289,8 +287,8 @@ export class RadiusHandler { ) ); - // Disconnect a session - this.typedrouter.addTypedHandler( + // Disconnect a session (write) + adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'disconnectRadiusSession', async (dataArg, toolsArg) => { @@ -314,8 +312,8 @@ export class RadiusHandler { ) ); - // Get accounting summary - this.typedrouter.addTypedHandler( + // Get accounting summary (read) + viewRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getRadiusAccountingSummary', async (dataArg, toolsArg) => { @@ -351,8 +349,8 @@ export class RadiusHandler { // Statistics // ======================================================================== - // Get RADIUS statistics - this.typedrouter.addTypedHandler( + // Get RADIUS statistics (read) + viewRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getRadiusStatistics', async (dataArg, toolsArg) => { diff --git a/ts/opsserver/handlers/remoteingress.handler.ts b/ts/opsserver/handlers/remoteingress.handler.ts index 28fea97..a50f7de 100644 --- a/ts/opsserver/handlers/remoteingress.handler.ts +++ b/ts/opsserver/handlers/remoteingress.handler.ts @@ -3,16 +3,18 @@ import type { OpsServer } from '../classes.opsserver.js'; import * as interfaces from '../../../ts_interfaces/index.js'; export class RemoteIngressHandler { - public typedrouter = new plugins.typedrequest.TypedRouter(); - constructor(private opsServerRef: OpsServer) { - this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.registerHandlers(); } private registerHandlers(): void { + const viewRouter = this.opsServerRef.viewRouter; + const adminRouter = this.opsServerRef.adminRouter; + + // ---- Read endpoints (viewRouter — valid identity required via middleware) ---- + // Get all remote ingress edges - this.typedrouter.addTypedHandler( + viewRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getRemoteIngresses', async (dataArg, toolsArg) => { @@ -36,8 +38,10 @@ export class RemoteIngressHandler { ), ); + // ---- Write endpoints (adminRouter) ---- + // Create a new remote ingress edge - this.typedrouter.addTypedHandler( + adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'createRemoteIngress', async (dataArg, toolsArg) => { @@ -69,7 +73,7 @@ export class RemoteIngressHandler { ); // Delete a remote ingress edge - this.typedrouter.addTypedHandler( + adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'deleteRemoteIngress', async (dataArg, toolsArg) => { @@ -94,7 +98,7 @@ export class RemoteIngressHandler { ); // Update a remote ingress edge - this.typedrouter.addTypedHandler( + adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'updateRemoteIngress', async (dataArg, toolsArg) => { @@ -138,7 +142,7 @@ export class RemoteIngressHandler { ); // Regenerate secret for an edge - this.typedrouter.addTypedHandler( + adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'regenerateRemoteIngressSecret', async (dataArg, toolsArg) => { @@ -164,8 +168,8 @@ export class RemoteIngressHandler { ), ); - // Get runtime status of all edges - this.typedrouter.addTypedHandler( + // Get runtime status of all edges (read) + viewRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getRemoteIngressStatus', async (dataArg, toolsArg) => { @@ -178,8 +182,8 @@ export class RemoteIngressHandler { ), ); - // Get a connection token for an edge - this.typedrouter.addTypedHandler( + // Get a connection token for an edge (write — exposes secret) + adminRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getRemoteIngressConnectionToken', async (dataArg, toolsArg) => { diff --git a/ts/opsserver/handlers/security.handler.ts b/ts/opsserver/handlers/security.handler.ts index 0c20177..dbb0972 100644 --- a/ts/opsserver/handlers/security.handler.ts +++ b/ts/opsserver/handlers/security.handler.ts @@ -4,17 +4,16 @@ import * as interfaces from '../../../ts_interfaces/index.js'; import { MetricsManager } from '../../monitoring/index.js'; export class SecurityHandler { - public typedrouter = new plugins.typedrequest.TypedRouter(); - constructor(private opsServerRef: OpsServer) { - // Add this handler's router to the parent - this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.registerHandlers(); } - + private registerHandlers(): void { + // All security endpoints register directly on viewRouter (valid identity required via middleware) + const router = this.opsServerRef.viewRouter; + // Security Metrics Handler - this.typedrouter.addTypedHandler( + router.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getSecurityMetrics', async (dataArg, toolsArg) => { @@ -40,7 +39,7 @@ export class SecurityHandler { ); // Active Connections Handler - this.typedrouter.addTypedHandler( + router.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getActiveConnections', async (dataArg, toolsArg) => { @@ -77,8 +76,8 @@ export class SecurityHandler { ); // Network Stats Handler - provides comprehensive network metrics - this.typedrouter.addTypedHandler( - new plugins.typedrequest.TypedHandler( + router.addTypedHandler( + new plugins.typedrequest.TypedHandler( 'getNetworkStats', async (dataArg, toolsArg) => { // Get network stats from MetricsManager if available @@ -121,7 +120,7 @@ export class SecurityHandler { ); // Rate Limit Status Handler - this.typedrouter.addTypedHandler( + router.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getRateLimitStatus', async (dataArg, toolsArg) => { diff --git a/ts/opsserver/handlers/stats.handler.ts b/ts/opsserver/handlers/stats.handler.ts index e9d650a..e99ab19 100644 --- a/ts/opsserver/handlers/stats.handler.ts +++ b/ts/opsserver/handlers/stats.handler.ts @@ -5,17 +5,16 @@ import { MetricsManager } from '../../monitoring/index.js'; import { SecurityLogger } from '../../security/classes.securitylogger.js'; export class StatsHandler { - public typedrouter = new plugins.typedrequest.TypedRouter(); - constructor(private opsServerRef: OpsServer) { - // Add this handler's router to the parent - this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.registerHandlers(); } - + private registerHandlers(): void { + // All stats endpoints register directly on viewRouter (valid identity required via middleware) + const router = this.opsServerRef.viewRouter; + // Server Statistics Handler - this.typedrouter.addTypedHandler( + router.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getServerStatistics', async (dataArg, toolsArg) => { @@ -38,7 +37,7 @@ export class StatsHandler { ); // Email Statistics Handler - this.typedrouter.addTypedHandler( + router.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getEmailStatistics', async (dataArg, toolsArg) => { @@ -77,7 +76,7 @@ export class StatsHandler { ); // DNS Statistics Handler - this.typedrouter.addTypedHandler( + router.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getDnsStatistics', async (dataArg, toolsArg) => { @@ -114,7 +113,7 @@ export class StatsHandler { ); // Queue Status Handler - this.typedrouter.addTypedHandler( + router.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getQueueStatus', async (dataArg, toolsArg) => { @@ -142,7 +141,7 @@ export class StatsHandler { ); // Health Status Handler - this.typedrouter.addTypedHandler( + router.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getHealthStatus', async (dataArg, toolsArg) => { @@ -167,7 +166,7 @@ export class StatsHandler { ); // Combined Metrics Handler - More efficient for frontend polling - this.typedrouter.addTypedHandler( + router.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getCombinedMetrics', async (dataArg, toolsArg) => { diff --git a/ts/opsserver/helpers/guards.ts b/ts/opsserver/helpers/guards.ts index 565a1fc..8a4b154 100644 --- a/ts/opsserver/helpers/guards.ts +++ b/ts/opsserver/helpers/guards.ts @@ -22,16 +22,17 @@ export async function passGuards( } /** - * Helper to check admin identity in handlers + * Helper to check admin identity in handlers and middleware. + * Accepts both optional and required identity for flexibility. */ -export async function requireAdminIdentity( +export async function requireAdminIdentity( adminHandler: AdminHandler, - dataArg: T + dataArg: { identity?: interfaces.data.IIdentity } ): Promise { if (!dataArg.identity) { throw new plugins.typedrequest.TypedResponseError('No identity provided'); } - + const passed = await adminHandler.adminIdentityGuard.exec({ identity: dataArg.identity }); if (!passed) { throw new plugins.typedrequest.TypedResponseError('Admin access required'); @@ -39,16 +40,17 @@ export async function requireAdminIdentity( +export async function requireValidIdentity( adminHandler: AdminHandler, - dataArg: T + dataArg: { identity?: interfaces.data.IIdentity } ): Promise { if (!dataArg.identity) { throw new plugins.typedrequest.TypedResponseError('No identity provided'); } - + const passed = await adminHandler.validIdentityGuard.exec({ identity: dataArg.identity }); if (!passed) { throw new plugins.typedrequest.TypedResponseError('Valid identity required'); diff --git a/ts_interfaces/requests/api-tokens.ts b/ts_interfaces/requests/api-tokens.ts index f4e94f3..648692a 100644 --- a/ts_interfaces/requests/api-tokens.ts +++ b/ts_interfaces/requests/api-tokens.ts @@ -16,7 +16,7 @@ export interface IReq_CreateApiToken extends plugins.typedrequestInterfaces.impl > { method: 'createApiToken'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; name: string; scopes: TApiTokenScope[]; expiresInDays?: number | null; @@ -38,7 +38,7 @@ export interface IReq_ListApiTokens extends plugins.typedrequestInterfaces.imple > { method: 'listApiTokens'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; }; response: { tokens: IApiTokenInfo[]; @@ -54,7 +54,7 @@ export interface IReq_RevokeApiToken extends plugins.typedrequestInterfaces.impl > { method: 'revokeApiToken'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; id: string; }; response: { @@ -73,7 +73,7 @@ export interface IReq_RollApiToken extends plugins.typedrequestInterfaces.implem > { method: 'rollApiToken'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; id: string; }; response: { @@ -92,7 +92,7 @@ export interface IReq_ToggleApiToken extends plugins.typedrequestInterfaces.impl > { method: 'toggleApiToken'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; id: string; enabled: boolean; }; diff --git a/ts_interfaces/requests/certificate.ts b/ts_interfaces/requests/certificate.ts index f44bddc..fed0de2 100644 --- a/ts_interfaces/requests/certificate.ts +++ b/ts_interfaces/requests/certificate.ts @@ -28,7 +28,7 @@ export interface IReq_GetCertificateOverview extends plugins.typedrequestInterfa > { method: 'getCertificateOverview'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; }; response: { certificates: ICertificateInfo[]; @@ -50,7 +50,7 @@ export interface IReq_ReprovisionCertificate extends plugins.typedrequestInterfa > { method: 'reprovisionCertificate'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; routeName: string; }; response: { @@ -66,7 +66,7 @@ export interface IReq_ReprovisionCertificateDomain extends plugins.typedrequestI > { method: 'reprovisionCertificateDomain'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; domain: string; }; response: { @@ -82,7 +82,7 @@ export interface IReq_DeleteCertificate extends plugins.typedrequestInterfaces.i > { method: 'deleteCertificate'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; domain: string; }; response: { @@ -98,7 +98,7 @@ export interface IReq_ExportCertificate extends plugins.typedrequestInterfaces.i > { method: 'exportCertificate'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; domain: string; }; response: { @@ -123,7 +123,7 @@ export interface IReq_ImportCertificate extends plugins.typedrequestInterfaces.i > { method: 'importCertificate'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; cert: { id: string; domainName: string; diff --git a/ts_interfaces/requests/config.ts b/ts_interfaces/requests/config.ts index 002bcc6..40d4309 100644 --- a/ts_interfaces/requests/config.ts +++ b/ts_interfaces/requests/config.ts @@ -81,7 +81,7 @@ export interface IReq_GetConfiguration extends plugins.typedrequestInterfaces.im > { method: 'getConfiguration'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; section?: string; }; response: { diff --git a/ts_interfaces/requests/email-ops.ts b/ts_interfaces/requests/email-ops.ts index 64d8a6a..6e4bad6 100644 --- a/ts_interfaces/requests/email-ops.ts +++ b/ts_interfaces/requests/email-ops.ts @@ -68,7 +68,7 @@ export interface IReq_GetAllEmails extends plugins.typedrequestInterfaces.implem > { method: 'getAllEmails'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; }; response: { emails: IEmail[]; @@ -84,7 +84,7 @@ export interface IReq_GetEmailDetail extends plugins.typedrequestInterfaces.impl > { method: 'getEmailDetail'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; emailId: string; }; response: { @@ -101,7 +101,7 @@ export interface IReq_ResendEmail extends plugins.typedrequestInterfaces.impleme > { method: 'resendEmail'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; emailId: string; }; response: { diff --git a/ts_interfaces/requests/logs.ts b/ts_interfaces/requests/logs.ts index 757d70c..b07cebd 100644 --- a/ts_interfaces/requests/logs.ts +++ b/ts_interfaces/requests/logs.ts @@ -9,7 +9,7 @@ export interface IReq_GetRecentLogs extends plugins.typedrequestInterfaces.imple > { method: 'getRecentLogs'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; level?: 'debug' | 'info' | 'warn' | 'error'; category?: 'smtp' | 'dns' | 'security' | 'system' | 'email'; limit?: number; @@ -31,7 +31,7 @@ export interface IReq_GetLogStream extends plugins.typedrequestInterfaces.implem > { method: 'getLogStream'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; follow?: boolean; filters?: { level?: string[]; diff --git a/ts_interfaces/requests/radius.ts b/ts_interfaces/requests/radius.ts index e3064b8..5ae502d 100644 --- a/ts_interfaces/requests/radius.ts +++ b/ts_interfaces/requests/radius.ts @@ -14,7 +14,7 @@ export interface IReq_GetRadiusClients extends plugins.typedrequestInterfaces.im > { method: 'getRadiusClients'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; }; response: { clients: Array<{ @@ -35,7 +35,7 @@ export interface IReq_SetRadiusClient extends plugins.typedrequestInterfaces.imp > { method: 'setRadiusClient'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; client: { name: string; ipRange: string; @@ -59,7 +59,7 @@ export interface IReq_RemoveRadiusClient extends plugins.typedrequestInterfaces. > { method: 'removeRadiusClient'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; name: string; }; response: { @@ -81,7 +81,7 @@ export interface IReq_GetVlanMappings extends plugins.typedrequestInterfaces.imp > { method: 'getVlanMappings'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; }; response: { mappings: Array<{ @@ -108,7 +108,7 @@ export interface IReq_SetVlanMapping extends plugins.typedrequestInterfaces.impl > { method: 'setVlanMapping'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; mapping: { mac: string; vlan: number; @@ -139,7 +139,7 @@ export interface IReq_RemoveVlanMapping extends plugins.typedrequestInterfaces.i > { method: 'removeVlanMapping'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; mac: string; }; response: { @@ -157,7 +157,7 @@ export interface IReq_UpdateVlanConfig extends plugins.typedrequestInterfaces.im > { method: 'updateVlanConfig'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; defaultVlan?: number; allowUnknownMacs?: boolean; }; @@ -179,7 +179,7 @@ export interface IReq_TestVlanAssignment extends plugins.typedrequestInterfaces. > { method: 'testVlanAssignment'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; mac: string; }; response: { @@ -207,7 +207,7 @@ export interface IReq_GetRadiusSessions extends plugins.typedrequestInterfaces.i > { method: 'getRadiusSessions'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; filter?: { username?: string; nasIpAddress?: string; @@ -243,7 +243,7 @@ export interface IReq_DisconnectRadiusSession extends plugins.typedrequestInterf > { method: 'disconnectRadiusSession'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; sessionId: string; reason?: string; }; @@ -262,7 +262,7 @@ export interface IReq_GetRadiusAccountingSummary extends plugins.typedrequestInt > { method: 'getRadiusAccountingSummary'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; startTime: number; endTime: number; }; @@ -296,7 +296,7 @@ export interface IReq_GetRadiusStatistics extends plugins.typedrequestInterfaces > { method: 'getRadiusStatistics'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; }; response: { stats: { diff --git a/ts_interfaces/requests/remoteingress.ts b/ts_interfaces/requests/remoteingress.ts index a8b5269..6d2e174 100644 --- a/ts_interfaces/requests/remoteingress.ts +++ b/ts_interfaces/requests/remoteingress.ts @@ -15,7 +15,7 @@ export interface IReq_CreateRemoteIngress extends plugins.typedrequestInterfaces > { method: 'createRemoteIngress'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; name: string; listenPorts?: number[]; autoDerivePorts?: boolean; @@ -36,7 +36,7 @@ export interface IReq_DeleteRemoteIngress extends plugins.typedrequestInterfaces > { method: 'deleteRemoteIngress'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; id: string; }; response: { @@ -54,7 +54,7 @@ export interface IReq_UpdateRemoteIngress extends plugins.typedrequestInterfaces > { method: 'updateRemoteIngress'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; id: string; name?: string; listenPorts?: number[]; @@ -77,7 +77,7 @@ export interface IReq_RegenerateRemoteIngressSecret extends plugins.typedrequest > { method: 'regenerateRemoteIngressSecret'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; id: string; }; response: { @@ -95,7 +95,7 @@ export interface IReq_GetRemoteIngresses extends plugins.typedrequestInterfaces. > { method: 'getRemoteIngresses'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; }; response: { edges: IRemoteIngress[]; @@ -111,7 +111,7 @@ export interface IReq_GetRemoteIngressStatus extends plugins.typedrequestInterfa > { method: 'getRemoteIngressStatus'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; }; response: { statuses: IRemoteIngressStatus[]; @@ -128,7 +128,7 @@ export interface IReq_GetRemoteIngressConnectionToken extends plugins.typedreque > { method: 'getRemoteIngressConnectionToken'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; edgeId: string; hubHost?: string; }; diff --git a/ts_interfaces/requests/stats.ts b/ts_interfaces/requests/stats.ts index b7afe28..5224b57 100644 --- a/ts_interfaces/requests/stats.ts +++ b/ts_interfaces/requests/stats.ts @@ -9,7 +9,7 @@ export interface IReq_GetServerStatistics extends plugins.typedrequestInterfaces > { method: 'getServerStatistics'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; includeHistory?: boolean; timeRange?: '1h' | '6h' | '24h' | '7d' | '30d'; }; @@ -29,7 +29,7 @@ export interface IReq_GetEmailStatistics extends plugins.typedrequestInterfaces. > { method: 'getEmailStatistics'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; timeRange?: '1h' | '6h' | '24h' | '7d' | '30d'; domain?: string; includeDetails?: boolean; @@ -49,7 +49,7 @@ export interface IReq_GetDnsStatistics extends plugins.typedrequestInterfaces.im > { method: 'getDnsStatistics'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; timeRange?: '1h' | '6h' | '24h' | '7d' | '30d'; domain?: string; includeQueryTypes?: boolean; @@ -69,7 +69,7 @@ export interface IReq_GetRateLimitStatus extends plugins.typedrequestInterfaces. > { method: 'getRateLimitStatus'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; domain?: string; ip?: string; includeBlocked?: boolean; @@ -91,7 +91,7 @@ export interface IReq_GetSecurityMetrics extends plugins.typedrequestInterfaces. > { method: 'getSecurityMetrics'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; timeRange?: '1h' | '6h' | '24h' | '7d' | '30d'; includeDetails?: boolean; }; @@ -112,7 +112,7 @@ export interface IReq_GetActiveConnections extends plugins.typedrequestInterface > { method: 'getActiveConnections'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; protocol?: 'smtp' | 'smtps' | 'http' | 'https'; state?: string; }; @@ -137,7 +137,7 @@ export interface IReq_GetQueueStatus extends plugins.typedrequestInterfaces.impl > { method: 'getQueueStatus'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; queueName?: string; }; response: { @@ -153,10 +153,31 @@ export interface IReq_GetHealthStatus extends plugins.typedrequestInterfaces.imp > { method: 'getHealthStatus'; request: { - identity?: authInterfaces.IIdentity; + identity: authInterfaces.IIdentity; detailed?: boolean; }; response: { health: statsInterfaces.IHealthStatus; }; +} + +// Network Stats (raw SmartProxy network data) +export interface IReq_GetNetworkStats extends plugins.typedrequestInterfaces.implementsTR< + plugins.typedrequestInterfaces.ITypedRequest, + IReq_GetNetworkStats +> { + method: 'getNetworkStats'; + request: { + identity: authInterfaces.IIdentity; + }; + response: { + connectionsByIP: Array<{ ip: string; count: number }>; + throughputRate: { bytesInPerSecond: number; bytesOutPerSecond: number }; + topIPs: Array<{ ip: string; count: number }>; + totalDataTransferred: { bytesIn: number; bytesOut: number }; + throughputHistory: Array<{ timestamp: number; in: number; out: number }>; + throughputByIP: Array<{ ip: string; in: number; out: number }>; + requestsPerSecond: number; + requestsTotal: number; + }; } \ No newline at end of file diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index 94c6eba..9690776 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/dcrouter', - version: '10.1.9', + version: '11.0.0', description: 'A multifaceted routing service handling mail and SMS delivery functions.' } diff --git a/ts_web/appstate.ts b/ts_web/appstate.ts index 68e31dd..d2833b9 100644 --- a/ts_web/appstate.ts +++ b/ts_web/appstate.ts @@ -298,8 +298,8 @@ export const logoutAction = loginStatePart.createAction(async (statePartArg) => // Fetch All Stats Action - Using combined endpoint for efficiency export const fetchAllStatsAction = statsStatePart.createAction(async (statePartArg) => { const context = getActionContext(); - const currentState = statePartArg.getState(); + if (!context.identity) return currentState; try { // Use combined metrics endpoint - single request instead of 4 @@ -340,8 +340,8 @@ export const fetchAllStatsAction = statsStatePart.createAction(async (statePartA // Fetch Configuration Action (read-only) export const fetchConfigurationAction = configStatePart.createAction(async (statePartArg) => { const context = getActionContext(); - const currentState = statePartArg.getState(); + if (!context.identity) return currentState; try { const configRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< @@ -373,6 +373,7 @@ export const fetchRecentLogsAction = logStatePart.createAction<{ category?: 'smtp' | 'dns' | 'security' | 'system' | 'email'; }>(async (statePartArg, dataArg) => { const context = getActionContext(); + if (!context.identity) return statePartArg.getState(); const logsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetRecentLogs @@ -448,8 +449,8 @@ export const setActiveViewAction = uiStatePart.createAction(async (state // Fetch Network Stats Action export const fetchNetworkStatsAction = networkStatePart.createAction(async (statePartArg) => { const context = getActionContext(); - const currentState = statePartArg.getState(); + if (!context.identity) return currentState; try { // Fetch active connections using the existing endpoint @@ -522,6 +523,7 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (statePartArg) => { const context = getActionContext(); const currentState = statePartArg.getState(); + if (!context.identity) return currentState; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< @@ -554,6 +556,7 @@ export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (stateP export const fetchCertificateOverviewAction = certificateStatePart.createAction(async (statePartArg) => { const context = getActionContext(); const currentState = statePartArg.getState(); + if (!context.identity) return currentState; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< @@ -697,6 +700,7 @@ export async function fetchConnectionToken(edgeId: string) { export const fetchRemoteIngressAction = remoteIngressStatePart.createAction(async (statePartArg) => { const context = getActionContext(); const currentState = statePartArg.getState(); + if (!context.identity) return currentState; try { const edgesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< @@ -903,6 +907,7 @@ export const toggleRemoteIngressAction = remoteIngressStatePart.createAction<{ export const fetchMergedRoutesAction = routeManagementStatePart.createAction(async (statePartArg) => { const context = getActionContext(); const currentState = statePartArg.getState(); + if (!context.identity) return currentState; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< @@ -1068,6 +1073,7 @@ export const removeRouteOverrideAction = routeManagementStatePart.createAction { const context = getActionContext(); const currentState = statePartArg.getState(); + if (!context.identity) return currentState; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< @@ -1220,8 +1226,9 @@ async function disconnectSocket() { // Combined refresh action for efficient polling async function dispatchCombinedRefreshAction() { const context = getActionContext(); + if (!context.identity) return; const currentView = uiStatePart.getState().activeView; - + try { // Always fetch basic stats for dashboard widgets const combinedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<