Compare commits

...

23 Commits

Author SHA1 Message Date
de65641f6f v11.0.7
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-05 08:57:55 +00:00
ffddc1a5f5 fix(deps): bump @git.zone/tsbuild to ^4.1.3 and @push.rocks/lik to ^6.3.1 2026-03-05 08:57:55 +00:00
26152e0520 11.0.6
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-04 07:40:52 +00:00
f79ad07a57 v11.0.5
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Failing after 12m9s
2026-03-04 07:37:12 +00:00
76d5b9bf7c fix(none): no changes detected; nothing to release 2026-03-04 07:37:12 +00:00
670b67eecf v11.0.4
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-04 07:32:50 +00:00
174af5cf86 fix(): no changes 2026-03-04 07:32:50 +00:00
a1f5e45e94 v11.0.3
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-04 07:31:37 +00:00
d06165bd0c fix(): no changes detected 2026-03-04 07:31:37 +00:00
8f3c6fdf23 v11.0.2
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-04 07:30:26 +00:00
106ef2919e fix(dcrouter): no changes detected; no files were modified 2026-03-04 07:30:26 +00:00
3d7fd233cf v11.0.1
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-04 01:11:19 +00:00
34d40f7370 fix(auth): treat expired JWTs as no identity, improve logout and token verification flow, and bump deps 2026-03-04 01:11:19 +00:00
89b9d01628 v11.0.0
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-03 21:39:20 +00:00
ed3964e892 BREAKING CHANGE(opsserver): Require authentication for OpsServer endpoints, split handlers into authenticated view/admin routers, and make identity required on many TypedRequest interfaces 2026-03-03 21:39:20 +00:00
baab152fd3 v10.1.9
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-03 16:19:42 +00:00
9baf09ff61 fix(deps): bump @push.rocks/smartproxy to ^25.9.1 2026-03-03 16:19:42 +00:00
71f23302d3 v10.1.8
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-03 11:49:28 +00:00
ecbaab3000 fix(deps): bump dependencies: @push.rocks/smartmetrics to ^3.0.2, @push.rocks/smartproxy to ^25.9.0, @serve.zone/remoteingress to ^4.4.0 2026-03-03 11:49:28 +00:00
8cb1f3c12d v10.1.7
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-03 07:29:03 +00:00
c7d7f92759 fix(ops-view-apitokens): use correct lucide icon name for roll/rotate actions in API tokens view 2026-03-03 07:29:03 +00:00
02e1b9231f v10.1.6
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-02 22:32:21 +00:00
4ec4dd2bdb fix(ts_web): use actionContext for dispatches in web state actions and bump @push.rocks/smartstate to ^2.2.0 2026-03-02 22:32:21 +00:00
38 changed files with 1631 additions and 1871 deletions

View File

@@ -0,0 +1,6 @@
[ 95ms] TypeError: Cannot read properties of null (reading 'appendChild')
at TypedserverStatusPill.show (http://localhost:3000/typedserver/devtools:17607:21)
at TypedserverStatusPill.updateStatus (http://localhost:3000/typedserver/devtools:17567:10)
at ReloadChecker.checkReload (http://localhost:3000/typedserver/devtools:18137:23)
at async ReloadChecker.start (http://localhost:3000/typedserver/devtools:18224:9)
[ 992ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/overview:0

View File

@@ -0,0 +1,5 @@
[ 329ms] [ERROR] method: >>getMergedRoutes<< got an ERROR: "unauthorized" with data undefined @ http://localhost:3000/bundle.js:13
[ 727ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/routes:0
[ 260513ms] [ERROR] method: >>adminLoginWithUsernameAndPassword<< got an ERROR: "login failed" with data undefined @ http://localhost:3000/bundle.js:13
[ 260514ms] [ERROR] Login failed: Ns @ http://localhost:3000/bundle.js:38066
[ 260518ms] [WARNING] FontAwesome icon not found: circle-xmark @ http://localhost:3000/bundle.js:1203

View File

@@ -0,0 +1,3 @@
[ 397ms] [ERROR] method: >>getMergedRoutes<< got an ERROR: "unauthorized" with data undefined @ http://localhost:3000/bundle.js:13
[ 657ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/routes:0
[ 24180ms] [WARNING] FontAwesome icon not found: circle-check @ http://localhost:3000/bundle.js:1203

View File

@@ -0,0 +1,15 @@
[ 916ms] [ERROR] method: >>getCombinedMetrics<< got an ERROR: "Valid identity required" with data {} @ http://localhost:3000/bundle.js:15
[ 972ms] [ERROR] method: >>getConfiguration<< got an ERROR: "Valid identity required" with data {} @ http://localhost:3000/bundle.js:15
[ 973ms] [ERROR] method: >>getRecentLogs<< got an ERROR: "Valid identity required" with data {} @ http://localhost:3000/bundle.js:15
[ 990ms] K2
[ 1024ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/overview:0
[ 37030ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: @ http://localhost:3000/typedserver/devtools:16227
[ 37031ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/typedserver/devtools:16251
[ 37923ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedserver/devtools:16227
[ 37923ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/typedserver/devtools:16251
[ 39699ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedserver/devtools:16227
[ 39699ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/typedserver/devtools:16251
[ 44287ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedserver/devtools:16227
[ 44288ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/typedserver/devtools:16251
[ 53685ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedserver/devtools:16227
[ 53685ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/typedserver/devtools:16251

View File

@@ -0,0 +1,90 @@
[ 1146ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/overview:0
[ 26151ms] [WARNING] FontAwesome icon not found: circle-check @ http://localhost:3000/bundle.js:1203
[ 257684ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: @ http://localhost:3000/bundle.js:38066
[ 257684ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: @ http://localhost:3000/typedserver/devtools:16227
[ 257684ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/bundle.js:38066
[ 257685ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/typedserver/devtools:16251
[ 258151ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 258500ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedserver/devtools:16227
[ 258500ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/typedserver/devtools:16251
[ 258568ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/bundle.js:38066
[ 258568ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/bundle.js:38066
[ 259149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 260149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 260245ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/bundle.js:38066
[ 260245ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/bundle.js:38066
[ 260324ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedserver/devtools:16227
[ 260324ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/typedserver/devtools:16251
[ 261149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 262149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 263149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 263917ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedserver/devtools:16227
[ 263917ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/typedserver/devtools:16251
[ 264149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 264781ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/bundle.js:38066
[ 264781ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/bundle.js:38066
[ 265169ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 266149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 267149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 268149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 269149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 270149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 271149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 272149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 272565ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedserver/devtools:16227
[ 272565ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/typedserver/devtools:16251
[ 273149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 273647ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/bundle.js:38066
[ 273647ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/bundle.js:38066
[ 274149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 275149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 276149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 277149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 278149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 279149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 280149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 281149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 282149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 283149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 284149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 285149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 286149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 287149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 288150ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 289149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 290149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 290179ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/bundle.js:38066
[ 290179ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/bundle.js:38066
[ 291147ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedserver/devtools:16227
[ 291147ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/typedserver/devtools:16251
[ 291149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 292149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 293149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 294149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 295149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 296149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 297149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 298149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 299149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 300149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 301149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 302149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 303149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 304149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 305149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 306149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 307149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 308149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 309149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 310149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 311149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 312150ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 313149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 314149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 315149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 316149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 317149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 318150ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 319149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 320149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0
[ 321149ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedrequest:0

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -1,5 +1,82 @@
# Changelog # Changelog
## 2026-03-05 - 11.0.7 - fix(deps)
bump @git.zone/tsbuild to ^4.1.3 and @push.rocks/lik to ^6.3.1
- Updated devDependency @git.zone/tsbuild from ^4.1.2 to ^4.1.3 in package.json
- Updated dependency @push.rocks/lik from ^6.2.2 to ^6.3.1 in package.json
- Changes are non-breaking dependency bumps; no source code changes
## 2026-03-04 - 11.0.5 - fix(none)
no changes detected; nothing to release
- Diff contained no changes
- No files modified; no version bump required
## 2026-03-04 - 11.0.4 - fix()
no changes
- No files changed in the provided diff; no release or version bump required.
## 2026-03-04 - 11.0.3 - fix()
no changes detected
- Diff shows no file changes; no code changes to release.
## 2026-03-04 - 11.0.2 - fix(dcrouter)
no changes detected; no files were modified
- diff was empty
- no source or package changes detected
## 2026-03-04 - 11.0.1 - fix(auth)
treat expired JWTs as no identity, improve logout and token verification flow, and bump deps
- App: getActionContext now treats expired JWTs as null to avoid using stale identities for requests.
- Logout action always clears local login state; server-side adminLogout is attempted only when a valid identity exists.
- Dashboard: verify persisted JWT with server (verifyIdentity) on startup; if verification fails, clear state and show login.
- Auto-refresh: on combined refresh failure, detect auth-related errors (invalid/unauthorized/401), dispatch logout and reload to force re-login.
- Deps: bumped devDependencies @git.zone/tstest (^3.2.0) and @git.zone/tswatch (^3.2.5); added runtime dependency @push.rocks/lik (^6.2.2).
- Tests/artifacts: added Playwright console logs and page screenshots (test artifacts) to the commit.
## 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
- Updated package.json dependency @push.rocks/smartproxy from ^25.9.0 to ^25.9.1
- No other code changes; current package version is 10.1.8, recommend a patch release
## 2026-03-03 - 10.1.8 - fix(deps)
bump dependencies: @push.rocks/smartmetrics to ^3.0.2, @push.rocks/smartproxy to ^25.9.0, @serve.zone/remoteingress to ^4.4.0
- @push.rocks/smartmetrics: 3.0.1 -> 3.0.2 (patch)
- @push.rocks/smartproxy: 25.8.5 -> 25.9.0 (minor)
- @serve.zone/remoteingress: 4.3.0 -> 4.4.0 (minor)
## 2026-03-03 - 10.1.7 - fix(ops-view-apitokens)
use correct lucide icon name for roll/rotate actions in API tokens view
- Updated iconName from 'lucide:rotate-cw' to 'lucide:rotateCw' in ts_web/elements/ops-view-apitokens.ts (two occurrences) to match lucide icon naming and ensure icons render correctly
- Non-functional UI fix; no API or behavior changes
## 2026-03-02 - 10.1.6 - fix(ts_web)
use actionContext for dispatches in web state actions and bump @push.rocks/smartstate to ^2.2.0
- Action handlers in ts_web/appstate.ts now accept an actionContext parameter and call await actionContext.dispatch(...) instead of using statePartArg.dispatchAction(...).
- Handlers return the awaited dispatch result (ensuring callers receive refreshed state) instead of returning the previous statePartArg.getState().
- Dependency bumped in package.json: @push.rocks/smartstate from ^2.1.1 to ^2.2.0.
- Playwright artifacts (logs and page screenshots) were added under .playwright-mcp.
## 2026-03-02 - 10.1.5 - fix(monitoring) ## 2026-03-02 - 10.1.5 - fix(monitoring)
use a per-second ring buffer for DNS query metrics, improve DNS logging rate limiting and security event aggregation, and bump smartmta dependency use a per-second ring buffer for DNS query metrics, improve DNS logging rate limiting and security event aggregation, and bump smartmta dependency

View File

@@ -1,7 +1,7 @@
{ {
"name": "@serve.zone/dcrouter", "name": "@serve.zone/dcrouter",
"private": false, "private": false,
"version": "10.1.5", "version": "11.0.7",
"description": "A multifaceted routing service handling mail and SMS delivery functions.", "description": "A multifaceted routing service handling mail and SMS delivery functions.",
"type": "module", "type": "module",
"exports": { "exports": {
@@ -19,21 +19,22 @@
"watch": "tswatch" "watch": "tswatch"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^4.1.2", "@git.zone/tsbuild": "^4.1.3",
"@git.zone/tsbundle": "^2.9.0", "@git.zone/tsbundle": "^2.9.0",
"@git.zone/tsrun": "^2.0.1", "@git.zone/tsrun": "^2.0.1",
"@git.zone/tstest": "^3.1.8", "@git.zone/tstest": "^3.2.0",
"@git.zone/tswatch": "^3.2.0", "@git.zone/tswatch": "^3.2.5",
"@types/node": "^25.3.3" "@types/node": "^25.3.3"
}, },
"dependencies": { "dependencies": {
"@api.global/typedrequest": "^3.2.7", "@api.global/typedrequest": "^3.3.0",
"@api.global/typedrequest-interfaces": "^3.0.19", "@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", "@api.global/typedsocket": "^4.1.2",
"@apiclient.xyz/cloudflare": "^7.1.0", "@apiclient.xyz/cloudflare": "^7.1.0",
"@design.estate/dees-catalog": "^3.43.3", "@design.estate/dees-catalog": "^3.43.3",
"@design.estate/dees-element": "^2.1.6", "@design.estate/dees-element": "^2.1.6",
"@push.rocks/lik": "^6.3.1",
"@push.rocks/projectinfo": "^5.0.2", "@push.rocks/projectinfo": "^5.0.2",
"@push.rocks/qenv": "^6.1.3", "@push.rocks/qenv": "^6.1.3",
"@push.rocks/smartacme": "^9.1.3", "@push.rocks/smartacme": "^9.1.3",
@@ -43,21 +44,21 @@
"@push.rocks/smartguard": "^3.1.0", "@push.rocks/smartguard": "^3.1.0",
"@push.rocks/smartjwt": "^2.2.1", "@push.rocks/smartjwt": "^2.2.1",
"@push.rocks/smartlog": "^3.2.1", "@push.rocks/smartlog": "^3.2.1",
"@push.rocks/smartmetrics": "^3.0.1", "@push.rocks/smartmetrics": "^3.0.2",
"@push.rocks/smartmongo": "^5.1.0", "@push.rocks/smartmongo": "^5.1.0",
"@push.rocks/smartmta": "^5.3.1", "@push.rocks/smartmta": "^5.3.1",
"@push.rocks/smartnetwork": "^4.4.0", "@push.rocks/smartnetwork": "^4.4.0",
"@push.rocks/smartpath": "^6.0.0", "@push.rocks/smartpath": "^6.0.0",
"@push.rocks/smartpromise": "^4.2.3", "@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartproxy": "^25.8.5", "@push.rocks/smartproxy": "^25.9.1",
"@push.rocks/smartradius": "^1.1.1", "@push.rocks/smartradius": "^1.1.1",
"@push.rocks/smartrequest": "^5.0.1", "@push.rocks/smartrequest": "^5.0.1",
"@push.rocks/smartrx": "^3.0.10", "@push.rocks/smartrx": "^3.0.10",
"@push.rocks/smartstate": "^2.1.1", "@push.rocks/smartstate": "^2.2.0",
"@push.rocks/smartunique": "^3.0.9", "@push.rocks/smartunique": "^3.0.9",
"@serve.zone/catalog": "^2.5.0", "@serve.zone/catalog": "^2.5.0",
"@serve.zone/interfaces": "^5.3.0", "@serve.zone/interfaces": "^5.3.0",
"@serve.zone/remoteingress": "^4.3.0", "@serve.zone/remoteingress": "^4.4.0",
"@tsclass/tsclass": "^9.3.0", "@tsclass/tsclass": "^9.3.0",
"lru-cache": "^11.2.6", "lru-cache": "^11.2.6",
"uuid": "^13.0.0" "uuid": "^13.0.0"

2658
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@ import { TypedRequest } from '@api.global/typedrequest';
import * as interfaces from '../ts_interfaces/index.js'; import * as interfaces from '../ts_interfaces/index.js';
let testDcRouter: DcRouter; let testDcRouter: DcRouter;
let adminIdentity: interfaces.data.IIdentity;
tap.test('should start DCRouter with OpsServer', async () => { tap.test('should start DCRouter with OpsServer', async () => {
testDcRouter = new DcRouter({ testDcRouter = new DcRouter({
@@ -15,6 +16,21 @@ tap.test('should start DCRouter with OpsServer', async () => {
expect(testDcRouter.opsServer).toBeInstanceOf(Object); 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 () => { tap.test('should respond to health status request', async () => {
const healthRequest = new TypedRequest<interfaces.requests.IReq_GetHealthStatus>( const healthRequest = new TypedRequest<interfaces.requests.IReq_GetHealthStatus>(
'http://localhost:3000/typedrequest', 'http://localhost:3000/typedrequest',
@@ -22,7 +38,8 @@ tap.test('should respond to health status request', async () => {
); );
const response = await healthRequest.fire({ const response = await healthRequest.fire({
detailed: false identity: adminIdentity,
detailed: false,
}); });
expect(response).toHaveProperty('health'); expect(response).toHaveProperty('health');
@@ -37,7 +54,8 @@ tap.test('should respond to server statistics request', async () => {
); );
const response = await statsRequest.fire({ const response = await statsRequest.fire({
includeHistory: false identity: adminIdentity,
includeHistory: false,
}); });
expect(response).toHaveProperty('stats'); expect(response).toHaveProperty('stats');
@@ -52,7 +70,9 @@ tap.test('should respond to configuration request', async () => {
'getConfiguration' 'getConfiguration'
); );
const response = await configRequest.fire({}); const response = await configRequest.fire({
identity: adminIdentity,
});
expect(response).toHaveProperty('config'); expect(response).toHaveProperty('config');
expect(response.config).toHaveProperty('system'); expect(response.config).toHaveProperty('system');
@@ -72,7 +92,8 @@ tap.test('should handle log retrieval request', async () => {
); );
const response = await logsRequest.fire({ const response = await logsRequest.fire({
limit: 10 identity: adminIdentity,
limit: 10,
}); });
expect(response).toHaveProperty('logs'); expect(response).toHaveProperty('logs');
@@ -81,6 +102,20 @@ tap.test('should handle log retrieval request', async () => {
expect(response.logs).toBeArray(); 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 () => { tap.test('should stop DCRouter', async () => {
await testDcRouter.stop(); await testDcRouter.stop();
}); });

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>( const healthRequest = new TypedRequest<interfaces.requests.IReq_GetHealthStatus>(
'http://localhost:3000/typedrequest', 'http://localhost:3000/typedrequest',
'getHealthStatus' 'getHealthStatus'
); );
// No identity provided try {
const response = await healthRequest.fire({}); // No identity provided — should be rejected
await healthRequest.fire({} as any);
expect(response).toHaveProperty('health'); expect(true).toBeFalse(); // Should not reach here
expect(response.health.healthy).toBeTrue(); } catch (error) {
console.log('Public endpoint accessible without auth'); 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>( const configRequest = new TypedRequest<interfaces.requests.IReq_GetConfiguration>(
'http://localhost:3000/typedrequest', 'http://localhost:3000/typedrequest',
'getConfiguration' '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).toHaveProperty('config');
expect(response.config).toHaveProperty('system'); 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('cache');
expect(response.config).toHaveProperty('radius'); expect(response.config).toHaveProperty('radius');
expect(response.config).toHaveProperty('remoteIngress'); expect(response.config).toHaveProperty('remoteIngress');
console.log('Configuration read successfully'); console.log('Authenticated access to config successful');
}); });
tap.test('should stop DCRouter', async () => { tap.test('should stop DCRouter', async () => {

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '10.1.5', version: '11.0.7',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' description: 'A multifaceted routing service handling mail and SMS delivery functions.'
} }

View File

@@ -2,14 +2,20 @@ import type DcRouter from '../classes.dcrouter.js';
import * as plugins from '../plugins.js'; import * as plugins from '../plugins.js';
import * as paths from '../paths.js'; import * as paths from '../paths.js';
import * as handlers from './handlers/index.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 { export class OpsServer {
public dcRouterRef: DcRouter; public dcRouterRef: DcRouter;
public server: plugins.typedserver.utilityservers.UtilityWebsiteServer; 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(); 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 // Handler instances
public adminHandler: handlers.AdminHandler; public adminHandler: handlers.AdminHandler;
private configHandler: handlers.ConfigHandler; private configHandler: handlers.ConfigHandler;
@@ -51,10 +57,25 @@ export class OpsServer {
* Set up all TypedRequest handlers * Set up all TypedRequest handlers
*/ */
private async setupHandlers(): Promise<void> { private async setupHandlers(): Promise<void> {
// 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); 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.configHandler = new handlers.ConfigHandler(this);
this.logsHandler = new handlers.LogsHandler(this); this.logsHandler = new handlers.LogsHandler(this);
this.securityHandler = new handlers.SecurityHandler(this); this.securityHandler = new handlers.SecurityHandler(this);

View File

@@ -3,34 +3,20 @@ import type { OpsServer } from '../classes.opsserver.js';
import * as interfaces from '../../../ts_interfaces/index.js'; import * as interfaces from '../../../ts_interfaces/index.js';
export class ApiTokenHandler { export class ApiTokenHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) { constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers(); this.registerHandlers();
} }
/**
* Token management requires admin JWT only (tokens cannot manage tokens).
*/
private async requireAdmin(identity?: interfaces.data.IIdentity): Promise<string> {
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 { 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 // Create API token
this.typedrouter.addTypedHandler( router.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateApiToken>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateApiToken>(
'createApiToken', 'createApiToken',
async (dataArg) => { async (dataArg) => {
const userId = await this.requireAdmin(dataArg.identity);
const manager = this.opsServerRef.dcRouterRef.apiTokenManager; const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
if (!manager) { if (!manager) {
return { success: false, message: 'Token management not initialized' }; return { success: false, message: 'Token management not initialized' };
@@ -39,7 +25,7 @@ export class ApiTokenHandler {
dataArg.name, dataArg.name,
dataArg.scopes, dataArg.scopes,
dataArg.expiresInDays ?? null, dataArg.expiresInDays ?? null,
userId, dataArg.identity.userId,
); );
return { success: true, tokenId: result.id, tokenValue: result.rawToken }; return { success: true, tokenId: result.id, tokenValue: result.rawToken };
}, },
@@ -47,11 +33,10 @@ export class ApiTokenHandler {
); );
// List API tokens // List API tokens
this.typedrouter.addTypedHandler( router.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ListApiTokens>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ListApiTokens>(
'listApiTokens', 'listApiTokens',
async (dataArg) => { async (dataArg) => {
await this.requireAdmin(dataArg.identity);
const manager = this.opsServerRef.dcRouterRef.apiTokenManager; const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
if (!manager) { if (!manager) {
return { tokens: [] }; return { tokens: [] };
@@ -62,11 +47,10 @@ export class ApiTokenHandler {
); );
// Revoke API token // Revoke API token
this.typedrouter.addTypedHandler( router.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RevokeApiToken>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RevokeApiToken>(
'revokeApiToken', 'revokeApiToken',
async (dataArg) => { async (dataArg) => {
await this.requireAdmin(dataArg.identity);
const manager = this.opsServerRef.dcRouterRef.apiTokenManager; const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
if (!manager) { if (!manager) {
return { success: false, message: 'Token management not initialized' }; return { success: false, message: 'Token management not initialized' };
@@ -78,11 +62,10 @@ export class ApiTokenHandler {
); );
// Roll API token // Roll API token
this.typedrouter.addTypedHandler( router.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RollApiToken>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RollApiToken>(
'rollApiToken', 'rollApiToken',
async (dataArg) => { async (dataArg) => {
await this.requireAdmin(dataArg.identity);
const manager = this.opsServerRef.dcRouterRef.apiTokenManager; const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
if (!manager) { if (!manager) {
return { success: false, message: 'Token management not initialized' }; return { success: false, message: 'Token management not initialized' };
@@ -97,11 +80,10 @@ export class ApiTokenHandler {
); );
// Toggle API token // Toggle API token
this.typedrouter.addTypedHandler( router.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ToggleApiToken>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ToggleApiToken>(
'toggleApiToken', 'toggleApiToken',
async (dataArg) => { async (dataArg) => {
await this.requireAdmin(dataArg.identity);
const manager = this.opsServerRef.dcRouterRef.apiTokenManager; const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
if (!manager) { if (!manager) {
return { success: false, message: 'Token management not initialized' }; return { success: false, message: 'Token management not initialized' };

View File

@@ -3,16 +3,18 @@ import type { OpsServer } from '../classes.opsserver.js';
import * as interfaces from '../../../ts_interfaces/index.js'; import * as interfaces from '../../../ts_interfaces/index.js';
export class CertificateHandler { export class CertificateHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) { constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers(); this.registerHandlers();
} }
private registerHandlers(): void { private registerHandlers(): void {
const viewRouter = this.opsServerRef.viewRouter;
const adminRouter = this.opsServerRef.adminRouter;
// ---- Read endpoints (viewRouter — valid identity required via middleware) ----
// Get Certificate Overview // Get Certificate Overview
this.typedrouter.addTypedHandler( viewRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetCertificateOverview>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetCertificateOverview>(
'getCertificateOverview', 'getCertificateOverview',
async (dataArg) => { async (dataArg) => {
@@ -23,8 +25,10 @@ export class CertificateHandler {
) )
); );
// ---- Write endpoints (adminRouter — admin identity required via middleware) ----
// Legacy route-based reprovision (backward compat) // Legacy route-based reprovision (backward compat)
this.typedrouter.addTypedHandler( adminRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ReprovisionCertificate>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ReprovisionCertificate>(
'reprovisionCertificate', 'reprovisionCertificate',
async (dataArg) => { async (dataArg) => {
@@ -34,7 +38,7 @@ export class CertificateHandler {
); );
// Domain-based reprovision (preferred) // Domain-based reprovision (preferred)
this.typedrouter.addTypedHandler( adminRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ReprovisionCertificateDomain>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ReprovisionCertificateDomain>(
'reprovisionCertificateDomain', 'reprovisionCertificateDomain',
async (dataArg) => { async (dataArg) => {
@@ -44,7 +48,7 @@ export class CertificateHandler {
); );
// Delete certificate // Delete certificate
this.typedrouter.addTypedHandler( adminRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteCertificate>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteCertificate>(
'deleteCertificate', 'deleteCertificate',
async (dataArg) => { async (dataArg) => {
@@ -54,7 +58,7 @@ export class CertificateHandler {
); );
// Export certificate // Export certificate
this.typedrouter.addTypedHandler( adminRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ExportCertificate>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ExportCertificate>(
'exportCertificate', 'exportCertificate',
async (dataArg) => { async (dataArg) => {
@@ -64,7 +68,7 @@ export class CertificateHandler {
); );
// Import certificate // Import certificate
this.typedrouter.addTypedHandler( adminRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ImportCertificate>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ImportCertificate>(
'importCertificate', 'importCertificate',
async (dataArg) => { async (dataArg) => {

View File

@@ -4,17 +4,16 @@ import type { OpsServer } from '../classes.opsserver.js';
import * as interfaces from '../../../ts_interfaces/index.js'; import * as interfaces from '../../../ts_interfaces/index.js';
export class ConfigHandler { export class ConfigHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) { constructor(private opsServerRef: OpsServer) {
// Add this handler's router to the parent
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers(); this.registerHandlers();
} }
private registerHandlers(): void { private registerHandlers(): void {
// Config endpoint registers directly on viewRouter (valid identity required via middleware)
const router = this.opsServerRef.viewRouter;
// Get Configuration Handler (read-only) // Get Configuration Handler (read-only)
this.typedrouter.addTypedHandler( router.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetConfiguration>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetConfiguration>(
'getConfiguration', 'getConfiguration',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {

View File

@@ -3,17 +3,18 @@ import type { OpsServer } from '../classes.opsserver.js';
import * as interfaces from '../../../ts_interfaces/index.js'; import * as interfaces from '../../../ts_interfaces/index.js';
export class EmailOpsHandler { export class EmailOpsHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) { constructor(private opsServerRef: OpsServer) {
// Add this handler's router to the parent
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers(); this.registerHandlers();
} }
private registerHandlers(): void { 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 // Get All Emails Handler
this.typedrouter.addTypedHandler( viewRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAllEmails>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAllEmails>(
'getAllEmails', 'getAllEmails',
async (dataArg) => { async (dataArg) => {
@@ -24,7 +25,7 @@ export class EmailOpsHandler {
); );
// Get Email Detail Handler // Get Email Detail Handler
this.typedrouter.addTypedHandler( viewRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetEmailDetail>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetEmailDetail>(
'getEmailDetail', 'getEmailDetail',
async (dataArg) => { async (dataArg) => {
@@ -34,8 +35,10 @@ export class EmailOpsHandler {
) )
); );
// ---- Write endpoints (adminRouter) ----
// Resend Failed Email Handler // Resend Failed Email Handler
this.typedrouter.addTypedHandler( adminRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ResendEmail>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ResendEmail>(
'resendEmail', 'resendEmail',
async (dataArg) => { async (dataArg) => {

View File

@@ -10,12 +10,9 @@ let logPushDestinationInstalled = false;
let currentOpsServerRef: OpsServer | null = null; let currentOpsServerRef: OpsServer | null = null;
export class LogsHandler { export class LogsHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
private activeStreamStops: Set<() => void> = new Set(); private activeStreamStops: Set<() => void> = new Set();
constructor(private opsServerRef: OpsServer) { constructor(private opsServerRef: OpsServer) {
// Add this handler's router to the parent
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers(); this.registerHandlers();
this.setupLogPushDestination(); this.setupLogPushDestination();
} }
@@ -35,8 +32,11 @@ export class LogsHandler {
} }
private registerHandlers(): void { private registerHandlers(): void {
// All log endpoints register directly on viewRouter (valid identity required via middleware)
const router = this.opsServerRef.viewRouter;
// Get Recent Logs Handler // Get Recent Logs Handler
this.typedrouter.addTypedHandler( router.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRecentLogs>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRecentLogs>(
'getRecentLogs', 'getRecentLogs',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -59,7 +59,7 @@ export class LogsHandler {
); );
// Get Log Stream Handler // Get Log Stream Handler
this.typedrouter.addTypedHandler( router.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetLogStream>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetLogStream>(
'getLogStream', 'getLogStream',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {

View File

@@ -3,21 +3,19 @@ import type { OpsServer } from '../classes.opsserver.js';
import * as interfaces from '../../../ts_interfaces/index.js'; import * as interfaces from '../../../ts_interfaces/index.js';
export class RadiusHandler { export class RadiusHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) { constructor(private opsServerRef: OpsServer) {
// Add this handler's router to the parent
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers(); this.registerHandlers();
} }
private registerHandlers(): void { private registerHandlers(): void {
const viewRouter = this.opsServerRef.viewRouter;
const adminRouter = this.opsServerRef.adminRouter;
// ======================================================================== // ========================================================================
// RADIUS Client Management // RADIUS Client Management
// ======================================================================== // ========================================================================
// Get all RADIUS clients // Get all RADIUS clients (read)
this.typedrouter.addTypedHandler( viewRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusClients>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusClients>(
'getRadiusClients', 'getRadiusClients',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -40,8 +38,8 @@ export class RadiusHandler {
) )
); );
// Add or update a RADIUS client // Add or update a RADIUS client (write)
this.typedrouter.addTypedHandler( adminRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SetRadiusClient>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SetRadiusClient>(
'setRadiusClient', 'setRadiusClient',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -61,8 +59,8 @@ export class RadiusHandler {
) )
); );
// Remove a RADIUS client // Remove a RADIUS client (write)
this.typedrouter.addTypedHandler( adminRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RemoveRadiusClient>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RemoveRadiusClient>(
'removeRadiusClient', 'removeRadiusClient',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -85,8 +83,8 @@ export class RadiusHandler {
// VLAN Mapping Management // VLAN Mapping Management
// ======================================================================== // ========================================================================
// Get all VLAN mappings // Get all VLAN mappings (read)
this.typedrouter.addTypedHandler( viewRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetVlanMappings>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetVlanMappings>(
'getVlanMappings', 'getVlanMappings',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -121,8 +119,8 @@ export class RadiusHandler {
) )
); );
// Add or update a VLAN mapping // Add or update a VLAN mapping (write)
this.typedrouter.addTypedHandler( adminRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SetVlanMapping>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SetVlanMapping>(
'setVlanMapping', 'setVlanMapping',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -153,8 +151,8 @@ export class RadiusHandler {
) )
); );
// Remove a VLAN mapping // Remove a VLAN mapping (write)
this.typedrouter.addTypedHandler( adminRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RemoveVlanMapping>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RemoveVlanMapping>(
'removeVlanMapping', 'removeVlanMapping',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -174,8 +172,8 @@ export class RadiusHandler {
) )
); );
// Update VLAN configuration // Update VLAN configuration (write)
this.typedrouter.addTypedHandler( adminRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateVlanConfig>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateVlanConfig>(
'updateVlanConfig', 'updateVlanConfig',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -206,8 +204,8 @@ export class RadiusHandler {
) )
); );
// Test VLAN assignment // Test VLAN assignment (read)
this.typedrouter.addTypedHandler( viewRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_TestVlanAssignment>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_TestVlanAssignment>(
'testVlanAssignment', 'testVlanAssignment',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -240,8 +238,8 @@ export class RadiusHandler {
// Accounting / Session Management // Accounting / Session Management
// ======================================================================== // ========================================================================
// Get active sessions // Get active sessions (read)
this.typedrouter.addTypedHandler( viewRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusSessions>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusSessions>(
'getRadiusSessions', 'getRadiusSessions',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -289,8 +287,8 @@ export class RadiusHandler {
) )
); );
// Disconnect a session // Disconnect a session (write)
this.typedrouter.addTypedHandler( adminRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DisconnectRadiusSession>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DisconnectRadiusSession>(
'disconnectRadiusSession', 'disconnectRadiusSession',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -314,8 +312,8 @@ export class RadiusHandler {
) )
); );
// Get accounting summary // Get accounting summary (read)
this.typedrouter.addTypedHandler( viewRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusAccountingSummary>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusAccountingSummary>(
'getRadiusAccountingSummary', 'getRadiusAccountingSummary',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -351,8 +349,8 @@ export class RadiusHandler {
// Statistics // Statistics
// ======================================================================== // ========================================================================
// Get RADIUS statistics // Get RADIUS statistics (read)
this.typedrouter.addTypedHandler( viewRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusStatistics>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusStatistics>(
'getRadiusStatistics', 'getRadiusStatistics',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {

View File

@@ -3,16 +3,18 @@ import type { OpsServer } from '../classes.opsserver.js';
import * as interfaces from '../../../ts_interfaces/index.js'; import * as interfaces from '../../../ts_interfaces/index.js';
export class RemoteIngressHandler { export class RemoteIngressHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) { constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers(); this.registerHandlers();
} }
private registerHandlers(): void { 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 // Get all remote ingress edges
this.typedrouter.addTypedHandler( viewRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngresses>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngresses>(
'getRemoteIngresses', 'getRemoteIngresses',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -36,8 +38,10 @@ export class RemoteIngressHandler {
), ),
); );
// ---- Write endpoints (adminRouter) ----
// Create a new remote ingress edge // Create a new remote ingress edge
this.typedrouter.addTypedHandler( adminRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateRemoteIngress>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateRemoteIngress>(
'createRemoteIngress', 'createRemoteIngress',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -69,7 +73,7 @@ export class RemoteIngressHandler {
); );
// Delete a remote ingress edge // Delete a remote ingress edge
this.typedrouter.addTypedHandler( adminRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteRemoteIngress>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteRemoteIngress>(
'deleteRemoteIngress', 'deleteRemoteIngress',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -94,7 +98,7 @@ export class RemoteIngressHandler {
); );
// Update a remote ingress edge // Update a remote ingress edge
this.typedrouter.addTypedHandler( adminRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateRemoteIngress>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateRemoteIngress>(
'updateRemoteIngress', 'updateRemoteIngress',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -138,7 +142,7 @@ export class RemoteIngressHandler {
); );
// Regenerate secret for an edge // Regenerate secret for an edge
this.typedrouter.addTypedHandler( adminRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RegenerateRemoteIngressSecret>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RegenerateRemoteIngressSecret>(
'regenerateRemoteIngressSecret', 'regenerateRemoteIngressSecret',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -164,8 +168,8 @@ export class RemoteIngressHandler {
), ),
); );
// Get runtime status of all edges // Get runtime status of all edges (read)
this.typedrouter.addTypedHandler( viewRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngressStatus>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngressStatus>(
'getRemoteIngressStatus', 'getRemoteIngressStatus',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -178,8 +182,8 @@ export class RemoteIngressHandler {
), ),
); );
// Get a connection token for an edge // Get a connection token for an edge (write — exposes secret)
this.typedrouter.addTypedHandler( adminRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngressConnectionToken>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngressConnectionToken>(
'getRemoteIngressConnectionToken', 'getRemoteIngressConnectionToken',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {

View File

@@ -4,17 +4,16 @@ import * as interfaces from '../../../ts_interfaces/index.js';
import { MetricsManager } from '../../monitoring/index.js'; import { MetricsManager } from '../../monitoring/index.js';
export class SecurityHandler { export class SecurityHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) { constructor(private opsServerRef: OpsServer) {
// Add this handler's router to the parent
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers(); this.registerHandlers();
} }
private registerHandlers(): void { private registerHandlers(): void {
// All security endpoints register directly on viewRouter (valid identity required via middleware)
const router = this.opsServerRef.viewRouter;
// Security Metrics Handler // Security Metrics Handler
this.typedrouter.addTypedHandler( router.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSecurityMetrics>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSecurityMetrics>(
'getSecurityMetrics', 'getSecurityMetrics',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -40,7 +39,7 @@ export class SecurityHandler {
); );
// Active Connections Handler // Active Connections Handler
this.typedrouter.addTypedHandler( router.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetActiveConnections>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetActiveConnections>(
'getActiveConnections', 'getActiveConnections',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -77,8 +76,8 @@ export class SecurityHandler {
); );
// Network Stats Handler - provides comprehensive network metrics // Network Stats Handler - provides comprehensive network metrics
this.typedrouter.addTypedHandler( router.addTypedHandler(
new plugins.typedrequest.TypedHandler( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetNetworkStats>(
'getNetworkStats', 'getNetworkStats',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
// Get network stats from MetricsManager if available // Get network stats from MetricsManager if available
@@ -121,7 +120,7 @@ export class SecurityHandler {
); );
// Rate Limit Status Handler // Rate Limit Status Handler
this.typedrouter.addTypedHandler( router.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRateLimitStatus>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRateLimitStatus>(
'getRateLimitStatus', 'getRateLimitStatus',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {

View File

@@ -5,17 +5,16 @@ import { MetricsManager } from '../../monitoring/index.js';
import { SecurityLogger } from '../../security/classes.securitylogger.js'; import { SecurityLogger } from '../../security/classes.securitylogger.js';
export class StatsHandler { export class StatsHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) { constructor(private opsServerRef: OpsServer) {
// Add this handler's router to the parent
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers(); this.registerHandlers();
} }
private registerHandlers(): void { private registerHandlers(): void {
// All stats endpoints register directly on viewRouter (valid identity required via middleware)
const router = this.opsServerRef.viewRouter;
// Server Statistics Handler // Server Statistics Handler
this.typedrouter.addTypedHandler( router.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServerStatistics>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServerStatistics>(
'getServerStatistics', 'getServerStatistics',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -38,7 +37,7 @@ export class StatsHandler {
); );
// Email Statistics Handler // Email Statistics Handler
this.typedrouter.addTypedHandler( router.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetEmailStatistics>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetEmailStatistics>(
'getEmailStatistics', 'getEmailStatistics',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -77,7 +76,7 @@ export class StatsHandler {
); );
// DNS Statistics Handler // DNS Statistics Handler
this.typedrouter.addTypedHandler( router.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDnsStatistics>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDnsStatistics>(
'getDnsStatistics', 'getDnsStatistics',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -114,7 +113,7 @@ export class StatsHandler {
); );
// Queue Status Handler // Queue Status Handler
this.typedrouter.addTypedHandler( router.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetQueueStatus>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetQueueStatus>(
'getQueueStatus', 'getQueueStatus',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -142,7 +141,7 @@ export class StatsHandler {
); );
// Health Status Handler // Health Status Handler
this.typedrouter.addTypedHandler( router.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetHealthStatus>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetHealthStatus>(
'getHealthStatus', 'getHealthStatus',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
@@ -167,7 +166,7 @@ export class StatsHandler {
); );
// Combined Metrics Handler - More efficient for frontend polling // Combined Metrics Handler - More efficient for frontend polling
this.typedrouter.addTypedHandler( router.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetCombinedMetrics>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetCombinedMetrics>(
'getCombinedMetrics', 'getCombinedMetrics',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {

View File

@@ -22,11 +22,12 @@ export async function passGuards<T extends { identity?: any }>(
} }
/** /**
* 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<T extends { identity?: interfaces.data.IIdentity }>( export async function requireAdminIdentity(
adminHandler: AdminHandler, adminHandler: AdminHandler,
dataArg: T dataArg: { identity?: interfaces.data.IIdentity }
): Promise<void> { ): Promise<void> {
if (!dataArg.identity) { if (!dataArg.identity) {
throw new plugins.typedrequest.TypedResponseError('No identity provided'); throw new plugins.typedrequest.TypedResponseError('No identity provided');
@@ -39,11 +40,12 @@ export async function requireAdminIdentity<T extends { identity?: interfaces.dat
} }
/** /**
* Helper to check valid identity in handlers * Helper to check valid identity in handlers and middleware.
* Accepts both optional and required identity for flexibility.
*/ */
export async function requireValidIdentity<T extends { identity?: interfaces.data.IIdentity }>( export async function requireValidIdentity(
adminHandler: AdminHandler, adminHandler: AdminHandler,
dataArg: T dataArg: { identity?: interfaces.data.IIdentity }
): Promise<void> { ): Promise<void> {
if (!dataArg.identity) { if (!dataArg.identity) {
throw new plugins.typedrequest.TypedResponseError('No identity provided'); throw new plugins.typedrequest.TypedResponseError('No identity provided');

View File

@@ -16,7 +16,7 @@ export interface IReq_CreateApiToken extends plugins.typedrequestInterfaces.impl
> { > {
method: 'createApiToken'; method: 'createApiToken';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
name: string; name: string;
scopes: TApiTokenScope[]; scopes: TApiTokenScope[];
expiresInDays?: number | null; expiresInDays?: number | null;
@@ -38,7 +38,7 @@ export interface IReq_ListApiTokens extends plugins.typedrequestInterfaces.imple
> { > {
method: 'listApiTokens'; method: 'listApiTokens';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
}; };
response: { response: {
tokens: IApiTokenInfo[]; tokens: IApiTokenInfo[];
@@ -54,7 +54,7 @@ export interface IReq_RevokeApiToken extends plugins.typedrequestInterfaces.impl
> { > {
method: 'revokeApiToken'; method: 'revokeApiToken';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
id: string; id: string;
}; };
response: { response: {
@@ -73,7 +73,7 @@ export interface IReq_RollApiToken extends plugins.typedrequestInterfaces.implem
> { > {
method: 'rollApiToken'; method: 'rollApiToken';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
id: string; id: string;
}; };
response: { response: {
@@ -92,7 +92,7 @@ export interface IReq_ToggleApiToken extends plugins.typedrequestInterfaces.impl
> { > {
method: 'toggleApiToken'; method: 'toggleApiToken';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
id: string; id: string;
enabled: boolean; enabled: boolean;
}; };

View File

@@ -28,7 +28,7 @@ export interface IReq_GetCertificateOverview extends plugins.typedrequestInterfa
> { > {
method: 'getCertificateOverview'; method: 'getCertificateOverview';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
}; };
response: { response: {
certificates: ICertificateInfo[]; certificates: ICertificateInfo[];
@@ -50,7 +50,7 @@ export interface IReq_ReprovisionCertificate extends plugins.typedrequestInterfa
> { > {
method: 'reprovisionCertificate'; method: 'reprovisionCertificate';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
routeName: string; routeName: string;
}; };
response: { response: {
@@ -66,7 +66,7 @@ export interface IReq_ReprovisionCertificateDomain extends plugins.typedrequestI
> { > {
method: 'reprovisionCertificateDomain'; method: 'reprovisionCertificateDomain';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
domain: string; domain: string;
}; };
response: { response: {
@@ -82,7 +82,7 @@ export interface IReq_DeleteCertificate extends plugins.typedrequestInterfaces.i
> { > {
method: 'deleteCertificate'; method: 'deleteCertificate';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
domain: string; domain: string;
}; };
response: { response: {
@@ -98,7 +98,7 @@ export interface IReq_ExportCertificate extends plugins.typedrequestInterfaces.i
> { > {
method: 'exportCertificate'; method: 'exportCertificate';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
domain: string; domain: string;
}; };
response: { response: {
@@ -123,7 +123,7 @@ export interface IReq_ImportCertificate extends plugins.typedrequestInterfaces.i
> { > {
method: 'importCertificate'; method: 'importCertificate';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
cert: { cert: {
id: string; id: string;
domainName: string; domainName: string;

View File

@@ -81,7 +81,7 @@ export interface IReq_GetConfiguration extends plugins.typedrequestInterfaces.im
> { > {
method: 'getConfiguration'; method: 'getConfiguration';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
section?: string; section?: string;
}; };
response: { response: {

View File

@@ -68,7 +68,7 @@ export interface IReq_GetAllEmails extends plugins.typedrequestInterfaces.implem
> { > {
method: 'getAllEmails'; method: 'getAllEmails';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
}; };
response: { response: {
emails: IEmail[]; emails: IEmail[];
@@ -84,7 +84,7 @@ export interface IReq_GetEmailDetail extends plugins.typedrequestInterfaces.impl
> { > {
method: 'getEmailDetail'; method: 'getEmailDetail';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
emailId: string; emailId: string;
}; };
response: { response: {
@@ -101,7 +101,7 @@ export interface IReq_ResendEmail extends plugins.typedrequestInterfaces.impleme
> { > {
method: 'resendEmail'; method: 'resendEmail';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
emailId: string; emailId: string;
}; };
response: { response: {

View File

@@ -9,7 +9,7 @@ export interface IReq_GetRecentLogs extends plugins.typedrequestInterfaces.imple
> { > {
method: 'getRecentLogs'; method: 'getRecentLogs';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
level?: 'debug' | 'info' | 'warn' | 'error'; level?: 'debug' | 'info' | 'warn' | 'error';
category?: 'smtp' | 'dns' | 'security' | 'system' | 'email'; category?: 'smtp' | 'dns' | 'security' | 'system' | 'email';
limit?: number; limit?: number;
@@ -31,7 +31,7 @@ export interface IReq_GetLogStream extends plugins.typedrequestInterfaces.implem
> { > {
method: 'getLogStream'; method: 'getLogStream';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
follow?: boolean; follow?: boolean;
filters?: { filters?: {
level?: string[]; level?: string[];

View File

@@ -14,7 +14,7 @@ export interface IReq_GetRadiusClients extends plugins.typedrequestInterfaces.im
> { > {
method: 'getRadiusClients'; method: 'getRadiusClients';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
}; };
response: { response: {
clients: Array<{ clients: Array<{
@@ -35,7 +35,7 @@ export interface IReq_SetRadiusClient extends plugins.typedrequestInterfaces.imp
> { > {
method: 'setRadiusClient'; method: 'setRadiusClient';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
client: { client: {
name: string; name: string;
ipRange: string; ipRange: string;
@@ -59,7 +59,7 @@ export interface IReq_RemoveRadiusClient extends plugins.typedrequestInterfaces.
> { > {
method: 'removeRadiusClient'; method: 'removeRadiusClient';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
name: string; name: string;
}; };
response: { response: {
@@ -81,7 +81,7 @@ export interface IReq_GetVlanMappings extends plugins.typedrequestInterfaces.imp
> { > {
method: 'getVlanMappings'; method: 'getVlanMappings';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
}; };
response: { response: {
mappings: Array<{ mappings: Array<{
@@ -108,7 +108,7 @@ export interface IReq_SetVlanMapping extends plugins.typedrequestInterfaces.impl
> { > {
method: 'setVlanMapping'; method: 'setVlanMapping';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
mapping: { mapping: {
mac: string; mac: string;
vlan: number; vlan: number;
@@ -139,7 +139,7 @@ export interface IReq_RemoveVlanMapping extends plugins.typedrequestInterfaces.i
> { > {
method: 'removeVlanMapping'; method: 'removeVlanMapping';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
mac: string; mac: string;
}; };
response: { response: {
@@ -157,7 +157,7 @@ export interface IReq_UpdateVlanConfig extends plugins.typedrequestInterfaces.im
> { > {
method: 'updateVlanConfig'; method: 'updateVlanConfig';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
defaultVlan?: number; defaultVlan?: number;
allowUnknownMacs?: boolean; allowUnknownMacs?: boolean;
}; };
@@ -179,7 +179,7 @@ export interface IReq_TestVlanAssignment extends plugins.typedrequestInterfaces.
> { > {
method: 'testVlanAssignment'; method: 'testVlanAssignment';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
mac: string; mac: string;
}; };
response: { response: {
@@ -207,7 +207,7 @@ export interface IReq_GetRadiusSessions extends plugins.typedrequestInterfaces.i
> { > {
method: 'getRadiusSessions'; method: 'getRadiusSessions';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
filter?: { filter?: {
username?: string; username?: string;
nasIpAddress?: string; nasIpAddress?: string;
@@ -243,7 +243,7 @@ export interface IReq_DisconnectRadiusSession extends plugins.typedrequestInterf
> { > {
method: 'disconnectRadiusSession'; method: 'disconnectRadiusSession';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
sessionId: string; sessionId: string;
reason?: string; reason?: string;
}; };
@@ -262,7 +262,7 @@ export interface IReq_GetRadiusAccountingSummary extends plugins.typedrequestInt
> { > {
method: 'getRadiusAccountingSummary'; method: 'getRadiusAccountingSummary';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
startTime: number; startTime: number;
endTime: number; endTime: number;
}; };
@@ -296,7 +296,7 @@ export interface IReq_GetRadiusStatistics extends plugins.typedrequestInterfaces
> { > {
method: 'getRadiusStatistics'; method: 'getRadiusStatistics';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
}; };
response: { response: {
stats: { stats: {

View File

@@ -15,7 +15,7 @@ export interface IReq_CreateRemoteIngress extends plugins.typedrequestInterfaces
> { > {
method: 'createRemoteIngress'; method: 'createRemoteIngress';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
name: string; name: string;
listenPorts?: number[]; listenPorts?: number[];
autoDerivePorts?: boolean; autoDerivePorts?: boolean;
@@ -36,7 +36,7 @@ export interface IReq_DeleteRemoteIngress extends plugins.typedrequestInterfaces
> { > {
method: 'deleteRemoteIngress'; method: 'deleteRemoteIngress';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
id: string; id: string;
}; };
response: { response: {
@@ -54,7 +54,7 @@ export interface IReq_UpdateRemoteIngress extends plugins.typedrequestInterfaces
> { > {
method: 'updateRemoteIngress'; method: 'updateRemoteIngress';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
id: string; id: string;
name?: string; name?: string;
listenPorts?: number[]; listenPorts?: number[];
@@ -77,7 +77,7 @@ export interface IReq_RegenerateRemoteIngressSecret extends plugins.typedrequest
> { > {
method: 'regenerateRemoteIngressSecret'; method: 'regenerateRemoteIngressSecret';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
id: string; id: string;
}; };
response: { response: {
@@ -95,7 +95,7 @@ export interface IReq_GetRemoteIngresses extends plugins.typedrequestInterfaces.
> { > {
method: 'getRemoteIngresses'; method: 'getRemoteIngresses';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
}; };
response: { response: {
edges: IRemoteIngress[]; edges: IRemoteIngress[];
@@ -111,7 +111,7 @@ export interface IReq_GetRemoteIngressStatus extends plugins.typedrequestInterfa
> { > {
method: 'getRemoteIngressStatus'; method: 'getRemoteIngressStatus';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
}; };
response: { response: {
statuses: IRemoteIngressStatus[]; statuses: IRemoteIngressStatus[];
@@ -128,7 +128,7 @@ export interface IReq_GetRemoteIngressConnectionToken extends plugins.typedreque
> { > {
method: 'getRemoteIngressConnectionToken'; method: 'getRemoteIngressConnectionToken';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
edgeId: string; edgeId: string;
hubHost?: string; hubHost?: string;
}; };

View File

@@ -9,7 +9,7 @@ export interface IReq_GetServerStatistics extends plugins.typedrequestInterfaces
> { > {
method: 'getServerStatistics'; method: 'getServerStatistics';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
includeHistory?: boolean; includeHistory?: boolean;
timeRange?: '1h' | '6h' | '24h' | '7d' | '30d'; timeRange?: '1h' | '6h' | '24h' | '7d' | '30d';
}; };
@@ -29,7 +29,7 @@ export interface IReq_GetEmailStatistics extends plugins.typedrequestInterfaces.
> { > {
method: 'getEmailStatistics'; method: 'getEmailStatistics';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
timeRange?: '1h' | '6h' | '24h' | '7d' | '30d'; timeRange?: '1h' | '6h' | '24h' | '7d' | '30d';
domain?: string; domain?: string;
includeDetails?: boolean; includeDetails?: boolean;
@@ -49,7 +49,7 @@ export interface IReq_GetDnsStatistics extends plugins.typedrequestInterfaces.im
> { > {
method: 'getDnsStatistics'; method: 'getDnsStatistics';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
timeRange?: '1h' | '6h' | '24h' | '7d' | '30d'; timeRange?: '1h' | '6h' | '24h' | '7d' | '30d';
domain?: string; domain?: string;
includeQueryTypes?: boolean; includeQueryTypes?: boolean;
@@ -69,7 +69,7 @@ export interface IReq_GetRateLimitStatus extends plugins.typedrequestInterfaces.
> { > {
method: 'getRateLimitStatus'; method: 'getRateLimitStatus';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
domain?: string; domain?: string;
ip?: string; ip?: string;
includeBlocked?: boolean; includeBlocked?: boolean;
@@ -91,7 +91,7 @@ export interface IReq_GetSecurityMetrics extends plugins.typedrequestInterfaces.
> { > {
method: 'getSecurityMetrics'; method: 'getSecurityMetrics';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
timeRange?: '1h' | '6h' | '24h' | '7d' | '30d'; timeRange?: '1h' | '6h' | '24h' | '7d' | '30d';
includeDetails?: boolean; includeDetails?: boolean;
}; };
@@ -112,7 +112,7 @@ export interface IReq_GetActiveConnections extends plugins.typedrequestInterface
> { > {
method: 'getActiveConnections'; method: 'getActiveConnections';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
protocol?: 'smtp' | 'smtps' | 'http' | 'https'; protocol?: 'smtp' | 'smtps' | 'http' | 'https';
state?: string; state?: string;
}; };
@@ -137,7 +137,7 @@ export interface IReq_GetQueueStatus extends plugins.typedrequestInterfaces.impl
> { > {
method: 'getQueueStatus'; method: 'getQueueStatus';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
queueName?: string; queueName?: string;
}; };
response: { response: {
@@ -153,10 +153,31 @@ export interface IReq_GetHealthStatus extends plugins.typedrequestInterfaces.imp
> { > {
method: 'getHealthStatus'; method: 'getHealthStatus';
request: { request: {
identity?: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
detailed?: boolean; detailed?: boolean;
}; };
response: { response: {
health: statsInterfaces.IHealthStatus; 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;
};
}

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '10.1.5', version: '11.0.7',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' description: 'A multifaceted routing service handling mail and SMS delivery functions.'
} }

View File

@@ -238,9 +238,12 @@ interface IActionContext {
} }
const getActionContext = (): IActionContext => { const getActionContext = (): IActionContext => {
return { const identity = loginStatePart.getState().identity;
identity: loginStatePart.getState().identity, // Treat expired JWTs as no identity — prevents stale persisted sessions from firing requests
}; if (identity && identity.expiresAt && identity.expiresAt < Date.now()) {
return { identity: null };
}
return { identity };
}; };
// Login Action // Login Action
@@ -271,24 +274,23 @@ export const loginAction = loginStatePart.createAction<{
} }
}); });
// Logout Action // Logout Action — always clears state, even if identity is expired/missing
export const logoutAction = loginStatePart.createAction(async (statePartArg) => { export const logoutAction = loginStatePart.createAction(async (statePartArg) => {
const context = getActionContext(); const context = getActionContext();
if (!context.identity) return statePartArg.getState();
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< // Try to notify server, but don't block logout if identity is missing/expired
interfaces.requests.IReq_AdminLogout if (context.identity) {
>('/typedrequest', 'adminLogout'); const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_AdminLogout
try { >('/typedrequest', 'adminLogout');
await typedRequest.fire({ try {
identity: context.identity, await typedRequest.fire({ identity: context.identity });
}); } catch (error) {
} catch (error) { console.error('Logout error:', error);
console.error('Logout error:', error); }
} }
// Clear login state regardless // Always clear login state
return { return {
identity: null, identity: null,
isLoggedIn: false, isLoggedIn: false,
@@ -298,8 +300,8 @@ export const logoutAction = loginStatePart.createAction(async (statePartArg) =>
// Fetch All Stats Action - Using combined endpoint for efficiency // Fetch All Stats Action - Using combined endpoint for efficiency
export const fetchAllStatsAction = statsStatePart.createAction(async (statePartArg) => { export const fetchAllStatsAction = statsStatePart.createAction(async (statePartArg) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
if (!context.identity) return currentState;
try { try {
// Use combined metrics endpoint - single request instead of 4 // Use combined metrics endpoint - single request instead of 4
@@ -340,8 +342,8 @@ export const fetchAllStatsAction = statsStatePart.createAction(async (statePartA
// Fetch Configuration Action (read-only) // Fetch Configuration Action (read-only)
export const fetchConfigurationAction = configStatePart.createAction(async (statePartArg) => { export const fetchConfigurationAction = configStatePart.createAction(async (statePartArg) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
if (!context.identity) return currentState;
try { try {
const configRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< const configRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -373,6 +375,7 @@ export const fetchRecentLogsAction = logStatePart.createAction<{
category?: 'smtp' | 'dns' | 'security' | 'system' | 'email'; category?: 'smtp' | 'dns' | 'security' | 'system' | 'email';
}>(async (statePartArg, dataArg) => { }>(async (statePartArg, dataArg) => {
const context = getActionContext(); const context = getActionContext();
if (!context.identity) return statePartArg.getState();
const logsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< const logsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetRecentLogs interfaces.requests.IReq_GetRecentLogs
@@ -448,8 +451,8 @@ export const setActiveViewAction = uiStatePart.createAction<string>(async (state
// Fetch Network Stats Action // Fetch Network Stats Action
export const fetchNetworkStatsAction = networkStatePart.createAction(async (statePartArg) => { export const fetchNetworkStatsAction = networkStatePart.createAction(async (statePartArg) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
if (!context.identity) return currentState;
try { try {
// Fetch active connections using the existing endpoint // Fetch active connections using the existing endpoint
@@ -522,6 +525,7 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (statePartArg) => { export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (statePartArg) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
if (!context.identity) return currentState;
try { try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest< const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -554,6 +558,7 @@ export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (stateP
export const fetchCertificateOverviewAction = certificateStatePart.createAction(async (statePartArg) => { export const fetchCertificateOverviewAction = certificateStatePart.createAction(async (statePartArg) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
if (!context.identity) return currentState;
try { try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest< const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -581,7 +586,7 @@ export const fetchCertificateOverviewAction = certificateStatePart.createAction(
}); });
export const reprovisionCertificateAction = certificateStatePart.createAction<string>( export const reprovisionCertificateAction = certificateStatePart.createAction<string>(
async (statePartArg, domain) => { async (statePartArg, domain, actionContext) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
@@ -596,8 +601,7 @@ export const reprovisionCertificateAction = certificateStatePart.createAction<st
}); });
// Re-fetch overview after reprovisioning // Re-fetch overview after reprovisioning
await certificateStatePart.dispatchAction(fetchCertificateOverviewAction, null); return await actionContext.dispatch(fetchCertificateOverviewAction, null);
return statePartArg.getState();
} catch (error) { } catch (error) {
return { return {
...currentState, ...currentState,
@@ -608,7 +612,7 @@ export const reprovisionCertificateAction = certificateStatePart.createAction<st
); );
export const deleteCertificateAction = certificateStatePart.createAction<string>( export const deleteCertificateAction = certificateStatePart.createAction<string>(
async (statePartArg, domain) => { async (statePartArg, domain, actionContext) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
@@ -623,8 +627,7 @@ export const deleteCertificateAction = certificateStatePart.createAction<string>
}); });
// Re-fetch overview after deletion // Re-fetch overview after deletion
await certificateStatePart.dispatchAction(fetchCertificateOverviewAction, null); return await actionContext.dispatch(fetchCertificateOverviewAction, null);
return statePartArg.getState();
} catch (error) { } catch (error) {
return { return {
...currentState, ...currentState,
@@ -643,7 +646,7 @@ export const importCertificateAction = certificateStatePart.createAction<{
publicKey: string; publicKey: string;
csr: string; csr: string;
}>( }>(
async (statePartArg, cert) => { async (statePartArg, cert, actionContext) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
@@ -658,8 +661,7 @@ export const importCertificateAction = certificateStatePart.createAction<{
}); });
// Re-fetch overview after import // Re-fetch overview after import
await certificateStatePart.dispatchAction(fetchCertificateOverviewAction, null); return await actionContext.dispatch(fetchCertificateOverviewAction, null);
return statePartArg.getState();
} catch (error) { } catch (error) {
return { return {
...currentState, ...currentState,
@@ -700,6 +702,7 @@ export async function fetchConnectionToken(edgeId: string) {
export const fetchRemoteIngressAction = remoteIngressStatePart.createAction(async (statePartArg) => { export const fetchRemoteIngressAction = remoteIngressStatePart.createAction(async (statePartArg) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
if (!context.identity) return currentState;
try { try {
const edgesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< const edgesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -737,7 +740,7 @@ export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
listenPorts?: number[]; listenPorts?: number[];
autoDerivePorts?: boolean; autoDerivePorts?: boolean;
tags?: string[]; tags?: string[];
}>(async (statePartArg, dataArg) => { }>(async (statePartArg, dataArg, actionContext) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
@@ -756,7 +759,7 @@ export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
if (response.success) { if (response.success) {
// Refresh the list // Refresh the list
await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null); await actionContext.dispatch(fetchRemoteIngressAction, null);
return { return {
...statePartArg.getState(), ...statePartArg.getState(),
@@ -774,7 +777,7 @@ export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
}); });
export const deleteRemoteIngressAction = remoteIngressStatePart.createAction<string>( export const deleteRemoteIngressAction = remoteIngressStatePart.createAction<string>(
async (statePartArg, edgeId) => { async (statePartArg, edgeId, actionContext) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
@@ -788,8 +791,7 @@ export const deleteRemoteIngressAction = remoteIngressStatePart.createAction<str
id: edgeId, id: edgeId,
}); });
await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null); return await actionContext.dispatch(fetchRemoteIngressAction, null);
return statePartArg.getState();
} catch (error) { } catch (error) {
return { return {
...currentState, ...currentState,
@@ -805,7 +807,7 @@ export const updateRemoteIngressAction = remoteIngressStatePart.createAction<{
listenPorts?: number[]; listenPorts?: number[];
autoDerivePorts?: boolean; autoDerivePorts?: boolean;
tags?: string[]; tags?: string[];
}>(async (statePartArg, dataArg) => { }>(async (statePartArg, dataArg, actionContext) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
@@ -823,8 +825,7 @@ export const updateRemoteIngressAction = remoteIngressStatePart.createAction<{
tags: dataArg.tags, tags: dataArg.tags,
}); });
await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null); return await actionContext.dispatch(fetchRemoteIngressAction, null);
return statePartArg.getState();
} catch (error) { } catch (error) {
return { return {
...currentState, ...currentState,
@@ -877,7 +878,7 @@ export const clearNewEdgeIdAction = remoteIngressStatePart.createAction(
export const toggleRemoteIngressAction = remoteIngressStatePart.createAction<{ export const toggleRemoteIngressAction = remoteIngressStatePart.createAction<{
id: string; id: string;
enabled: boolean; enabled: boolean;
}>(async (statePartArg, dataArg) => { }>(async (statePartArg, dataArg, actionContext) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
@@ -892,8 +893,7 @@ export const toggleRemoteIngressAction = remoteIngressStatePart.createAction<{
enabled: dataArg.enabled, enabled: dataArg.enabled,
}); });
await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null); return await actionContext.dispatch(fetchRemoteIngressAction, null);
return statePartArg.getState();
} catch (error) { } catch (error) {
return { return {
...currentState, ...currentState,
@@ -909,6 +909,7 @@ export const toggleRemoteIngressAction = remoteIngressStatePart.createAction<{
export const fetchMergedRoutesAction = routeManagementStatePart.createAction(async (statePartArg) => { export const fetchMergedRoutesAction = routeManagementStatePart.createAction(async (statePartArg) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
if (!context.identity) return currentState;
try { try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest< const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -939,7 +940,7 @@ export const fetchMergedRoutesAction = routeManagementStatePart.createAction(asy
export const createRouteAction = routeManagementStatePart.createAction<{ export const createRouteAction = routeManagementStatePart.createAction<{
route: any; route: any;
enabled?: boolean; enabled?: boolean;
}>(async (statePartArg, dataArg) => { }>(async (statePartArg, dataArg, actionContext) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
@@ -954,8 +955,7 @@ export const createRouteAction = routeManagementStatePart.createAction<{
enabled: dataArg.enabled, enabled: dataArg.enabled,
}); });
await routeManagementStatePart.dispatchAction(fetchMergedRoutesAction, null); return await actionContext.dispatch(fetchMergedRoutesAction, null);
return statePartArg.getState();
} catch (error) { } catch (error) {
return { return {
...currentState, ...currentState,
@@ -965,7 +965,7 @@ export const createRouteAction = routeManagementStatePart.createAction<{
}); });
export const deleteRouteAction = routeManagementStatePart.createAction<string>( export const deleteRouteAction = routeManagementStatePart.createAction<string>(
async (statePartArg, routeId) => { async (statePartArg, routeId, actionContext) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
@@ -979,8 +979,7 @@ export const deleteRouteAction = routeManagementStatePart.createAction<string>(
id: routeId, id: routeId,
}); });
await routeManagementStatePart.dispatchAction(fetchMergedRoutesAction, null); return await actionContext.dispatch(fetchMergedRoutesAction, null);
return statePartArg.getState();
} catch (error) { } catch (error) {
return { return {
...currentState, ...currentState,
@@ -993,7 +992,7 @@ export const deleteRouteAction = routeManagementStatePart.createAction<string>(
export const toggleRouteAction = routeManagementStatePart.createAction<{ export const toggleRouteAction = routeManagementStatePart.createAction<{
id: string; id: string;
enabled: boolean; enabled: boolean;
}>(async (statePartArg, dataArg) => { }>(async (statePartArg, dataArg, actionContext) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
@@ -1008,8 +1007,7 @@ export const toggleRouteAction = routeManagementStatePart.createAction<{
enabled: dataArg.enabled, enabled: dataArg.enabled,
}); });
await routeManagementStatePart.dispatchAction(fetchMergedRoutesAction, null); return await actionContext.dispatch(fetchMergedRoutesAction, null);
return statePartArg.getState();
} catch (error) { } catch (error) {
return { return {
...currentState, ...currentState,
@@ -1021,7 +1019,7 @@ export const toggleRouteAction = routeManagementStatePart.createAction<{
export const setRouteOverrideAction = routeManagementStatePart.createAction<{ export const setRouteOverrideAction = routeManagementStatePart.createAction<{
routeName: string; routeName: string;
enabled: boolean; enabled: boolean;
}>(async (statePartArg, dataArg) => { }>(async (statePartArg, dataArg, actionContext) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
@@ -1036,8 +1034,7 @@ export const setRouteOverrideAction = routeManagementStatePart.createAction<{
enabled: dataArg.enabled, enabled: dataArg.enabled,
}); });
await routeManagementStatePart.dispatchAction(fetchMergedRoutesAction, null); return await actionContext.dispatch(fetchMergedRoutesAction, null);
return statePartArg.getState();
} catch (error) { } catch (error) {
return { return {
...currentState, ...currentState,
@@ -1047,7 +1044,7 @@ export const setRouteOverrideAction = routeManagementStatePart.createAction<{
}); });
export const removeRouteOverrideAction = routeManagementStatePart.createAction<string>( export const removeRouteOverrideAction = routeManagementStatePart.createAction<string>(
async (statePartArg, routeName) => { async (statePartArg, routeName, actionContext) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
@@ -1061,8 +1058,7 @@ export const removeRouteOverrideAction = routeManagementStatePart.createAction<s
routeName, routeName,
}); });
await routeManagementStatePart.dispatchAction(fetchMergedRoutesAction, null); return await actionContext.dispatch(fetchMergedRoutesAction, null);
return statePartArg.getState();
} catch (error) { } catch (error) {
return { return {
...currentState, ...currentState,
@@ -1079,6 +1075,7 @@ export const removeRouteOverrideAction = routeManagementStatePart.createAction<s
export const fetchApiTokensAction = routeManagementStatePart.createAction(async (statePartArg) => { export const fetchApiTokensAction = routeManagementStatePart.createAction(async (statePartArg) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
if (!context.identity) return currentState;
try { try {
const request = new plugins.domtools.plugins.typedrequest.TypedRequest< const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
@@ -1128,7 +1125,7 @@ export async function rollApiToken(id: string) {
} }
export const revokeApiTokenAction = routeManagementStatePart.createAction<string>( export const revokeApiTokenAction = routeManagementStatePart.createAction<string>(
async (statePartArg, tokenId) => { async (statePartArg, tokenId, actionContext) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
@@ -1142,8 +1139,7 @@ export const revokeApiTokenAction = routeManagementStatePart.createAction<string
id: tokenId, id: tokenId,
}); });
await routeManagementStatePart.dispatchAction(fetchApiTokensAction, null); return await actionContext.dispatch(fetchApiTokensAction, null);
return statePartArg.getState();
} catch (error) { } catch (error) {
return { return {
...currentState, ...currentState,
@@ -1156,7 +1152,7 @@ export const revokeApiTokenAction = routeManagementStatePart.createAction<string
export const toggleApiTokenAction = routeManagementStatePart.createAction<{ export const toggleApiTokenAction = routeManagementStatePart.createAction<{
id: string; id: string;
enabled: boolean; enabled: boolean;
}>(async (statePartArg, dataArg) => { }>(async (statePartArg, dataArg, actionContext) => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
@@ -1171,8 +1167,7 @@ export const toggleApiTokenAction = routeManagementStatePart.createAction<{
enabled: dataArg.enabled, enabled: dataArg.enabled,
}); });
await routeManagementStatePart.dispatchAction(fetchApiTokensAction, null); return await actionContext.dispatch(fetchApiTokensAction, null);
return statePartArg.getState();
} catch (error) { } catch (error) {
return { return {
...currentState, ...currentState,
@@ -1233,6 +1228,7 @@ async function disconnectSocket() {
// Combined refresh action for efficient polling // Combined refresh action for efficient polling
async function dispatchCombinedRefreshAction() { async function dispatchCombinedRefreshAction() {
const context = getActionContext(); const context = getActionContext();
if (!context.identity) return;
const currentView = uiStatePart.getState().activeView; const currentView = uiStatePart.getState().activeView;
try { try {
@@ -1344,6 +1340,12 @@ async function dispatchCombinedRefreshAction() {
} }
} catch (error) { } catch (error) {
console.error('Combined refresh failed:', error); console.error('Combined refresh failed:', error);
// If the error looks like an auth failure (invalid JWT), force re-login
const errMsg = String(error);
if (errMsg.includes('invalid') || errMsg.includes('unauthorized') || errMsg.includes('401')) {
await loginStatePart.dispatchAction(logoutAction, null);
window.location.reload();
}
} }
} }

View File

@@ -1,5 +1,6 @@
import * as plugins from '../plugins.js'; import * as plugins from '../plugins.js';
import * as appstate from '../appstate.js'; import * as appstate from '../appstate.js';
import * as interfaces from '../../dist_ts_interfaces/index.js';
import { appRouter } from '../router.js'; import { appRouter } from '../router.js';
import { import {
@@ -218,13 +219,27 @@ export class OpsDashboard extends DeesElement {
// Handle initial state - check if we have a stored session that's still valid // Handle initial state - check if we have a stored session that's still valid
const loginState = appstate.loginStatePart.getState(); const loginState = appstate.loginStatePart.getState();
if (loginState.identity?.jwt) { if (loginState.identity?.jwt) {
// Verify JWT hasn't expired
if (loginState.identity.expiresAt > Date.now()) { if (loginState.identity.expiresAt > Date.now()) {
// JWT still valid, restore logged-in state // Client-side expiry looks valid — verify with server (keypair may have changed)
this.loginState = loginState; try {
await simpleLogin.switchToSlottedContent(); const verifyRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
await appstate.statsStatePart.dispatchAction(appstate.fetchAllStatsAction, null); interfaces.requests.IReq_VerifyIdentity
await appstate.configStatePart.dispatchAction(appstate.fetchConfigurationAction, null); >('/typedrequest', 'verifyIdentity');
const response = await verifyRequest.fire({ identity: loginState.identity });
if (response.valid) {
// JWT confirmed valid by server
this.loginState = loginState;
await simpleLogin.switchToSlottedContent();
await appstate.statsStatePart.dispatchAction(appstate.fetchAllStatsAction, null);
await appstate.configStatePart.dispatchAction(appstate.fetchConfigurationAction, null);
} else {
// Server rejected the JWT — clear state, show login
await appstate.loginStatePart.dispatchAction(appstate.logoutAction, null);
}
} catch {
// Server unreachable or error — clear state, show login
await appstate.loginStatePart.dispatchAction(appstate.logoutAction, null);
}
} else { } else {
// JWT expired, clear the stored state // JWT expired, clear the stored state
await appstate.loginStatePart.dispatchAction(appstate.logoutAction, null); await appstate.loginStatePart.dispatchAction(appstate.logoutAction, null);

View File

@@ -154,7 +154,7 @@ export class OpsViewApiTokens extends DeesElement {
}, },
{ {
name: 'Roll', name: 'Roll',
iconName: 'lucide:rotate-cw', iconName: 'lucide:rotateCw',
type: ['inRow', 'contextmenu'] as any, type: ['inRow', 'contextmenu'] as any,
actionFunc: async (actionData: any) => { actionFunc: async (actionData: any) => {
const token = actionData.item as interfaces.data.IApiTokenInfo; const token = actionData.item as interfaces.data.IApiTokenInfo;
@@ -306,7 +306,7 @@ export class OpsViewApiTokens extends DeesElement {
}, },
{ {
name: 'Roll Token', name: 'Roll Token',
iconName: 'lucide:rotate-cw', iconName: 'lucide:rotateCw',
action: async (modalArg: any) => { action: async (modalArg: any) => {
await modalArg.destroy(); await modalArg.destroy();
try { try {