@serve.zone/dcrouter 12.1.0 → 12.2.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 (72) hide show
  1. package/dist_serve/bundle.js +750 -688
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.dcrouter.d.ts +6 -1
  4. package/dist_ts/classes.dcrouter.js +11 -3
  5. package/dist_ts/config/classes.db-seeder.d.ts +25 -0
  6. package/dist_ts/config/classes.db-seeder.js +69 -0
  7. package/dist_ts/config/classes.reference-resolver.d.ts +80 -0
  8. package/dist_ts/config/classes.reference-resolver.js +482 -0
  9. package/dist_ts/config/classes.route-config-manager.d.ts +13 -3
  10. package/dist_ts/config/classes.route-config-manager.js +53 -3
  11. package/dist_ts/config/index.d.ts +2 -0
  12. package/dist_ts/config/index.js +3 -1
  13. package/dist_ts/db/documents/classes.network-target.doc.d.ts +15 -0
  14. package/dist_ts/db/documents/classes.network-target.doc.js +118 -0
  15. package/dist_ts/db/documents/classes.security-profile.doc.d.ts +16 -0
  16. package/dist_ts/db/documents/classes.security-profile.doc.js +118 -0
  17. package/dist_ts/db/documents/classes.stored-route.doc.d.ts +2 -0
  18. package/dist_ts/db/documents/classes.stored-route.doc.js +8 -2
  19. package/dist_ts/db/documents/index.d.ts +2 -0
  20. package/dist_ts/db/documents/index.js +3 -1
  21. package/dist_ts/opsserver/classes.opsserver.d.ts +2 -0
  22. package/dist_ts/opsserver/classes.opsserver.js +5 -1
  23. package/dist_ts/opsserver/handlers/index.d.ts +2 -0
  24. package/dist_ts/opsserver/handlers/index.js +3 -1
  25. package/dist_ts/opsserver/handlers/network-target.handler.d.ts +10 -0
  26. package/dist_ts/opsserver/handlers/network-target.handler.js +117 -0
  27. package/dist_ts/opsserver/handlers/route-management.handler.js +3 -2
  28. package/dist_ts/opsserver/handlers/security-profile.handler.d.ts +10 -0
  29. package/dist_ts/opsserver/handlers/security-profile.handler.js +119 -0
  30. package/dist_ts_interfaces/data/route-management.d.ts +48 -1
  31. package/dist_ts_interfaces/requests/index.d.ts +2 -0
  32. package/dist_ts_interfaces/requests/index.js +3 -1
  33. package/dist_ts_interfaces/requests/network-targets.d.ts +102 -0
  34. package/dist_ts_interfaces/requests/network-targets.js +2 -0
  35. package/dist_ts_interfaces/requests/route-management.d.ts +3 -1
  36. package/dist_ts_interfaces/requests/security-profiles.d.ts +102 -0
  37. package/dist_ts_interfaces/requests/security-profiles.js +2 -0
  38. package/dist_ts_web/00_commitinfo_data.js +1 -1
  39. package/dist_ts_web/appstate.d.ts +43 -0
  40. package/dist_ts_web/appstate.js +176 -2
  41. package/dist_ts_web/elements/index.d.ts +2 -0
  42. package/dist_ts_web/elements/index.js +3 -1
  43. package/dist_ts_web/elements/ops-dashboard.js +13 -1
  44. package/dist_ts_web/elements/ops-view-networktargets.d.ts +17 -0
  45. package/dist_ts_web/elements/ops-view-networktargets.js +246 -0
  46. package/dist_ts_web/elements/ops-view-securityprofiles.d.ts +17 -0
  47. package/dist_ts_web/elements/ops-view-securityprofiles.js +275 -0
  48. package/dist_ts_web/router.d.ts +1 -1
  49. package/dist_ts_web/router.js +2 -2
  50. package/package.json +1 -1
  51. package/ts/00_commitinfo_data.ts +1 -1
  52. package/ts/classes.dcrouter.ts +19 -1
  53. package/ts/config/classes.db-seeder.ts +95 -0
  54. package/ts/config/classes.reference-resolver.ts +576 -0
  55. package/ts/config/classes.route-config-manager.ts +64 -1
  56. package/ts/config/index.ts +3 -1
  57. package/ts/db/documents/classes.network-target.doc.ts +48 -0
  58. package/ts/db/documents/classes.security-profile.doc.ts +49 -0
  59. package/ts/db/documents/classes.stored-route.doc.ts +4 -0
  60. package/ts/db/documents/index.ts +2 -0
  61. package/ts/opsserver/classes.opsserver.ts +4 -0
  62. package/ts/opsserver/handlers/index.ts +3 -1
  63. package/ts/opsserver/handlers/network-target.handler.ts +167 -0
  64. package/ts/opsserver/handlers/route-management.handler.ts +2 -1
  65. package/ts/opsserver/handlers/security-profile.handler.ts +169 -0
  66. package/ts_web/00_commitinfo_data.ts +1 -1
  67. package/ts_web/appstate.ts +243 -1
  68. package/ts_web/elements/index.ts +2 -0
  69. package/ts_web/elements/ops-dashboard.ts +12 -0
  70. package/ts_web/elements/ops-view-networktargets.ts +214 -0
  71. package/ts_web/elements/ops-view-securityprofiles.ts +242 -0
  72. package/ts_web/router.ts +1 -1
@@ -6,9 +6,11 @@ import type {
6
6
  IRouteOverride,
7
7
  IMergedRoute,
8
8
  IRouteWarning,
9
+ IRouteMetadata,
9
10
  } from '../../ts_interfaces/data/route-management.js';
10
11
  import type { IDcRouterRouteConfig } from '../../ts_interfaces/data/remoteingress.js';
11
12
  import { type IHttp3Config, augmentRouteWithHttp3 } from '../http3/index.js';
13
+ import type { ReferenceResolver } from './classes.reference-resolver.js';
12
14
 
13
15
  export class RouteConfigManager {
14
16
  private storedRoutes = new Map<string, IStoredRoute>();
@@ -20,8 +22,14 @@ export class RouteConfigManager {
20
22
  private getSmartProxy: () => plugins.smartproxy.SmartProxy | undefined,
21
23
  private getHttp3Config?: () => IHttp3Config | undefined,
22
24
  private getVpnAllowList?: (tags?: string[]) => string[],
25
+ private referenceResolver?: ReferenceResolver,
23
26
  ) {}
24
27
 
28
+ /** Expose stored routes map for reference resolution lookups. */
29
+ public getStoredRoutes(): Map<string, IStoredRoute> {
30
+ return this.storedRoutes;
31
+ }
32
+
25
33
  /**
26
34
  * Load persisted routes and overrides, compute warnings, apply to SmartProxy.
27
35
  */
@@ -62,6 +70,7 @@ export class RouteConfigManager {
62
70
  storedRouteId: stored.id,
63
71
  createdAt: stored.createdAt,
64
72
  updatedAt: stored.updatedAt,
73
+ metadata: stored.metadata,
65
74
  });
66
75
  }
67
76
 
@@ -76,6 +85,7 @@ export class RouteConfigManager {
76
85
  route: plugins.smartproxy.IRouteConfig,
77
86
  createdBy: string,
78
87
  enabled = true,
88
+ metadata?: IRouteMetadata,
79
89
  ): Promise<string> {
80
90
  const id = plugins.uuid.v4();
81
91
  const now = Date.now();
@@ -85,6 +95,14 @@ export class RouteConfigManager {
85
95
  route.name = `programmatic-${id.slice(0, 8)}`;
86
96
  }
87
97
 
98
+ // Resolve references if metadata has refs and resolver is available
99
+ let resolvedMetadata = metadata;
100
+ if (metadata && this.referenceResolver) {
101
+ const resolved = this.referenceResolver.resolveRoute(route, metadata);
102
+ route = resolved.route;
103
+ resolvedMetadata = resolved.metadata;
104
+ }
105
+
88
106
  const stored: IStoredRoute = {
89
107
  id,
90
108
  route,
@@ -92,6 +110,7 @@ export class RouteConfigManager {
92
110
  createdAt: now,
93
111
  updatedAt: now,
94
112
  createdBy,
113
+ metadata: resolvedMetadata,
95
114
  };
96
115
 
97
116
  this.storedRoutes.set(id, stored);
@@ -102,7 +121,11 @@ export class RouteConfigManager {
102
121
 
103
122
  public async updateRoute(
104
123
  id: string,
105
- patch: { route?: Partial<plugins.smartproxy.IRouteConfig>; enabled?: boolean },
124
+ patch: {
125
+ route?: Partial<plugins.smartproxy.IRouteConfig>;
126
+ enabled?: boolean;
127
+ metadata?: Partial<IRouteMetadata>;
128
+ },
106
129
  ): Promise<boolean> {
107
130
  const stored = this.storedRoutes.get(id);
108
131
  if (!stored) return false;
@@ -113,6 +136,17 @@ export class RouteConfigManager {
113
136
  if (patch.enabled !== undefined) {
114
137
  stored.enabled = patch.enabled;
115
138
  }
139
+ if (patch.metadata !== undefined) {
140
+ stored.metadata = { ...stored.metadata, ...patch.metadata };
141
+ }
142
+
143
+ // Re-resolve if metadata refs exist and resolver is available
144
+ if (stored.metadata && this.referenceResolver) {
145
+ const resolved = this.referenceResolver.resolveRoute(stored.route, stored.metadata);
146
+ stored.route = resolved.route;
147
+ stored.metadata = resolved.metadata;
148
+ }
149
+
116
150
  stored.updatedAt = Date.now();
117
151
 
118
152
  await this.persistRoute(stored);
@@ -188,6 +222,7 @@ export class RouteConfigManager {
188
222
  createdAt: doc.createdAt,
189
223
  updatedAt: doc.updatedAt,
190
224
  createdBy: doc.createdBy,
225
+ metadata: doc.metadata,
191
226
  });
192
227
  }
193
228
  }
@@ -220,6 +255,7 @@ export class RouteConfigManager {
220
255
  existingDoc.enabled = stored.enabled;
221
256
  existingDoc.updatedAt = stored.updatedAt;
222
257
  existingDoc.createdBy = stored.createdBy;
258
+ existingDoc.metadata = stored.metadata;
223
259
  await existingDoc.save();
224
260
  } else {
225
261
  const doc = new StoredRouteDoc();
@@ -229,6 +265,7 @@ export class RouteConfigManager {
229
265
  doc.createdAt = stored.createdAt;
230
266
  doc.updatedAt = stored.updatedAt;
231
267
  doc.createdBy = stored.createdBy;
268
+ doc.metadata = stored.metadata;
232
269
  await doc.save();
233
270
  }
234
271
  }
@@ -277,6 +314,32 @@ export class RouteConfigManager {
277
314
  }
278
315
  }
279
316
 
317
+ // =========================================================================
318
+ // Re-resolve routes after profile/target changes
319
+ // =========================================================================
320
+
321
+ /**
322
+ * Re-resolve specific routes by ID (after a profile or target is updated).
323
+ * Persists each route and calls applyRoutes() once at the end.
324
+ */
325
+ public async reResolveRoutes(routeIds: string[]): Promise<void> {
326
+ if (!this.referenceResolver || routeIds.length === 0) return;
327
+
328
+ for (const routeId of routeIds) {
329
+ const stored = this.storedRoutes.get(routeId);
330
+ if (!stored?.metadata) continue;
331
+
332
+ const resolved = this.referenceResolver.resolveRoute(stored.route, stored.metadata);
333
+ stored.route = resolved.route;
334
+ stored.metadata = resolved.metadata;
335
+ stored.updatedAt = Date.now();
336
+ await this.persistRoute(stored);
337
+ }
338
+
339
+ await this.applyRoutes();
340
+ logger.log('info', `Re-resolved ${routeIds.length} route(s) after profile/target change`);
341
+ }
342
+
280
343
  // =========================================================================
281
344
  // Private: apply merged routes to SmartProxy
282
345
  // =========================================================================
@@ -1,4 +1,6 @@
1
1
  // Export validation tools only
2
2
  export * from './validator.js';
3
3
  export { RouteConfigManager } from './classes.route-config-manager.js';
4
- export { ApiTokenManager } from './classes.api-token-manager.js';
4
+ export { ApiTokenManager } from './classes.api-token-manager.js';
5
+ export { ReferenceResolver } from './classes.reference-resolver.js';
6
+ export { DbSeeder } from './classes.db-seeder.js';
@@ -0,0 +1,48 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import { DcRouterDb } from '../classes.dcrouter-db.js';
3
+
4
+ const getDb = () => DcRouterDb.getInstance().getDb();
5
+
6
+ @plugins.smartdata.Collection(() => getDb())
7
+ export class NetworkTargetDoc extends plugins.smartdata.SmartDataDbDoc<NetworkTargetDoc, NetworkTargetDoc> {
8
+ @plugins.smartdata.unI()
9
+ @plugins.smartdata.svDb()
10
+ public id!: string;
11
+
12
+ @plugins.smartdata.svDb()
13
+ public name: string = '';
14
+
15
+ @plugins.smartdata.svDb()
16
+ public description?: string;
17
+
18
+ @plugins.smartdata.svDb()
19
+ public host!: string | string[];
20
+
21
+ @plugins.smartdata.svDb()
22
+ public port!: number;
23
+
24
+ @plugins.smartdata.svDb()
25
+ public createdAt!: number;
26
+
27
+ @plugins.smartdata.svDb()
28
+ public updatedAt!: number;
29
+
30
+ @plugins.smartdata.svDb()
31
+ public createdBy!: string;
32
+
33
+ constructor() {
34
+ super();
35
+ }
36
+
37
+ public static async findById(id: string): Promise<NetworkTargetDoc | null> {
38
+ return await NetworkTargetDoc.getInstance({ id });
39
+ }
40
+
41
+ public static async findByName(name: string): Promise<NetworkTargetDoc | null> {
42
+ return await NetworkTargetDoc.getInstance({ name });
43
+ }
44
+
45
+ public static async findAll(): Promise<NetworkTargetDoc[]> {
46
+ return await NetworkTargetDoc.getInstances({});
47
+ }
48
+ }
@@ -0,0 +1,49 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import { DcRouterDb } from '../classes.dcrouter-db.js';
3
+ import type { IRouteSecurity } from '../../../ts_interfaces/data/route-management.js';
4
+
5
+ const getDb = () => DcRouterDb.getInstance().getDb();
6
+
7
+ @plugins.smartdata.Collection(() => getDb())
8
+ export class SecurityProfileDoc extends plugins.smartdata.SmartDataDbDoc<SecurityProfileDoc, SecurityProfileDoc> {
9
+ @plugins.smartdata.unI()
10
+ @plugins.smartdata.svDb()
11
+ public id!: string;
12
+
13
+ @plugins.smartdata.svDb()
14
+ public name: string = '';
15
+
16
+ @plugins.smartdata.svDb()
17
+ public description?: string;
18
+
19
+ @plugins.smartdata.svDb()
20
+ public security!: IRouteSecurity;
21
+
22
+ @plugins.smartdata.svDb()
23
+ public extendsProfiles?: string[];
24
+
25
+ @plugins.smartdata.svDb()
26
+ public createdAt!: number;
27
+
28
+ @plugins.smartdata.svDb()
29
+ public updatedAt!: number;
30
+
31
+ @plugins.smartdata.svDb()
32
+ public createdBy!: string;
33
+
34
+ constructor() {
35
+ super();
36
+ }
37
+
38
+ public static async findById(id: string): Promise<SecurityProfileDoc | null> {
39
+ return await SecurityProfileDoc.getInstance({ id });
40
+ }
41
+
42
+ public static async findByName(name: string): Promise<SecurityProfileDoc | null> {
43
+ return await SecurityProfileDoc.getInstance({ name });
44
+ }
45
+
46
+ public static async findAll(): Promise<SecurityProfileDoc[]> {
47
+ return await SecurityProfileDoc.getInstances({});
48
+ }
49
+ }
@@ -1,5 +1,6 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import { DcRouterDb } from '../classes.dcrouter-db.js';
3
+ import type { IRouteMetadata } from '../../../ts_interfaces/data/route-management.js';
3
4
 
4
5
  const getDb = () => DcRouterDb.getInstance().getDb();
5
6
 
@@ -24,6 +25,9 @@ export class StoredRouteDoc extends plugins.smartdata.SmartDataDbDoc<StoredRoute
24
25
  @plugins.smartdata.svDb()
25
26
  public createdBy!: string;
26
27
 
28
+ @plugins.smartdata.svDb()
29
+ public metadata?: IRouteMetadata;
30
+
27
31
  constructor() {
28
32
  super();
29
33
  }
@@ -6,6 +6,8 @@ export * from './classes.cached.ip.reputation.js';
6
6
  export * from './classes.stored-route.doc.js';
7
7
  export * from './classes.route-override.doc.js';
8
8
  export * from './classes.api-token.doc.js';
9
+ export * from './classes.security-profile.doc.js';
10
+ export * from './classes.network-target.doc.js';
9
11
 
10
12
  // VPN document classes
11
13
  export * from './classes.vpn-server-keys.doc.js';
@@ -29,6 +29,8 @@ export class OpsServer {
29
29
  private routeManagementHandler!: handlers.RouteManagementHandler;
30
30
  private apiTokenHandler!: handlers.ApiTokenHandler;
31
31
  private vpnHandler!: handlers.VpnHandler;
32
+ private securityProfileHandler!: handlers.SecurityProfileHandler;
33
+ private networkTargetHandler!: handlers.NetworkTargetHandler;
32
34
 
33
35
  constructor(dcRouterRefArg: DcRouter) {
34
36
  this.dcRouterRef = dcRouterRefArg;
@@ -88,6 +90,8 @@ export class OpsServer {
88
90
  this.routeManagementHandler = new handlers.RouteManagementHandler(this);
89
91
  this.apiTokenHandler = new handlers.ApiTokenHandler(this);
90
92
  this.vpnHandler = new handlers.VpnHandler(this);
93
+ this.securityProfileHandler = new handlers.SecurityProfileHandler(this);
94
+ this.networkTargetHandler = new handlers.NetworkTargetHandler(this);
91
95
 
92
96
  console.log('✅ OpsServer TypedRequest handlers initialized');
93
97
  }
@@ -9,4 +9,6 @@ export * from './certificate.handler.js';
9
9
  export * from './remoteingress.handler.js';
10
10
  export * from './route-management.handler.js';
11
11
  export * from './api-token.handler.js';
12
- export * from './vpn.handler.js';
12
+ export * from './vpn.handler.js';
13
+ export * from './security-profile.handler.js';
14
+ export * from './network-target.handler.js';
@@ -0,0 +1,167 @@
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 NetworkTargetHandler {
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
+ private async requireAuth(
14
+ request: { identity?: interfaces.data.IIdentity; apiToken?: string },
15
+ requiredScope?: interfaces.data.TApiTokenScope,
16
+ ): Promise<string> {
17
+ if (request.identity?.jwt) {
18
+ try {
19
+ const isAdmin = await this.opsServerRef.adminHandler.adminIdentityGuard.exec({
20
+ identity: request.identity,
21
+ });
22
+ if (isAdmin) return request.identity.userId;
23
+ } catch { /* fall through */ }
24
+ }
25
+
26
+ if (request.apiToken) {
27
+ const tokenManager = this.opsServerRef.dcRouterRef.apiTokenManager;
28
+ if (tokenManager) {
29
+ const token = await tokenManager.validateToken(request.apiToken);
30
+ if (token) {
31
+ if (!requiredScope || tokenManager.hasScope(token, requiredScope)) {
32
+ return token.createdBy;
33
+ }
34
+ throw new plugins.typedrequest.TypedResponseError('insufficient scope');
35
+ }
36
+ }
37
+ }
38
+
39
+ throw new plugins.typedrequest.TypedResponseError('unauthorized');
40
+ }
41
+
42
+ private registerHandlers(): void {
43
+ // Get all network targets
44
+ this.typedrouter.addTypedHandler(
45
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetNetworkTargets>(
46
+ 'getNetworkTargets',
47
+ async (dataArg) => {
48
+ await this.requireAuth(dataArg, 'targets:read');
49
+ const resolver = this.opsServerRef.dcRouterRef.referenceResolver;
50
+ if (!resolver) {
51
+ return { targets: [] };
52
+ }
53
+ return { targets: resolver.listTargets() };
54
+ },
55
+ ),
56
+ );
57
+
58
+ // Get a single network target
59
+ this.typedrouter.addTypedHandler(
60
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetNetworkTarget>(
61
+ 'getNetworkTarget',
62
+ async (dataArg) => {
63
+ await this.requireAuth(dataArg, 'targets:read');
64
+ const resolver = this.opsServerRef.dcRouterRef.referenceResolver;
65
+ if (!resolver) {
66
+ return { target: null };
67
+ }
68
+ return { target: resolver.getTarget(dataArg.id) || null };
69
+ },
70
+ ),
71
+ );
72
+
73
+ // Create a network target
74
+ this.typedrouter.addTypedHandler(
75
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateNetworkTarget>(
76
+ 'createNetworkTarget',
77
+ async (dataArg) => {
78
+ const userId = await this.requireAuth(dataArg, 'targets:write');
79
+ const resolver = this.opsServerRef.dcRouterRef.referenceResolver;
80
+ if (!resolver) {
81
+ return { success: false, message: 'Reference resolver not initialized' };
82
+ }
83
+ const id = await resolver.createTarget({
84
+ name: dataArg.name,
85
+ description: dataArg.description,
86
+ host: dataArg.host,
87
+ port: dataArg.port,
88
+ createdBy: userId,
89
+ });
90
+ return { success: true, id };
91
+ },
92
+ ),
93
+ );
94
+
95
+ // Update a network target
96
+ this.typedrouter.addTypedHandler(
97
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateNetworkTarget>(
98
+ 'updateNetworkTarget',
99
+ async (dataArg) => {
100
+ await this.requireAuth(dataArg, 'targets:write');
101
+ const resolver = this.opsServerRef.dcRouterRef.referenceResolver;
102
+ const manager = this.opsServerRef.dcRouterRef.routeConfigManager;
103
+ if (!resolver || !manager) {
104
+ return { success: false, message: 'Not initialized' };
105
+ }
106
+
107
+ const { affectedRouteIds } = await resolver.updateTarget(dataArg.id, {
108
+ name: dataArg.name,
109
+ description: dataArg.description,
110
+ host: dataArg.host,
111
+ port: dataArg.port,
112
+ });
113
+
114
+ if (affectedRouteIds.length > 0) {
115
+ await manager.reResolveRoutes(affectedRouteIds);
116
+ }
117
+
118
+ return { success: true, affectedRouteCount: affectedRouteIds.length };
119
+ },
120
+ ),
121
+ );
122
+
123
+ // Delete a network target
124
+ this.typedrouter.addTypedHandler(
125
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteNetworkTarget>(
126
+ 'deleteNetworkTarget',
127
+ async (dataArg) => {
128
+ await this.requireAuth(dataArg, 'targets:write');
129
+ const resolver = this.opsServerRef.dcRouterRef.referenceResolver;
130
+ const manager = this.opsServerRef.dcRouterRef.routeConfigManager;
131
+ if (!resolver || !manager) {
132
+ return { success: false, message: 'Not initialized' };
133
+ }
134
+
135
+ const result = await resolver.deleteTarget(
136
+ dataArg.id,
137
+ dataArg.force ?? false,
138
+ manager.getStoredRoutes(),
139
+ );
140
+
141
+ if (result.success && dataArg.force) {
142
+ await manager.applyRoutes();
143
+ }
144
+
145
+ return result;
146
+ },
147
+ ),
148
+ );
149
+
150
+ // Get routes using a network target
151
+ this.typedrouter.addTypedHandler(
152
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetNetworkTargetUsage>(
153
+ 'getNetworkTargetUsage',
154
+ async (dataArg) => {
155
+ await this.requireAuth(dataArg, 'targets:read');
156
+ const resolver = this.opsServerRef.dcRouterRef.referenceResolver;
157
+ const manager = this.opsServerRef.dcRouterRef.routeConfigManager;
158
+ if (!resolver || !manager) {
159
+ return { routes: [] };
160
+ }
161
+ const usage = resolver.getTargetUsageForId(dataArg.id, manager.getStoredRoutes());
162
+ return { routes: usage.map((u) => ({ id: u.id, name: u.routeName })) };
163
+ },
164
+ ),
165
+ );
166
+ }
167
+ }
@@ -71,7 +71,7 @@ export class RouteManagementHandler {
71
71
  if (!manager) {
72
72
  return { success: false, message: 'Route management not initialized' };
73
73
  }
74
- const id = await manager.createRoute(dataArg.route, userId, dataArg.enabled ?? true);
74
+ const id = await manager.createRoute(dataArg.route, userId, dataArg.enabled ?? true, dataArg.metadata);
75
75
  return { success: true, storedRouteId: id };
76
76
  },
77
77
  ),
@@ -90,6 +90,7 @@ export class RouteManagementHandler {
90
90
  const ok = await manager.updateRoute(dataArg.id, {
91
91
  route: dataArg.route as any,
92
92
  enabled: dataArg.enabled,
93
+ metadata: dataArg.metadata,
93
94
  });
94
95
  return { success: ok, message: ok ? undefined : 'Route not found' };
95
96
  },
@@ -0,0 +1,169 @@
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 SecurityProfileHandler {
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
+ private async requireAuth(
14
+ request: { identity?: interfaces.data.IIdentity; apiToken?: string },
15
+ requiredScope?: interfaces.data.TApiTokenScope,
16
+ ): Promise<string> {
17
+ if (request.identity?.jwt) {
18
+ try {
19
+ const isAdmin = await this.opsServerRef.adminHandler.adminIdentityGuard.exec({
20
+ identity: request.identity,
21
+ });
22
+ if (isAdmin) return request.identity.userId;
23
+ } catch { /* fall through */ }
24
+ }
25
+
26
+ if (request.apiToken) {
27
+ const tokenManager = this.opsServerRef.dcRouterRef.apiTokenManager;
28
+ if (tokenManager) {
29
+ const token = await tokenManager.validateToken(request.apiToken);
30
+ if (token) {
31
+ if (!requiredScope || tokenManager.hasScope(token, requiredScope)) {
32
+ return token.createdBy;
33
+ }
34
+ throw new plugins.typedrequest.TypedResponseError('insufficient scope');
35
+ }
36
+ }
37
+ }
38
+
39
+ throw new plugins.typedrequest.TypedResponseError('unauthorized');
40
+ }
41
+
42
+ private registerHandlers(): void {
43
+ // Get all security profiles
44
+ this.typedrouter.addTypedHandler(
45
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSecurityProfiles>(
46
+ 'getSecurityProfiles',
47
+ async (dataArg) => {
48
+ await this.requireAuth(dataArg, 'profiles:read');
49
+ const resolver = this.opsServerRef.dcRouterRef.referenceResolver;
50
+ if (!resolver) {
51
+ return { profiles: [] };
52
+ }
53
+ return { profiles: resolver.listProfiles() };
54
+ },
55
+ ),
56
+ );
57
+
58
+ // Get a single security profile
59
+ this.typedrouter.addTypedHandler(
60
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSecurityProfile>(
61
+ 'getSecurityProfile',
62
+ async (dataArg) => {
63
+ await this.requireAuth(dataArg, 'profiles:read');
64
+ const resolver = this.opsServerRef.dcRouterRef.referenceResolver;
65
+ if (!resolver) {
66
+ return { profile: null };
67
+ }
68
+ return { profile: resolver.getProfile(dataArg.id) || null };
69
+ },
70
+ ),
71
+ );
72
+
73
+ // Create a security profile
74
+ this.typedrouter.addTypedHandler(
75
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateSecurityProfile>(
76
+ 'createSecurityProfile',
77
+ async (dataArg) => {
78
+ const userId = await this.requireAuth(dataArg, 'profiles:write');
79
+ const resolver = this.opsServerRef.dcRouterRef.referenceResolver;
80
+ if (!resolver) {
81
+ return { success: false, message: 'Reference resolver not initialized' };
82
+ }
83
+ const id = await resolver.createProfile({
84
+ name: dataArg.name,
85
+ description: dataArg.description,
86
+ security: dataArg.security,
87
+ extendsProfiles: dataArg.extendsProfiles,
88
+ createdBy: userId,
89
+ });
90
+ return { success: true, id };
91
+ },
92
+ ),
93
+ );
94
+
95
+ // Update a security profile
96
+ this.typedrouter.addTypedHandler(
97
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateSecurityProfile>(
98
+ 'updateSecurityProfile',
99
+ async (dataArg) => {
100
+ await this.requireAuth(dataArg, 'profiles:write');
101
+ const resolver = this.opsServerRef.dcRouterRef.referenceResolver;
102
+ const manager = this.opsServerRef.dcRouterRef.routeConfigManager;
103
+ if (!resolver || !manager) {
104
+ return { success: false, message: 'Not initialized' };
105
+ }
106
+
107
+ const { affectedRouteIds } = await resolver.updateProfile(dataArg.id, {
108
+ name: dataArg.name,
109
+ description: dataArg.description,
110
+ security: dataArg.security,
111
+ extendsProfiles: dataArg.extendsProfiles,
112
+ });
113
+
114
+ // Propagate to affected routes
115
+ if (affectedRouteIds.length > 0) {
116
+ await manager.reResolveRoutes(affectedRouteIds);
117
+ }
118
+
119
+ return { success: true, affectedRouteCount: affectedRouteIds.length };
120
+ },
121
+ ),
122
+ );
123
+
124
+ // Delete a security profile
125
+ this.typedrouter.addTypedHandler(
126
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteSecurityProfile>(
127
+ 'deleteSecurityProfile',
128
+ async (dataArg) => {
129
+ await this.requireAuth(dataArg, 'profiles:write');
130
+ const resolver = this.opsServerRef.dcRouterRef.referenceResolver;
131
+ const manager = this.opsServerRef.dcRouterRef.routeConfigManager;
132
+ if (!resolver || !manager) {
133
+ return { success: false, message: 'Not initialized' };
134
+ }
135
+
136
+ const result = await resolver.deleteProfile(
137
+ dataArg.id,
138
+ dataArg.force ?? false,
139
+ manager.getStoredRoutes(),
140
+ );
141
+
142
+ // If force-deleted with affected routes, re-apply
143
+ if (result.success && dataArg.force) {
144
+ await manager.applyRoutes();
145
+ }
146
+
147
+ return result;
148
+ },
149
+ ),
150
+ );
151
+
152
+ // Get routes using a security profile
153
+ this.typedrouter.addTypedHandler(
154
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSecurityProfileUsage>(
155
+ 'getSecurityProfileUsage',
156
+ async (dataArg) => {
157
+ await this.requireAuth(dataArg, 'profiles:read');
158
+ const resolver = this.opsServerRef.dcRouterRef.referenceResolver;
159
+ const manager = this.opsServerRef.dcRouterRef.routeConfigManager;
160
+ if (!resolver || !manager) {
161
+ return { routes: [] };
162
+ }
163
+ const usage = resolver.getProfileUsageForId(dataArg.id, manager.getStoredRoutes());
164
+ return { routes: usage.map((u) => ({ id: u.id, name: u.routeName })) };
165
+ },
166
+ ),
167
+ );
168
+ }
169
+ }
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '12.1.0',
6
+ version: '12.2.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }