@push.rocks/smartproxy 17.0.0 → 18.0.1

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.
@@ -40,9 +40,10 @@ export abstract class ForwardingHandler extends plugins.EventEmitter implements
40
40
 
41
41
  /**
42
42
  * Get a target from the configuration, supporting round-robin selection
43
+ * @param incomingPort Optional incoming port for 'preserve' mode
43
44
  * @returns A resolved target object with host and port
44
45
  */
45
- protected getTargetFromConfig(): { host: string, port: number } {
46
+ protected getTargetFromConfig(incomingPort: number = 80): { host: string, port: number } {
46
47
  const { target } = this.config;
47
48
 
48
49
  // Handle round-robin host selection
@@ -55,32 +56,37 @@ export abstract class ForwardingHandler extends plugins.EventEmitter implements
55
56
  const randomIndex = Math.floor(Math.random() * target.host.length);
56
57
  return {
57
58
  host: target.host[randomIndex],
58
- port: this.resolvePort(target.port)
59
+ port: this.resolvePort(target.port, incomingPort)
59
60
  };
60
61
  }
61
62
 
62
63
  // Single host
63
64
  return {
64
65
  host: target.host,
65
- port: this.resolvePort(target.port)
66
+ port: this.resolvePort(target.port, incomingPort)
66
67
  };
67
68
  }
68
69
 
69
70
  /**
70
71
  * Resolves a port value, handling 'preserve' and function ports
72
+ * @param port The port value to resolve
73
+ * @param incomingPort Optional incoming port to use for 'preserve' mode
71
74
  */
72
- protected resolvePort(port: number | 'preserve' | ((ctx: any) => number)): number {
75
+ protected resolvePort(
76
+ port: number | 'preserve' | ((ctx: any) => number),
77
+ incomingPort: number = 80
78
+ ): number {
73
79
  if (typeof port === 'function') {
74
80
  try {
75
- // Create a minimal context for the function
76
- const ctx = { port: 80 }; // Default port for minimal context
81
+ // Create a minimal context for the function that includes the incoming port
82
+ const ctx = { port: incomingPort };
77
83
  return port(ctx);
78
84
  } catch (err) {
79
85
  console.error('Error resolving port function:', err);
80
- return 80; // Default fallback port
86
+ return incomingPort; // Fall back to incoming port
81
87
  }
82
88
  } else if (port === 'preserve') {
83
- return 80; // Default port for 'preserve' in base handler
89
+ return incomingPort; // Use the actual incoming port for 'preserve'
84
90
  } else {
85
91
  return port;
86
92
  }
@@ -38,6 +38,7 @@ export class HttpForwardingHandler extends ForwardingHandler {
38
38
  // For HTTP, we mainly handle parsed requests, but we can still set up
39
39
  // some basic connection tracking
40
40
  const remoteAddress = socket.remoteAddress || 'unknown';
41
+ const localPort = socket.localPort || 80;
41
42
 
42
43
  socket.on('close', (hadError) => {
43
44
  this.emit(ForwardingHandlerEvents.DISCONNECTED, {
@@ -54,7 +55,8 @@ export class HttpForwardingHandler extends ForwardingHandler {
54
55
  });
55
56
 
56
57
  this.emit(ForwardingHandlerEvents.CONNECTED, {
57
- remoteAddress
58
+ remoteAddress,
59
+ localPort
58
60
  });
59
61
  }
60
62
 
@@ -64,8 +66,11 @@ export class HttpForwardingHandler extends ForwardingHandler {
64
66
  * @param res The HTTP response
65
67
  */
66
68
  public handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void {
67
- // Get the target from configuration
68
- const target = this.getTargetFromConfig();
69
+ // Get the local port from the request (for 'preserve' port handling)
70
+ const localPort = req.socket.localPort || 80;
71
+
72
+ // Get the target from configuration, passing the incoming port
73
+ const target = this.getTargetFromConfig(localPort);
69
74
 
70
75
  // Create a custom headers object with variables for substitution
71
76
  const variables = {
@@ -41,11 +41,12 @@ export class HttpRequestHandler {
41
41
  };
42
42
 
43
43
  // Optionally rewrite host header to match target
44
- if (options.headers && options.headers.host) {
44
+ if (options.headers && 'host' in options.headers) {
45
45
  // Only apply if host header rewrite is enabled or not explicitly disabled
46
46
  const shouldRewriteHost = route?.action.options?.rewriteHostHeader !== false;
47
47
  if (shouldRewriteHost) {
48
- options.headers.host = `${destination.host}:${destination.port}`;
48
+ // Safely cast to OutgoingHttpHeaders to access host property
49
+ (options.headers as plugins.http.OutgoingHttpHeaders).host = `${destination.host}:${destination.port}`;
49
50
  }
50
51
  }
51
52
 
@@ -27,8 +27,8 @@ export interface ISmartProxyOptions {
27
27
  port: number; // Default port to use when not specified in routes
28
28
  };
29
29
  security?: {
30
- allowedIps?: string[]; // Default allowed IPs
31
- blockedIps?: string[]; // Default blocked IPs
30
+ ipAllowList?: string[]; // Default allowed IPs
31
+ ipBlockList?: string[]; // Default blocked IPs
32
32
  maxConnections?: number; // Default max connections
33
33
  };
34
34
  preserveSourceIP?: boolean; // Default source IP preservation
@@ -112,13 +112,39 @@ export interface IRouteAuthentication {
112
112
  }
113
113
 
114
114
  /**
115
- * Security options for route actions
115
+ * Security options for routes
116
116
  */
117
117
  export interface IRouteSecurity {
118
- allowedIps?: string[];
119
- blockedIps?: string[];
120
- maxConnections?: number;
118
+ // Access control lists
119
+ ipAllowList?: string[]; // IP addresses that are allowed to connect
120
+ ipBlockList?: string[]; // IP addresses that are blocked from connecting
121
+
122
+ // Connection limits
123
+ maxConnections?: number; // Maximum concurrent connections
124
+
125
+ // Authentication
121
126
  authentication?: IRouteAuthentication;
127
+
128
+ // Rate limiting
129
+ rateLimit?: IRouteRateLimit;
130
+
131
+ // Authentication methods
132
+ basicAuth?: {
133
+ enabled: boolean;
134
+ users: Array<{ username: string; password: string }>;
135
+ realm?: string;
136
+ excludePaths?: string[];
137
+ };
138
+
139
+ jwtAuth?: {
140
+ enabled: boolean;
141
+ secret: string;
142
+ algorithm?: string;
143
+ issuer?: string;
144
+ audience?: string;
145
+ expiresIn?: number;
146
+ excludePaths?: string[];
147
+ };
122
148
  }
123
149
 
124
150
  /**
@@ -247,29 +273,7 @@ export interface IRouteRateLimit {
247
273
  errorMessage?: string;
248
274
  }
249
275
 
250
- /**
251
- * Security features for routes
252
- */
253
- export interface IRouteSecurity {
254
- rateLimit?: IRouteRateLimit;
255
- basicAuth?: {
256
- enabled: boolean;
257
- users: Array<{ username: string; password: string }>;
258
- realm?: string;
259
- excludePaths?: string[];
260
- };
261
- jwtAuth?: {
262
- enabled: boolean;
263
- secret: string;
264
- algorithm?: string;
265
- issuer?: string;
266
- audience?: string;
267
- expiresIn?: number;
268
- excludePaths?: string[];
269
- };
270
- ipAllowList?: string[];
271
- ipBlockList?: string[];
272
- }
276
+ // IRouteSecurity is defined above - unified definition is used for all routes
273
277
 
274
278
  /**
275
279
  * CORS configuration for a route
@@ -289,11 +289,11 @@ export class RouteConnectionHandler {
289
289
  // Check default security settings
290
290
  const defaultSecuritySettings = this.settings.defaults?.security;
291
291
  if (defaultSecuritySettings) {
292
- if (defaultSecuritySettings.allowedIps && defaultSecuritySettings.allowedIps.length > 0) {
292
+ if (defaultSecuritySettings.ipAllowList && defaultSecuritySettings.ipAllowList.length > 0) {
293
293
  const isAllowed = this.securityManager.isIPAuthorized(
294
294
  remoteIP,
295
- defaultSecuritySettings.allowedIps,
296
- defaultSecuritySettings.blockedIps || []
295
+ defaultSecuritySettings.ipAllowList,
296
+ defaultSecuritySettings.ipBlockList || []
297
297
  );
298
298
 
299
299
  if (!isAllowed) {
@@ -213,8 +213,8 @@ export class RouteManager extends plugins.EventEmitter {
213
213
  }
214
214
 
215
215
  // Check blocked IPs first
216
- if (security.blockedIps && security.blockedIps.length > 0) {
217
- for (const pattern of security.blockedIps) {
216
+ if (security.ipBlockList && security.ipBlockList.length > 0) {
217
+ for (const pattern of security.ipBlockList) {
218
218
  if (this.matchIpPattern(pattern, clientIp)) {
219
219
  return false; // IP is blocked
220
220
  }
@@ -222,8 +222,8 @@ export class RouteManager extends plugins.EventEmitter {
222
222
  }
223
223
 
224
224
  // If there are allowed IPs, check them
225
- if (security.allowedIps && security.allowedIps.length > 0) {
226
- for (const pattern of security.allowedIps) {
225
+ if (security.ipAllowList && security.ipAllowList.length > 0) {
226
+ for (const pattern of security.ipAllowList) {
227
227
  if (this.matchIpPattern(pattern, clientIp)) {
228
228
  return true; // IP is allowed
229
229
  }
@@ -63,16 +63,15 @@ export class SecurityManager {
63
63
  }
64
64
 
65
65
  /**
66
- * Check if an IP is authorized using forwarding security rules
66
+ * Check if an IP is authorized using security rules
67
67
  *
68
68
  * This method is used to determine if an IP is allowed to connect, based on security
69
- * rules configured in the forwarding configuration. The allowed and blocked IPs are
70
- * typically derived from domain.forwarding.security.allowedIps and blockedIps through
71
- * DomainConfigManager.getEffectiveIPRules().
69
+ * rules configured in the route configuration. The allowed and blocked IPs are
70
+ * typically derived from route.security.ipAllowList and ipBlockList.
72
71
  *
73
72
  * @param ip - The IP address to check
74
- * @param allowedIPs - Array of allowed IP patterns from forwarding.security.allowedIps
75
- * @param blockedIPs - Array of blocked IP patterns from forwarding.security.blockedIps
73
+ * @param allowedIPs - Array of allowed IP patterns from security.ipAllowList
74
+ * @param blockedIPs - Array of blocked IP patterns from security.ipBlockList
76
75
  * @returns true if IP is authorized, false if blocked
77
76
  */
78
77
  public isIPAuthorized(ip: string, allowedIPs: string[], blockedIPs: string[] = []): boolean {
@@ -94,10 +93,10 @@ export class SecurityManager {
94
93
  * Check if the IP matches any of the glob patterns from security configuration
95
94
  *
96
95
  * This method checks IP addresses against glob patterns and handles IPv4/IPv6 normalization.
97
- * It's used to implement IP filtering based on the forwarding.security configuration.
96
+ * It's used to implement IP filtering based on the route.security configuration.
98
97
  *
99
98
  * @param ip - The IP address to check
100
- * @param patterns - Array of glob patterns from forwarding.security.allowedIps or blockedIps
99
+ * @param patterns - Array of glob patterns from security.ipAllowList or ipBlockList
101
100
  * @returns true if IP matches any pattern, false otherwise
102
101
  */
103
102
  private isGlobIPMatch(ip: string, patterns: string[]): boolean {