@push.rocks/smartproxy 19.4.2 → 19.5.3

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 (42) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/proxies/http-proxy/handlers/index.d.ts +1 -2
  3. package/dist_ts/proxies/http-proxy/handlers/index.js +3 -3
  4. package/dist_ts/proxies/http-proxy/models/types.js +2 -2
  5. package/dist_ts/proxies/smart-proxy/certificate-manager.js +30 -25
  6. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +2 -5
  7. package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +9 -41
  8. package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
  9. package/dist_ts/proxies/smart-proxy/nftables-manager.js +5 -6
  10. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +4 -12
  11. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +152 -47
  12. package/dist_ts/proxies/smart-proxy/route-manager.d.ts +2 -0
  13. package/dist_ts/proxies/smart-proxy/route-manager.js +7 -8
  14. package/dist_ts/proxies/smart-proxy/utils/index.d.ts +2 -2
  15. package/dist_ts/proxies/smart-proxy/utils/index.js +3 -3
  16. package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +61 -20
  17. package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +249 -53
  18. package/dist_ts/proxies/smart-proxy/utils/route-patterns.d.ts +0 -18
  19. package/dist_ts/proxies/smart-proxy/utils/route-patterns.js +4 -43
  20. package/dist_ts/proxies/smart-proxy/utils/route-utils.js +14 -15
  21. package/dist_ts/proxies/smart-proxy/utils/route-validators.js +10 -31
  22. package/package.json +7 -7
  23. package/readme.hints.md +168 -5
  24. package/readme.plan.md +314 -382
  25. package/readme.plan2.md +764 -0
  26. package/readme.problems.md +86 -0
  27. package/ts/00_commitinfo_data.ts +1 -1
  28. package/ts/proxies/http-proxy/handlers/index.ts +1 -2
  29. package/ts/proxies/http-proxy/models/types.ts +1 -1
  30. package/ts/proxies/smart-proxy/certificate-manager.ts +29 -23
  31. package/ts/proxies/smart-proxy/http-proxy-bridge.ts +1 -4
  32. package/ts/proxies/smart-proxy/models/route-types.ts +12 -59
  33. package/ts/proxies/smart-proxy/nftables-manager.ts +4 -5
  34. package/ts/proxies/smart-proxy/route-connection-handler.ts +170 -64
  35. package/ts/proxies/smart-proxy/route-manager.ts +7 -8
  36. package/ts/proxies/smart-proxy/utils/index.ts +0 -2
  37. package/ts/proxies/smart-proxy/utils/route-helpers.ts +289 -70
  38. package/ts/proxies/smart-proxy/utils/route-patterns.ts +6 -56
  39. package/ts/proxies/smart-proxy/utils/route-utils.ts +12 -15
  40. package/ts/proxies/smart-proxy/utils/route-validators.ts +9 -31
  41. package/ts/proxies/http-proxy/handlers/redirect-handler.ts +0 -105
  42. package/ts/proxies/http-proxy/handlers/static-handler.ts +0 -261
@@ -0,0 +1,86 @@
1
+ # SmartProxy Module Problems
2
+
3
+ Based on test analysis, the following potential issues have been identified in the SmartProxy module:
4
+
5
+ ## 1. HttpProxy Route Configuration Issue
6
+ **Location**: `ts/proxies/http-proxy/http-proxy.ts:380`
7
+ **Problem**: The HttpProxy is trying to read the 'type' property of an undefined object when updating route configurations.
8
+ **Evidence**: `test.http-forwarding-fix.ts` fails with:
9
+ ```
10
+ TypeError: Cannot read properties of undefined (reading 'type')
11
+ at HttpProxy.updateRouteConfigs (/mnt/data/lossless/push.rocks/smartproxy/ts/proxies/http-proxy/http-proxy.ts:380:24)
12
+ ```
13
+ **Impact**: Routes with `useHttpProxy` configuration may not work properly.
14
+
15
+ ## 2. Connection Forwarding Issues
16
+ **Problem**: Basic TCP forwarding appears to not be working correctly after the simplification to just 'forward' and 'socket-handler' action types.
17
+ **Evidence**: Multiple forwarding tests timeout waiting for data to be forwarded:
18
+ - `test.forwarding-fix-verification.ts` - times out waiting for forwarded data
19
+ - `test.connection-forwarding.ts` - times out on SNI-based forwarding
20
+ **Impact**: The 'forward' action type may not be properly forwarding connections to target servers.
21
+
22
+ ## 3. Missing Certificate Manager Methods
23
+ **Problem**: Tests expect `provisionAllCertificates` method on certificate manager but it may not exist or may not be properly initialized.
24
+ **Evidence**: Multiple tests fail with "this.certManager.provisionAllCertificates is not a function"
25
+ **Impact**: Certificate provisioning may not work as expected.
26
+
27
+ ## 4. Route Update Mechanism
28
+ **Problem**: The route update mechanism may have issues preserving certificate manager callbacks and other state.
29
+ **Evidence**: Tests specifically designed to verify callback preservation after route updates.
30
+ **Impact**: Dynamic route updates might break certificate management functionality.
31
+
32
+ ## 5. Route-Specific Security Not Fully Implemented
33
+ **Problem**: While the route definitions support security configurations (ipAllowList, ipBlockList, authentication), these are not being enforced at the route level.
34
+ **Evidence**:
35
+ - SecurityManager has methods like `isIPAuthorized` for route-specific security
36
+ - Route connection handler only checks global IP validation, not route-specific security rules
37
+ - No evidence of route.action.security being checked when handling connections
38
+ **Impact**: Route-specific security rules defined in configuration are not enforced, potentially allowing unauthorized access.
39
+ **Status**: ✅ FIXED - Route-specific IP allow/block lists are now enforced when a route is matched. Authentication is logged as not enforceable for non-terminated connections.
40
+ **Additional Fix**: Removed security checks from route matching logic - security is now properly enforced AFTER a route is matched, not during matching.
41
+
42
+ ## 6. Security Property Location Consolidation
43
+ **Problem**: Security was defined in two places - route.security and route.action.security - causing confusion.
44
+ **Status**: ✅ FIXED - Consolidated to only route.security. Removed action.security from types and updated all references.
45
+
46
+ ## Recommendations
47
+
48
+ 1. **Verify Forward Action Implementation**: Check that the 'forward' action type properly establishes bidirectional data flow between client and target server. ✅ FIXED - Basic forwarding now works correctly.
49
+
50
+ 2. **Fix HttpProxy Route Handling**: Ensure that route objects passed to HttpProxy.updateRouteConfigs have the expected structure with all required properties. ✅ FIXED - Routes now preserve their structure.
51
+
52
+ 3. **Review Certificate Manager API**: Ensure all expected methods exist and are properly documented.
53
+
54
+ 4. **Add Integration Tests**: Many unit tests are testing internal implementation details. Consider adding more integration tests that test the public API.
55
+
56
+ 5. **Implement Route-Specific Security**: Add security checks when a route is matched to enforce route-specific IP allow/block lists and authentication rules. ✅ FIXED - IP allow/block lists are now enforced at the route level.
57
+
58
+ 6. **Fix TLS Detection Logic**: The connection handler was treating all connections as TLS. This has been partially fixed but needs proper testing for all TLS modes.
59
+
60
+ ## 7. HTTP Domain Matching Issue
61
+ **Problem**: Routes with domain restrictions fail to match HTTP connections because domain information is only available after HTTP headers are received, but route matching happens immediately upon connection.
62
+ **Evidence**:
63
+ - `test.http-port8080-forwarding.ts` - "No route found for connection on port 8080" despite having a matching route
64
+ - HTTP connections provide domain info via the Host header, which arrives after the initial TCP connection
65
+ - Route matching in `handleInitialData` happens before HTTP headers are parsed
66
+ **Impact**: HTTP routes with domain restrictions cannot be matched, forcing users to remove domain restrictions for HTTP routes.
67
+ **Root Cause**: For non-TLS connections, SmartProxy attempts to match routes immediately, but the domain information needed for matching is only available after parsing HTTP headers.
68
+ **Status**: ✅ FIXED - Added skipDomainCheck parameter to route matching for HTTP proxy ports. When a port is configured with useHttpProxy and the connection is not TLS, domain validation is skipped at the initial route matching stage, allowing the HttpProxy to handle domain-based routing after headers are received.
69
+
70
+ ## 8. HttpProxy Plain HTTP Forwarding Issue
71
+ **Problem**: HttpProxy is an HTTPS server but SmartProxy forwards plain HTTP connections to it via `useHttpProxy` configuration.
72
+ **Evidence**:
73
+ - `test.http-port8080-forwarding.ts` - Connection immediately closed after forwarding to HttpProxy
74
+ - HttpProxy is created with `http2.createSecureServer` expecting TLS connections
75
+ - SmartProxy forwards raw HTTP data to HttpProxy's HTTPS port
76
+ **Impact**: Plain HTTP connections cannot be handled by HttpProxy, despite `useHttpProxy` configuration suggesting this should work.
77
+ **Root Cause**: Design mismatch - HttpProxy is designed for HTTPS/TLS termination, not plain HTTP forwarding.
78
+ **Status**: Documented. The `useHttpProxy` configuration should only be used for ports that receive TLS connections requiring termination. For plain HTTP forwarding, use direct forwarding without HttpProxy.
79
+
80
+ ## 9. Route Security Configuration Location Issue
81
+ **Problem**: Tests were placing security configuration in `route.action.security` instead of `route.security`.
82
+ **Evidence**:
83
+ - `test.route-security.ts` - IP block list test failing because security was in wrong location
84
+ - IRouteConfig interface defines security at route level, not inside action
85
+ **Impact**: Security rules defined in action.security were ignored, causing tests to fail.
86
+ **Status**: ✅ FIXED - Updated tests to place security configuration at the correct location (route.security).
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartproxy',
6
- version: '19.4.2',
6
+ version: '19.5.3',
7
7
  description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.'
8
8
  }
@@ -2,5 +2,4 @@
2
2
  * HTTP handlers for various route types
3
3
  */
4
4
 
5
- export { RedirectHandler } from './redirect-handler.js';
6
- export { StaticHandler } from './static-handler.js';
5
+ // Empty - all handlers have been removed
@@ -153,7 +153,7 @@ export function convertLegacyConfigToRouteConfig(
153
153
 
154
154
  // Add authentication if present
155
155
  if (legacyConfig.authentication) {
156
- routeConfig.action.security = {
156
+ routeConfig.security = {
157
157
  authentication: {
158
158
  type: 'basic',
159
159
  credentials: [{
@@ -5,6 +5,7 @@ import type { IAcmeOptions } from './models/interfaces.js';
5
5
  import { CertStore } from './cert-store.js';
6
6
  import type { AcmeStateManager } from './acme-state-manager.js';
7
7
  import { logger } from '../../core/utils/logger.js';
8
+ import { SocketHandlers } from './utils/route-helpers.js';
8
9
 
9
10
  export interface ICertStatus {
10
11
  domain: string;
@@ -693,22 +694,24 @@ export class SmartCertManager {
693
694
  path: '/.well-known/acme-challenge/*'
694
695
  },
695
696
  action: {
696
- type: 'static',
697
- handler: async (context) => {
697
+ type: 'socket-handler',
698
+ socketHandler: SocketHandlers.httpServer((req, res) => {
698
699
  // Extract the token from the path
699
- const token = context.path?.split('/').pop();
700
+ const token = req.url?.split('/').pop();
700
701
  if (!token) {
701
- return { status: 404, body: 'Not found' };
702
+ res.status(404);
703
+ res.send('Not found');
704
+ return;
702
705
  }
703
706
 
704
707
  // Create mock request/response objects for SmartAcme
708
+ let responseData: any = null;
705
709
  const mockReq = {
706
- url: context.path,
707
- method: 'GET',
708
- headers: context.headers || {}
710
+ url: req.url,
711
+ method: req.method,
712
+ headers: req.headers
709
713
  };
710
714
 
711
- let responseData: any = null;
712
715
  const mockRes = {
713
716
  statusCode: 200,
714
717
  setHeader: (name: string, value: string) => {},
@@ -718,24 +721,27 @@ export class SmartCertManager {
718
721
  };
719
722
 
720
723
  // Use SmartAcme's handler
721
- const handled = await new Promise<boolean>((resolve) => {
724
+ const handleAcme = () => {
722
725
  http01Handler.handleRequest(mockReq as any, mockRes as any, () => {
723
- resolve(false);
726
+ // Not handled by ACME
727
+ res.status(404);
728
+ res.send('Not found');
724
729
  });
725
- // Give it a moment to process
726
- setTimeout(() => resolve(true), 100);
727
- });
730
+
731
+ // Give it a moment to process, then send response
732
+ setTimeout(() => {
733
+ if (responseData) {
734
+ res.header('Content-Type', 'text/plain');
735
+ res.send(String(responseData));
736
+ } else {
737
+ res.status(404);
738
+ res.send('Not found');
739
+ }
740
+ }, 100);
741
+ };
728
742
 
729
- if (handled && responseData) {
730
- return {
731
- status: mockRes.statusCode,
732
- headers: { 'Content-Type': 'text/plain' },
733
- body: responseData
734
- };
735
- } else {
736
- return { status: 404, body: 'Not found' };
737
- }
738
- }
743
+ handleAcme();
744
+ })
739
745
  }
740
746
  };
741
747
 
@@ -73,10 +73,7 @@ export class HttpProxyBridge {
73
73
  }
74
74
 
75
75
  return {
76
- domain,
77
- target: route.action.target,
78
- tls: route.action.tls,
79
- security: route.action.security,
76
+ ...route, // Keep the original route structure
80
77
  match: {
81
78
  ...route.match,
82
79
  domains: domain // Ensure domains is always set for HttpProxy
@@ -2,11 +2,20 @@ import * as plugins from '../../../plugins.js';
2
2
  // Certificate types removed - use local definition
3
3
  import type { TForwardingType } from '../../../forwarding/config/forwarding-types.js';
4
4
  import type { PortRange } from '../../../proxies/nftables-proxy/models/interfaces.js';
5
+ import type { IRouteContext } from '../../../core/models/route-context.js';
6
+
7
+ // Re-export IRouteContext for convenience
8
+ export type { IRouteContext };
5
9
 
6
10
  /**
7
11
  * Supported action types for route configurations
8
12
  */
9
- export type TRouteActionType = 'forward' | 'redirect' | 'block' | 'static';
13
+ export type TRouteActionType = 'forward' | 'socket-handler';
14
+
15
+ /**
16
+ * Socket handler function type
17
+ */
18
+ export type TSocketHandler = (socket: plugins.net.Socket, context: IRouteContext) => void | Promise<void>;
10
19
 
11
20
  /**
12
21
  * TLS handling modes for route configurations
@@ -35,36 +44,6 @@ export interface IRouteMatch {
35
44
  headers?: Record<string, string | RegExp>; // Match specific HTTP headers
36
45
  }
37
46
 
38
- /**
39
- * Context provided to port and host mapping functions
40
- */
41
- export interface IRouteContext {
42
- // Connection information
43
- port: number; // The matched incoming port
44
- domain?: string; // The domain from SNI or Host header
45
- clientIp: string; // The client's IP address
46
- serverIp: string; // The server's IP address
47
- path?: string; // URL path (for HTTP connections)
48
- query?: string; // Query string (for HTTP connections)
49
- headers?: Record<string, string>; // HTTP headers (for HTTP connections)
50
- method?: string; // HTTP method (for HTTP connections)
51
-
52
- // TLS information
53
- isTls: boolean; // Whether the connection is TLS
54
- tlsVersion?: string; // TLS version if applicable
55
-
56
- // Route information
57
- routeName?: string; // The name of the matched route
58
- routeId?: string; // The ID of the matched route
59
-
60
- // Target information (resolved from dynamic mapping)
61
- targetHost?: string | string[]; // The resolved target host(s)
62
- targetPort?: number; // The resolved target port
63
-
64
- // Additional properties
65
- timestamp: number; // The request timestamp
66
- connectionId: string; // Unique connection identifier
67
- }
68
47
 
69
48
  /**
70
49
  * Target configuration for forwarding
@@ -84,15 +63,6 @@ export interface IRouteAcme {
84
63
  renewBeforeDays?: number; // Days before expiry to renew (default: 30)
85
64
  }
86
65
 
87
- /**
88
- * Static route handler response
89
- */
90
- export interface IStaticResponse {
91
- status: number;
92
- headers?: Record<string, string>;
93
- body: string | Buffer;
94
- }
95
-
96
66
  /**
97
67
  * TLS configuration for route actions
98
68
  */
@@ -112,14 +82,6 @@ export interface IRouteTls {
112
82
  sessionTimeout?: number; // TLS session timeout in seconds
113
83
  }
114
84
 
115
- /**
116
- * Redirect configuration for route actions
117
- */
118
- export interface IRouteRedirect {
119
- to: string; // URL or template with {domain}, {port}, etc.
120
- status: 301 | 302 | 307 | 308;
121
- }
122
-
123
85
  /**
124
86
  * Authentication options
125
87
  */
@@ -265,21 +227,12 @@ export interface IRouteAction {
265
227
  // TLS handling
266
228
  tls?: IRouteTls;
267
229
 
268
- // For redirects
269
- redirect?: IRouteRedirect;
270
-
271
- // For static files
272
- static?: IRouteStaticFiles;
273
-
274
230
  // WebSocket support
275
231
  websocket?: IRouteWebSocket;
276
232
 
277
233
  // Load balancing options
278
234
  loadBalancing?: IRouteLoadBalancing;
279
235
 
280
- // Security options
281
- security?: IRouteSecurity;
282
-
283
236
  // Advanced options
284
237
  advanced?: IRouteAdvanced;
285
238
 
@@ -295,8 +248,8 @@ export interface IRouteAction {
295
248
  // NFTables-specific options
296
249
  nftables?: INfTablesOptions;
297
250
 
298
- // Handler function for static routes
299
- handler?: (context: IRouteContext) => Promise<IStaticResponse>;
251
+ // Socket handler function (when type is 'socket-handler')
252
+ socketHandler?: TSocketHandler;
300
253
  }
301
254
 
302
255
  /**
@@ -175,13 +175,12 @@ export class NFTablesManager {
175
175
  };
176
176
 
177
177
  // Add security-related options
178
- const security = action.security || route.security;
179
- if (security?.ipAllowList?.length) {
180
- options.ipAllowList = security.ipAllowList;
178
+ if (route.security?.ipAllowList?.length) {
179
+ options.ipAllowList = route.security.ipAllowList;
181
180
  }
182
181
 
183
- if (security?.ipBlockList?.length) {
184
- options.ipBlockList = security.ipBlockList;
182
+ if (route.security?.ipBlockList?.length) {
183
+ options.ipBlockList = route.security.ipBlockList;
185
184
  }
186
185
 
187
186
  // Add QoS options
@@ -10,7 +10,6 @@ import { HttpProxyBridge } from './http-proxy-bridge.js';
10
10
  import { TimeoutManager } from './timeout-manager.js';
11
11
  import { RouteManager } from './route-manager.js';
12
12
  import type { ForwardingHandler } from '../../forwarding/handlers/base-handler.js';
13
- import { RedirectHandler, StaticHandler } from '../http-proxy/handlers/index.js';
14
13
 
15
14
  /**
16
15
  * Handles new connection processing and setup logic with support for route-based configuration
@@ -147,18 +146,42 @@ export class RouteConnectionHandler {
147
146
  );
148
147
  }
149
148
 
150
- // Start TLS SNI handling
151
- this.handleTlsConnection(socket, record);
149
+ // Handle the connection - wait for initial data to determine if it's TLS
150
+ this.handleInitialData(socket, record);
152
151
  }
153
152
 
154
153
  /**
155
- * Handle a connection and wait for TLS handshake for SNI extraction if needed
154
+ * Handle initial data from a connection to determine routing
156
155
  */
157
- private handleTlsConnection(socket: plugins.net.Socket, record: IConnectionRecord): void {
156
+ private handleInitialData(socket: plugins.net.Socket, record: IConnectionRecord): void {
158
157
  const connectionId = record.id;
159
158
  const localPort = record.localPort;
160
159
  let initialDataReceived = false;
161
160
 
161
+ // Check if any routes on this port require TLS handling
162
+ const allRoutes = this.routeManager.getAllRoutes();
163
+ const needsTlsHandling = allRoutes.some(route => {
164
+ // Check if route matches this port
165
+ const matchesPort = this.routeManager.getRoutesForPort(localPort).includes(route);
166
+
167
+ return matchesPort &&
168
+ route.action.type === 'forward' &&
169
+ route.action.tls &&
170
+ (route.action.tls.mode === 'terminate' ||
171
+ route.action.tls.mode === 'passthrough');
172
+ });
173
+
174
+ // If no routes require TLS handling and it's not port 443, route immediately
175
+ if (!needsTlsHandling && localPort !== 443) {
176
+ // Set up error handler
177
+ socket.on('error', this.connectionManager.handleError('incoming', record));
178
+
179
+ // Route immediately for non-TLS connections
180
+ this.routeConnection(socket, record, '', undefined);
181
+ return;
182
+ }
183
+
184
+ // Otherwise, wait for initial data to check if it's TLS
162
185
  // Set an initial timeout for handshake data
163
186
  let initialTimeout: NodeJS.Timeout | null = setTimeout(() => {
164
187
  if (!initialDataReceived) {
@@ -297,6 +320,12 @@ export class RouteConnectionHandler {
297
320
  const localPort = record.localPort;
298
321
  const remoteIP = record.remoteIP;
299
322
 
323
+ // Check if this is an HTTP proxy port
324
+ const isHttpProxyPort = this.settings.useHttpProxy?.includes(localPort);
325
+
326
+ // For HTTP proxy ports without TLS, skip domain check since domain info comes from HTTP headers
327
+ const skipDomainCheck = isHttpProxyPort && !record.isTLS;
328
+
300
329
  // Find matching route
301
330
  const routeMatch = this.routeManager.findMatchingRoute({
302
331
  port: localPort,
@@ -304,6 +333,7 @@ export class RouteConnectionHandler {
304
333
  clientIp: remoteIP,
305
334
  path: undefined, // We don't have path info at this point
306
335
  tlsVersion: undefined, // We don't extract TLS version yet
336
+ skipDomainCheck: skipDomainCheck,
307
337
  });
308
338
 
309
339
  if (!routeMatch) {
@@ -383,20 +413,69 @@ export class RouteConnectionHandler {
383
413
  });
384
414
  }
385
415
 
416
+ // Apply route-specific security checks
417
+ if (route.security) {
418
+ // Check IP allow/block lists
419
+ if (route.security.ipAllowList || route.security.ipBlockList) {
420
+ const isIPAllowed = this.securityManager.isIPAuthorized(
421
+ remoteIP,
422
+ route.security.ipAllowList || [],
423
+ route.security.ipBlockList || []
424
+ );
425
+
426
+ if (!isIPAllowed) {
427
+ logger.log('warn', `IP ${remoteIP} blocked by route security for route ${route.name || 'unnamed'} (connection: ${connectionId})`, {
428
+ connectionId,
429
+ remoteIP,
430
+ routeName: route.name || 'unnamed',
431
+ component: 'route-handler'
432
+ });
433
+ socket.end();
434
+ this.connectionManager.cleanupConnection(record, 'route_ip_blocked');
435
+ return;
436
+ }
437
+ }
438
+
439
+ // Check max connections per route
440
+ if (route.security.maxConnections !== undefined) {
441
+ // TODO: Implement per-route connection tracking
442
+ // For now, log that this feature is not yet implemented
443
+ if (this.settings.enableDetailedLogging) {
444
+ logger.log('warn', `Route ${route.name} has maxConnections=${route.security.maxConnections} configured but per-route connection limits are not yet implemented`, {
445
+ connectionId,
446
+ routeName: route.name,
447
+ component: 'route-handler'
448
+ });
449
+ }
450
+ }
451
+
452
+ // Check authentication requirements
453
+ if (route.security.authentication || route.security.basicAuth || route.security.jwtAuth) {
454
+ // Authentication checks would typically happen at the HTTP layer
455
+ // For non-HTTP connections or passthrough, we can't enforce authentication
456
+ if (route.action.type === 'forward' && route.action.tls?.mode !== 'terminate') {
457
+ logger.log('warn', `Route ${route.name} has authentication configured but it cannot be enforced for non-terminated connections`, {
458
+ connectionId,
459
+ routeName: route.name,
460
+ tlsMode: route.action.tls?.mode || 'none',
461
+ component: 'route-handler'
462
+ });
463
+ }
464
+ }
465
+ }
386
466
 
387
467
  // Handle the route based on its action type
388
468
  switch (route.action.type) {
389
469
  case 'forward':
390
470
  return this.handleForwardAction(socket, record, route, initialChunk);
391
471
 
392
- case 'redirect':
393
- return this.handleRedirectAction(socket, record, route);
394
-
395
- case 'block':
396
- return this.handleBlockAction(socket, record, route);
397
-
398
- case 'static':
399
- this.handleStaticAction(socket, record, route, initialChunk);
472
+ case 'socket-handler':
473
+ logger.log('info', `Handling socket-handler action for route ${route.name}`, {
474
+ connectionId,
475
+ routeName: route.name,
476
+ component: 'route-handler'
477
+ });
478
+ this.handleSocketHandlerAction(socket, record, route, initialChunk);
400
479
  return;
401
480
 
402
481
  default:
@@ -636,6 +715,18 @@ export class RouteConnectionHandler {
636
715
  // No TLS settings - check if this port should use HttpProxy
637
716
  const isHttpProxyPort = this.settings.useHttpProxy?.includes(record.localPort);
638
717
 
718
+ // Debug logging
719
+ if (this.settings.enableDetailedLogging) {
720
+ logger.log('debug', `Checking HttpProxy forwarding: port=${record.localPort}, useHttpProxy=${JSON.stringify(this.settings.useHttpProxy)}, isHttpProxyPort=${isHttpProxyPort}, hasHttpProxy=${!!this.httpProxyBridge.getHttpProxy()}`, {
721
+ connectionId,
722
+ localPort: record.localPort,
723
+ useHttpProxy: this.settings.useHttpProxy,
724
+ isHttpProxyPort,
725
+ hasHttpProxy: !!this.httpProxyBridge.getHttpProxy(),
726
+ component: 'route-handler'
727
+ });
728
+ }
729
+
639
730
  if (isHttpProxyPort && this.httpProxyBridge.getHttpProxy()) {
640
731
  // Forward non-TLS connections to HttpProxy if configured
641
732
  if (this.settings.enableDetailedLogging) {
@@ -710,70 +801,85 @@ export class RouteConnectionHandler {
710
801
  }
711
802
 
712
803
  /**
713
- * Handle a redirect action for a route
804
+ * Handle a socket-handler action for a route
714
805
  */
715
- private handleRedirectAction(
806
+ private async handleSocketHandlerAction(
716
807
  socket: plugins.net.Socket,
717
808
  record: IConnectionRecord,
718
- route: IRouteConfig
719
- ): void {
720
- // For TLS connections, we can't do redirects at the TCP level
721
- if (record.isTLS) {
722
- logger.log('warn', `Cannot redirect TLS connection ${record.id} at TCP level`, {
723
- connectionId: record.id,
809
+ route: IRouteConfig,
810
+ initialChunk?: Buffer
811
+ ): Promise<void> {
812
+ const connectionId = record.id;
813
+
814
+ if (!route.action.socketHandler) {
815
+ logger.log('error', 'socket-handler action missing socketHandler function', {
816
+ connectionId,
817
+ routeName: route.name,
724
818
  component: 'route-handler'
725
819
  });
726
- socket.end();
727
- this.connectionManager.cleanupConnection(record, 'tls_redirect_error');
820
+ socket.destroy();
821
+ this.connectionManager.cleanupConnection(record, 'missing_handler');
728
822
  return;
729
823
  }
730
-
731
- // Delegate to HttpProxy's RedirectHandler
732
- RedirectHandler.handleRedirect(socket, route, {
824
+
825
+ // Create route context for the handler
826
+ const routeContext = this.createRouteContext({
733
827
  connectionId: record.id,
734
- connectionManager: this.connectionManager,
735
- settings: this.settings
828
+ port: record.localPort,
829
+ domain: record.lockedDomain,
830
+ clientIp: record.remoteIP,
831
+ serverIp: socket.localAddress || '',
832
+ isTls: record.isTLS || false,
833
+ tlsVersion: record.tlsVersion,
834
+ routeName: route.name,
835
+ routeId: route.id,
736
836
  });
737
- }
738
-
739
- /**
740
- * Handle a block action for a route
741
- */
742
- private handleBlockAction(
743
- socket: plugins.net.Socket,
744
- record: IConnectionRecord,
745
- route: IRouteConfig
746
- ): void {
747
- const connectionId = record.id;
748
-
749
- if (this.settings.enableDetailedLogging) {
750
- logger.log('info', `Blocking connection ${connectionId} based on route '${route.name || 'unnamed'}'`, {
837
+
838
+ try {
839
+ // Call the handler with socket AND context
840
+ const result = route.action.socketHandler(socket, routeContext);
841
+
842
+ // Handle async handlers properly
843
+ if (result instanceof Promise) {
844
+ result
845
+ .then(() => {
846
+ // Emit initial chunk after async handler completes
847
+ if (initialChunk && initialChunk.length > 0) {
848
+ socket.emit('data', initialChunk);
849
+ }
850
+ })
851
+ .catch(error => {
852
+ logger.log('error', 'Socket handler error', {
853
+ connectionId,
854
+ routeName: route.name,
855
+ error: error.message,
856
+ component: 'route-handler'
857
+ });
858
+ if (!socket.destroyed) {
859
+ socket.destroy();
860
+ }
861
+ this.connectionManager.cleanupConnection(record, 'handler_error');
862
+ });
863
+ } else {
864
+ // For sync handlers, emit on next tick
865
+ if (initialChunk && initialChunk.length > 0) {
866
+ process.nextTick(() => {
867
+ socket.emit('data', initialChunk);
868
+ });
869
+ }
870
+ }
871
+ } catch (error) {
872
+ logger.log('error', 'Socket handler error', {
751
873
  connectionId,
752
- routeName: route.name || 'unnamed',
874
+ routeName: route.name,
875
+ error: error.message,
753
876
  component: 'route-handler'
754
877
  });
878
+ if (!socket.destroyed) {
879
+ socket.destroy();
880
+ }
881
+ this.connectionManager.cleanupConnection(record, 'handler_error');
755
882
  }
756
-
757
- // Simply close the connection
758
- socket.end();
759
- this.connectionManager.initiateCleanupOnce(record, 'route_blocked');
760
- }
761
-
762
- /**
763
- * Handle a static action for a route
764
- */
765
- private async handleStaticAction(
766
- socket: plugins.net.Socket,
767
- record: IConnectionRecord,
768
- route: IRouteConfig,
769
- initialChunk?: Buffer
770
- ): Promise<void> {
771
- // Delegate to HttpProxy's StaticHandler
772
- await StaticHandler.handleStatic(socket, route, {
773
- connectionId: record.id,
774
- connectionManager: this.connectionManager,
775
- settings: this.settings
776
- }, record, initialChunk);
777
883
  }
778
884
 
779
885
  /**