This commit is contained in:
Juergen Kunz
2025-07-17 20:51:50 +00:00
parent b26abbfd87
commit 6aa5f415c1
7 changed files with 116 additions and 89 deletions

View File

@@ -232,10 +232,10 @@ tap.test('should start the proxy server', async () => {
}, },
action: { action: {
type: 'forward', type: 'forward',
target: { targets: [{
host: 'localhost', host: 'localhost',
port: 3100 port: 3100
}, }],
tls: { tls: {
mode: 'terminate' mode: 'terminate'
}, },

View File

@@ -29,7 +29,7 @@ tap.test('MetricsCollector provides accurate metrics', async (tools) => {
match: { ports: 8700 }, match: { ports: 8700 },
action: { action: {
type: 'forward', type: 'forward',
target: { host: 'localhost', port: 9995 } targets: [{ host: 'localhost', port: 9995 }]
} }
}, },
{ {
@@ -37,7 +37,7 @@ tap.test('MetricsCollector provides accurate metrics', async (tools) => {
match: { ports: 8701 }, match: { ports: 8701 },
action: { action: {
type: 'forward', type: 'forward',
target: { host: 'localhost', port: 9995 } targets: [{ host: 'localhost', port: 9995 }]
} }
} }
], ],

View File

@@ -56,8 +56,8 @@ tap.test('Routes: Should create basic HTTP route', async () => {
expect(httpRoute.match.ports).toEqual(80); expect(httpRoute.match.ports).toEqual(80);
expect(httpRoute.match.domains).toEqual('example.com'); expect(httpRoute.match.domains).toEqual('example.com');
expect(httpRoute.action.type).toEqual('forward'); expect(httpRoute.action.type).toEqual('forward');
expect(httpRoute.action.target?.host).toEqual('localhost'); expect(httpRoute.action.targets?.[0]?.host).toEqual('localhost');
expect(httpRoute.action.target?.port).toEqual(3000); expect(httpRoute.action.targets?.[0]?.port).toEqual(3000);
expect(httpRoute.name).toEqual('Basic HTTP Route'); expect(httpRoute.name).toEqual('Basic HTTP Route');
}); });
@@ -74,8 +74,8 @@ tap.test('Routes: Should create HTTPS route with TLS termination', async () => {
expect(httpsRoute.action.type).toEqual('forward'); expect(httpsRoute.action.type).toEqual('forward');
expect(httpsRoute.action.tls?.mode).toEqual('terminate'); expect(httpsRoute.action.tls?.mode).toEqual('terminate');
expect(httpsRoute.action.tls?.certificate).toEqual('auto'); expect(httpsRoute.action.tls?.certificate).toEqual('auto');
expect(httpsRoute.action.target?.host).toEqual('localhost'); expect(httpsRoute.action.targets?.[0]?.host).toEqual('localhost');
expect(httpsRoute.action.target?.port).toEqual(8080); expect(httpsRoute.action.targets?.[0]?.port).toEqual(8080);
expect(httpsRoute.name).toEqual('HTTPS Route'); expect(httpsRoute.name).toEqual('HTTPS Route');
}); });
@@ -131,10 +131,10 @@ tap.test('Routes: Should create load balancer route', async () => {
// Validate the route configuration // Validate the route configuration
expect(lbRoute.match.domains).toEqual('app.example.com'); expect(lbRoute.match.domains).toEqual('app.example.com');
expect(lbRoute.action.type).toEqual('forward'); expect(lbRoute.action.type).toEqual('forward');
expect(Array.isArray(lbRoute.action.target?.host)).toBeTrue(); expect(Array.isArray(lbRoute.action.targets?.[0]?.host)).toBeTrue();
expect((lbRoute.action.target?.host as string[]).length).toEqual(3); expect((lbRoute.action.targets?.[0]?.host as string[]).length).toEqual(3);
expect((lbRoute.action.target?.host as string[])[0]).toEqual('10.0.0.1'); expect((lbRoute.action.targets?.[0]?.host as string[])[0]).toEqual('10.0.0.1');
expect(lbRoute.action.target?.port).toEqual(8080); expect(lbRoute.action.targets?.[0]?.port).toEqual(8080);
expect(lbRoute.action.tls?.mode).toEqual('terminate'); expect(lbRoute.action.tls?.mode).toEqual('terminate');
}); });
@@ -152,8 +152,8 @@ tap.test('Routes: Should create API route with CORS', async () => {
expect(apiRoute.match.path).toEqual('/v1/*'); expect(apiRoute.match.path).toEqual('/v1/*');
expect(apiRoute.action.type).toEqual('forward'); expect(apiRoute.action.type).toEqual('forward');
expect(apiRoute.action.tls?.mode).toEqual('terminate'); expect(apiRoute.action.tls?.mode).toEqual('terminate');
expect(apiRoute.action.target?.host).toEqual('localhost'); expect(apiRoute.action.targets?.[0]?.host).toEqual('localhost');
expect(apiRoute.action.target?.port).toEqual(3000); expect(apiRoute.action.targets?.[0]?.port).toEqual(3000);
// Check CORS headers // Check CORS headers
expect(apiRoute.headers).toBeDefined(); expect(apiRoute.headers).toBeDefined();
@@ -177,8 +177,8 @@ tap.test('Routes: Should create WebSocket route', async () => {
expect(wsRoute.match.path).toEqual('/socket'); expect(wsRoute.match.path).toEqual('/socket');
expect(wsRoute.action.type).toEqual('forward'); expect(wsRoute.action.type).toEqual('forward');
expect(wsRoute.action.tls?.mode).toEqual('terminate'); expect(wsRoute.action.tls?.mode).toEqual('terminate');
expect(wsRoute.action.target?.host).toEqual('localhost'); expect(wsRoute.action.targets?.[0]?.host).toEqual('localhost');
expect(wsRoute.action.target?.port).toEqual(5000); expect(wsRoute.action.targets?.[0]?.port).toEqual(5000);
// Check WebSocket configuration // Check WebSocket configuration
expect(wsRoute.action.websocket).toBeDefined(); expect(wsRoute.action.websocket).toBeDefined();
@@ -294,13 +294,13 @@ tap.test('Edge Case - Wildcard Domains and Path Matching', async () => {
const bestMatch = findBestMatchingRoute(routes, { domain: 'api.example.com', path: '/api/users', port: 443 }); const bestMatch = findBestMatchingRoute(routes, { domain: 'api.example.com', path: '/api/users', port: 443 });
expect(bestMatch).not.toBeUndefined(); expect(bestMatch).not.toBeUndefined();
if (bestMatch) { if (bestMatch) {
expect(bestMatch.action.target.port).toEqual(3001); // Should match the exact domain route expect(bestMatch.action.targets[0].port).toEqual(3001); // Should match the exact domain route
} }
// Test with a different subdomain - should only match the wildcard route // Test with a different subdomain - should only match the wildcard route
const otherMatches = findMatchingRoutes(routes, { domain: 'other.example.com', path: '/api/products', port: 443 }); const otherMatches = findMatchingRoutes(routes, { domain: 'other.example.com', path: '/api/products', port: 443 });
expect(otherMatches.length).toEqual(1); expect(otherMatches.length).toEqual(1);
expect(otherMatches[0].action.target.port).toEqual(3000); // Should match the wildcard domain route expect(otherMatches[0].action.targets[0].port).toEqual(3000); // Should match the wildcard domain route
}); });
tap.test('Edge Case - Disabled Routes', async () => { tap.test('Edge Case - Disabled Routes', async () => {
@@ -316,7 +316,7 @@ tap.test('Edge Case - Disabled Routes', async () => {
// Should only find the enabled route // Should only find the enabled route
expect(matches.length).toEqual(1); expect(matches.length).toEqual(1);
expect(matches[0].action.target.port).toEqual(3000); expect(matches[0].action.targets[0].port).toEqual(3000);
}); });
tap.test('Edge Case - Complex Path and Headers Matching', async () => { tap.test('Edge Case - Complex Path and Headers Matching', async () => {
@@ -452,7 +452,7 @@ tap.test('Wildcard Domain Handling', async () => {
expect(bestSpecificMatch).not.toBeUndefined(); expect(bestSpecificMatch).not.toBeUndefined();
if (bestSpecificMatch) { if (bestSpecificMatch) {
// Find which route was matched // Find which route was matched
const matchedPort = bestSpecificMatch.action.target.port; const matchedPort = bestSpecificMatch.action.targets[0].port;
console.log(`Matched route with port: ${matchedPort}`); console.log(`Matched route with port: ${matchedPort}`);
// Verify it's the specific subdomain route (with highest priority) // Verify it's the specific subdomain route (with highest priority)
@@ -465,7 +465,7 @@ tap.test('Wildcard Domain Handling', async () => {
expect(bestWildcardMatch).not.toBeUndefined(); expect(bestWildcardMatch).not.toBeUndefined();
if (bestWildcardMatch) { if (bestWildcardMatch) {
// Find which route was matched // Find which route was matched
const matchedPort = bestWildcardMatch.action.target.port; const matchedPort = bestWildcardMatch.action.targets[0].port;
console.log(`Matched route with port: ${matchedPort}`); console.log(`Matched route with port: ${matchedPort}`);
// Verify it's the wildcard subdomain route (with medium priority) // Verify it's the wildcard subdomain route (with medium priority)
@@ -513,7 +513,7 @@ tap.test('Route Integration - Combining Multiple Route Types', async () => {
expect(webServerMatch).not.toBeUndefined(); expect(webServerMatch).not.toBeUndefined();
if (webServerMatch) { if (webServerMatch) {
expect(webServerMatch.action.type).toEqual('forward'); expect(webServerMatch.action.type).toEqual('forward');
expect(webServerMatch.action.target.host).toEqual('web-server'); expect(webServerMatch.action.targets[0].host).toEqual('web-server');
} }
// Web server (HTTP redirect via socket handler) // Web server (HTTP redirect via socket handler)
@@ -532,7 +532,7 @@ tap.test('Route Integration - Combining Multiple Route Types', async () => {
expect(apiMatch).not.toBeUndefined(); expect(apiMatch).not.toBeUndefined();
if (apiMatch) { if (apiMatch) {
expect(apiMatch.action.type).toEqual('forward'); expect(apiMatch.action.type).toEqual('forward');
expect(apiMatch.action.target.host).toEqual('api-server'); expect(apiMatch.action.targets[0].host).toEqual('api-server');
} }
// WebSocket server // WebSocket server
@@ -544,7 +544,7 @@ tap.test('Route Integration - Combining Multiple Route Types', async () => {
expect(wsMatch).not.toBeUndefined(); expect(wsMatch).not.toBeUndefined();
if (wsMatch) { if (wsMatch) {
expect(wsMatch.action.type).toEqual('forward'); expect(wsMatch.action.type).toEqual('forward');
expect(wsMatch.action.target.host).toEqual('websocket-server'); expect(wsMatch.action.targets[0].host).toEqual('websocket-server');
expect(wsMatch.action.websocket?.enabled).toBeTrue(); expect(wsMatch.action.websocket?.enabled).toBeTrue();
} }

View File

@@ -134,10 +134,10 @@ tap.test('Route Validation - validateRouteAction', async () => {
// Valid forward action // Valid forward action
const validForwardAction: IRouteAction = { const validForwardAction: IRouteAction = {
type: 'forward', type: 'forward',
target: { targets: [{
host: 'localhost', host: 'localhost',
port: 3000 port: 3000
} }]
}; };
const validForwardResult = validateRouteAction(validForwardAction); const validForwardResult = validateRouteAction(validForwardAction);
expect(validForwardResult.valid).toBeTrue(); expect(validForwardResult.valid).toBeTrue();
@@ -154,14 +154,14 @@ tap.test('Route Validation - validateRouteAction', async () => {
expect(validSocketResult.valid).toBeTrue(); expect(validSocketResult.valid).toBeTrue();
expect(validSocketResult.errors.length).toEqual(0); expect(validSocketResult.errors.length).toEqual(0);
// Invalid action (missing target) // Invalid action (missing targets)
const invalidAction: IRouteAction = { const invalidAction: IRouteAction = {
type: 'forward' type: 'forward'
}; };
const invalidResult = validateRouteAction(invalidAction); const invalidResult = validateRouteAction(invalidAction);
expect(invalidResult.valid).toBeFalse(); expect(invalidResult.valid).toBeFalse();
expect(invalidResult.errors.length).toBeGreaterThan(0); expect(invalidResult.errors.length).toBeGreaterThan(0);
expect(invalidResult.errors[0]).toInclude('Target is required'); expect(invalidResult.errors[0]).toInclude('Targets array is required');
// Invalid action (missing socket handler) // Invalid action (missing socket handler)
const invalidSocketAction: IRouteAction = { const invalidSocketAction: IRouteAction = {
@@ -180,7 +180,7 @@ tap.test('Route Validation - validateRouteConfig', async () => {
expect(validResult.valid).toBeTrue(); expect(validResult.valid).toBeTrue();
expect(validResult.errors.length).toEqual(0); expect(validResult.errors.length).toEqual(0);
// Invalid route config (missing target) // Invalid route config (missing targets)
const invalidRoute: IRouteConfig = { const invalidRoute: IRouteConfig = {
match: { match: {
domains: 'example.com', domains: 'example.com',
@@ -309,16 +309,16 @@ tap.test('Route Utilities - mergeRouteConfigs', async () => {
const actionOverride: Partial<IRouteConfig> = { const actionOverride: Partial<IRouteConfig> = {
action: { action: {
type: 'forward', type: 'forward',
target: { targets: [{
host: 'new-host.local', host: 'new-host.local',
port: 5000 port: 5000
} }]
} }
}; };
const actionMergedRoute = mergeRouteConfigs(baseRoute, actionOverride); const actionMergedRoute = mergeRouteConfigs(baseRoute, actionOverride);
expect(actionMergedRoute.action.target.host).toEqual('new-host.local'); expect(actionMergedRoute.action.targets?.[0]?.host).toEqual('new-host.local');
expect(actionMergedRoute.action.target.port).toEqual(5000); expect(actionMergedRoute.action.targets?.[0]?.port).toEqual(5000);
// Test replacing action with socket handler // Test replacing action with socket handler
const typeChangeOverride: Partial<IRouteConfig> = { const typeChangeOverride: Partial<IRouteConfig> = {
@@ -336,7 +336,7 @@ tap.test('Route Utilities - mergeRouteConfigs', async () => {
const typeChangedRoute = mergeRouteConfigs(baseRoute, typeChangeOverride); const typeChangedRoute = mergeRouteConfigs(baseRoute, typeChangeOverride);
expect(typeChangedRoute.action.type).toEqual('socket-handler'); expect(typeChangedRoute.action.type).toEqual('socket-handler');
expect(typeChangedRoute.action.socketHandler).toBeDefined(); expect(typeChangedRoute.action.socketHandler).toBeDefined();
expect(typeChangedRoute.action.target).toBeUndefined(); expect(typeChangedRoute.action.targets).toBeUndefined();
}); });
tap.test('Route Matching - routeMatchesDomain', async () => { tap.test('Route Matching - routeMatchesDomain', async () => {
@@ -379,10 +379,10 @@ tap.test('Route Matching - routeMatchesPort', async () => {
}, },
action: { action: {
type: 'forward', type: 'forward',
target: { targets: [{
host: 'localhost', host: 'localhost',
port: 3000 port: 3000
} }]
} }
}; };
@@ -393,10 +393,10 @@ tap.test('Route Matching - routeMatchesPort', async () => {
}, },
action: { action: {
type: 'forward', type: 'forward',
target: { targets: [{
host: 'localhost', host: 'localhost',
port: 3000 port: 3000
} }]
} }
}; };
@@ -427,10 +427,10 @@ tap.test('Route Matching - routeMatchesPath', async () => {
}, },
action: { action: {
type: 'forward', type: 'forward',
target: { targets: [{
host: 'localhost', host: 'localhost',
port: 3000 port: 3000
} }]
} }
}; };
@@ -443,10 +443,10 @@ tap.test('Route Matching - routeMatchesPath', async () => {
}, },
action: { action: {
type: 'forward', type: 'forward',
target: { targets: [{
host: 'localhost', host: 'localhost',
port: 3000 port: 3000
} }]
} }
}; };
@@ -458,10 +458,10 @@ tap.test('Route Matching - routeMatchesPath', async () => {
}, },
action: { action: {
type: 'forward', type: 'forward',
target: { targets: [{
host: 'localhost', host: 'localhost',
port: 3000 port: 3000
} }]
} }
}; };
@@ -494,10 +494,10 @@ tap.test('Route Matching - routeMatchesHeaders', async () => {
}, },
action: { action: {
type: 'forward', type: 'forward',
target: { targets: [{
host: 'localhost', host: 'localhost',
port: 3000 port: 3000
} }]
} }
}; };
@@ -641,7 +641,7 @@ tap.test('Route Utilities - cloneRoute', async () => {
expect(clonedRoute.name).toEqual(originalRoute.name); expect(clonedRoute.name).toEqual(originalRoute.name);
expect(clonedRoute.match.domains).toEqual(originalRoute.match.domains); expect(clonedRoute.match.domains).toEqual(originalRoute.match.domains);
expect(clonedRoute.action.type).toEqual(originalRoute.action.type); expect(clonedRoute.action.type).toEqual(originalRoute.action.type);
expect(clonedRoute.action.target.port).toEqual(originalRoute.action.target.port); expect(clonedRoute.action.targets?.[0]?.port).toEqual(originalRoute.action.targets?.[0]?.port);
// Modify the clone and check that the original is unchanged // Modify the clone and check that the original is unchanged
clonedRoute.name = 'Modified Clone'; clonedRoute.name = 'Modified Clone';
@@ -656,8 +656,8 @@ tap.test('Route Helpers - createHttpRoute', async () => {
expect(route.match.domains).toEqual('example.com'); expect(route.match.domains).toEqual('example.com');
expect(route.match.ports).toEqual(80); expect(route.match.ports).toEqual(80);
expect(route.action.type).toEqual('forward'); expect(route.action.type).toEqual('forward');
expect(route.action.target.host).toEqual('localhost'); expect(route.action.targets?.[0]?.host).toEqual('localhost');
expect(route.action.target.port).toEqual(3000); expect(route.action.targets?.[0]?.port).toEqual(3000);
const validationResult = validateRouteConfig(route); const validationResult = validateRouteConfig(route);
expect(validationResult.valid).toBeTrue(); expect(validationResult.valid).toBeTrue();
@@ -790,11 +790,11 @@ tap.test('Route Helpers - createLoadBalancerRoute', async () => {
expect(route.match.domains).toEqual('loadbalancer.example.com'); expect(route.match.domains).toEqual('loadbalancer.example.com');
expect(route.match.ports).toEqual(443); expect(route.match.ports).toEqual(443);
expect(route.action.type).toEqual('forward'); expect(route.action.type).toEqual('forward');
expect(Array.isArray(route.action.target.host)).toBeTrue(); expect(route.action.targets).toBeDefined();
if (Array.isArray(route.action.target.host)) { if (route.action.targets && Array.isArray(route.action.targets[0]?.host)) {
expect(route.action.target.host.length).toEqual(3); expect((route.action.targets[0].host as string[]).length).toEqual(3);
} }
expect(route.action.target.port).toEqual(8080); expect(route.action.targets?.[0]?.port).toEqual(8080);
expect(route.action.tls.mode).toEqual('terminate'); expect(route.action.tls.mode).toEqual('terminate');
const validationResult = validateRouteConfig(route); const validationResult = validateRouteConfig(route);
@@ -819,7 +819,7 @@ tap.test('Route Patterns - createApiGatewayRoute', async () => {
expect(apiGatewayRoute.match.domains).toEqual('api.example.com'); expect(apiGatewayRoute.match.domains).toEqual('api.example.com');
expect(apiGatewayRoute.match.path).toInclude('/v1'); expect(apiGatewayRoute.match.path).toInclude('/v1');
expect(apiGatewayRoute.action.type).toEqual('forward'); expect(apiGatewayRoute.action.type).toEqual('forward');
expect(apiGatewayRoute.action.target.port).toEqual(3000); expect(apiGatewayRoute.action.targets?.[0]?.port).toEqual(3000);
// Check TLS configuration // Check TLS configuration
if (apiGatewayRoute.action.tls) { if (apiGatewayRoute.action.tls) {
@@ -854,7 +854,7 @@ tap.test('Route Patterns - createWebSocketPattern', async () => {
expect(wsRoute.match.domains).toEqual('ws.example.com'); expect(wsRoute.match.domains).toEqual('ws.example.com');
expect(wsRoute.match.path).toEqual('/socket'); expect(wsRoute.match.path).toEqual('/socket');
expect(wsRoute.action.type).toEqual('forward'); expect(wsRoute.action.type).toEqual('forward');
expect(wsRoute.action.target.port).toEqual(3000); expect(wsRoute.action.targets?.[0]?.port).toEqual(3000);
// Check TLS configuration // Check TLS configuration
if (wsRoute.action.tls) { if (wsRoute.action.tls) {
@@ -891,8 +891,8 @@ tap.test('Route Patterns - createLoadBalancerRoute pattern', async () => {
expect(lbRoute.action.type).toEqual('forward'); expect(lbRoute.action.type).toEqual('forward');
// Check target hosts // Check target hosts
if (Array.isArray(lbRoute.action.target.host)) { if (lbRoute.action.targets && Array.isArray(lbRoute.action.targets[0]?.host)) {
expect(lbRoute.action.target.host.length).toEqual(3); expect((lbRoute.action.targets[0].host as string[]).length).toEqual(3);
} }
// Check TLS configuration // Check TLS configuration

View File

@@ -38,15 +38,17 @@ tap.test('Per-IP connection limits validation', async () => {
// Track connections up to limit // Track connections up to limit
for (let i = 1; i <= 5; i++) { for (let i = 1; i <= 5; i++) {
securityManager.trackConnectionByIP(testIP, `conn${i}`); // Validate BEFORE tracking the connection (checking if we can add a new connection)
const result = securityManager.validateIP(testIP); const result = securityManager.validateIP(testIP);
expect(result.allowed).toBeTrue(); expect(result.allowed).toBeTrue();
// Now track the connection
securityManager.trackConnectionByIP(testIP, `conn${i}`);
} }
// Verify we're at the limit // Verify we're at the limit
expect(securityManager.getConnectionCountByIP(testIP)).toEqual(5); expect(securityManager.getConnectionCountByIP(testIP)).toEqual(5);
// Next connection should be rejected // Next connection should be rejected (we're already at 5)
const result = securityManager.validateIP(testIP); const result = securityManager.validateIP(testIP);
expect(result.allowed).toBeFalse(); expect(result.allowed).toBeFalse();
expect(result.reason).toInclude('Maximum connections per IP'); expect(result.reason).toInclude('Maximum connections per IP');
@@ -61,21 +63,16 @@ tap.test('Connection rate limiting', async () => {
const testIP = '192.168.1.102'; const testIP = '192.168.1.102';
// Make connections at the rate limit // Make connections at the rate limit
// Note: validateIP() already tracks timestamps internally for rate limiting
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const result = securityManager.validateIP(testIP); const result = securityManager.validateIP(testIP);
expect(result.allowed).toBeTrue(); expect(result.allowed).toBeTrue();
securityManager.trackConnectionByIP(testIP, `conn${i}`);
} }
// Next connection should exceed rate limit // Next connection should exceed rate limit
const result = securityManager.validateIP(testIP); const result = securityManager.validateIP(testIP);
expect(result.allowed).toBeFalse(); expect(result.allowed).toBeFalse();
expect(result.reason).toInclude('Connection rate limit'); expect(result.reason).toInclude('Connection rate limit');
// Clean up connections
for (let i = 0; i < 10; i++) {
securityManager.removeConnectionByIP(testIP, `conn${i}`);
}
}); });
tap.test('Route-level connection limits', async () => { tap.test('Route-level connection limits', async () => {
@@ -93,7 +90,8 @@ tap.test('Route-level connection limits', async () => {
clientIp: '192.168.1.103', clientIp: '192.168.1.103',
serverIp: '0.0.0.0', serverIp: '0.0.0.0',
timestamp: Date.now(), timestamp: Date.now(),
connectionId: 'test-conn' connectionId: 'test-conn',
isTls: true
}; };
// Test with connection counts below limit // Test with connection counts below limit

View File

@@ -73,10 +73,10 @@ tap.test('setup port proxy test environment', async () => {
}, },
action: { action: {
type: 'forward', type: 'forward',
target: { targets: [{
host: 'localhost', host: 'localhost',
port: TEST_SERVER_PORT port: TEST_SERVER_PORT
} }]
} }
} }
], ],
@@ -112,10 +112,10 @@ tap.test('should forward TCP connections to custom host', async () => {
}, },
action: { action: {
type: 'forward', type: 'forward',
target: { targets: [{
host: '127.0.0.1', host: '127.0.0.1',
port: TEST_SERVER_PORT port: TEST_SERVER_PORT
} }]
} }
} }
], ],
@@ -157,10 +157,10 @@ tap.test('should forward connections to custom IP', async () => {
}, },
action: { action: {
type: 'forward', type: 'forward',
target: { targets: [{
host: '127.0.0.1', host: '127.0.0.1',
port: targetServerPort port: targetServerPort
} }]
} }
} }
], ],
@@ -252,10 +252,10 @@ tap.test('should support optional source IP preservation in chained proxies', as
}, },
action: { action: {
type: 'forward', type: 'forward',
target: { targets: [{
host: 'localhost', host: 'localhost',
port: PROXY_PORT + 5 port: PROXY_PORT + 5
} }]
} }
} }
], ],
@@ -273,10 +273,10 @@ tap.test('should support optional source IP preservation in chained proxies', as
}, },
action: { action: {
type: 'forward', type: 'forward',
target: { targets: [{
host: 'localhost', host: 'localhost',
port: TEST_SERVER_PORT port: TEST_SERVER_PORT
} }]
} }
} }
], ],
@@ -311,10 +311,10 @@ tap.test('should support optional source IP preservation in chained proxies', as
}, },
action: { action: {
type: 'forward', type: 'forward',
target: { targets: [{
host: 'localhost', host: 'localhost',
port: PROXY_PORT + 7 port: PROXY_PORT + 7
} }]
} }
} }
], ],
@@ -334,10 +334,10 @@ tap.test('should support optional source IP preservation in chained proxies', as
}, },
action: { action: {
type: 'forward', type: 'forward',
target: { targets: [{
host: 'localhost', host: 'localhost',
port: TEST_SERVER_PORT port: TEST_SERVER_PORT
} }]
} }
} }
], ],
@@ -377,10 +377,10 @@ tap.test('should use round robin for multiple target hosts in domain config', as
}, },
action: { action: {
type: 'forward' as const, type: 'forward' as const,
target: { targets: [{
host: ['hostA', 'hostB'], // Array of hosts for round-robin host: ['hostA', 'hostB'], // Array of hosts for round-robin
port: 80 port: 80
} }]
} }
}; };
@@ -400,9 +400,9 @@ tap.test('should use round robin for multiple target hosts in domain config', as
// For route-based approach, the actual round-robin logic happens in connection handling // For route-based approach, the actual round-robin logic happens in connection handling
// Just make sure our config has the expected hosts // Just make sure our config has the expected hosts
expect(Array.isArray(routeConfig.action.targets[0].host)).toBeTrue(); expect(Array.isArray(routeConfig.action.targets![0].host)).toBeTrue();
expect(routeConfig.action.targets[0].host).toContain('hostA'); expect(routeConfig.action.targets![0].host).toContain('hostA');
expect(routeConfig.action.targets[0].host).toContain('hostB'); expect(routeConfig.action.targets![0].host).toContain('hostB');
}); });
// CLEANUP: Tear down all servers and proxies // CLEANUP: Tear down all servers and proxies

View File

@@ -13,7 +13,8 @@ import {
trackConnection, trackConnection,
removeConnection, removeConnection,
cleanupExpiredRateLimits, cleanupExpiredRateLimits,
parseBasicAuthHeader parseBasicAuthHeader,
normalizeIP
} from './security-utils.js'; } from './security-utils.js';
/** /**
@@ -78,7 +79,15 @@ export class SharedSecurityManager {
* @returns Number of connections from this IP * @returns Number of connections from this IP
*/ */
public getConnectionCountByIP(ip: string): number { public getConnectionCountByIP(ip: string): number {
return this.connectionsByIP.get(ip)?.connections.size || 0; // Check all normalized variants of the IP
const variants = normalizeIP(ip);
for (const variant of variants) {
const info = this.connectionsByIP.get(variant);
if (info) {
return info.connections.size;
}
}
return 0;
} }
/** /**
@@ -88,7 +97,19 @@ export class SharedSecurityManager {
* @param connectionId - The connection ID to associate * @param connectionId - The connection ID to associate
*/ */
public trackConnectionByIP(ip: string, connectionId: string): void { public trackConnectionByIP(ip: string, connectionId: string): void {
trackConnection(ip, connectionId, this.connectionsByIP); // Check if any variant already exists
const variants = normalizeIP(ip);
let existingKey: string | null = null;
for (const variant of variants) {
if (this.connectionsByIP.has(variant)) {
existingKey = variant;
break;
}
}
// Use existing key or the original IP
trackConnection(existingKey || ip, connectionId, this.connectionsByIP);
} }
/** /**
@@ -98,7 +119,15 @@ export class SharedSecurityManager {
* @param connectionId - The connection ID to remove * @param connectionId - The connection ID to remove
*/ */
public removeConnectionByIP(ip: string, connectionId: string): void { public removeConnectionByIP(ip: string, connectionId: string): void {
removeConnection(ip, connectionId, this.connectionsByIP); // Check all variants to find where the connection is tracked
const variants = normalizeIP(ip);
for (const variant of variants) {
if (this.connectionsByIP.has(variant)) {
removeConnection(variant, connectionId, this.connectionsByIP);
break;
}
}
} }
/** /**