@serve.zone/dcrouter 7.4.3 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist_serve/bundle.js +11567 -3516
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.dcrouter.d.ts +9 -0
  4. package/dist_ts/classes.dcrouter.js +27 -1
  5. package/dist_ts/config/classes.api-token-manager.d.ts +38 -0
  6. package/dist_ts/config/classes.api-token-manager.js +134 -0
  7. package/dist_ts/config/classes.route-config-manager.d.ts +35 -0
  8. package/dist_ts/config/classes.route-config-manager.js +231 -0
  9. package/dist_ts/config/index.d.ts +2 -0
  10. package/dist_ts/config/index.js +3 -1
  11. package/dist_ts/opsserver/classes.opsserver.d.ts +2 -0
  12. package/dist_ts/opsserver/classes.opsserver.js +5 -1
  13. package/dist_ts/opsserver/handlers/{email-ops.handler.d.ts → api-token.handler.d.ts} +4 -4
  14. package/dist_ts/opsserver/handlers/api-token.handler.js +66 -0
  15. package/dist_ts/opsserver/handlers/index.d.ts +2 -0
  16. package/dist_ts/opsserver/handlers/index.js +3 -1
  17. package/dist_ts/opsserver/handlers/{radius.handler.d.ts → route-management.handler.d.ts} +6 -1
  18. package/dist_ts/opsserver/handlers/route-management.handler.js +117 -0
  19. package/dist_ts_interfaces/data/index.d.ts +1 -0
  20. package/dist_ts_interfaces/data/index.js +2 -1
  21. package/dist_ts_interfaces/data/route-management.d.ts +68 -0
  22. package/dist_ts_interfaces/data/route-management.js +2 -0
  23. package/dist_ts_interfaces/requests/api-tokens.d.ts +63 -0
  24. package/dist_ts_interfaces/requests/api-tokens.js +2 -0
  25. package/dist_ts_interfaces/requests/email-ops.d.ts +51 -108
  26. package/dist_ts_interfaces/requests/index.d.ts +2 -0
  27. package/dist_ts_interfaces/requests/index.js +3 -1
  28. package/dist_ts_interfaces/requests/route-management.d.ts +114 -0
  29. package/dist_ts_interfaces/requests/route-management.js +2 -0
  30. package/dist_ts_web/00_commitinfo_data.js +1 -1
  31. package/dist_ts_web/appstate.d.ts +38 -16
  32. package/dist_ts_web/appstate.js +226 -177
  33. package/dist_ts_web/elements/index.d.ts +2 -0
  34. package/dist_ts_web/elements/index.js +3 -1
  35. package/dist_ts_web/elements/ops-dashboard.js +11 -1
  36. package/dist_ts_web/elements/ops-view-apitokens.d.ts +12 -0
  37. package/dist_ts_web/elements/ops-view-apitokens.js +306 -0
  38. package/dist_ts_web/elements/ops-view-emails.d.ts +8 -31
  39. package/dist_ts_web/elements/ops-view-emails.js +54 -769
  40. package/dist_ts_web/elements/ops-view-logs.d.ts +2 -8
  41. package/dist_ts_web/elements/ops-view-logs.js +4 -101
  42. package/dist_ts_web/elements/ops-view-routes.d.ts +12 -0
  43. package/dist_ts_web/elements/ops-view-routes.js +404 -0
  44. package/dist_ts_web/plugins.d.ts +2 -1
  45. package/dist_ts_web/plugins.js +4 -2
  46. package/dist_ts_web/router.d.ts +1 -7
  47. package/dist_ts_web/router.js +8 -82
  48. package/package.json +2 -1
  49. package/ts/00_commitinfo_data.ts +1 -1
  50. package/ts/classes.dcrouter.ts +37 -1
  51. package/ts/config/classes.api-token-manager.ts +155 -0
  52. package/ts/config/classes.route-config-manager.ts +271 -0
  53. package/ts/config/index.ts +3 -1
  54. package/ts/opsserver/classes.opsserver.ts +4 -0
  55. package/ts/opsserver/handlers/api-token.handler.ts +96 -0
  56. package/ts/opsserver/handlers/email-ops.handler.ts +177 -225
  57. package/ts/opsserver/handlers/index.ts +3 -1
  58. package/ts/opsserver/handlers/route-management.handler.ts +163 -0
  59. package/ts_web/00_commitinfo_data.ts +1 -1
  60. package/ts_web/appstate.ts +316 -222
  61. package/ts_web/elements/index.ts +2 -0
  62. package/ts_web/elements/ops-dashboard.ts +10 -0
  63. package/ts_web/elements/ops-view-apitokens.ts +281 -0
  64. package/ts_web/elements/ops-view-emails.ts +40 -749
  65. package/ts_web/elements/ops-view-logs.ts +2 -87
  66. package/ts_web/elements/ops-view-routes.ts +389 -0
  67. package/ts_web/plugins.ts +4 -0
  68. package/ts_web/router.ts +7 -82
  69. package/dist_ts/cache/classes.cache.cleaner.d.ts +0 -47
  70. package/dist_ts/cache/classes.cache.cleaner.js +0 -130
  71. package/dist_ts/cache/classes.cached.document.d.ts +0 -76
  72. package/dist_ts/cache/classes.cached.document.js +0 -100
  73. package/dist_ts/cache/classes.cachedb.d.ts +0 -60
  74. package/dist_ts/cache/classes.cachedb.js +0 -126
  75. package/dist_ts/cache/documents/classes.cached.email.d.ts +0 -125
  76. package/dist_ts/cache/documents/classes.cached.email.js +0 -337
  77. package/dist_ts/cache/documents/classes.cached.ip.reputation.d.ts +0 -119
  78. package/dist_ts/cache/documents/classes.cached.ip.reputation.js +0 -323
  79. package/dist_ts/cache/documents/index.d.ts +0 -2
  80. package/dist_ts/cache/documents/index.js +0 -3
  81. package/dist_ts/cache/index.d.ts +0 -4
  82. package/dist_ts/cache/index.js +0 -7
  83. package/dist_ts/monitoring/classes.metricscache.d.ts +0 -32
  84. package/dist_ts/monitoring/classes.metricscache.js +0 -63
  85. package/dist_ts/monitoring/classes.metricsmanager.d.ts +0 -169
  86. package/dist_ts/monitoring/classes.metricsmanager.js +0 -591
  87. package/dist_ts/monitoring/index.d.ts +0 -1
  88. package/dist_ts/monitoring/index.js +0 -2
  89. package/dist_ts/opsserver/handlers/admin.handler.d.ts +0 -31
  90. package/dist_ts/opsserver/handlers/admin.handler.js +0 -180
  91. package/dist_ts/opsserver/handlers/certificate.handler.d.ts +0 -34
  92. package/dist_ts/opsserver/handlers/certificate.handler.js +0 -419
  93. package/dist_ts/opsserver/handlers/config.handler.d.ts +0 -9
  94. package/dist_ts/opsserver/handlers/config.handler.js +0 -67
  95. package/dist_ts/opsserver/handlers/email-ops.handler.js +0 -219
  96. package/dist_ts/opsserver/handlers/logs.handler.d.ts +0 -17
  97. package/dist_ts/opsserver/handlers/logs.handler.js +0 -215
  98. package/dist_ts/opsserver/handlers/radius.handler.js +0 -296
  99. package/dist_ts/opsserver/handlers/remoteingress.handler.d.ts +0 -8
  100. package/dist_ts/opsserver/handlers/remoteingress.handler.js +0 -154
  101. package/dist_ts/opsserver/handlers/security.handler.d.ts +0 -11
  102. package/dist_ts/opsserver/handlers/security.handler.js +0 -232
  103. package/dist_ts/opsserver/handlers/stats.handler.d.ts +0 -13
  104. package/dist_ts/opsserver/handlers/stats.handler.js +0 -400
  105. package/dist_ts/security/classes.securitylogger.d.ts +0 -140
  106. package/dist_ts/security/classes.securitylogger.js +0 -235
  107. package/dist_ts/storage/classes.storagemanager.d.ts +0 -82
  108. package/dist_ts/storage/classes.storagemanager.js +0 -344
  109. package/dist_ts/storage/index.d.ts +0 -1
  110. package/dist_ts/storage/index.js +0 -3
@@ -1,9 +1,11 @@
1
1
  // @design.estate scope
2
2
  import * as deesElement from '@design.estate/dees-element';
3
3
  import * as deesCatalog from '@design.estate/dees-catalog';
4
+ // @serve.zone scope
5
+ import * as szCatalog from '@serve.zone/catalog';
4
6
  // TypedSocket for real-time push communication
5
7
  import * as typedsocket from '@api.global/typedsocket';
6
- export { deesElement, deesCatalog, typedsocket, };
8
+ export { deesElement, deesCatalog, szCatalog, typedsocket, };
7
9
  // domtools gives us TypedRequest and other utilities
8
10
  export const domtools = deesElement.domtools;
9
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX3dlYi9wbHVnaW5zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLHVCQUF1QjtBQUN2QixPQUFPLEtBQUssV0FBVyxNQUFNLDZCQUE2QixDQUFDO0FBQzNELE9BQU8sS0FBSyxXQUFXLE1BQU0sNkJBQTZCLENBQUM7QUFFM0QsK0NBQStDO0FBQy9DLE9BQU8sS0FBSyxXQUFXLE1BQU0seUJBQXlCLENBQUM7QUFFdkQsT0FBTyxFQUNMLFdBQVcsRUFDWCxXQUFXLEVBQ1gsV0FBVyxHQUNaLENBQUE7QUFFRCxxREFBcUQ7QUFDckQsTUFBTSxDQUFDLE1BQU0sUUFBUSxHQUFHLFdBQVcsQ0FBQyxRQUFRLENBQUMifQ==
11
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX3dlYi9wbHVnaW5zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLHVCQUF1QjtBQUN2QixPQUFPLEtBQUssV0FBVyxNQUFNLDZCQUE2QixDQUFDO0FBQzNELE9BQU8sS0FBSyxXQUFXLE1BQU0sNkJBQTZCLENBQUM7QUFFM0Qsb0JBQW9CO0FBQ3BCLE9BQU8sS0FBSyxTQUFTLE1BQU0scUJBQXFCLENBQUM7QUFFakQsK0NBQStDO0FBQy9DLE9BQU8sS0FBSyxXQUFXLE1BQU0seUJBQXlCLENBQUM7QUFFdkQsT0FBTyxFQUNMLFdBQVcsRUFDWCxXQUFXLEVBQ1gsU0FBUyxFQUNULFdBQVcsR0FDWixDQUFBO0FBRUQscURBQXFEO0FBQ3JELE1BQU0sQ0FBQyxNQUFNLFFBQVEsR0FBRyxXQUFXLENBQUMsUUFBUSxDQUFDIn0=
@@ -1,7 +1,5 @@
1
- export declare const validViews: readonly ["overview", "network", "emails", "logs", "configuration", "security", "certificates", "remoteingress"];
2
- export declare const validEmailFolders: readonly ["queued", "sent", "failed", "security"];
1
+ export declare const validViews: readonly ["overview", "network", "emails", "logs", "routes", "apitokens", "configuration", "security", "certificates", "remoteingress"];
3
2
  export type TValidView = typeof validViews[number];
4
- export type TValidEmailFolder = typeof validEmailFolders[number];
5
3
  declare class AppRouter {
6
4
  private router;
7
5
  private initialized;
@@ -10,15 +8,11 @@ declare class AppRouter {
10
8
  init(): void;
11
9
  private setupRoutes;
12
10
  private setupStateSync;
13
- private getExpectedPath;
14
11
  private handleInitialRoute;
15
12
  private updateViewState;
16
- private updateEmailFolder;
17
13
  navigateTo(path: string): void;
18
14
  navigateToView(view: string): void;
19
- navigateToEmailFolder(folder: string): void;
20
15
  getCurrentView(): string;
21
- getCurrentEmailFolder(): string;
22
16
  destroy(): void;
23
17
  }
24
18
  export declare const appRouter: AppRouter;
@@ -1,8 +1,7 @@
1
1
  import * as plugins from './plugins.js';
2
2
  import * as appstate from './appstate.js';
3
3
  const SmartRouter = plugins.domtools.plugins.smartrouter.SmartRouter;
4
- export const validViews = ['overview', 'network', 'emails', 'logs', 'configuration', 'security', 'certificates', 'remoteingress'];
5
- export const validEmailFolders = ['queued', 'sent', 'failed', 'security'];
4
+ export const validViews = ['overview', 'network', 'emails', 'logs', 'routes', 'apitokens', 'configuration', 'security', 'certificates', 'remoteingress'];
6
5
  class AppRouter {
7
6
  router;
8
7
  initialized = false;
@@ -19,32 +18,10 @@ class AppRouter {
19
18
  this.initialized = true;
20
19
  }
21
20
  setupRoutes() {
22
- // Main views
23
21
  for (const view of validViews) {
24
- if (view === 'emails') {
25
- // Email root - default to queued
26
- this.router.on('/emails', async () => {
27
- this.updateViewState('emails');
28
- this.updateEmailFolder('queued');
29
- });
30
- // Email with folder parameter
31
- this.router.on('/emails/:folder', async (routeInfo) => {
32
- const folder = routeInfo.params.folder;
33
- if (validEmailFolders.includes(folder)) {
34
- this.updateViewState('emails');
35
- this.updateEmailFolder(folder);
36
- }
37
- else {
38
- // Invalid folder, redirect to queued
39
- this.navigateTo('/emails/queued');
40
- }
41
- });
42
- }
43
- else {
44
- this.router.on(`/${view}`, async () => {
45
- this.updateViewState(view);
46
- });
47
- }
22
+ this.router.on(`/${view}`, async () => {
23
+ this.updateViewState(view);
24
+ });
48
25
  }
49
26
  // Root redirect
50
27
  this.router.on('/', async () => {
@@ -52,59 +29,30 @@ class AppRouter {
52
29
  });
53
30
  }
54
31
  setupStateSync() {
55
- // Sync URL when state changes programmatically (not from router)
56
32
  appstate.uiStatePart.state.subscribe((uiState) => {
57
33
  if (this.suppressStateUpdate)
58
34
  return;
59
35
  const currentPath = window.location.pathname;
60
- const expectedPath = this.getExpectedPath(uiState.activeView);
61
- // Only update URL if it doesn't match current state
62
- if (!currentPath.startsWith(expectedPath)) {
36
+ const expectedPath = `/${uiState.activeView}`;
37
+ if (currentPath !== expectedPath) {
63
38
  this.suppressStateUpdate = true;
64
- if (uiState.activeView === 'emails') {
65
- const emailState = appstate.emailOpsStatePart.getState();
66
- this.router.pushUrl(`/emails/${emailState.currentView}`);
67
- }
68
- else {
69
- this.router.pushUrl(`/${uiState.activeView}`);
70
- }
39
+ this.router.pushUrl(expectedPath);
71
40
  this.suppressStateUpdate = false;
72
41
  }
73
42
  });
74
43
  }
75
- getExpectedPath(view) {
76
- if (view === 'emails') {
77
- return '/emails';
78
- }
79
- return `/${view}`;
80
- }
81
44
  handleInitialRoute() {
82
45
  const path = window.location.pathname;
83
46
  if (!path || path === '/') {
84
- // Redirect root to overview
85
47
  this.router.pushUrl('/overview');
86
48
  }
87
49
  else {
88
- // Parse current path and update state
89
50
  const segments = path.split('/').filter(Boolean);
90
51
  const view = segments[0];
91
52
  if (validViews.includes(view)) {
92
53
  this.updateViewState(view);
93
- if (view === 'emails' && segments[1]) {
94
- const folder = segments[1];
95
- if (validEmailFolders.includes(folder)) {
96
- this.updateEmailFolder(folder);
97
- }
98
- else {
99
- this.updateEmailFolder('queued');
100
- }
101
- }
102
- else if (view === 'emails') {
103
- this.updateEmailFolder('queued');
104
- }
105
54
  }
106
55
  else {
107
- // Invalid view, redirect to overview
108
56
  this.router.pushUrl('/overview');
109
57
  }
110
58
  }
@@ -120,17 +68,6 @@ class AppRouter {
120
68
  }
121
69
  this.suppressStateUpdate = false;
122
70
  }
123
- updateEmailFolder(folder) {
124
- this.suppressStateUpdate = true;
125
- const currentState = appstate.emailOpsStatePart.getState();
126
- if (currentState.currentView !== folder) {
127
- appstate.emailOpsStatePart.setState({
128
- ...currentState,
129
- currentView: folder,
130
- });
131
- }
132
- this.suppressStateUpdate = false;
133
- }
134
71
  navigateTo(path) {
135
72
  this.router.pushUrl(path);
136
73
  }
@@ -142,24 +79,13 @@ class AppRouter {
142
79
  this.navigateTo('/overview');
143
80
  }
144
81
  }
145
- navigateToEmailFolder(folder) {
146
- if (validEmailFolders.includes(folder)) {
147
- this.navigateTo(`/emails/${folder}`);
148
- }
149
- else {
150
- this.navigateTo('/emails/queued');
151
- }
152
- }
153
82
  getCurrentView() {
154
83
  return appstate.uiStatePart.getState().activeView;
155
84
  }
156
- getCurrentEmailFolder() {
157
- return appstate.emailOpsStatePart.getState().currentView;
158
- }
159
85
  destroy() {
160
86
  this.router.destroy();
161
87
  this.initialized = false;
162
88
  }
163
89
  }
164
90
  export const appRouter = new AppRouter();
165
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHNfd2ViL3JvdXRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEtBQUssUUFBUSxNQUFNLGVBQWUsQ0FBQztBQUUxQyxNQUFNLFdBQVcsR0FBRyxPQUFPLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsV0FBVyxDQUFDO0FBRXJFLE1BQU0sQ0FBQyxNQUFNLFVBQVUsR0FBRyxDQUFDLFVBQVUsRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLE1BQU0sRUFBRSxlQUFlLEVBQUUsVUFBVSxFQUFFLGNBQWMsRUFBRSxlQUFlLENBQVUsQ0FBQztBQUMzSSxNQUFNLENBQUMsTUFBTSxpQkFBaUIsR0FBRyxDQUFDLFFBQVEsRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUFFLFVBQVUsQ0FBVSxDQUFDO0FBS25GLE1BQU0sU0FBUztJQUNMLE1BQU0sQ0FBbUM7SUFDekMsV0FBVyxHQUFHLEtBQUssQ0FBQztJQUNwQixtQkFBbUIsR0FBRyxLQUFLLENBQUM7SUFFcEM7UUFDRSxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksV0FBVyxDQUFDLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUM7SUFDbEQsQ0FBQztJQUVNLElBQUk7UUFDVCxJQUFJLElBQUksQ0FBQyxXQUFXO1lBQUUsT0FBTztRQUM3QixJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDbkIsSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO1FBQ3RCLElBQUksQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO1FBQzFCLElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDO0lBQzFCLENBQUM7SUFFTyxXQUFXO1FBQ2pCLGFBQWE7UUFDYixLQUFLLE1BQU0sSUFBSSxJQUFJLFVBQVUsRUFBRSxDQUFDO1lBQzlCLElBQUksSUFBSSxLQUFLLFFB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
91
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHNfd2ViL3JvdXRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEtBQUssUUFBUSxNQUFNLGVBQWUsQ0FBQztBQUUxQyxNQUFNLFdBQVcsR0FBRyxPQUFPLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsV0FBVyxDQUFDO0FBRXJFLE1BQU0sQ0FBQyxNQUFNLFVBQVUsR0FBRyxDQUFDLFVBQVUsRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsV0FBVyxFQUFFLGVBQWUsRUFBRSxVQUFVLEVBQUUsY0FBYyxFQUFFLGVBQWUsQ0FBVSxDQUFDO0FBSWxLLE1BQU0sU0FBUztJQUNMLE1BQU0sQ0FBbUM7SUFDekMsV0FBVyxHQUFHLEtBQUssQ0FBQztJQUNwQixtQkFBbUIsR0FBRyxLQUFLLENBQUM7SUFFcEM7UUFDRSxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksV0FBVyxDQUFDLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUM7SUFDbEQsQ0FBQztJQUVNLElBQUk7UUFDVCxJQUFJLElBQUksQ0FBQyxXQUFXO1lBQUUsT0FBTztRQUM3QixJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDbkIsSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO1FBQ3RCLElBQUksQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO1FBQzFCLElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDO0lBQzFCLENBQUM7SUFFTyxXQUFXO1FBQ2pCLEtBQUssTUFBTSxJQUFJLElBQUksVUFBVSxFQUFFLENBQUM7WUFDOUIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsSUFBSSxJQUFJLEVBQUUsRUFBRSxLQUFLLElBQUksRUFBRTtnQkFDcEMsSUFBSSxDQUFDLGVBQWUsQ0FB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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@serve.zone/dcrouter",
3
3
  "private": false,
4
- "version": "7.4.3",
4
+ "version": "8.1.0",
5
5
  "description": "A multifaceted routing service handling mail and SMS delivery functions.",
6
6
  "type": "module",
7
7
  "exports": {
@@ -55,6 +55,7 @@
55
55
  "@push.rocks/smartrx": "^3.0.10",
56
56
  "@push.rocks/smartstate": "^2.0.30",
57
57
  "@push.rocks/smartunique": "^3.0.9",
58
+ "@serve.zone/catalog": "^2.3.0",
58
59
  "@serve.zone/interfaces": "^5.3.0",
59
60
  "@serve.zone/remoteingress": "^4.0.0",
60
61
  "@tsclass/tsclass": "^9.3.0",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '7.4.3',
6
+ version: '8.1.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -22,6 +22,7 @@ import { OpsServer } from './opsserver/index.js';
22
22
  import { MetricsManager } from './monitoring/index.js';
23
23
  import { RadiusServer, type IRadiusServerConfig } from './radius/index.js';
24
24
  import { RemoteIngressManager, TunnelManager } from './remoteingress/index.js';
25
+ import { RouteConfigManager, ApiTokenManager } from './config/index.js';
25
26
 
26
27
  export interface IDcRouterOptions {
27
28
  /** Base directory for all dcrouter data. Defaults to ~/.serve.zone/dcrouter */
@@ -212,6 +213,10 @@ export class DcRouter {
212
213
  public remoteIngressManager?: RemoteIngressManager;
213
214
  public tunnelManager?: TunnelManager;
214
215
 
216
+ // Programmatic config API
217
+ public routeConfigManager?: RouteConfigManager;
218
+ public apiTokenManager?: ApiTokenManager;
219
+
215
220
  // DNS query logging rate limiter state
216
221
  private dnsLogWindow: number[] = [];
217
222
  private dnsBatchCount: number = 0;
@@ -233,6 +238,9 @@ export class DcRouter {
233
238
  // TypedRouter for API endpoints
234
239
  public typedrouter = new plugins.typedrequest.TypedRouter();
235
240
 
241
+ // Cached constructor routes (computed once during setupSmartProxy, used by RouteConfigManager)
242
+ private constructorRoutes: plugins.smartproxy.IRouteConfig[] = [];
243
+
236
244
  // Environment access
237
245
  private qenv = new plugins.qenv.Qenv('./', '.nogit/');
238
246
 
@@ -275,7 +283,17 @@ export class DcRouter {
275
283
 
276
284
  // Set up SmartProxy for HTTP/HTTPS and all traffic including email routes
277
285
  await this.setupSmartProxy();
278
-
286
+
287
+ // Initialize programmatic config API managers
288
+ this.routeConfigManager = new RouteConfigManager(
289
+ this.storageManager,
290
+ () => this.getConstructorRoutes(),
291
+ () => this.smartProxy,
292
+ );
293
+ this.apiTokenManager = new ApiTokenManager(this.storageManager);
294
+ await this.apiTokenManager.initialize();
295
+ await this.routeConfigManager.initialize();
296
+
279
297
  // Set up unified email handling if configured
280
298
  if (this.options.emailConfig) {
281
299
  await this.setupUnifiedEmailHandling();
@@ -443,6 +461,9 @@ export class DcRouter {
443
461
  challengeHandlers.push(dns01Handler);
444
462
  }
445
463
 
464
+ // Cache constructor routes for RouteConfigManager
465
+ this.constructorRoutes = [...routes];
466
+
446
467
  // If we have routes or need a basic SmartProxy instance, create it
447
468
  if (routes.length > 0 || this.options.smartProxyConfig) {
448
469
  logger.log('info', 'Setting up SmartProxy with combined configuration');
@@ -857,6 +878,14 @@ export class DcRouter {
857
878
  return names;
858
879
  }
859
880
 
881
+ /**
882
+ * Get the routes derived from constructor config (smartProxy + email + DNS).
883
+ * Used by RouteConfigManager as the "hardcoded" base.
884
+ */
885
+ public getConstructorRoutes(): plugins.smartproxy.IRouteConfig[] {
886
+ return this.constructorRoutes;
887
+ }
888
+
860
889
  public async stop() {
861
890
  logger.log('info', 'Stopping DcRouter services...');
862
891
 
@@ -929,6 +958,8 @@ export class DcRouter {
929
958
  this.smartAcme = undefined;
930
959
  this.certProvisionScheduler = undefined;
931
960
  this.remoteIngressManager = undefined;
961
+ this.routeConfigManager = undefined;
962
+ this.apiTokenManager = undefined;
932
963
  this.certificateStatusMap.clear();
933
964
 
934
965
  logger.log('info', 'All DcRouter services stopped');
@@ -960,6 +991,11 @@ export class DcRouter {
960
991
  // Start new SmartProxy with updated configuration (will include email routes if configured)
961
992
  await this.setupSmartProxy();
962
993
 
994
+ // Re-apply programmatic routes and overrides after SmartProxy restart
995
+ if (this.routeConfigManager) {
996
+ await this.routeConfigManager.initialize();
997
+ }
998
+
963
999
  logger.log('info', 'SmartProxy configuration updated');
964
1000
  }
965
1001
 
@@ -0,0 +1,155 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { logger } from '../logger.js';
3
+ import type { StorageManager } from '../storage/index.js';
4
+ import type {
5
+ IStoredApiToken,
6
+ IApiTokenInfo,
7
+ TApiTokenScope,
8
+ } from '../../ts_interfaces/data/route-management.js';
9
+
10
+ const TOKENS_PREFIX = '/config-api/tokens/';
11
+ const TOKEN_PREFIX_STR = 'dcr_';
12
+
13
+ export class ApiTokenManager {
14
+ private tokens = new Map<string, IStoredApiToken>();
15
+
16
+ constructor(private storageManager: StorageManager) {}
17
+
18
+ public async initialize(): Promise<void> {
19
+ await this.loadTokens();
20
+ if (this.tokens.size > 0) {
21
+ logger.log('info', `Loaded ${this.tokens.size} API token(s) from storage`);
22
+ }
23
+ }
24
+
25
+ // =========================================================================
26
+ // Token lifecycle
27
+ // =========================================================================
28
+
29
+ /**
30
+ * Create a new API token. Returns the raw token value (shown once).
31
+ */
32
+ public async createToken(
33
+ name: string,
34
+ scopes: TApiTokenScope[],
35
+ expiresInDays: number | null,
36
+ createdBy: string,
37
+ ): Promise<{ id: string; rawToken: string }> {
38
+ const id = plugins.uuid.v4();
39
+ const randomBytes = plugins.crypto.randomBytes(32);
40
+ const rawPayload = `${id}:${randomBytes.toString('base64url')}`;
41
+ const rawToken = `${TOKEN_PREFIX_STR}${rawPayload}`;
42
+
43
+ const tokenHash = plugins.crypto.createHash('sha256').update(rawToken).digest('hex');
44
+
45
+ const now = Date.now();
46
+ const stored: IStoredApiToken = {
47
+ id,
48
+ name,
49
+ tokenHash,
50
+ scopes,
51
+ createdAt: now,
52
+ expiresAt: expiresInDays != null ? now + expiresInDays * 86400000 : null,
53
+ lastUsedAt: null,
54
+ createdBy,
55
+ enabled: true,
56
+ };
57
+
58
+ this.tokens.set(id, stored);
59
+ await this.persistToken(stored);
60
+ logger.log('info', `API token '${name}' created (id: ${id})`);
61
+ return { id, rawToken };
62
+ }
63
+
64
+ /**
65
+ * Validate a raw token string. Returns the stored token if valid, null otherwise.
66
+ * Also updates lastUsedAt.
67
+ */
68
+ public async validateToken(rawToken: string): Promise<IStoredApiToken | null> {
69
+ if (!rawToken.startsWith(TOKEN_PREFIX_STR)) return null;
70
+
71
+ const hash = plugins.crypto.createHash('sha256').update(rawToken).digest('hex');
72
+
73
+ for (const stored of this.tokens.values()) {
74
+ if (stored.tokenHash === hash) {
75
+ if (!stored.enabled) return null;
76
+ if (stored.expiresAt !== null && stored.expiresAt < Date.now()) return null;
77
+
78
+ // Update lastUsedAt (fire and forget)
79
+ stored.lastUsedAt = Date.now();
80
+ this.persistToken(stored).catch(() => {});
81
+ return stored;
82
+ }
83
+ }
84
+ return null;
85
+ }
86
+
87
+ /**
88
+ * Check if a token has a specific scope.
89
+ */
90
+ public hasScope(token: IStoredApiToken, scope: TApiTokenScope): boolean {
91
+ return token.scopes.includes(scope);
92
+ }
93
+
94
+ /**
95
+ * List all tokens (safe info only, no hashes).
96
+ */
97
+ public listTokens(): IApiTokenInfo[] {
98
+ const result: IApiTokenInfo[] = [];
99
+ for (const stored of this.tokens.values()) {
100
+ result.push({
101
+ id: stored.id,
102
+ name: stored.name,
103
+ scopes: stored.scopes,
104
+ createdAt: stored.createdAt,
105
+ expiresAt: stored.expiresAt,
106
+ lastUsedAt: stored.lastUsedAt,
107
+ enabled: stored.enabled,
108
+ });
109
+ }
110
+ return result;
111
+ }
112
+
113
+ /**
114
+ * Revoke (delete) a token.
115
+ */
116
+ public async revokeToken(id: string): Promise<boolean> {
117
+ if (!this.tokens.has(id)) return false;
118
+ const token = this.tokens.get(id)!;
119
+ this.tokens.delete(id);
120
+ await this.storageManager.delete(`${TOKENS_PREFIX}${id}.json`);
121
+ logger.log('info', `API token '${token.name}' revoked (id: ${id})`);
122
+ return true;
123
+ }
124
+
125
+ /**
126
+ * Enable or disable a token.
127
+ */
128
+ public async toggleToken(id: string, enabled: boolean): Promise<boolean> {
129
+ const stored = this.tokens.get(id);
130
+ if (!stored) return false;
131
+ stored.enabled = enabled;
132
+ await this.persistToken(stored);
133
+ logger.log('info', `API token '${stored.name}' ${enabled ? 'enabled' : 'disabled'} (id: ${id})`);
134
+ return true;
135
+ }
136
+
137
+ // =========================================================================
138
+ // Private
139
+ // =========================================================================
140
+
141
+ private async loadTokens(): Promise<void> {
142
+ const keys = await this.storageManager.list(TOKENS_PREFIX);
143
+ for (const key of keys) {
144
+ if (!key.endsWith('.json')) continue;
145
+ const stored = await this.storageManager.getJSON<IStoredApiToken>(key);
146
+ if (stored?.id) {
147
+ this.tokens.set(stored.id, stored);
148
+ }
149
+ }
150
+ }
151
+
152
+ private async persistToken(stored: IStoredApiToken): Promise<void> {
153
+ await this.storageManager.setJSON(`${TOKENS_PREFIX}${stored.id}.json`, stored);
154
+ }
155
+ }