@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,7 +1,6 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import type { OpsServer } from '../classes.opsserver.js';
3
3
  import * as interfaces from '../../../ts_interfaces/index.js';
4
- import { SecurityLogger } from '../../security/index.js';
5
4
 
6
5
  export class EmailOpsHandler {
7
6
  public typedrouter = new plugins.typedrequest.TypedRouter();
@@ -13,68 +12,24 @@ export class EmailOpsHandler {
13
12
  }
14
13
 
15
14
  private registerHandlers(): void {
16
- // Get Queued Emails Handler
15
+ // Get All Emails Handler
17
16
  this.typedrouter.addTypedHandler(
18
- new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetQueuedEmails>(
19
- 'getQueuedEmails',
17
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAllEmails>(
18
+ 'getAllEmails',
20
19
  async (dataArg) => {
21
- const emailServer = this.opsServerRef.dcRouterRef.emailServer;
22
- if (!emailServer?.deliveryQueue) {
23
- return { items: [], total: 0 };
24
- }
25
-
26
- const queue = emailServer.deliveryQueue;
27
- const stats = queue.getStats();
28
-
29
- // Get all queue items and filter by status if provided
30
- const items = this.getQueueItems(
31
- dataArg.status,
32
- dataArg.limit || 50,
33
- dataArg.offset || 0
34
- );
35
-
36
- return {
37
- items,
38
- total: stats.queueSize,
39
- };
40
- }
41
- )
42
- );
43
-
44
- // Get Sent Emails Handler
45
- this.typedrouter.addTypedHandler(
46
- new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSentEmails>(
47
- 'getSentEmails',
48
- async (dataArg) => {
49
- const items = this.getQueueItems(
50
- 'delivered',
51
- dataArg.limit || 50,
52
- dataArg.offset || 0
53
- );
54
-
55
- return {
56
- items,
57
- total: items.length, // Note: total would ideally come from a counter
58
- };
20
+ const emails = this.getAllQueueEmails();
21
+ return { emails };
59
22
  }
60
23
  )
61
24
  );
62
25
 
63
- // Get Failed Emails Handler
26
+ // Get Email Detail Handler
64
27
  this.typedrouter.addTypedHandler(
65
- new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetFailedEmails>(
66
- 'getFailedEmails',
28
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetEmailDetail>(
29
+ 'getEmailDetail',
67
30
  async (dataArg) => {
68
- const items = this.getQueueItems(
69
- 'failed',
70
- dataArg.limit || 50,
71
- dataArg.offset || 0
72
- );
73
-
74
- return {
75
- items,
76
- total: items.length,
77
- };
31
+ const email = this.getEmailDetail(dataArg.emailId);
32
+ return { email };
78
33
  }
79
34
  )
80
35
  );
@@ -101,17 +56,12 @@ export class EmailOpsHandler {
101
56
  }
102
57
 
103
58
  try {
104
- // Re-enqueue the failed email by creating a new queue entry
105
- // with the same data but reset attempt count
106
59
  const newQueueId = await queue.enqueue(
107
60
  item.processingResult,
108
61
  item.processingMode,
109
62
  item.route
110
63
  );
111
-
112
- // Optionally remove the old failed entry
113
64
  await queue.removeItem(dataArg.emailId);
114
-
115
65
  return { success: true, newQueueId };
116
66
  } catch (error) {
117
67
  return {
@@ -122,197 +72,199 @@ export class EmailOpsHandler {
122
72
  }
123
73
  )
124
74
  );
75
+ }
125
76
 
126
- // Get Security Incidents Handler
127
- this.typedrouter.addTypedHandler(
128
- new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSecurityIncidents>(
129
- 'getSecurityIncidents',
130
- async (dataArg) => {
131
- const securityLogger = SecurityLogger.getInstance();
132
-
133
- const filter: {
134
- level?: any;
135
- type?: any;
136
- } = {};
137
-
138
- if (dataArg.level) {
139
- filter.level = dataArg.level;
140
- }
141
-
142
- if (dataArg.type) {
143
- filter.type = dataArg.type;
144
- }
145
-
146
- const incidents = securityLogger.getRecentEvents(
147
- dataArg.limit || 100,
148
- Object.keys(filter).length > 0 ? filter : undefined
149
- );
150
-
151
- return {
152
- incidents: incidents.map(event => ({
153
- timestamp: event.timestamp,
154
- level: event.level as interfaces.requests.TSecurityLogLevel,
155
- type: event.type as interfaces.requests.TSecurityEventType,
156
- message: event.message,
157
- details: event.details,
158
- ipAddress: event.ipAddress,
159
- userId: event.userId,
160
- sessionId: event.sessionId,
161
- emailId: event.emailId,
162
- domain: event.domain,
163
- action: event.action,
164
- result: event.result,
165
- success: event.success,
166
- })),
167
- total: incidents.length,
168
- };
169
- }
170
- )
171
- );
172
-
173
- // Get Bounce Records Handler
174
- this.typedrouter.addTypedHandler(
175
- new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetBounceRecords>(
176
- 'getBounceRecords',
177
- async (dataArg) => {
178
- const emailServer = this.opsServerRef.dcRouterRef.emailServer;
179
-
180
- if (!emailServer) {
181
- return { records: [], suppressionList: [], total: 0 };
182
- }
77
+ /**
78
+ * Get all queue items mapped to catalog IEmail format
79
+ */
80
+ private getAllQueueEmails(): interfaces.requests.IEmail[] {
81
+ const emailServer = this.opsServerRef.dcRouterRef.emailServer;
82
+ if (!emailServer?.deliveryQueue) {
83
+ return [];
84
+ }
183
85
 
184
- // Use smartmta's public API for bounce/suppression data
185
- const suppressionList = emailServer.getSuppressionList();
186
- const hardBouncedAddresses = emailServer.getHardBouncedAddresses();
187
-
188
- // Create bounce records from the available data
189
- const records: interfaces.requests.IBounceRecord[] = [];
190
-
191
- for (const email of hardBouncedAddresses) {
192
- const bounceInfo = emailServer.getBounceHistory(email);
193
- if (bounceInfo) {
194
- records.push({
195
- id: `bounce-${email}`,
196
- recipient: email,
197
- sender: '',
198
- domain: email.split('@')[1] || '',
199
- bounceType: (bounceInfo as any).type as interfaces.requests.TBounceType,
200
- bounceCategory: (bounceInfo as any).category as interfaces.requests.TBounceCategory,
201
- timestamp: (bounceInfo as any).lastBounce,
202
- processed: true,
203
- });
204
- }
205
- }
86
+ const queue = emailServer.deliveryQueue;
87
+ const queueMap = (queue as any).queue as Map<string, any>;
206
88
 
207
- // Apply limit and offset
208
- const limit = dataArg.limit || 50;
209
- const offset = dataArg.offset || 0;
210
- const paginatedRecords = records.slice(offset, offset + limit);
89
+ if (!queueMap) {
90
+ return [];
91
+ }
211
92
 
212
- return {
213
- records: paginatedRecords,
214
- suppressionList,
215
- total: records.length,
216
- };
217
- }
218
- )
219
- );
93
+ const emails: interfaces.requests.IEmail[] = [];
220
94
 
221
- // Remove from Suppression List Handler
222
- this.typedrouter.addTypedHandler(
223
- new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RemoveFromSuppressionList>(
224
- 'removeFromSuppressionList',
225
- async (dataArg) => {
226
- const emailServer = this.opsServerRef.dcRouterRef.emailServer;
95
+ for (const [id, item] of queueMap.entries()) {
96
+ emails.push(this.mapQueueItemToEmail(item));
97
+ }
227
98
 
228
- if (!emailServer) {
229
- return { success: false, error: 'Email server not available' };
230
- }
99
+ // Sort by createdAt descending (newest first)
100
+ emails.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
231
101
 
232
- try {
233
- emailServer.removeFromSuppressionList(dataArg.email);
234
- return { success: true };
235
- } catch (error) {
236
- return {
237
- success: false,
238
- error: error instanceof Error ? error.message : 'Failed to remove from suppression list'
239
- };
240
- }
241
- }
242
- )
243
- );
102
+ return emails;
244
103
  }
245
104
 
246
105
  /**
247
- * Helper method to get queue items with filtering and pagination
106
+ * Get a single email detail by ID
248
107
  */
249
- private getQueueItems(
250
- status?: interfaces.requests.TEmailQueueStatus,
251
- limit: number = 50,
252
- offset: number = 0
253
- ): interfaces.requests.IEmailQueueItem[] {
108
+ private getEmailDetail(emailId: string): interfaces.requests.IEmailDetail | null {
254
109
  const emailServer = this.opsServerRef.dcRouterRef.emailServer;
255
110
  if (!emailServer?.deliveryQueue) {
256
- return [];
111
+ return null;
257
112
  }
258
113
 
259
114
  const queue = emailServer.deliveryQueue;
260
- const items: interfaces.requests.IEmailQueueItem[] = [];
115
+ const item = queue.getItem(emailId);
261
116
 
262
- // Access the internal queue map via reflection
263
- // This is necessary because the queue doesn't expose iteration methods
264
- const queueMap = (queue as any).queue as Map<string, any>;
265
-
266
- if (!queueMap) {
267
- return [];
117
+ if (!item) {
118
+ return null;
268
119
  }
269
120
 
270
- // Filter and convert items
271
- for (const [id, item] of queueMap.entries()) {
272
- // Apply status filter if provided
273
- if (status && item.status !== status) {
274
- continue;
121
+ return this.mapQueueItemToEmailDetail(item);
122
+ }
123
+
124
+ /**
125
+ * Map a queue item to catalog IEmail format
126
+ */
127
+ private mapQueueItemToEmail(item: any): interfaces.requests.IEmail {
128
+ const processingResult = item.processingResult;
129
+ let from = '';
130
+ let to = '';
131
+ let subject = '';
132
+ let messageId = '';
133
+ let size = '0 B';
134
+
135
+ if (processingResult) {
136
+ if (processingResult.email) {
137
+ from = processingResult.email.from || '';
138
+ to = (processingResult.email.to || [])[0] || '';
139
+ subject = processingResult.email.subject || '';
140
+ } else if (processingResult.from) {
141
+ from = processingResult.from;
142
+ to = (processingResult.to || [])[0] || '';
143
+ subject = processingResult.subject || '';
275
144
  }
276
145
 
277
- // Extract email details from processingResult if available
278
- const processingResult = item.processingResult;
279
- let from = '';
280
- let to: string[] = [];
281
- let subject = '';
282
-
283
- if (processingResult) {
284
- // Check if it's an Email object or raw email data
285
- if (processingResult.email) {
286
- from = processingResult.email.from || '';
287
- to = processingResult.email.to || [];
288
- subject = processingResult.email.subject || '';
289
- } else if (processingResult.from) {
290
- from = processingResult.from;
291
- to = processingResult.to || [];
292
- subject = processingResult.subject || '';
146
+ // Try to get messageId
147
+ if (typeof processingResult.getMessageId === 'function') {
148
+ try {
149
+ messageId = processingResult.getMessageId() || '';
150
+ } catch {
151
+ messageId = '';
293
152
  }
294
153
  }
295
154
 
296
- items.push({
297
- id: item.id,
298
- processingMode: item.processingMode,
299
- status: item.status,
300
- attempts: item.attempts,
301
- nextAttempt: item.nextAttempt instanceof Date ? item.nextAttempt.getTime() : item.nextAttempt,
302
- lastError: item.lastError,
303
- createdAt: item.createdAt instanceof Date ? item.createdAt.getTime() : item.createdAt,
304
- updatedAt: item.updatedAt instanceof Date ? item.updatedAt.getTime() : item.updatedAt,
305
- deliveredAt: item.deliveredAt instanceof Date ? item.deliveredAt.getTime() : item.deliveredAt,
306
- from,
307
- to,
308
- subject,
309
- });
155
+ // Compute approximate size
156
+ const textLen = processingResult.text?.length || 0;
157
+ const htmlLen = processingResult.html?.length || 0;
158
+ let attachSize = 0;
159
+ if (typeof processingResult.getAttachmentsSize === 'function') {
160
+ try {
161
+ attachSize = processingResult.getAttachmentsSize() || 0;
162
+ } catch {
163
+ attachSize = 0;
164
+ }
165
+ }
166
+ size = this.formatSize(textLen + htmlLen + attachSize);
310
167
  }
311
168
 
312
- // Sort by createdAt descending (newest first)
313
- items.sort((a, b) => b.createdAt - a.createdAt);
169
+ // Map queue status to catalog TEmailStatus
170
+ const status = this.mapStatus(item.status);
171
+
172
+ const createdAt = item.createdAt instanceof Date ? item.createdAt.getTime() : item.createdAt;
173
+
174
+ return {
175
+ id: item.id,
176
+ direction: 'outbound' as interfaces.requests.TEmailDirection,
177
+ status,
178
+ from,
179
+ to,
180
+ subject,
181
+ timestamp: new Date(createdAt).toISOString(),
182
+ messageId,
183
+ size,
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Map a queue item to catalog IEmailDetail format
189
+ */
190
+ private mapQueueItemToEmailDetail(item: any): interfaces.requests.IEmailDetail {
191
+ const base = this.mapQueueItemToEmail(item);
192
+ const processingResult = item.processingResult;
193
+
194
+ let toList: string[] = [];
195
+ let cc: string[] = [];
196
+ let headers: Record<string, string> = {};
197
+ let body = '';
198
+
199
+ if (processingResult) {
200
+ if (processingResult.email) {
201
+ toList = processingResult.email.to || [];
202
+ cc = processingResult.email.cc || [];
203
+ } else {
204
+ toList = processingResult.to || [];
205
+ cc = processingResult.cc || [];
206
+ }
314
207
 
315
- // Apply pagination
316
- return items.slice(offset, offset + limit);
208
+ headers = processingResult.headers || {};
209
+ body = processingResult.html || processingResult.text || '';
210
+ }
211
+
212
+ return {
213
+ ...base,
214
+ toList,
215
+ cc,
216
+ smtpLog: [],
217
+ connectionInfo: {
218
+ sourceIp: '',
219
+ sourceHostname: '',
220
+ destinationIp: '',
221
+ destinationPort: 0,
222
+ tlsVersion: '',
223
+ tlsCipher: '',
224
+ authenticated: false,
225
+ authMethod: '',
226
+ authUser: '',
227
+ },
228
+ authenticationResults: {
229
+ spf: 'none',
230
+ spfDomain: '',
231
+ dkim: 'none',
232
+ dkimDomain: '',
233
+ dmarc: 'none',
234
+ dmarcPolicy: '',
235
+ },
236
+ rejectionReason: item.status === 'failed' ? item.lastError : undefined,
237
+ bounceMessage: item.status === 'failed' ? item.lastError : undefined,
238
+ headers,
239
+ body,
240
+ };
241
+ }
242
+
243
+ /**
244
+ * Map queue status to catalog TEmailStatus
245
+ */
246
+ private mapStatus(queueStatus: string): interfaces.requests.TEmailStatus {
247
+ switch (queueStatus) {
248
+ case 'pending':
249
+ case 'processing':
250
+ return 'pending';
251
+ case 'delivered':
252
+ return 'delivered';
253
+ case 'failed':
254
+ return 'bounced';
255
+ case 'deferred':
256
+ return 'deferred';
257
+ default:
258
+ return 'pending';
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Format byte size to human-readable string
264
+ */
265
+ private formatSize(bytes: number): string {
266
+ if (bytes < 1024) return `${bytes} B`;
267
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
268
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
317
269
  }
318
270
  }
@@ -6,4 +6,6 @@ export * from './stats.handler.js';
6
6
  export * from './radius.handler.js';
7
7
  export * from './email-ops.handler.js';
8
8
  export * from './certificate.handler.js';
9
- export * from './remoteingress.handler.js';
9
+ export * from './remoteingress.handler.js';
10
+ export * from './route-management.handler.js';
11
+ export * from './api-token.handler.js';
@@ -0,0 +1,163 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import type { OpsServer } from '../classes.opsserver.js';
3
+ import * as interfaces from '../../../ts_interfaces/index.js';
4
+
5
+ export class RouteManagementHandler {
6
+ public typedrouter = new plugins.typedrequest.TypedRouter();
7
+
8
+ constructor(private opsServerRef: OpsServer) {
9
+ this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
10
+ this.registerHandlers();
11
+ }
12
+
13
+ /**
14
+ * Validate auth: JWT identity OR API token with required scope.
15
+ * Returns a userId string on success, throws on failure.
16
+ */
17
+ private async requireAuth(
18
+ request: { identity?: interfaces.data.IIdentity; apiToken?: string },
19
+ requiredScope?: interfaces.data.TApiTokenScope,
20
+ ): Promise<string> {
21
+ // Try JWT identity first
22
+ if (request.identity?.jwt) {
23
+ try {
24
+ const isAdmin = await this.opsServerRef.adminHandler.adminIdentityGuard.exec({
25
+ identity: request.identity,
26
+ });
27
+ if (isAdmin) return request.identity.userId;
28
+ } catch { /* fall through */ }
29
+ }
30
+
31
+ // Try API token
32
+ if (request.apiToken) {
33
+ const tokenManager = this.opsServerRef.dcRouterRef.apiTokenManager;
34
+ if (tokenManager) {
35
+ const token = await tokenManager.validateToken(request.apiToken);
36
+ if (token) {
37
+ if (!requiredScope || tokenManager.hasScope(token, requiredScope)) {
38
+ return token.createdBy;
39
+ }
40
+ throw new plugins.typedrequest.TypedResponseError('insufficient scope');
41
+ }
42
+ }
43
+ }
44
+
45
+ throw new plugins.typedrequest.TypedResponseError('unauthorized');
46
+ }
47
+
48
+ private registerHandlers(): void {
49
+ // Get merged routes
50
+ this.typedrouter.addTypedHandler(
51
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetMergedRoutes>(
52
+ 'getMergedRoutes',
53
+ async (dataArg) => {
54
+ await this.requireAuth(dataArg, 'routes:read');
55
+ const manager = this.opsServerRef.dcRouterRef.routeConfigManager;
56
+ if (!manager) {
57
+ return { routes: [], warnings: [] };
58
+ }
59
+ return manager.getMergedRoutes();
60
+ },
61
+ ),
62
+ );
63
+
64
+ // Create route
65
+ this.typedrouter.addTypedHandler(
66
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateRoute>(
67
+ 'createRoute',
68
+ async (dataArg) => {
69
+ const userId = await this.requireAuth(dataArg, 'routes:write');
70
+ const manager = this.opsServerRef.dcRouterRef.routeConfigManager;
71
+ if (!manager) {
72
+ return { success: false, message: 'Route management not initialized' };
73
+ }
74
+ const id = await manager.createRoute(dataArg.route, userId, dataArg.enabled ?? true);
75
+ return { success: true, storedRouteId: id };
76
+ },
77
+ ),
78
+ );
79
+
80
+ // Update route
81
+ this.typedrouter.addTypedHandler(
82
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateRoute>(
83
+ 'updateRoute',
84
+ async (dataArg) => {
85
+ await this.requireAuth(dataArg, 'routes:write');
86
+ const manager = this.opsServerRef.dcRouterRef.routeConfigManager;
87
+ if (!manager) {
88
+ return { success: false, message: 'Route management not initialized' };
89
+ }
90
+ const ok = await manager.updateRoute(dataArg.id, {
91
+ route: dataArg.route as any,
92
+ enabled: dataArg.enabled,
93
+ });
94
+ return { success: ok, message: ok ? undefined : 'Route not found' };
95
+ },
96
+ ),
97
+ );
98
+
99
+ // Delete route
100
+ this.typedrouter.addTypedHandler(
101
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteRoute>(
102
+ 'deleteRoute',
103
+ async (dataArg) => {
104
+ await this.requireAuth(dataArg, 'routes:write');
105
+ const manager = this.opsServerRef.dcRouterRef.routeConfigManager;
106
+ if (!manager) {
107
+ return { success: false, message: 'Route management not initialized' };
108
+ }
109
+ const ok = await manager.deleteRoute(dataArg.id);
110
+ return { success: ok, message: ok ? undefined : 'Route not found' };
111
+ },
112
+ ),
113
+ );
114
+
115
+ // Set override on a hardcoded route
116
+ this.typedrouter.addTypedHandler(
117
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SetRouteOverride>(
118
+ 'setRouteOverride',
119
+ async (dataArg) => {
120
+ const userId = await this.requireAuth(dataArg, 'routes:write');
121
+ const manager = this.opsServerRef.dcRouterRef.routeConfigManager;
122
+ if (!manager) {
123
+ return { success: false, message: 'Route management not initialized' };
124
+ }
125
+ await manager.setOverride(dataArg.routeName, dataArg.enabled, userId);
126
+ return { success: true };
127
+ },
128
+ ),
129
+ );
130
+
131
+ // Remove override from a hardcoded route
132
+ this.typedrouter.addTypedHandler(
133
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RemoveRouteOverride>(
134
+ 'removeRouteOverride',
135
+ async (dataArg) => {
136
+ await this.requireAuth(dataArg, 'routes:write');
137
+ const manager = this.opsServerRef.dcRouterRef.routeConfigManager;
138
+ if (!manager) {
139
+ return { success: false, message: 'Route management not initialized' };
140
+ }
141
+ const ok = await manager.removeOverride(dataArg.routeName);
142
+ return { success: ok, message: ok ? undefined : 'Override not found' };
143
+ },
144
+ ),
145
+ );
146
+
147
+ // Toggle programmatic route
148
+ this.typedrouter.addTypedHandler(
149
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ToggleRoute>(
150
+ 'toggleRoute',
151
+ async (dataArg) => {
152
+ await this.requireAuth(dataArg, 'routes:write');
153
+ const manager = this.opsServerRef.dcRouterRef.routeConfigManager;
154
+ if (!manager) {
155
+ return { success: false, message: 'Route management not initialized' };
156
+ }
157
+ const ok = await manager.toggleRoute(dataArg.id, dataArg.enabled);
158
+ return { success: ok, message: ok ? undefined : 'Route not found' };
159
+ },
160
+ ),
161
+ );
162
+ }
163
+ }
@@ -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
  }