@serve.zone/dcrouter 11.13.0 → 11.15.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.
- package/dist_serve/bundle.js +4 -4
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.d.ts +6 -0
- package/dist_ts/classes.dcrouter.js +25 -4
- package/dist_ts/config/classes.route-config-manager.d.ts +3 -3
- package/dist_ts/config/classes.route-config-manager.js +9 -8
- package/dist_ts/opsserver/handlers/vpn.handler.js +4 -4
- package/dist_ts/vpn/classes.vpn-manager.d.ts +16 -2
- package/dist_ts/vpn/classes.vpn-manager.js +42 -4
- package/dist_ts_interfaces/data/remoteingress.d.ts +2 -0
- package/dist_ts_interfaces/data/vpn.d.ts +1 -1
- package/dist_ts_interfaces/requests/vpn.d.ts +1 -1
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +1 -1
- package/dist_ts_web/appstate.js +2 -2
- package/dist_ts_web/elements/ops-view-vpn.js +5 -5
- package/dist_ts_web/router.d.ts +1 -1
- package/dist_ts_web/router.js +2 -2
- package/package.json +2 -2
- package/readme.md +107 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +33 -3
- package/ts/config/classes.route-config-manager.ts +7 -6
- package/ts/opsserver/handlers/vpn.handler.ts +3 -3
- package/ts/vpn/classes.vpn-manager.ts +56 -5
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +2 -2
- package/ts_web/elements/ops-view-vpn.ts +4 -4
- package/ts_web/readme.md +17 -0
- package/ts_web/router.ts +1 -1
|
@@ -287,8 +287,8 @@ let OpsViewVpn = (() => {
|
|
|
287
287
|
? html `<span class="statusBadge enabled">enabled</span>`
|
|
288
288
|
: html `<span class="statusBadge disabled">disabled</span>`,
|
|
289
289
|
'VPN IP': client.assignedIp || '-',
|
|
290
|
-
'Tags': client.
|
|
291
|
-
? html `${client.
|
|
290
|
+
'Tags': client.serverDefinedClientTags?.length
|
|
291
|
+
? html `${client.serverDefinedClientTags.map(t => html `<span class="tagBadge">${t}</span>`)}`
|
|
292
292
|
: '-',
|
|
293
293
|
'Description': client.description || '-',
|
|
294
294
|
'Created': new Date(client.createdAt).toLocaleDateString(),
|
|
@@ -344,11 +344,11 @@ let OpsViewVpn = (() => {
|
|
|
344
344
|
action: async (modal) => {
|
|
345
345
|
const form = modal.shadowRoot.querySelector('dees-form');
|
|
346
346
|
const data = await form.collectFormData();
|
|
347
|
-
const
|
|
347
|
+
const serverDefinedClientTags = data.tags ? data.tags.split(',').map((t) => t.trim()).filter(Boolean) : undefined;
|
|
348
348
|
await appstate.vpnStatePart.dispatchAction(appstate.createVpnClientAction, {
|
|
349
349
|
clientId: data.clientId,
|
|
350
350
|
description: data.description || undefined,
|
|
351
|
-
|
|
351
|
+
serverDefinedClientTags,
|
|
352
352
|
});
|
|
353
353
|
modal.destroy();
|
|
354
354
|
},
|
|
@@ -366,4 +366,4 @@ let OpsViewVpn = (() => {
|
|
|
366
366
|
return OpsViewVpn = _classThis;
|
|
367
367
|
})();
|
|
368
368
|
export { OpsViewVpn };
|
|
369
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
369
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BzLXZpZXctdnBuLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfd2ViL2VsZW1lbnRzL29wcy12aWV3LXZwbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsT0FBTyxFQUNMLFdBQVcsRUFDWCxJQUFJLEVBQ0osYUFBYSxFQUViLEdBQUcsRUFDSCxLQUFLLEVBQ0wsVUFBVSxHQUNYLE1BQU0sNkJBQTZCLENBQUM7QUFDckMsT0FBTyxLQUFLLFFBQVEsTUFBTSxnQkFBZ0IsQ0FBQztBQUMzQyxPQUFPLEtBQUssVUFBVSxNQUFNLG1DQUFtQyxDQUFDO0FBQ2hFLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUM5QyxPQUFPLEVBQW1CLE1BQU0sNkJBQTZCLENBQUM7SUFTakQsVUFBVTs0QkFEdEIsYUFBYSxDQUFDLGNBQWMsQ0FBQzs7OztzQkFDRSxXQUFXOzs7OzBCQUFuQixTQUFRLFdBQVc7Ozs7b0NBQ3hDLEtBQUssRUFBRTtZQUNSLDZLQUFTLFFBQVEsNkJBQVIsUUFBUSwyRkFBeUQ7WUFGNUUsNktBb1RDOzs7O1FBbFRDLDZFQUF3QyxRQUFRLENBQUMsWUFBWSxDQUFDLFFBQVEsRUFBRyxFQUFDO1FBQTFFLElBQVMsUUFBUSw4Q0FBeUQ7UUFBMUUsSUFBUyxRQUFRLG9EQUF5RDtRQUUxRTtZQUNFLEtBQUssRUFBRSxDQUFDOztZQUNSLE1BQU0sR0FBRyxHQUFHLFFBQVEsQ0FBQyxZQUFZLENBQUMsTUFBTSxFQUFFLENBQUMsU0FBUyxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUU7Z0JBQ2hFLElBQUksQ0FBQyxRQUFRLEdBQUcsUUFBUSxDQUFDO1lBQzNCLENBQUMsQ0FBQyxDQUFDO1lBQ0gsSUFBSSxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7U0FDaEM7UUFFRCxLQUFLLENBQUMsaUJBQWlCO1lBQ3JCLE1BQU0sS0FBSyxDQUFDLGlCQUFpQixFQUFFLENBQUM7WUFDaEMsTUFBTSxRQUFRLENBQUMsWUFBWSxDQUFDLGNBQWMsQ0FBQyxRQUFRLENBQUMsY0FBYyxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQzVFLENBQUM7UUFFTSxNQUFNLENBQUMsTUFBTSxHQUFHO1lBQ3JCLFVBQVUsQ0FBQyxhQUFhO1lBQ3hCLFdBQVc7WUFDWCxHQUFHLENBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7c0JBbUJlLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQztpQkFDN0MsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDOzs7O3NCQUluQyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7aUJBQzdDLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQzs7Ozs7c0JBS25DLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQzs0QkFDbEMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDOzs7Ozs7OztzQkFROUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDOzs7Ozs7Ozs7Ozs7Ozs7aUJBZTdDLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQzs7Ozs7Ozs7OztzQkFVbkMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDO2lCQUM3QyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7Ozs7Ozs7OztzQkFTbkMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDOzs0QkFFbEMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDOzs7Ozs7Ozs7Ozs7OztpQkFjbkQsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDOzs7Ozs7aUJBTXhDLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQzs7S0FFcEQ7U0FDRixDQUFDO1FBRUYsTUFBTTtZQUNKLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDO1lBQ3BDLE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDO1lBQ3RDLE1BQU0sY0FBYyxHQUFHLE1BQU0sRUFBRSxnQkFBZ0IsSUFBSSxDQUFDLENBQUM7WUFDckQsTUFBTSxZQUFZLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQztZQUNwQyxNQUFNLGNBQWMsR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLE1BQU0sQ0FBQztZQUU3RCxNQUFNLFVBQVUsR0FBaUI7Z0JBQy9CO29CQUNFLEVBQUUsRUFBRSxjQUFjO29CQUNsQixLQUFLLEVBQUUsZUFBZTtvQkFDdEIsSUFBSSxFQUFFLFFBQVE7b0JBQ2QsS0FBSyxFQUFFLFlBQVk7b0JBQ25CLElBQUksRUFBRSxjQUFjO29CQUNwQixXQUFXLEVBQUUsd0JBQXdCO29CQUNyQyxLQUFLLEVBQUUsU0FBUztpQkFDakI7Z0JBQ0Q7b0JBQ0UsRUFBRSxFQUFFLGtCQUFrQjtvQkFDdEIsS0FBSyxFQUFFLFdBQVc7b0JBQ2xCLElBQUksRUFBRSxRQUFRO29CQUNkLEtBQUssRUFBRSxjQUFjO29CQUNyQixJQUFJLEVBQUUsYUFBYTtvQkFDbkIsV0FBVyxFQUFFLHFCQUFxQjtvQkFDbEMsS0FBSyxFQUFFLFNBQVM7aUJBQ2pCO2dCQUNEO29CQUNFLEVBQUUsRUFBRSxnQkFBZ0I7b0JBQ3BCLEtBQUssRUFBRSxTQUFTO29CQUNoQixJQUFJLEVBQUUsUUFBUTtvQkFDZCxLQUFLLEVBQUUsY0FBYztvQkFDckIsSUFBSSxFQUFFLG9CQUFvQjtvQkFDMUIsV0FBVyxFQUFFLDZCQUE2QjtvQkFDMUMsS0FBSyxFQUFFLFNBQVM7aUJBQ2pCO2dCQUNEO29CQUNFLEVBQUUsRUFBRSxjQUFjO29CQUNsQixLQUFLLEVBQUUsUUFBUTtvQkFDZixJQUFJLEVBQUUsTUFBTTtvQkFDWixLQUFLLEVBQUUsTUFBTSxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxTQUFTO29CQUM5QyxJQUFJLEVBQUUsZUFBZTtvQkFDckIsV0FBVyxFQUFFLE1BQU0sRUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDLEdBQUcsTUFBTSxDQUFDLGNBQWMsT0FBTyxDQUFDLENBQUMsQ0FBQyx3QkFBd0I7b0JBQ3pGLEtBQUssRUFBRSxNQUFNLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLFNBQVM7aUJBQy9DO2FBQ0YsQ0FBQztZQUVGLE9BQU8sSUFBSSxDQUFBOzs7UUFHUCxJQUFJLENBQUMsUUFBUSxDQUFDLGVBQWUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFBOzs7O2lCQUkzQixJQUFJLENBQUMsUUFBUSxDQUFDLGVBQWU7O3FCQUV6QixLQUFLLElBQUksRUFBRTtnQkFDbEIsSUFBSSxTQUFTLENBQUMsU0FBUyxJQUFJLE9BQU8sU0FBUyxDQUFDLFNBQVMsQ0FBQyxTQUFTLEtBQUssVUFBVSxFQUFFLENBQUM7b0JBQy9FLE1BQU0sU0FBUyxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxlQUFnQixDQUFDLENBQUM7Z0JBQ3RFLENBQUM7Z0JBQ0QsTUFBTSxFQUFFLFNBQVMsRUFBRSxHQUFHLE1BQU0sTUFBTSxDQUFDLDZCQUE2QixDQUFDLENBQUM7Z0JBQ2xFLFNBQVMsQ0FBQyxhQUFhLENBQUMsRUFBRSxPQUFPLEVBQUUsNEJBQTRCLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztZQUN0RyxDQUFDOzs7cUJBR1EsR0FBRyxFQUFFO2dCQUNaLE1BQU0sSUFBSSxHQUFHLElBQUksSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxlQUFnQixDQUFDLEVBQUUsRUFBRSxJQUFJLEVBQUUsWUFBWSxFQUFFLENBQUMsQ0FBQztnQkFDaEYsTUFBTSxHQUFHLEdBQUcsR0FBRyxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsQ0FBQztnQkFDdEMsTUFBTSxDQUFDLEdBQUcsUUFBUSxDQUFDLGFBQWEsQ0FBQyxHQUFHLENBQUMsQ0FBQztnQkFDdEMsQ0FBQyxDQUFDLElBQUksR0FBRyxHQUFHLENBQUM7Z0JBQ2IsQ0FBQyxDQUFDLFFBQVEsR0FBRyxnQkFBZ0IsQ0FBQztnQkFDOUIsQ0FBQyxDQUFDLEtBQUssRUFBRSxDQUFDO2dCQUNWLEdBQUcsQ0FBQyxlQUFlLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDM0IsQ0FBQzs7O3FCQUdRLEdBQUcsRUFBRSxDQUFDLFFBQVEsQ0FBQyxZQUFZLENBQUMsY0FBYyxDQUFDLFFBQVEsQ0FBQywwQkFBMEIsRUFBRSxJQUFJLENBQUM7OztPQUduRyxDQUFDLENBQUMsQ0FBQyxFQUFFOztvQ0FFd0IsVUFBVTs7UUFFdEMsTUFBTSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUE7Ozs7c0NBSWlCLE1BQU0sQ0FBQyxNQUFNOzs7O3NDQUliLE1BQU0sQ0FBQyxZQUFZOzs7O3NDQUluQixNQUFNLENBQUMsY0FBYzs7WUFFL0MsTUFBTSxDQUFDLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUE7Ozt3RkFHOEMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLFdBQVc7O1dBRWhILENBQUMsQ0FBQyxDQUFDLEVBQUU7O09BRVQsQ0FBQyxDQUFDLENBQUMsRUFBRTs7O29CQUdRLGFBQWE7b0JBQ2Isb0RBQW9EO2dCQUN4RCxPQUFPOzJCQUNJLENBQUMsTUFBa0MsRUFBRSxFQUFFLENBQUMsQ0FBQztnQkFDMUQsV0FBVyxFQUFFLE1BQU0sQ0FBQyxRQUFRO2dCQUM1QixRQUFRLEVBQUUsTUFBTSxDQUFDLE9BQU87b0JBQ3RCLENBQUMsQ0FBQyxJQUFJLENBQUEsa0RBQWtEO29CQUN4RCxDQUFDLENBQUMsSUFBSSxDQUFBLG9EQUFvRDtnQkFDNUQsUUFBUSxFQUFFLE1BQU0sQ0FBQyxVQUFVLElBQUksR0FBRztnQkFDbEMsTUFBTSxFQUFFLE1BQU0sQ0FBQyx1QkFBdUIsRUFBRSxNQUFNO29CQUM1QyxDQUFDLENBQUMsSUFBSSxDQUFBLEdBQUcsTUFBTSxDQUFDLHVCQUF1QixDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQSwwQkFBMEIsQ0FBQyxTQUFTLENBQUMsRUFBRTtvQkFDNUYsQ0FBQyxDQUFDLEdBQUc7Z0JBQ1AsYUFBYSxFQUFFLE1BQU0sQ0FBQyxXQUFXLElBQUksR0FBRztnQkFDeEMsU0FBUyxFQUFFLElBQUksSUFBSSxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQyxrQkFBa0IsRUFBRTthQUMzRCxDQUFDO3VCQUNhO2dCQUNiO29CQUNFLElBQUksRUFBRSxRQUFRO29CQUNkLFFBQVEsRUFBRSxjQUFjO29CQUN4QixNQUFNLEVBQUUsS0FBSyxFQUFFLE1BQWtDLEVBQUUsRUFBRTt3QkFDbkQsTUFBTSxRQUFRLENBQUMsWUFBWSxDQUFDLGNBQWMsQ0FBQyxRQUFRLENBQUMscUJBQXFCLEVBQUU7NEJBQ3pFLFFBQVEsRUFBRSxNQUFNLENBQUMsUUFBUTs0QkFDekIsT0FBTyxFQUFFLENBQUMsTUFBTSxDQUFDLE9BQU87eUJBQ3pCLENBQUMsQ0FBQztvQkFDTCxDQUFDO2lCQUNGO2dCQUNEO29CQUNFLElBQUksRUFBRSxRQUFRO29CQUNkLFFBQVEsRUFBRSxlQUFlO29CQUN6QixNQUFNLEVBQUUsS0FBSyxFQUFFLE1BQWtDLEVBQUUsRUFBRTt3QkFDbkQsTUFBTSxFQUFFLFNBQVMsRUFBRSxHQUFHLE1BQU0sTUFBTSxDQUFDLDZCQUE2QixDQUFDLENBQUM7d0JBQ2xFLFNBQVMsQ0FBQyxhQUFhLENBQUM7NEJBQ3RCLE9BQU8sRUFBRSxtQkFBbUI7NEJBQzVCLE9BQU8sRUFBRSxJQUFJLENBQUEsOENBQThDLE1BQU0sQ0FBQyxRQUFRLFFBQVE7NEJBQ2xGLFdBQVcsRUFBRTtnQ0FDWCxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxLQUFVLEVBQUUsRUFBRSxDQUFDLEtBQUssQ0FBQyxPQUFPLEVBQUUsRUFBRTtnQ0FDakU7b0NBQ0UsSUFBSSxFQUFFLFFBQVE7b0NBQ2QsTUFBTSxFQUFFLEtBQUssRUFBRSxLQUFVLEVBQUUsRUFBRTt3Q0FDM0IsTUFBTSxRQUFRLENBQUMsWUFBWSxDQUFDLGNBQWMsQ0FBQyxRQUFRLENBQUMscUJBQXFCLEVBQUUsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDO3dDQUM1RixLQUFLLENBQUMsT0FBTyxFQUFFLENBQUM7b0NBQ2xCLENBQUM7aUNBQ0Y7NkJBQ0Y7eUJBQ0YsQ0FBQyxDQUFDO29CQUNMLENBQUM7aUJBQ0Y7YUFDRjt5QkFDZ0IsS0FBSyxJQUFJLEVBQUU7Z0JBQzFCLE1BQU0sRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLGFBQWEsRUFBRSxHQUFHLE1BQU0sTUFBTSxDQUFDLDZCQUE2QixDQUFDLENBQUM7Z0JBQzNGLFNBQVMsQ0FBQyxhQUFhLENBQUM7b0JBQ3RCLE9BQU8sRUFBRSxtQkFBbUI7b0JBQzVCLE9BQU8sRUFBRSxJQUFJLENBQUE7O3dEQUUrQixXQUFXLFNBQVMsVUFBVTsyREFDM0IsYUFBYSxTQUFTLGFBQWE7b0RBQzFDLHdCQUF3QixTQUFTLE1BQU07O2FBRTlFO29CQUNELFdBQVcsRUFBRTt3QkFDWCxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxLQUFVLEVBQUUsRUFBRSxDQUFDLEtBQUssQ0FBQyxPQUFPLEVBQUUsRUFBRTt3QkFDakU7NEJBQ0UsSUFBSSxFQUFFLFFBQVE7NEJBQ2QsTUFBTSxFQUFFLEtBQUssRUFBRSxLQUFVLEVBQUUsRUFBRTtnQ0FDM0IsTUFBTSxJQUFJLEdBQUcsS0FBSyxDQUFDLFVBQVcsQ0FBQyxhQUFhLENBQUMsV0FBVyxDQUFRLENBQUM7Z0NBQ2pFLE1BQU0sSUFBSSxHQUFHLE1BQU0sSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDO2dDQUMxQyxNQUFNLHVCQUF1QixHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQVMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7Z0NBQzFILE1BQU0sUUFBUSxDQUFDLFlBQVksQ0FBQyxjQUFjLENBQUMsUUFBUSxDQUFDLHFCQUFxQixFQUFFO29DQUN6RSxRQUFRLEVBQUUsSUFBSSxDQUFDLFFBQVE7b0NBQ3ZCLFdBQVcsRUFBRSxJQUFJLENBQUMsV0FBVyxJQUFJLFNBQVM7b0NBQzFDLHVCQUF1QjtpQ0FDeEIsQ0FBQyxDQUFDO2dDQUNILEtBQUssQ0FBQyxPQUFPLEVBQUUsQ0FBQzs0QkFDbEIsQ0FBQzt5QkFDRjtxQkFDRjtpQkFDRixDQUFDLENBQUM7WUFDTCxDQUFDOztLQUVKLENBQUM7UUFDSixDQUFDOztZQW5UVSx1REFBVTs7Ozs7U0FBVixVQUFVIn0=
|
package/dist_ts_web/router.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const validViews: readonly ["overview", "network", "emails", "logs", "routes", "apitokens", "configuration", "security", "certificates", "remoteingress"];
|
|
1
|
+
export declare const validViews: readonly ["overview", "network", "emails", "logs", "routes", "apitokens", "configuration", "security", "certificates", "remoteingress", "vpn"];
|
|
2
2
|
export type TValidView = typeof validViews[number];
|
|
3
3
|
declare class AppRouter {
|
|
4
4
|
private router;
|
package/dist_ts_web/router.js
CHANGED
|
@@ -1,7 +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', 'routes', 'apitokens', 'configuration', 'security', 'certificates', 'remoteingress'];
|
|
4
|
+
export const validViews = ['overview', 'network', 'emails', 'logs', 'routes', 'apitokens', 'configuration', 'security', 'certificates', 'remoteingress', 'vpn'];
|
|
5
5
|
class AppRouter {
|
|
6
6
|
router;
|
|
7
7
|
initialized = false;
|
|
@@ -88,4 +88,4 @@ class AppRouter {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
export const appRouter = new AppRouter();
|
|
91
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
91
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHNfd2ViL3JvdXRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEtBQUssUUFBUSxNQUFNLGVBQWUsQ0FBQztBQUUxQyxNQUFNLFdBQVcsR0FBRyxPQUFPLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsV0FBVyxDQUFDO0FBRXJFLE1BQU0sQ0FBQyxNQUFNLFVBQVUsR0FBRyxDQUFDLFVBQVUsRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsV0FBVyxFQUFFLGVBQWUsRUFBRSxVQUFVLEVBQUUsY0FBYyxFQUFFLGVBQWUsRUFBRSxLQUFLLENBQVUsQ0FBQztBQUl6SyxNQUFNLFNBQVM7SUFDTCxNQUFNLENBQW1DO0lBQ3pDLFdBQVcsR0FBRyxLQUFLLENBQUM7SUFDcEIsbUJBQW1CLEdBQUcsS0FBSyxDQUFDO0lBRXBDO1FBQ0UsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLFdBQVcsQ0FBQyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDO0lBQ2xELENBQUM7SUFFTSxJQUFJO1FBQ1QsSUFBSSxJQUFJLENBQUMsV0FBVztZQUFFLE9BQU87UUFDN0IsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBQ25CLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztRQUN0QixJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztRQUMxQixJQUFJLENBQUMsV0FBVyxHQUFHLElBQUksQ0FBQztJQUMxQixDQUFDO0lBRU8sV0FBVztRQUNqQixLQUFLLE1BQU0sSUFBSSxJQUFJLFVBQVUsRUFBRSxDQUFDO1lBQzlCLElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLElBQUksSUFBSSxFQUFFLEVBQUUsS0FBSyxJQUFJLEVBQUU7Z0JBQ3BDLElBQUksQ0FB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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": "11.
|
|
4
|
+
"version": "11.15.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.
|
|
62
|
+
"@push.rocks/smartvpn": "1.13.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",
|
package/readme.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**dcrouter: The all-in-one gateway for your datacenter.** 🚀
|
|
6
6
|
|
|
7
|
-
A comprehensive traffic routing solution that provides unified gateway capabilities for HTTP/HTTPS, TCP/SNI, email (SMTP), DNS, RADIUS, and remote edge ingress — all from a single process. Designed for enterprises requiring robust traffic management, automatic TLS certificate provisioning, distributed edge networking, and enterprise-grade email infrastructure.
|
|
7
|
+
A comprehensive traffic routing solution that provides unified gateway capabilities for HTTP/HTTPS, TCP/SNI, email (SMTP), DNS, RADIUS, VPN, and remote edge ingress — all from a single process. Designed for enterprises requiring robust traffic management, automatic TLS certificate provisioning, VPN-based access control, distributed edge networking, and enterprise-grade email infrastructure.
|
|
8
8
|
|
|
9
9
|
## Issue Reporting and Security
|
|
10
10
|
|
|
@@ -23,6 +23,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
23
23
|
- [DNS Server](#dns-server)
|
|
24
24
|
- [RADIUS Server](#radius-server)
|
|
25
25
|
- [Remote Ingress](#remote-ingress)
|
|
26
|
+
- [VPN Access Control](#vpn-access-control)
|
|
26
27
|
- [Certificate Management](#certificate-management)
|
|
27
28
|
- [Storage & Caching](#storage--caching)
|
|
28
29
|
- [Security Features](#security-features)
|
|
@@ -73,6 +74,14 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
73
74
|
- **Real-time status monitoring** — connected/disconnected state, public IP, active tunnels, heartbeat tracking
|
|
74
75
|
- **OpsServer dashboard** with enable/disable, edit, secret regeneration, token copy, and delete actions
|
|
75
76
|
|
|
77
|
+
### 🔐 VPN Access Control (powered by [smartvpn](https://code.foss.global/push.rocks/smartvpn))
|
|
78
|
+
- **WireGuard + native transports** — standard WireGuard clients (iOS, Android, macOS, Windows, Linux) plus custom WebSocket/QUIC tunnels
|
|
79
|
+
- **Route-level VPN gating** — mark any route with `vpn: { required: true }` to restrict access to VPN clients only
|
|
80
|
+
- **Rootless operation** — auto-detects privileges: kernel TUN when running as root, userspace NAT (smoltcp) when not
|
|
81
|
+
- **Client management** — create, enable, disable, rotate keys, export WireGuard `.conf` files via OpsServer API
|
|
82
|
+
- **IP-based enforcement** — VPN clients get IPs from a configurable subnet; SmartProxy enforces `ipAllowList` per route
|
|
83
|
+
- **PROXY protocol v2** — in socket mode, the NAT engine sends PP v2 on outbound connections to preserve VPN client identity
|
|
84
|
+
|
|
76
85
|
### ⚡ High Performance
|
|
77
86
|
- **Rust-powered proxy engine** via SmartProxy for maximum throughput
|
|
78
87
|
- **Rust-powered MTA engine** via smartmta (TypeScript + Rust hybrid) for reliable email delivery
|
|
@@ -89,7 +98,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
89
98
|
### 🖥️ OpsServer Dashboard
|
|
90
99
|
- **Web-based management interface** with real-time monitoring
|
|
91
100
|
- **JWT authentication** with session persistence
|
|
92
|
-
- **Live views** for connections, email queues, DNS queries, RADIUS sessions, certificates, remote ingress edges, and security events
|
|
101
|
+
- **Live views** for connections, email queues, DNS queries, RADIUS sessions, certificates, remote ingress edges, VPN clients, and security events
|
|
93
102
|
- **Domain-centric certificate overview** with backoff status and one-click reprovisioning
|
|
94
103
|
- **Remote ingress management** with connection token generation and one-click copy
|
|
95
104
|
- **Read-only configuration display** — DcRouter is configured through code
|
|
@@ -248,6 +257,13 @@ const router = new DcRouter({
|
|
|
248
257
|
hubDomain: 'hub.example.com',
|
|
249
258
|
},
|
|
250
259
|
|
|
260
|
+
// VPN — restrict sensitive routes to VPN clients
|
|
261
|
+
vpnConfig: {
|
|
262
|
+
enabled: true,
|
|
263
|
+
serverEndpoint: 'vpn.example.com',
|
|
264
|
+
wgListenPort: 51820,
|
|
265
|
+
},
|
|
266
|
+
|
|
251
267
|
// Persistent storage
|
|
252
268
|
storage: { fsPath: '/var/lib/dcrouter/data' },
|
|
253
269
|
|
|
@@ -276,6 +292,7 @@ graph TB
|
|
|
276
292
|
DNS[DNS Queries]
|
|
277
293
|
RAD[RADIUS Clients]
|
|
278
294
|
EDGE[Edge Nodes]
|
|
295
|
+
VPN[VPN Clients]
|
|
279
296
|
end
|
|
280
297
|
|
|
281
298
|
subgraph "DcRouter Core"
|
|
@@ -285,6 +302,7 @@ graph TB
|
|
|
285
302
|
DS[SmartDNS Server<br/><i>Rust-powered</i>]
|
|
286
303
|
RS[SmartRadius Server]
|
|
287
304
|
RI[RemoteIngress Hub<br/><i>Rust data plane</i>]
|
|
305
|
+
VS[SmartVPN Server<br/><i>Rust data plane</i>]
|
|
288
306
|
CM[Certificate Manager<br/><i>smartacme v9</i>]
|
|
289
307
|
OS[OpsServer Dashboard]
|
|
290
308
|
MM[Metrics Manager]
|
|
@@ -305,12 +323,14 @@ graph TB
|
|
|
305
323
|
DNS --> DS
|
|
306
324
|
RAD --> RS
|
|
307
325
|
EDGE --> RI
|
|
326
|
+
VPN --> VS
|
|
308
327
|
|
|
309
328
|
DC --> SP
|
|
310
329
|
DC --> ES
|
|
311
330
|
DC --> DS
|
|
312
331
|
DC --> RS
|
|
313
332
|
DC --> RI
|
|
333
|
+
DC --> VS
|
|
314
334
|
DC --> CM
|
|
315
335
|
DC --> OS
|
|
316
336
|
DC --> MM
|
|
@@ -428,6 +448,17 @@ interface IDcRouterOptions {
|
|
|
428
448
|
};
|
|
429
449
|
};
|
|
430
450
|
|
|
451
|
+
// ── VPN ───────────────────────────────────────────────────────
|
|
452
|
+
/** VPN server for route-level access control */
|
|
453
|
+
vpnConfig?: {
|
|
454
|
+
enabled?: boolean; // default: false
|
|
455
|
+
subnet?: string; // default: '10.8.0.0/24'
|
|
456
|
+
wgListenPort?: number; // default: 51820
|
|
457
|
+
dns?: string[]; // DNS servers pushed to VPN clients
|
|
458
|
+
serverEndpoint?: string; // Hostname in generated client configs
|
|
459
|
+
forwardingMode?: 'tun' | 'socket'; // default: auto-detect (root → tun, else socket)
|
|
460
|
+
};
|
|
461
|
+
|
|
431
462
|
// ── HTTP/3 (QUIC) ────────────────────────────────────────────
|
|
432
463
|
/** HTTP/3 config — enabled by default on qualifying HTTPS routes */
|
|
433
464
|
http3?: {
|
|
@@ -975,6 +1006,78 @@ The OpsServer Remote Ingress view provides:
|
|
|
975
1006
|
| **Copy Token** | Generate and copy a base64url connection token to clipboard |
|
|
976
1007
|
| **Delete** | Remove the edge registration |
|
|
977
1008
|
|
|
1009
|
+
## VPN Access Control
|
|
1010
|
+
|
|
1011
|
+
DcRouter integrates [`@push.rocks/smartvpn`](https://code.foss.global/push.rocks/smartvpn) to provide VPN-based route access control. VPN clients connect via standard WireGuard or native WebSocket/QUIC transports, receive an IP from a configurable subnet, and can then access routes that are restricted to VPN-only traffic.
|
|
1012
|
+
|
|
1013
|
+
### How It Works
|
|
1014
|
+
|
|
1015
|
+
1. **SmartVPN daemon** runs inside dcrouter with a Rust data plane (WireGuard via `boringtun`, custom protocol via Noise IK)
|
|
1016
|
+
2. Clients connect and get assigned an IP from the VPN subnet (e.g. `10.8.0.0/24`)
|
|
1017
|
+
3. Routes with `vpn: { required: true }` get `security.ipAllowList` automatically injected with the VPN subnet
|
|
1018
|
+
4. SmartProxy enforces the allowlist — only VPN-sourced traffic is accepted on those routes
|
|
1019
|
+
|
|
1020
|
+
### Two Operating Modes
|
|
1021
|
+
|
|
1022
|
+
| Mode | Root Required? | How It Works |
|
|
1023
|
+
|------|---------------|-------------|
|
|
1024
|
+
| **TUN** (`forwardingMode: 'tun'`) | Yes | Kernel TUN device — VPN traffic enters the network stack with real VPN IPs |
|
|
1025
|
+
| **Socket** (`forwardingMode: 'socket'`) | No | Userspace NAT via smoltcp — outbound connections send PROXY protocol v2 to preserve VPN client IPs |
|
|
1026
|
+
|
|
1027
|
+
DcRouter auto-detects: if running as root, it uses TUN mode; otherwise, it falls back to socket mode. You can override this with the `forwardingMode` option.
|
|
1028
|
+
|
|
1029
|
+
### Configuration
|
|
1030
|
+
|
|
1031
|
+
```typescript
|
|
1032
|
+
const router = new DcRouter({
|
|
1033
|
+
vpnConfig: {
|
|
1034
|
+
enabled: true,
|
|
1035
|
+
subnet: '10.8.0.0/24', // VPN client IP pool (default)
|
|
1036
|
+
wgListenPort: 51820, // WireGuard UDP port (default)
|
|
1037
|
+
serverEndpoint: 'vpn.example.com', // Hostname in generated client configs
|
|
1038
|
+
dns: ['1.1.1.1', '8.8.8.8'], // DNS servers pushed to clients
|
|
1039
|
+
// forwardingMode: 'socket', // Override auto-detection
|
|
1040
|
+
},
|
|
1041
|
+
smartProxyConfig: {
|
|
1042
|
+
routes: [
|
|
1043
|
+
// This route is VPN-only — non-VPN clients are blocked
|
|
1044
|
+
{
|
|
1045
|
+
name: 'admin-panel',
|
|
1046
|
+
match: { domains: ['admin.example.com'], ports: [443] },
|
|
1047
|
+
action: {
|
|
1048
|
+
type: 'forward',
|
|
1049
|
+
targets: [{ host: '192.168.1.50', port: 8080 }],
|
|
1050
|
+
tls: { mode: 'terminate', certificate: 'auto' },
|
|
1051
|
+
},
|
|
1052
|
+
vpn: { required: true }, // 🔐 Only VPN clients can access this
|
|
1053
|
+
},
|
|
1054
|
+
// This route is public — anyone can access it
|
|
1055
|
+
{
|
|
1056
|
+
name: 'public-site',
|
|
1057
|
+
match: { domains: ['example.com'], ports: [443] },
|
|
1058
|
+
action: {
|
|
1059
|
+
type: 'forward',
|
|
1060
|
+
targets: [{ host: '192.168.1.10', port: 80 }],
|
|
1061
|
+
tls: { mode: 'terminate', certificate: 'auto' },
|
|
1062
|
+
},
|
|
1063
|
+
},
|
|
1064
|
+
],
|
|
1065
|
+
},
|
|
1066
|
+
});
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
### Client Management via OpsServer API
|
|
1070
|
+
|
|
1071
|
+
Once the VPN server is running, you can manage clients through the OpsServer dashboard or API:
|
|
1072
|
+
|
|
1073
|
+
- **Create client** — generates WireGuard keypairs, assigns IP, returns a ready-to-use `.conf` file
|
|
1074
|
+
- **Enable / Disable** — toggle client access without deleting
|
|
1075
|
+
- **Rotate keys** — generate fresh keypairs (invalidates old ones)
|
|
1076
|
+
- **Export config** — re-export in WireGuard or SmartVPN format
|
|
1077
|
+
- **Telemetry** — per-client bytes sent/received, keepalives, rate limiting
|
|
1078
|
+
|
|
1079
|
+
Standard WireGuard clients on any platform (iOS, Android, macOS, Windows, Linux) can connect using the generated `.conf` file or QR code — no custom VPN software needed.
|
|
1080
|
+
|
|
978
1081
|
## Certificate Management
|
|
979
1082
|
|
|
980
1083
|
DcRouter uses [`@push.rocks/smartacme`](https://code.foss.global/push.rocks/smartacme) v9 for ACME certificate provisioning. smartacme v9 brings significant improvements over previous versions:
|
|
@@ -1458,6 +1561,7 @@ The container exposes all service ports:
|
|
|
1458
1561
|
| 1812, 1813 | UDP | RADIUS auth/acct |
|
|
1459
1562
|
| 3000 | TCP | OpsServer dashboard |
|
|
1460
1563
|
| 8443 | TCP | Remote ingress tunnels |
|
|
1564
|
+
| 51820 | UDP | WireGuard VPN |
|
|
1461
1565
|
| 29000–30000 | TCP | Dynamic port range |
|
|
1462
1566
|
|
|
1463
1567
|
### Building the Image
|
|
@@ -1471,7 +1575,7 @@ The Docker build supports multi-platform (`linux/amd64`, `linux/arm64`) via [tsd
|
|
|
1471
1575
|
|
|
1472
1576
|
## License and Legal Information
|
|
1473
1577
|
|
|
1474
|
-
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [
|
|
1578
|
+
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
|
1475
1579
|
|
|
1476
1580
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
|
1477
1581
|
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/classes.dcrouter.ts
CHANGED
|
@@ -208,6 +208,12 @@ export interface IDcRouterOptions {
|
|
|
208
208
|
serverEndpoint?: string;
|
|
209
209
|
/** Override forwarding mode. Default: auto-detect (tun if root, socket otherwise) */
|
|
210
210
|
forwardingMode?: 'tun' | 'socket';
|
|
211
|
+
/** Pre-defined VPN clients created on startup */
|
|
212
|
+
clients?: Array<{
|
|
213
|
+
clientId: string;
|
|
214
|
+
serverDefinedClientTags?: string[];
|
|
215
|
+
description?: string;
|
|
216
|
+
}>;
|
|
211
217
|
};
|
|
212
218
|
}
|
|
213
219
|
|
|
@@ -453,7 +459,14 @@ export class DcRouter {
|
|
|
453
459
|
() => this.getConstructorRoutes(),
|
|
454
460
|
() => this.smartProxy,
|
|
455
461
|
() => this.options.http3,
|
|
456
|
-
|
|
462
|
+
this.options.vpnConfig?.enabled
|
|
463
|
+
? (tags?: string[]) => {
|
|
464
|
+
if (tags?.length && this.vpnManager) {
|
|
465
|
+
return this.vpnManager.getClientIpsForServerDefinedTags(tags);
|
|
466
|
+
}
|
|
467
|
+
return [this.options.vpnConfig?.subnet || '10.8.0.0/24'];
|
|
468
|
+
}
|
|
469
|
+
: undefined,
|
|
457
470
|
);
|
|
458
471
|
this.apiTokenManager = new ApiTokenManager(this.storageManager);
|
|
459
472
|
await this.apiTokenManager.initialize();
|
|
@@ -2086,6 +2099,11 @@ export class DcRouter {
|
|
|
2086
2099
|
dns: this.options.vpnConfig.dns,
|
|
2087
2100
|
serverEndpoint: this.options.vpnConfig.serverEndpoint,
|
|
2088
2101
|
forwardingMode: this.options.vpnConfig.forwardingMode,
|
|
2102
|
+
initialClients: this.options.vpnConfig.clients,
|
|
2103
|
+
onClientChanged: () => {
|
|
2104
|
+
// Re-apply routes so tag-based ipAllowLists get updated
|
|
2105
|
+
this.routeConfigManager?.applyRoutes();
|
|
2106
|
+
},
|
|
2089
2107
|
});
|
|
2090
2108
|
|
|
2091
2109
|
await this.vpnManager.start();
|
|
@@ -2104,11 +2122,23 @@ export class DcRouter {
|
|
|
2104
2122
|
if (dcrouterRoute.vpn?.required) {
|
|
2105
2123
|
injectedCount++;
|
|
2106
2124
|
const existing = route.security?.ipAllowList || [];
|
|
2125
|
+
|
|
2126
|
+
let vpnAllowList: string[];
|
|
2127
|
+
if (dcrouterRoute.vpn.allowedServerDefinedClientTags?.length && this.vpnManager) {
|
|
2128
|
+
// Tag-based: only specific client IPs
|
|
2129
|
+
vpnAllowList = this.vpnManager.getClientIpsForServerDefinedTags(
|
|
2130
|
+
dcrouterRoute.vpn.allowedServerDefinedClientTags,
|
|
2131
|
+
);
|
|
2132
|
+
} else {
|
|
2133
|
+
// No tags specified: entire VPN subnet
|
|
2134
|
+
vpnAllowList = [vpnSubnet];
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2107
2137
|
return {
|
|
2108
2138
|
...route,
|
|
2109
2139
|
security: {
|
|
2110
2140
|
...route.security,
|
|
2111
|
-
ipAllowList: [...existing,
|
|
2141
|
+
ipAllowList: [...existing, ...vpnAllowList],
|
|
2112
2142
|
},
|
|
2113
2143
|
};
|
|
2114
2144
|
}
|
|
@@ -2116,7 +2146,7 @@ export class DcRouter {
|
|
|
2116
2146
|
});
|
|
2117
2147
|
|
|
2118
2148
|
if (injectedCount > 0) {
|
|
2119
|
-
logger.log('info', `VPN: Injected ipAllowList
|
|
2149
|
+
logger.log('info', `VPN: Injected ipAllowList into ${injectedCount} VPN-protected route(s)`);
|
|
2120
2150
|
}
|
|
2121
2151
|
|
|
2122
2152
|
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
|
|
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
|
-
|
|
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
|
|
265
|
+
// Add enabled programmatic routes (with HTTP/3 and VPN augmentation)
|
|
266
266
|
const http3Config = this.getHttp3Config?.();
|
|
267
|
-
const
|
|
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 (
|
|
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,
|
|
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
|
-
|
|
28
|
+
serverDefinedClientTags: c.serverDefinedClientTags,
|
|
29
29
|
description: c.description,
|
|
30
30
|
assignedIp: c.assignedIp,
|
|
31
31
|
createdAt: c.createdAt,
|
|
@@ -89,7 +89,7 @@ export class VpnHandler {
|
|
|
89
89
|
try {
|
|
90
90
|
const bundle = await manager.createClient({
|
|
91
91
|
clientId: dataArg.clientId,
|
|
92
|
-
|
|
92
|
+
serverDefinedClientTags: dataArg.serverDefinedClientTags,
|
|
93
93
|
description: dataArg.description,
|
|
94
94
|
});
|
|
95
95
|
|
|
@@ -98,7 +98,7 @@ export class VpnHandler {
|
|
|
98
98
|
client: {
|
|
99
99
|
clientId: bundle.entry.clientId,
|
|
100
100
|
enabled: bundle.entry.enabled ?? true,
|
|
101
|
-
|
|
101
|
+
serverDefinedClientTags: bundle.entry.serverDefinedClientTags,
|
|
102
102
|
description: bundle.entry.description,
|
|
103
103
|
assignedIp: bundle.entry.assignedIp,
|
|
104
104
|
createdAt: Date.now(),
|
|
@@ -16,6 +16,14 @@ export interface IVpnManagerConfig {
|
|
|
16
16
|
serverEndpoint?: string;
|
|
17
17
|
/** Override forwarding mode. Default: auto-detect (tun if root, socket otherwise) */
|
|
18
18
|
forwardingMode?: 'tun' | 'socket';
|
|
19
|
+
/** Pre-defined VPN clients created on startup (idempotent — skips already-persisted clients) */
|
|
20
|
+
initialClients?: Array<{
|
|
21
|
+
clientId: string;
|
|
22
|
+
serverDefinedClientTags?: string[];
|
|
23
|
+
description?: string;
|
|
24
|
+
}>;
|
|
25
|
+
/** Called when clients are created/deleted/toggled — triggers route re-application */
|
|
26
|
+
onClientChanged?: () => void;
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
interface IPersistedServerKeys {
|
|
@@ -28,7 +36,7 @@ interface IPersistedServerKeys {
|
|
|
28
36
|
interface IPersistedClient {
|
|
29
37
|
clientId: string;
|
|
30
38
|
enabled: boolean;
|
|
31
|
-
|
|
39
|
+
serverDefinedClientTags?: string[];
|
|
32
40
|
description?: string;
|
|
33
41
|
assignedIp?: string;
|
|
34
42
|
noisePublicKey: string;
|
|
@@ -36,6 +44,8 @@ interface IPersistedClient {
|
|
|
36
44
|
createdAt: number;
|
|
37
45
|
updatedAt: number;
|
|
38
46
|
expiresAt?: string;
|
|
47
|
+
/** @deprecated Legacy field — migrated to serverDefinedClientTags on load */
|
|
48
|
+
tags?: string[];
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
/**
|
|
@@ -92,7 +102,7 @@ export class VpnManager {
|
|
|
92
102
|
publicKey: client.noisePublicKey,
|
|
93
103
|
wgPublicKey: client.wgPublicKey,
|
|
94
104
|
enabled: client.enabled,
|
|
95
|
-
|
|
105
|
+
serverDefinedClientTags: client.serverDefinedClientTags,
|
|
96
106
|
description: client.description,
|
|
97
107
|
assignedIp: client.assignedIp,
|
|
98
108
|
expiresAt: client.expiresAt,
|
|
@@ -122,6 +132,21 @@ export class VpnManager {
|
|
|
122
132
|
};
|
|
123
133
|
|
|
124
134
|
await this.vpnServer.start(serverConfig);
|
|
135
|
+
|
|
136
|
+
// Create initial clients from config (idempotent — skip already-persisted)
|
|
137
|
+
if (this.config.initialClients) {
|
|
138
|
+
for (const initial of this.config.initialClients) {
|
|
139
|
+
if (!this.clients.has(initial.clientId)) {
|
|
140
|
+
const bundle = await this.createClient({
|
|
141
|
+
clientId: initial.clientId,
|
|
142
|
+
serverDefinedClientTags: initial.serverDefinedClientTags,
|
|
143
|
+
description: initial.description,
|
|
144
|
+
});
|
|
145
|
+
logger.log('info', `VPN: Created initial client '${initial.clientId}' (IP: ${bundle.entry.assignedIp})`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
125
150
|
logger.log('info', `VPN server started: mode=${this._forwardingMode}, subnet=${subnet}, wg=:${wgListenPort}, clients=${this.clients.size}`);
|
|
126
151
|
}
|
|
127
152
|
|
|
@@ -148,7 +173,7 @@ export class VpnManager {
|
|
|
148
173
|
*/
|
|
149
174
|
public async createClient(opts: {
|
|
150
175
|
clientId: string;
|
|
151
|
-
|
|
176
|
+
serverDefinedClientTags?: string[];
|
|
152
177
|
description?: string;
|
|
153
178
|
}): Promise<plugins.smartvpn.IClientConfigBundle> {
|
|
154
179
|
if (!this.vpnServer) {
|
|
@@ -157,7 +182,7 @@ export class VpnManager {
|
|
|
157
182
|
|
|
158
183
|
const bundle = await this.vpnServer.createClient({
|
|
159
184
|
clientId: opts.clientId,
|
|
160
|
-
|
|
185
|
+
serverDefinedClientTags: opts.serverDefinedClientTags,
|
|
161
186
|
description: opts.description,
|
|
162
187
|
});
|
|
163
188
|
|
|
@@ -174,7 +199,7 @@ export class VpnManager {
|
|
|
174
199
|
const persisted: IPersistedClient = {
|
|
175
200
|
clientId: bundle.entry.clientId,
|
|
176
201
|
enabled: bundle.entry.enabled ?? true,
|
|
177
|
-
|
|
202
|
+
serverDefinedClientTags: bundle.entry.serverDefinedClientTags,
|
|
178
203
|
description: bundle.entry.description,
|
|
179
204
|
assignedIp: bundle.entry.assignedIp,
|
|
180
205
|
noisePublicKey: bundle.entry.publicKey,
|
|
@@ -186,6 +211,7 @@ export class VpnManager {
|
|
|
186
211
|
this.clients.set(persisted.clientId, persisted);
|
|
187
212
|
await this.persistClient(persisted);
|
|
188
213
|
|
|
214
|
+
this.config.onClientChanged?.();
|
|
189
215
|
return bundle;
|
|
190
216
|
}
|
|
191
217
|
|
|
@@ -199,6 +225,7 @@ export class VpnManager {
|
|
|
199
225
|
await this.vpnServer.removeClient(clientId);
|
|
200
226
|
this.clients.delete(clientId);
|
|
201
227
|
await this.storageManager.delete(`${STORAGE_PREFIX_CLIENTS}${clientId}`);
|
|
228
|
+
this.config.onClientChanged?.();
|
|
202
229
|
}
|
|
203
230
|
|
|
204
231
|
/**
|
|
@@ -220,6 +247,7 @@ export class VpnManager {
|
|
|
220
247
|
client.updatedAt = Date.now();
|
|
221
248
|
await this.persistClient(client);
|
|
222
249
|
}
|
|
250
|
+
this.config.onClientChanged?.();
|
|
223
251
|
}
|
|
224
252
|
|
|
225
253
|
/**
|
|
@@ -234,6 +262,7 @@ export class VpnManager {
|
|
|
234
262
|
client.updatedAt = Date.now();
|
|
235
263
|
await this.persistClient(client);
|
|
236
264
|
}
|
|
265
|
+
this.config.onClientChanged?.();
|
|
237
266
|
}
|
|
238
267
|
|
|
239
268
|
/**
|
|
@@ -283,6 +312,22 @@ export class VpnManager {
|
|
|
283
312
|
return config;
|
|
284
313
|
}
|
|
285
314
|
|
|
315
|
+
// ── Tag-based access control ───────────────────────────────────────────
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Get assigned IPs for all enabled clients matching any of the given server-defined tags.
|
|
319
|
+
*/
|
|
320
|
+
public getClientIpsForServerDefinedTags(tags: string[]): string[] {
|
|
321
|
+
const ips: string[] = [];
|
|
322
|
+
for (const client of this.clients.values()) {
|
|
323
|
+
if (!client.enabled || !client.assignedIp) continue;
|
|
324
|
+
if (client.serverDefinedClientTags?.some(t => tags.includes(t))) {
|
|
325
|
+
ips.push(client.assignedIp);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return ips;
|
|
329
|
+
}
|
|
330
|
+
|
|
286
331
|
// ── Status and telemetry ───────────────────────────────────────────────
|
|
287
332
|
|
|
288
333
|
/**
|
|
@@ -364,6 +409,12 @@ export class VpnManager {
|
|
|
364
409
|
for (const key of keys) {
|
|
365
410
|
const client = await this.storageManager.getJSON<IPersistedClient>(key);
|
|
366
411
|
if (client) {
|
|
412
|
+
// Migrate legacy `tags` → `serverDefinedClientTags`
|
|
413
|
+
if (!client.serverDefinedClientTags && client.tags) {
|
|
414
|
+
client.serverDefinedClientTags = client.tags;
|
|
415
|
+
delete client.tags;
|
|
416
|
+
await this.persistClient(client);
|
|
417
|
+
}
|
|
367
418
|
this.clients.set(client.clientId, client);
|
|
368
419
|
}
|
|
369
420
|
}
|