@serve.zone/dcrouter 11.14.0 → 11.16.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 (207) hide show
  1. package/dist_serve/bundle.js +5 -9
  2. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  3. package/dist_ts/00_commitinfo_data.js +9 -0
  4. package/dist_ts/cache/classes.cache.cleaner.d.ts +47 -0
  5. package/dist_ts/cache/classes.cache.cleaner.js +130 -0
  6. package/dist_ts/cache/classes.cached.document.d.ts +76 -0
  7. package/dist_ts/cache/classes.cached.document.js +100 -0
  8. package/dist_ts/cache/classes.cachedb.d.ts +60 -0
  9. package/dist_ts/cache/classes.cachedb.js +126 -0
  10. package/dist_ts/cache/documents/classes.cached.email.d.ts +125 -0
  11. package/dist_ts/cache/documents/classes.cached.email.js +337 -0
  12. package/dist_ts/cache/documents/classes.cached.ip.reputation.d.ts +119 -0
  13. package/dist_ts/cache/documents/classes.cached.ip.reputation.js +323 -0
  14. package/dist_ts/cache/documents/index.d.ts +2 -0
  15. package/dist_ts/cache/documents/index.js +3 -0
  16. package/dist_ts/cache/index.d.ts +4 -0
  17. package/dist_ts/cache/index.js +7 -0
  18. package/dist_ts/classes.cert-provision-scheduler.d.ts +54 -0
  19. package/dist_ts/classes.cert-provision-scheduler.js +118 -0
  20. package/dist_ts/classes.dcrouter.d.ts +399 -0
  21. package/dist_ts/classes.dcrouter.js +1697 -0
  22. package/dist_ts/classes.storage-cert-manager.d.ts +18 -0
  23. package/dist_ts/classes.storage-cert-manager.js +43 -0
  24. package/dist_ts/config/classes.api-token-manager.d.ts +46 -0
  25. package/dist_ts/config/classes.api-token-manager.js +150 -0
  26. package/dist_ts/config/classes.route-config-manager.d.ts +38 -0
  27. package/dist_ts/config/classes.route-config-manager.js +257 -0
  28. package/dist_ts/config/index.d.ts +3 -0
  29. package/dist_ts/config/index.js +5 -0
  30. package/dist_ts/config/validator.d.ts +104 -0
  31. package/dist_ts/config/validator.js +152 -0
  32. package/dist_ts/errors/base.errors.d.ts +224 -0
  33. package/dist_ts/errors/base.errors.js +320 -0
  34. package/dist_ts/errors/error-handler.d.ts +98 -0
  35. package/dist_ts/errors/error-handler.js +282 -0
  36. package/dist_ts/errors/error.codes.d.ts +115 -0
  37. package/dist_ts/errors/error.codes.js +136 -0
  38. package/dist_ts/errors/index.d.ts +54 -0
  39. package/dist_ts/errors/index.js +136 -0
  40. package/dist_ts/errors/reputation.errors.d.ts +183 -0
  41. package/dist_ts/errors/reputation.errors.js +292 -0
  42. package/dist_ts/http3/http3-route-augmentation.d.ts +50 -0
  43. package/dist_ts/http3/http3-route-augmentation.js +98 -0
  44. package/dist_ts/http3/index.d.ts +1 -0
  45. package/dist_ts/http3/index.js +2 -0
  46. package/dist_ts/index.d.ts +8 -0
  47. package/dist_ts/index.js +29 -0
  48. package/dist_ts/logger.d.ts +21 -0
  49. package/dist_ts/logger.js +81 -0
  50. package/dist_ts/monitoring/classes.metricscache.d.ts +32 -0
  51. package/dist_ts/monitoring/classes.metricscache.js +63 -0
  52. package/dist_ts/monitoring/classes.metricsmanager.d.ts +184 -0
  53. package/dist_ts/monitoring/classes.metricsmanager.js +744 -0
  54. package/dist_ts/monitoring/index.d.ts +1 -0
  55. package/dist_ts/monitoring/index.js +2 -0
  56. package/dist_ts/opsserver/classes.opsserver.d.ts +38 -0
  57. package/dist_ts/opsserver/classes.opsserver.js +87 -0
  58. package/dist_ts/opsserver/handlers/admin.handler.d.ts +31 -0
  59. package/dist_ts/opsserver/handlers/admin.handler.js +180 -0
  60. package/dist_ts/opsserver/handlers/api-token.handler.d.ts +6 -0
  61. package/dist_ts/opsserver/handlers/api-token.handler.js +62 -0
  62. package/dist_ts/opsserver/handlers/certificate.handler.d.ts +32 -0
  63. package/dist_ts/opsserver/handlers/certificate.handler.js +421 -0
  64. package/dist_ts/opsserver/handlers/config.handler.d.ts +7 -0
  65. package/dist_ts/opsserver/handlers/config.handler.js +192 -0
  66. package/dist_ts/opsserver/handlers/email-ops.handler.d.ts +30 -0
  67. package/dist_ts/opsserver/handlers/email-ops.handler.js +227 -0
  68. package/dist_ts/opsserver/handlers/index.d.ts +12 -0
  69. package/dist_ts/opsserver/handlers/index.js +13 -0
  70. package/dist_ts/opsserver/handlers/logs.handler.d.ts +25 -0
  71. package/dist_ts/opsserver/handlers/logs.handler.js +256 -0
  72. package/dist_ts/opsserver/handlers/radius.handler.d.ts +6 -0
  73. package/dist_ts/opsserver/handlers/radius.handler.js +295 -0
  74. package/dist_ts/opsserver/handlers/remoteingress.handler.d.ts +6 -0
  75. package/dist_ts/opsserver/handlers/remoteingress.handler.js +156 -0
  76. package/dist_ts/opsserver/handlers/route-management.handler.d.ts +14 -0
  77. package/dist_ts/opsserver/handlers/route-management.handler.js +117 -0
  78. package/dist_ts/opsserver/handlers/security.handler.d.ts +9 -0
  79. package/dist_ts/opsserver/handlers/security.handler.js +233 -0
  80. package/dist_ts/opsserver/handlers/stats.handler.d.ts +11 -0
  81. package/dist_ts/opsserver/handlers/stats.handler.js +403 -0
  82. package/dist_ts/opsserver/handlers/vpn.handler.d.ts +6 -0
  83. package/dist_ts/opsserver/handlers/vpn.handler.js +197 -0
  84. package/dist_ts/opsserver/helpers/guards.d.ts +27 -0
  85. package/dist_ts/opsserver/helpers/guards.js +43 -0
  86. package/dist_ts/opsserver/index.d.ts +1 -0
  87. package/dist_ts/opsserver/index.js +2 -0
  88. package/dist_ts/paths.d.ts +26 -0
  89. package/dist_ts/paths.js +45 -0
  90. package/dist_ts/plugins.d.ts +81 -0
  91. package/dist_ts/plugins.js +115 -0
  92. package/dist_ts/radius/classes.accounting.manager.d.ts +231 -0
  93. package/dist_ts/radius/classes.accounting.manager.js +462 -0
  94. package/dist_ts/radius/classes.radius.server.d.ts +171 -0
  95. package/dist_ts/radius/classes.radius.server.js +386 -0
  96. package/dist_ts/radius/classes.vlan.manager.d.ts +128 -0
  97. package/dist_ts/radius/classes.vlan.manager.js +279 -0
  98. package/dist_ts/radius/index.d.ts +13 -0
  99. package/dist_ts/radius/index.js +14 -0
  100. package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +94 -0
  101. package/dist_ts/remoteingress/classes.remoteingress-manager.js +271 -0
  102. package/dist_ts/remoteingress/classes.tunnel-manager.d.ts +59 -0
  103. package/dist_ts/remoteingress/classes.tunnel-manager.js +165 -0
  104. package/dist_ts/remoteingress/index.d.ts +2 -0
  105. package/dist_ts/remoteingress/index.js +3 -0
  106. package/dist_ts/security/classes.contentscanner.d.ts +164 -0
  107. package/dist_ts/security/classes.contentscanner.js +642 -0
  108. package/dist_ts/security/classes.ipreputationchecker.d.ts +160 -0
  109. package/dist_ts/security/classes.ipreputationchecker.js +537 -0
  110. package/dist_ts/security/classes.securitylogger.d.ts +144 -0
  111. package/dist_ts/security/classes.securitylogger.js +235 -0
  112. package/dist_ts/security/index.d.ts +3 -0
  113. package/dist_ts/security/index.js +4 -0
  114. package/dist_ts/sms/classes.smsservice.d.ts +15 -0
  115. package/dist_ts/sms/classes.smsservice.js +72 -0
  116. package/dist_ts/sms/config/sms.config.d.ts +93 -0
  117. package/dist_ts/sms/config/sms.config.js +2 -0
  118. package/dist_ts/sms/config/sms.schema.d.ts +5 -0
  119. package/dist_ts/sms/config/sms.schema.js +121 -0
  120. package/dist_ts/sms/index.d.ts +1 -0
  121. package/dist_ts/sms/index.js +2 -0
  122. package/dist_ts/storage/classes.storagemanager.d.ts +83 -0
  123. package/dist_ts/storage/classes.storagemanager.js +348 -0
  124. package/dist_ts/storage/index.d.ts +1 -0
  125. package/dist_ts/storage/index.js +3 -0
  126. package/dist_ts/vpn/classes.vpn-manager.d.ts +129 -0
  127. package/dist_ts/vpn/classes.vpn-manager.js +329 -0
  128. package/dist_ts/vpn/index.d.ts +1 -0
  129. package/dist_ts/vpn/index.js +2 -0
  130. package/dist_ts_apiclient/classes.apitoken.d.ts +41 -0
  131. package/dist_ts_apiclient/classes.apitoken.js +115 -0
  132. package/dist_ts_apiclient/classes.certificate.d.ts +57 -0
  133. package/dist_ts_apiclient/classes.certificate.js +69 -0
  134. package/dist_ts_apiclient/classes.config.d.ts +7 -0
  135. package/dist_ts_apiclient/classes.config.js +11 -0
  136. package/dist_ts_apiclient/classes.dcrouterapiclient.d.ts +41 -0
  137. package/dist_ts_apiclient/classes.dcrouterapiclient.js +81 -0
  138. package/dist_ts_apiclient/classes.email.d.ts +30 -0
  139. package/dist_ts_apiclient/classes.email.js +52 -0
  140. package/dist_ts_apiclient/classes.logs.d.ts +21 -0
  141. package/dist_ts_apiclient/classes.logs.js +14 -0
  142. package/dist_ts_apiclient/classes.radius.d.ts +59 -0
  143. package/dist_ts_apiclient/classes.radius.js +95 -0
  144. package/dist_ts_apiclient/classes.remoteingress.d.ts +54 -0
  145. package/dist_ts_apiclient/classes.remoteingress.js +136 -0
  146. package/dist_ts_apiclient/classes.route.d.ts +42 -0
  147. package/dist_ts_apiclient/classes.route.js +154 -0
  148. package/dist_ts_apiclient/classes.stats.d.ts +47 -0
  149. package/dist_ts_apiclient/classes.stats.js +38 -0
  150. package/dist_ts_apiclient/index.d.ts +10 -0
  151. package/dist_ts_apiclient/index.js +14 -0
  152. package/dist_ts_apiclient/plugins.d.ts +3 -0
  153. package/dist_ts_apiclient/plugins.js +5 -0
  154. package/dist_ts_interfaces/data/remoteingress.d.ts +2 -0
  155. package/dist_ts_interfaces/data/vpn.d.ts +1 -2
  156. package/dist_ts_interfaces/requests/vpn.d.ts +1 -1
  157. package/dist_ts_web/00_commitinfo_data.d.ts +8 -0
  158. package/dist_ts_web/00_commitinfo_data.js +9 -0
  159. package/dist_ts_web/appstate.d.ts +238 -0
  160. package/dist_ts_web/appstate.js +1174 -0
  161. package/dist_ts_web/elements/index.d.ts +13 -0
  162. package/dist_ts_web/elements/index.js +14 -0
  163. package/dist_ts_web/elements/ops-dashboard.d.ts +23 -0
  164. package/dist_ts_web/elements/ops-dashboard.js +323 -0
  165. package/dist_ts_web/elements/ops-view-apitokens.d.ts +13 -0
  166. package/dist_ts_web/elements/ops-view-apitokens.js +371 -0
  167. package/dist_ts_web/elements/ops-view-certificates.d.ts +22 -0
  168. package/dist_ts_web/elements/ops-view-certificates.js +528 -0
  169. package/dist_ts_web/elements/ops-view-config.d.ts +19 -0
  170. package/dist_ts_web/elements/ops-view-config.js +339 -0
  171. package/dist_ts_web/elements/ops-view-emails.d.ts +21 -0
  172. package/dist_ts_web/elements/ops-view-emails.js +165 -0
  173. package/dist_ts_web/elements/ops-view-logs.d.ts +13 -0
  174. package/dist_ts_web/elements/ops-view-logs.js +159 -0
  175. package/dist_ts_web/elements/ops-view-network.d.ts +71 -0
  176. package/dist_ts_web/elements/ops-view-network.js +764 -0
  177. package/dist_ts_web/elements/ops-view-overview.d.ts +22 -0
  178. package/dist_ts_web/elements/ops-view-overview.js +456 -0
  179. package/dist_ts_web/elements/ops-view-remoteingress.d.ts +20 -0
  180. package/dist_ts_web/elements/ops-view-remoteingress.js +494 -0
  181. package/dist_ts_web/elements/ops-view-routes.d.ts +12 -0
  182. package/dist_ts_web/elements/ops-view-routes.js +404 -0
  183. package/dist_ts_web/elements/ops-view-security.d.ts +21 -0
  184. package/dist_ts_web/elements/ops-view-security.js +574 -0
  185. package/dist_ts_web/elements/ops-view-vpn.d.ts +14 -0
  186. package/dist_ts_web/elements/ops-view-vpn.js +365 -0
  187. package/dist_ts_web/elements/shared/css.d.ts +1 -0
  188. package/dist_ts_web/elements/shared/css.js +10 -0
  189. package/dist_ts_web/elements/shared/index.d.ts +2 -0
  190. package/dist_ts_web/elements/shared/index.js +3 -0
  191. package/dist_ts_web/elements/shared/ops-sectionheading.d.ts +5 -0
  192. package/dist_ts_web/elements/shared/ops-sectionheading.js +82 -0
  193. package/dist_ts_web/index.d.ts +1 -0
  194. package/dist_ts_web/index.js +10 -0
  195. package/dist_ts_web/plugins.d.ts +6 -0
  196. package/dist_ts_web/plugins.js +11 -0
  197. package/dist_ts_web/router.d.ts +19 -0
  198. package/dist_ts_web/router.js +91 -0
  199. package/package.json +2 -2
  200. package/ts/00_commitinfo_data.ts +1 -1
  201. package/ts/classes.dcrouter.ts +51 -20
  202. package/ts/config/classes.route-config-manager.ts +7 -6
  203. package/ts/opsserver/handlers/vpn.handler.ts +3 -5
  204. package/ts/vpn/classes.vpn-manager.ts +68 -19
  205. package/ts_web/00_commitinfo_data.ts +1 -1
  206. package/ts_web/appstate.ts +2 -2
  207. package/ts_web/elements/ops-view-vpn.ts +5 -9
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@serve.zone/dcrouter",
3
3
  "private": false,
4
- "version": "11.14.0",
4
+ "version": "11.16.0",
5
5
  "description": "A multifaceted routing service handling mail and SMS delivery functions.",
6
6
  "type": "module",
7
7
  "exports": {
@@ -59,7 +59,7 @@
59
59
  "@push.rocks/smartrx": "^3.0.10",
60
60
  "@push.rocks/smartstate": "^2.3.0",
61
61
  "@push.rocks/smartunique": "^3.0.9",
62
- "@push.rocks/smartvpn": "1.12.0",
62
+ "@push.rocks/smartvpn": "1.14.0",
63
63
  "@push.rocks/taskbuffer": "^8.0.2",
64
64
  "@serve.zone/catalog": "^2.9.0",
65
65
  "@serve.zone/interfaces": "^5.3.0",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '11.14.0',
6
+ version: '11.16.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -206,8 +206,21 @@ export interface IDcRouterOptions {
206
206
  dns?: string[];
207
207
  /** Server endpoint hostname for client configs (e.g. 'vpn.example.com') */
208
208
  serverEndpoint?: string;
209
- /** Override forwarding mode. Default: auto-detect (tun if root, socket otherwise) */
210
- forwardingMode?: 'tun' | 'socket';
209
+ /** Pre-defined VPN clients created on startup */
210
+ clients?: Array<{
211
+ clientId: string;
212
+ serverDefinedClientTags?: string[];
213
+ description?: string;
214
+ }>;
215
+ /** Destination routing policy for VPN client traffic.
216
+ * Default in socket mode: { default: 'forceTarget', target: '127.0.0.1' } (all traffic → SmartProxy).
217
+ * Default in tun mode: not set (all traffic passes through). */
218
+ destinationPolicy?: {
219
+ default: 'forceTarget' | 'block' | 'allow';
220
+ target?: string;
221
+ allowList?: string[];
222
+ blockList?: string[];
223
+ };
211
224
  };
212
225
  }
213
226
 
@@ -453,7 +466,14 @@ export class DcRouter {
453
466
  () => this.getConstructorRoutes(),
454
467
  () => this.smartProxy,
455
468
  () => this.options.http3,
456
- () => this.options.vpnConfig?.enabled ? (this.options.vpnConfig.subnet || '10.8.0.0/24') : undefined,
469
+ this.options.vpnConfig?.enabled
470
+ ? (tags?: string[]) => {
471
+ if (tags?.length && this.vpnManager) {
472
+ return this.vpnManager.getClientIpsForServerDefinedTags(tags);
473
+ }
474
+ return [this.options.vpnConfig?.subnet || '10.8.0.0/24'];
475
+ }
476
+ : undefined,
457
477
  );
458
478
  this.apiTokenManager = new ApiTokenManager(this.storageManager);
459
479
  await this.apiTokenManager.initialize();
@@ -664,9 +684,8 @@ export class DcRouter {
664
684
  if (this.vpnManager && this.options.vpnConfig?.enabled) {
665
685
  const subnet = this.vpnManager.getSubnet();
666
686
  const wgPort = this.options.vpnConfig.wgListenPort ?? 51820;
667
- const mode = this.vpnManager.forwardingMode;
668
687
  const clientCount = this.vpnManager.listClients().length;
669
- logger.log('info', `VPN Service: mode=${mode}, subnet=${subnet}, wg=:${wgPort}, clients=${clientCount}`);
688
+ logger.log('info', `VPN Service: subnet=${subnet}, wg=:${wgPort}, clients=${clientCount}`);
670
689
  }
671
690
 
672
691
  // Remote Ingress summary
@@ -950,19 +969,14 @@ export class DcRouter {
950
969
  smartProxyConfig.proxyIPs = ['127.0.0.1'];
951
970
  }
952
971
 
953
- // When VPN is in socket mode, the userspace NAT engine sends PP v2 headers
954
- // on outbound connections to SmartProxy to preserve VPN client tunnel IPs.
972
+ // VPN uses socket mode with PP v2 SmartProxy must accept proxy protocol from localhost
955
973
  if (this.options.vpnConfig?.enabled) {
956
- const vpnForwardingMode = this.options.vpnConfig.forwardingMode
957
- ?? (process.getuid?.() === 0 ? 'tun' : 'socket');
958
- if (vpnForwardingMode === 'socket') {
959
- smartProxyConfig.acceptProxyProtocol = true;
960
- if (!smartProxyConfig.proxyIPs) {
961
- smartProxyConfig.proxyIPs = [];
962
- }
963
- if (!smartProxyConfig.proxyIPs.includes('127.0.0.1')) {
964
- smartProxyConfig.proxyIPs.push('127.0.0.1');
965
- }
974
+ smartProxyConfig.acceptProxyProtocol = true;
975
+ if (!smartProxyConfig.proxyIPs) {
976
+ smartProxyConfig.proxyIPs = [];
977
+ }
978
+ if (!smartProxyConfig.proxyIPs.includes('127.0.0.1')) {
979
+ smartProxyConfig.proxyIPs.push('127.0.0.1');
966
980
  }
967
981
  }
968
982
 
@@ -2085,7 +2099,12 @@ export class DcRouter {
2085
2099
  wgListenPort: this.options.vpnConfig.wgListenPort,
2086
2100
  dns: this.options.vpnConfig.dns,
2087
2101
  serverEndpoint: this.options.vpnConfig.serverEndpoint,
2088
- forwardingMode: this.options.vpnConfig.forwardingMode,
2102
+ initialClients: this.options.vpnConfig.clients,
2103
+ destinationPolicy: this.options.vpnConfig.destinationPolicy,
2104
+ onClientChanged: () => {
2105
+ // Re-apply routes so tag-based ipAllowLists get updated
2106
+ this.routeConfigManager?.applyRoutes();
2107
+ },
2089
2108
  });
2090
2109
 
2091
2110
  await this.vpnManager.start();
@@ -2104,11 +2123,23 @@ export class DcRouter {
2104
2123
  if (dcrouterRoute.vpn?.required) {
2105
2124
  injectedCount++;
2106
2125
  const existing = route.security?.ipAllowList || [];
2126
+
2127
+ let vpnAllowList: string[];
2128
+ if (dcrouterRoute.vpn.allowedServerDefinedClientTags?.length && this.vpnManager) {
2129
+ // Tag-based: only specific client IPs
2130
+ vpnAllowList = this.vpnManager.getClientIpsForServerDefinedTags(
2131
+ dcrouterRoute.vpn.allowedServerDefinedClientTags,
2132
+ );
2133
+ } else {
2134
+ // No tags specified: entire VPN subnet
2135
+ vpnAllowList = [vpnSubnet];
2136
+ }
2137
+
2107
2138
  return {
2108
2139
  ...route,
2109
2140
  security: {
2110
2141
  ...route.security,
2111
- ipAllowList: [...existing, vpnSubnet],
2142
+ ipAllowList: [...existing, ...vpnAllowList],
2112
2143
  },
2113
2144
  };
2114
2145
  }
@@ -2116,7 +2147,7 @@ export class DcRouter {
2116
2147
  });
2117
2148
 
2118
2149
  if (injectedCount > 0) {
2119
- logger.log('info', `VPN: Injected ipAllowList (${vpnSubnet}) into ${injectedCount} VPN-protected route(s)`);
2150
+ logger.log('info', `VPN: Injected ipAllowList into ${injectedCount} VPN-protected route(s)`);
2120
2151
  }
2121
2152
 
2122
2153
  return result;
@@ -23,7 +23,7 @@ export class RouteConfigManager {
23
23
  private getHardcodedRoutes: () => plugins.smartproxy.IRouteConfig[],
24
24
  private getSmartProxy: () => plugins.smartproxy.SmartProxy | undefined,
25
25
  private getHttp3Config?: () => IHttp3Config | undefined,
26
- private getVpnSubnet?: () => string | undefined,
26
+ private getVpnAllowList?: (tags?: string[]) => string[],
27
27
  ) {}
28
28
 
29
29
  /**
@@ -246,7 +246,7 @@ export class RouteConfigManager {
246
246
  // Private: apply merged routes to SmartProxy
247
247
  // =========================================================================
248
248
 
249
- private async applyRoutes(): Promise<void> {
249
+ public async applyRoutes(): Promise<void> {
250
250
  const smartProxy = this.getSmartProxy();
251
251
  if (!smartProxy) return;
252
252
 
@@ -262,9 +262,9 @@ export class RouteConfigManager {
262
262
  enabledRoutes.push(route);
263
263
  }
264
264
 
265
- // Add enabled programmatic routes (with HTTP/3 augmentation if enabled)
265
+ // Add enabled programmatic routes (with HTTP/3 and VPN augmentation)
266
266
  const http3Config = this.getHttp3Config?.();
267
- const vpnSubnet = this.getVpnSubnet?.();
267
+ const vpnAllowList = this.getVpnAllowList;
268
268
  for (const stored of this.storedRoutes.values()) {
269
269
  if (stored.enabled) {
270
270
  let route = stored.route;
@@ -272,15 +272,16 @@ export class RouteConfigManager {
272
272
  route = augmentRouteWithHttp3(route, { enabled: true, ...http3Config });
273
273
  }
274
274
  // Inject VPN security for programmatic routes with vpn.required
275
- if (vpnSubnet) {
275
+ if (vpnAllowList) {
276
276
  const dcRoute = route as IDcRouterRouteConfig;
277
277
  if (dcRoute.vpn?.required) {
278
278
  const existing = route.security?.ipAllowList || [];
279
+ const allowList = vpnAllowList(dcRoute.vpn.allowedServerDefinedClientTags);
279
280
  route = {
280
281
  ...route,
281
282
  security: {
282
283
  ...route.security,
283
- ipAllowList: [...existing, vpnSubnet],
284
+ ipAllowList: [...existing, ...allowList],
284
285
  },
285
286
  };
286
287
  }
@@ -25,7 +25,7 @@ export class VpnHandler {
25
25
  const clients = manager.listClients().map((c) => ({
26
26
  clientId: c.clientId,
27
27
  enabled: c.enabled,
28
- tags: c.tags,
28
+ serverDefinedClientTags: c.serverDefinedClientTags,
29
29
  description: c.description,
30
30
  assignedIp: c.assignedIp,
31
31
  createdAt: c.createdAt,
@@ -48,7 +48,6 @@ export class VpnHandler {
48
48
  return {
49
49
  status: {
50
50
  running: false,
51
- forwardingMode: 'socket' as const,
52
51
  subnet: vpnConfig?.subnet || '10.8.0.0/24',
53
52
  wgListenPort: vpnConfig?.wgListenPort ?? 51820,
54
53
  serverPublicKeys: null,
@@ -62,7 +61,6 @@ export class VpnHandler {
62
61
  return {
63
62
  status: {
64
63
  running: manager.running,
65
- forwardingMode: manager.forwardingMode,
66
64
  subnet: manager.getSubnet(),
67
65
  wgListenPort: vpnConfig?.wgListenPort ?? 51820,
68
66
  serverPublicKeys: manager.getServerPublicKeys(),
@@ -89,7 +87,7 @@ export class VpnHandler {
89
87
  try {
90
88
  const bundle = await manager.createClient({
91
89
  clientId: dataArg.clientId,
92
- tags: dataArg.tags,
90
+ serverDefinedClientTags: dataArg.serverDefinedClientTags,
93
91
  description: dataArg.description,
94
92
  });
95
93
 
@@ -98,7 +96,7 @@ export class VpnHandler {
98
96
  client: {
99
97
  clientId: bundle.entry.clientId,
100
98
  enabled: bundle.entry.enabled ?? true,
101
- tags: bundle.entry.tags,
99
+ serverDefinedClientTags: bundle.entry.serverDefinedClientTags,
102
100
  description: bundle.entry.description,
103
101
  assignedIp: bundle.entry.assignedIp,
104
102
  createdAt: Date.now(),
@@ -14,8 +14,21 @@ export interface IVpnManagerConfig {
14
14
  dns?: string[];
15
15
  /** Server endpoint hostname for client configs (e.g. 'vpn.example.com') */
16
16
  serverEndpoint?: string;
17
- /** Override forwarding mode. Default: auto-detect (tun if root, socket otherwise) */
18
- forwardingMode?: 'tun' | 'socket';
17
+ /** Pre-defined VPN clients created on startup (idempotent skips already-persisted clients) */
18
+ initialClients?: Array<{
19
+ clientId: string;
20
+ serverDefinedClientTags?: string[];
21
+ description?: string;
22
+ }>;
23
+ /** Called when clients are created/deleted/toggled — triggers route re-application */
24
+ onClientChanged?: () => void;
25
+ /** Destination routing policy override. Default: forceTarget to 127.0.0.1 */
26
+ destinationPolicy?: {
27
+ default: 'forceTarget' | 'block' | 'allow';
28
+ target?: string;
29
+ allowList?: string[];
30
+ blockList?: string[];
31
+ };
19
32
  }
20
33
 
21
34
  interface IPersistedServerKeys {
@@ -28,7 +41,7 @@ interface IPersistedServerKeys {
28
41
  interface IPersistedClient {
29
42
  clientId: string;
30
43
  enabled: boolean;
31
- tags?: string[];
44
+ serverDefinedClientTags?: string[];
32
45
  description?: string;
33
46
  assignedIp?: string;
34
47
  noisePublicKey: string;
@@ -36,6 +49,8 @@ interface IPersistedClient {
36
49
  createdAt: number;
37
50
  updatedAt: number;
38
51
  expiresAt?: string;
52
+ /** @deprecated Legacy field — migrated to serverDefinedClientTags on load */
53
+ tags?: string[];
39
54
  }
40
55
 
41
56
  /**
@@ -48,19 +63,10 @@ export class VpnManager {
48
63
  private vpnServer?: plugins.smartvpn.VpnServer;
49
64
  private clients: Map<string, IPersistedClient> = new Map();
50
65
  private serverKeys?: IPersistedServerKeys;
51
- private _forwardingMode: 'tun' | 'socket';
52
66
 
53
67
  constructor(storageManager: StorageManager, config: IVpnManagerConfig) {
54
68
  this.storageManager = storageManager;
55
69
  this.config = config;
56
- // Auto-detect forwarding mode: tun if root, socket otherwise
57
- this._forwardingMode = config.forwardingMode
58
- ?? (process.getuid?.() === 0 ? 'tun' : 'socket');
59
- }
60
-
61
- /** The effective forwarding mode (tun or socket). */
62
- public get forwardingMode(): 'tun' | 'socket' {
63
- return this._forwardingMode;
64
70
  }
65
71
 
66
72
  /** The VPN subnet CIDR. */
@@ -92,7 +98,7 @@ export class VpnManager {
92
98
  publicKey: client.noisePublicKey,
93
99
  wgPublicKey: client.wgPublicKey,
94
100
  enabled: client.enabled,
95
- tags: client.tags,
101
+ serverDefinedClientTags: client.serverDefinedClientTags,
96
102
  description: client.description,
97
103
  assignedIp: client.assignedIp,
98
104
  expiresAt: client.expiresAt,
@@ -113,16 +119,33 @@ export class VpnManager {
113
119
  publicKey: this.serverKeys.noisePublicKey,
114
120
  subnet,
115
121
  dns: this.config.dns,
116
- forwardingMode: this._forwardingMode,
122
+ forwardingMode: 'socket',
117
123
  transportMode: 'all',
118
124
  wgPrivateKey: this.serverKeys.wgPrivateKey,
119
125
  wgListenPort,
120
126
  clients: clientEntries,
121
- socketForwardProxyProtocol: this._forwardingMode === 'socket',
127
+ socketForwardProxyProtocol: true,
128
+ destinationPolicy: this.config.destinationPolicy
129
+ ?? { default: 'forceTarget' as const, target: '127.0.0.1' },
122
130
  };
123
131
 
124
132
  await this.vpnServer.start(serverConfig);
125
- logger.log('info', `VPN server started: mode=${this._forwardingMode}, subnet=${subnet}, wg=:${wgListenPort}, clients=${this.clients.size}`);
133
+
134
+ // Create initial clients from config (idempotent — skip already-persisted)
135
+ if (this.config.initialClients) {
136
+ for (const initial of this.config.initialClients) {
137
+ if (!this.clients.has(initial.clientId)) {
138
+ const bundle = await this.createClient({
139
+ clientId: initial.clientId,
140
+ serverDefinedClientTags: initial.serverDefinedClientTags,
141
+ description: initial.description,
142
+ });
143
+ logger.log('info', `VPN: Created initial client '${initial.clientId}' (IP: ${bundle.entry.assignedIp})`);
144
+ }
145
+ }
146
+ }
147
+
148
+ logger.log('info', `VPN server started: subnet=${subnet}, wg=:${wgListenPort}, clients=${this.clients.size}`);
126
149
  }
127
150
 
128
151
  /**
@@ -148,7 +171,7 @@ export class VpnManager {
148
171
  */
149
172
  public async createClient(opts: {
150
173
  clientId: string;
151
- tags?: string[];
174
+ serverDefinedClientTags?: string[];
152
175
  description?: string;
153
176
  }): Promise<plugins.smartvpn.IClientConfigBundle> {
154
177
  if (!this.vpnServer) {
@@ -157,7 +180,7 @@ export class VpnManager {
157
180
 
158
181
  const bundle = await this.vpnServer.createClient({
159
182
  clientId: opts.clientId,
160
- tags: opts.tags,
183
+ serverDefinedClientTags: opts.serverDefinedClientTags,
161
184
  description: opts.description,
162
185
  });
163
186
 
@@ -174,7 +197,7 @@ export class VpnManager {
174
197
  const persisted: IPersistedClient = {
175
198
  clientId: bundle.entry.clientId,
176
199
  enabled: bundle.entry.enabled ?? true,
177
- tags: bundle.entry.tags,
200
+ serverDefinedClientTags: bundle.entry.serverDefinedClientTags,
178
201
  description: bundle.entry.description,
179
202
  assignedIp: bundle.entry.assignedIp,
180
203
  noisePublicKey: bundle.entry.publicKey,
@@ -186,6 +209,7 @@ export class VpnManager {
186
209
  this.clients.set(persisted.clientId, persisted);
187
210
  await this.persistClient(persisted);
188
211
 
212
+ this.config.onClientChanged?.();
189
213
  return bundle;
190
214
  }
191
215
 
@@ -199,6 +223,7 @@ export class VpnManager {
199
223
  await this.vpnServer.removeClient(clientId);
200
224
  this.clients.delete(clientId);
201
225
  await this.storageManager.delete(`${STORAGE_PREFIX_CLIENTS}${clientId}`);
226
+ this.config.onClientChanged?.();
202
227
  }
203
228
 
204
229
  /**
@@ -220,6 +245,7 @@ export class VpnManager {
220
245
  client.updatedAt = Date.now();
221
246
  await this.persistClient(client);
222
247
  }
248
+ this.config.onClientChanged?.();
223
249
  }
224
250
 
225
251
  /**
@@ -234,6 +260,7 @@ export class VpnManager {
234
260
  client.updatedAt = Date.now();
235
261
  await this.persistClient(client);
236
262
  }
263
+ this.config.onClientChanged?.();
237
264
  }
238
265
 
239
266
  /**
@@ -283,6 +310,22 @@ export class VpnManager {
283
310
  return config;
284
311
  }
285
312
 
313
+ // ── Tag-based access control ───────────────────────────────────────────
314
+
315
+ /**
316
+ * Get assigned IPs for all enabled clients matching any of the given server-defined tags.
317
+ */
318
+ public getClientIpsForServerDefinedTags(tags: string[]): string[] {
319
+ const ips: string[] = [];
320
+ for (const client of this.clients.values()) {
321
+ if (!client.enabled || !client.assignedIp) continue;
322
+ if (client.serverDefinedClientTags?.some(t => tags.includes(t))) {
323
+ ips.push(client.assignedIp);
324
+ }
325
+ }
326
+ return ips;
327
+ }
328
+
286
329
  // ── Status and telemetry ───────────────────────────────────────────────
287
330
 
288
331
  /**
@@ -364,6 +407,12 @@ export class VpnManager {
364
407
  for (const key of keys) {
365
408
  const client = await this.storageManager.getJSON<IPersistedClient>(key);
366
409
  if (client) {
410
+ // Migrate legacy `tags` → `serverDefinedClientTags`
411
+ if (!client.serverDefinedClientTags && client.tags) {
412
+ client.serverDefinedClientTags = client.tags;
413
+ delete client.tags;
414
+ await this.persistClient(client);
415
+ }
367
416
  this.clients.set(client.clientId, client);
368
417
  }
369
418
  }
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '11.14.0',
6
+ version: '11.16.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -974,7 +974,7 @@ export const fetchVpnAction = vpnStatePart.createAction(async (statePartArg): Pr
974
974
 
975
975
  export const createVpnClientAction = vpnStatePart.createAction<{
976
976
  clientId: string;
977
- tags?: string[];
977
+ serverDefinedClientTags?: string[];
978
978
  description?: string;
979
979
  }>(async (statePartArg, dataArg, actionContext): Promise<IVpnState> => {
980
980
  const context = getActionContext();
@@ -988,7 +988,7 @@ export const createVpnClientAction = vpnStatePart.createAction<{
988
988
  const response = await request.fire({
989
989
  identity: context.identity!,
990
990
  clientId: dataArg.clientId,
991
- tags: dataArg.tags,
991
+ serverDefinedClientTags: dataArg.serverDefinedClientTags,
992
992
  description: dataArg.description,
993
993
  });
994
994
 
@@ -181,7 +181,7 @@ export class OpsViewVpn extends DeesElement {
181
181
  type: 'text',
182
182
  value: status?.running ? 'Running' : 'Stopped',
183
183
  icon: 'lucide:server',
184
- description: status?.running ? `${status.forwardingMode} mode` : 'VPN server not running',
184
+ description: status?.running ? 'Active' : 'VPN server not running',
185
185
  color: status?.running ? '#10b981' : '#ef4444',
186
186
  },
187
187
  ];
@@ -232,10 +232,6 @@ export class OpsViewVpn extends DeesElement {
232
232
  <span class="infoLabel">WireGuard Port</span>
233
233
  <span class="infoValue">${status.wgListenPort}</span>
234
234
  </div>
235
- <div class="infoItem">
236
- <span class="infoLabel">Forwarding Mode</span>
237
- <span class="infoValue">${status.forwardingMode}</span>
238
- </div>
239
235
  ${status.serverPublicKeys ? html`
240
236
  <div class="infoItem">
241
237
  <span class="infoLabel">WG Public Key</span>
@@ -255,8 +251,8 @@ export class OpsViewVpn extends DeesElement {
255
251
  ? html`<span class="statusBadge enabled">enabled</span>`
256
252
  : html`<span class="statusBadge disabled">disabled</span>`,
257
253
  'VPN IP': client.assignedIp || '-',
258
- 'Tags': client.tags?.length
259
- ? html`${client.tags.map(t => html`<span class="tagBadge">${t}</span>`)}`
254
+ 'Tags': client.serverDefinedClientTags?.length
255
+ ? html`${client.serverDefinedClientTags.map(t => html`<span class="tagBadge">${t}</span>`)}`
260
256
  : '-',
261
257
  'Description': client.description || '-',
262
258
  'Created': new Date(client.createdAt).toLocaleDateString(),
@@ -312,11 +308,11 @@ export class OpsViewVpn extends DeesElement {
312
308
  action: async (modal: any) => {
313
309
  const form = modal.shadowRoot!.querySelector('dees-form') as any;
314
310
  const data = await form.collectFormData();
315
- const tags = data.tags ? data.tags.split(',').map((t: string) => t.trim()).filter(Boolean) : undefined;
311
+ const serverDefinedClientTags = data.tags ? data.tags.split(',').map((t: string) => t.trim()).filter(Boolean) : undefined;
316
312
  await appstate.vpnStatePart.dispatchAction(appstate.createVpnClientAction, {
317
313
  clientId: data.clientId,
318
314
  description: data.description || undefined,
319
- tags,
315
+ serverDefinedClientTags,
320
316
  });
321
317
  modal.destroy();
322
318
  },