import { expect, tap } from '@git.zone/tstest/tapbundle'; import { DcRouter } from '../ts/classes.dcrouter.js'; import { VpnManager } from '../ts/vpn/classes.vpn-manager.js'; import { RouteConfigManager } from '../ts/config/classes.route-config-manager.js'; import { TargetProfileManager } from '../ts/config/classes.target-profile-manager.js'; tap.test('VpnManager downgrades back to socket mode when no host-IP clients remain', async () => { const manager = new VpnManager({ forwardingMode: 'socket' }); let stopCalls = 0; let startCalls = 0; (manager as any).vpnServer = { running: true }; (manager as any).resolvedForwardingMode = 'hybrid'; (manager as any).clients = new Map([ ['client-1', { useHostIp: false }], ]); (manager as any).stop = async () => { stopCalls++; }; (manager as any).start = async () => { startCalls++; (manager as any).resolvedForwardingMode = (manager as any).forwardingModeOverride ?? 'socket'; (manager as any).forwardingModeOverride = undefined; (manager as any).vpnServer = { running: true }; }; const restarted = await (manager as any).reconcileForwardingMode(); expect(restarted).toEqual(true); expect(stopCalls).toEqual(1); expect(startCalls).toEqual(1); expect((manager as any).resolvedForwardingMode).toEqual('socket'); }); tap.test('VpnManager keeps explicit hybrid mode even without host-IP clients', async () => { const manager = new VpnManager({ forwardingMode: 'hybrid' }); let stopCalls = 0; let startCalls = 0; (manager as any).vpnServer = { running: true }; (manager as any).resolvedForwardingMode = 'hybrid'; (manager as any).clients = new Map([ ['client-1', { useHostIp: false }], ]); (manager as any).stop = async () => { stopCalls++; }; (manager as any).start = async () => { startCalls++; }; const restarted = await (manager as any).reconcileForwardingMode(); expect(restarted).toEqual(false); expect(stopCalls).toEqual(0); expect(startCalls).toEqual(0); expect((manager as any).resolvedForwardingMode).toEqual('hybrid'); }); tap.test('DcRouter.updateVpnConfig swaps the runtime VPN resolver and restarts VPN services', async () => { const dcRouter = new DcRouter({ smartProxyConfig: { routes: [] }, dbConfig: { enabled: false }, vpnConfig: { enabled: false }, }); let stopCalls = 0; let setupCalls = 0; let applyCalls = 0; const resolverValues: Array = []; dcRouter.vpnManager = { stop: async () => { stopCalls++; }, } as any; (dcRouter as any).routeConfigManager = { setVpnClientAccessResolver: (resolver: unknown) => { resolverValues.push(resolver); }, applyRoutes: async () => { applyCalls++; }, }; (dcRouter as any).setupVpnServer = async () => { setupCalls++; dcRouter.vpnManager = { stop: async () => { stopCalls++; }, } as any; }; await dcRouter.updateVpnConfig({ enabled: true, subnet: '10.9.0.0/24' }); expect(stopCalls).toEqual(1); expect(setupCalls).toEqual(1); expect(applyCalls).toEqual(0); expect(typeof resolverValues.at(-1)).toEqual('function'); await dcRouter.updateVpnConfig({ enabled: false }); expect(stopCalls).toEqual(2); expect(setupCalls).toEqual(1); expect(applyCalls).toEqual(1); expect(resolverValues.at(-1)).toBeUndefined(); expect(dcRouter.vpnManager).toBeUndefined(); }); tap.test('RouteConfigManager makes vpnOnly routes fail closed without VPN clients', async () => { const manager = new RouteConfigManager(() => undefined); const route = { name: 'private-route', vpnOnly: true, match: { domains: ['private.example.com'] }, action: { type: 'forward', targets: [{ host: '127.0.0.1', port: 8080 }] }, security: { ipAllowList: ['*'] }, } as any; const prepared = (manager as any).injectVpnSecurity(route); expect(prepared.security.ipAllowList).toEqual(['*']); expect(prepared.security.vpn).toEqual({ required: true, allowedClients: [] }); }); tap.test('RouteConfigManager adds VPN client grants for vpnOnly routes', async () => { const manager = new RouteConfigManager( () => undefined, undefined, () => ['client-1'], ); const route = { name: 'private-route', vpnOnly: true, match: { domains: ['private.example.com'] }, action: { type: 'forward', targets: [{ host: '127.0.0.1', port: 8080 }] }, security: { ipAllowList: ['*', '203.0.113.10'], ipBlockList: ['198.51.100.5'], }, } as any; const prepared = (manager as any).injectVpnSecurity(route); expect(prepared.security.ipAllowList).toEqual(['*', '203.0.113.10']); expect(prepared.security.ipBlockList).toEqual(['198.51.100.5']); expect(prepared.security.vpn).toEqual({ required: true, allowedClients: ['client-1'] }); }); tap.test('RouteConfigManager adds matching VPN clients to restricted non-vpnOnly routes', async () => { const manager = new RouteConfigManager( () => undefined, undefined, () => ['client-1'], ); const route = { name: 'shared-private-route', match: { domains: ['app.example.com'] }, action: { type: 'forward', targets: [{ host: '127.0.0.1', port: 8080 }] }, security: { ipAllowList: ['203.0.113.10'], ipBlockList: ['198.51.100.5'], }, } as any; const prepared = (manager as any).injectVpnSecurity(route); expect(prepared.security.ipAllowList).toEqual(['203.0.113.10']); expect(prepared.security.ipBlockList).toEqual(['198.51.100.5']); expect(prepared.security.vpn).toEqual({ required: undefined, allowedClients: ['client-1'] }); }); tap.test('TargetProfileManager matches wildcard profiles against string route domains', async () => { const manager = new TargetProfileManager(); (manager as any).profiles.set('profile-1', { id: 'profile-1', name: 'hagen.team VPN access', domains: ['*.hagen.team'], createdAt: 1, updatedAt: 1, createdBy: 'test', }); const entries = manager.getMatchingVpnClients( { name: 'hagen-app', match: { domains: 'app.hagen.team', ports: [443] }, action: { type: 'forward', targets: [{ host: '10.0.0.5', port: 443 }] }, } as any, 'route-1', [{ clientId: 'client-1', enabled: true, assignedIp: '10.8.0.2', targetProfileIds: ['profile-1'] }] as any, ); expect(entries).toEqual(['client-1']); }); tap.test('TargetProfileManager expands wildcard profile domains to matching concrete route domains', async () => { const manager = new TargetProfileManager(); (manager as any).profiles.set('profile-1', { id: 'profile-1', name: 'hagen.team VPN access', domains: ['*.hagen.team'], createdAt: 1, updatedAt: 1, createdBy: 'test', }); const routes = new Map([ ['route-1', { id: 'route-1', enabled: true, createdAt: 1, updatedAt: 1, createdBy: 'test', origin: 'api', route: { name: 'hagen-app', match: { domains: 'app.hagen.team', ports: [443] }, action: { type: 'forward', targets: [{ host: '10.0.0.5', port: 443 }] }, }, }], ]) as any; const accessSpec = manager.getClientAccessSpec(['profile-1'], routes); expect(accessSpec.domains).toContain('*.hagen.team'); expect(accessSpec.domains).toContain('app.hagen.team'); }); tap.test('TargetProfileManager allows source-IP reachable routes for opted-in profiles', async () => { const manager = new TargetProfileManager(); (manager as any).profiles.set('profile-1', { id: 'profile-1', name: 'source-ip access', allowRoutesByClientSourceIp: true, createdAt: 1, updatedAt: 1, createdBy: 'test', }); const entries = manager.getMatchingVpnClients( { name: 'restricted-public-route', match: { domains: 'app.example.com', ports: [443] }, action: { type: 'forward', targets: [{ host: '10.0.0.5', port: 443 }] }, security: { ipAllowList: ['203.0.113.10'] }, } as any, 'route-1', [{ clientId: 'client-1', enabled: true, assignedIp: '10.8.0.2', targetProfileIds: ['profile-1'] }] as any, new Map(), ); expect(entries).toEqual(['client-1']); }); tap.test('TargetProfileManager leaves real source-IP enforcement to SmartProxy', async () => { const manager = new TargetProfileManager(); (manager as any).profiles.set('profile-1', { id: 'profile-1', name: 'source-ip access', allowRoutesByClientSourceIp: true, createdAt: 1, updatedAt: 1, createdBy: 'test', }); const entries = manager.getMatchingVpnClients( { name: 'restricted-public-route', match: { domains: 'app.example.com', ports: [443] }, action: { type: 'forward', targets: [{ host: '10.0.0.5', port: 443 }] }, security: { ipAllowList: ['203.0.113.10'] }, } as any, 'route-1', [{ clientId: 'client-1', enabled: true, assignedIp: '10.8.0.2', targetProfileIds: ['profile-1'] }] as any, new Map(), ); expect(entries).toEqual(['client-1']); }); tap.test('TargetProfileManager does not grant routes with wildcard source block', async () => { const manager = new TargetProfileManager(); (manager as any).profiles.set('profile-1', { id: 'profile-1', name: 'source-ip access', allowRoutesByClientSourceIp: true, createdAt: 1, updatedAt: 1, createdBy: 'test', }); const entries = manager.getMatchingVpnClients( { name: 'blocked-route', match: { domains: 'app.example.com', ports: [443] }, action: { type: 'forward', targets: [{ host: '10.0.0.5', port: 443 }] }, security: { ipAllowList: ['203.0.113.0/24'], ipBlockList: ['*'], }, } as any, 'route-1', [{ clientId: 'client-1', enabled: true, assignedIp: '10.8.0.2', targetProfileIds: ['profile-1'] }] as any, new Map(), ); expect(entries).toEqual([]); }); tap.test('TargetProfileManager treats public non-vpnOnly routes as source-IP reachable', async () => { const manager = new TargetProfileManager(); (manager as any).profiles.set('profile-1', { id: 'profile-1', name: 'source-ip access', allowRoutesByClientSourceIp: true, createdAt: 1, updatedAt: 1, createdBy: 'test', }); const entries = manager.getMatchingVpnClients( { name: 'public-route', match: { domains: 'public.example.com', ports: [443] }, action: { type: 'forward', targets: [{ host: '10.0.0.5', port: 443 }] }, } as any, 'route-1', [{ clientId: 'client-1', enabled: true, assignedIp: '10.8.0.2', targetProfileIds: ['profile-1'] }] as any, new Map(), ); expect(entries).toEqual(['client-1']); }); tap.test('TargetProfileManager grants vpnOnly routes through source-policy profiles', async () => { const manager = new TargetProfileManager(); (manager as any).profiles.set('profile-1', { id: 'profile-1', name: 'source-ip access', allowRoutesByClientSourceIp: true, createdAt: 1, updatedAt: 1, createdBy: 'test', }); const entries = manager.getMatchingVpnClients( { name: 'vpn-only-route', vpnOnly: true, match: { domains: 'private.example.com', ports: [443] }, action: { type: 'forward', targets: [{ host: '10.0.0.5', port: 443 }] }, security: { ipAllowList: ['203.0.113.10'] }, } as any, 'route-1', [{ clientId: 'client-1', enabled: true, assignedIp: '10.8.0.2', targetProfileIds: ['profile-1'] }] as any, new Map(), ); expect(entries).toEqual(['client-1']); }); tap.test('TargetProfileManager includes source-IP reachable route domains in client access specs', async () => { const manager = new TargetProfileManager(); (manager as any).profiles.set('profile-1', { id: 'profile-1', name: 'source-ip access', allowRoutesByClientSourceIp: true, createdAt: 1, updatedAt: 1, createdBy: 'test', }); const routes = new Map([ ['route-1', { id: 'route-1', enabled: true, createdAt: 1, updatedAt: 1, createdBy: 'test', origin: 'api', route: { name: 'source-reachable-app', match: { domains: 'app.example.com', ports: [443] }, action: { type: 'forward', targets: [{ host: '10.0.0.5', port: 443 }] }, security: { ipAllowList: ['203.0.113.0/24'] }, }, }], ]) as any; const accessSpec = manager.getClientAccessSpec(['profile-1'], routes); expect(accessSpec.domains).toContain('app.example.com'); }); tap.test('VpnManager normalizes real remote addresses', async () => { expect(VpnManager.normalizeRemoteAddress('203.0.113.10:51234')).toEqual('203.0.113.10'); expect(VpnManager.normalizeRemoteAddress('[2001:db8::1]:51234')).toEqual('2001:db8::1'); expect(VpnManager.normalizeRemoteAddress('2001:db8::1')).toEqual('2001:db8::1'); }); tap.test('VpnManager refreshes live source IPs from WireGuard peer endpoints', async () => { const manager = new VpnManager({}); let sourceIpChangeCalls = 0; (manager as any).config.onClientSourceIpsChanged = () => { sourceIpChangeCalls++; }; (manager as any).clients = new Map([ ['client-1', { clientId: 'client-1', wgPublicKey: 'wg-public-key' }], ]); (manager as any).vpnServer = { listClients: async () => ([ { clientId: 'runtime-client-1', registeredClientId: 'client-1', assignedIp: '10.8.0.2', transportType: 'wireguard', }, ]), listWgPeers: async () => ([ { publicKey: 'wg-public-key', allowedIps: ['10.8.0.2/32'], endpoint: '198.51.100.44:61234', bytesSent: 0, bytesReceived: 0, packetsSent: 0, packetsReceived: 0, }, ]), }; const changed = await manager.refreshClientSourceIps(); const changedAgain = await manager.refreshClientSourceIps(); expect(changed).toEqual(true); expect(changedAgain).toEqual(false); expect(manager.getClientSourceIp('client-1')).toEqual('198.51.100.44'); expect(sourceIpChangeCalls).toEqual(1); }); tap.test('VpnManager rewrites WireGuard AllowedIPs after key rotation', async () => { const manager = new VpnManager({ serverEndpoint: 'vpn.example.com', getClientAllowedIPs: async () => ['10.8.0.0/24', '203.0.113.10/32'], }); (manager as any).vpnServer = { rotateClientKey: async () => ({ entry: { clientId: 'client-1', publicKey: 'noise-public-key', wgPublicKey: 'wg-public-key', }, wireguardConfig: '[Interface]\nPrivateKey = old\nAddress = 10.8.0.2/24\n[Peer]\nAllowedIPs = 0.0.0.0/0\nEndpoint = vpn.example.com:51820\n', secrets: { noisePrivateKey: 'noise-private-key', wgPrivateKey: 'wg-private-key' }, }), }; (manager as any).clients = new Map([ ['client-1', { clientId: 'client-1', targetProfileIds: ['profile-1'] }], ]); (manager as any).persistClient = async () => {}; const bundle = await manager.rotateClientKey('client-1'); expect(bundle.wireguardConfig).toContain('AllowedIPs = 10.8.0.0/24, 203.0.113.10/32'); }); export default tap.start()