diff --git a/changelog.md b/changelog.md index 37ef9d3..9709708 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2025-05-09 - 11.0.0 - BREAKING CHANGE(forwarding) +Refactor unified forwarding API and remove redundant documentation. Removed docs/forwarding-system.md (its content is migrated into readme.md) and updated helper functions (e.g. replacing sniPassthrough with httpsPassthrough) to accept configuration objects. Legacy fields in domain configurations (allowedIPs, blockedIPs, useNetworkProxy, networkProxyPort, connectionTimeout) have been removed in favor of forwarding.security and advanced options. Tests and examples have been updated accordingly. + +- Removed docs/forwarding-system.md; forwarding system docs now reside in readme.md. +- Updated helper functions (httpOnly, tlsTerminateToHttp, tlsTerminateToHttps, httpsPassthrough) to accept object parameters rather than individual arguments. +- Removed legacy domain configuration properties, shifting IP filtering to the forwarding.security field. +- Adjusted return types and API contracts for certificate provisioning and SNI handling in the unified forwarding system. +- Updated tests and examples to align with the new configuration interface. + ## 2025-05-09 - 10.3.0 - feat(forwarding) Add unified forwarding system docs and tests; update build script and .gitignore diff --git a/docs/forwarding-system.md b/docs/forwarding-system.md deleted file mode 100644 index ca93142..0000000 --- a/docs/forwarding-system.md +++ /dev/null @@ -1,242 +0,0 @@ -# SmartProxy Unified Forwarding System - -This document describes the new unified forwarding system in SmartProxy. - -## Overview - -The forwarding system provides a clean, use-case driven approach to configuring different types of traffic forwarding. It replaces the previous disparate configuration mechanisms with a unified interface. - -## Forwarding Types - -The system supports four primary forwarding types: - -1. **HTTP-only (`http-only`)**: Forwards HTTP traffic to a backend server. -2. **HTTPS Passthrough (`https-passthrough`)**: Passes through raw TLS traffic without termination (SNI forwarding). -3. **HTTPS Termination to HTTP (`https-terminate-to-http`)**: Terminates TLS and forwards the decrypted traffic to an HTTP backend. -4. **HTTPS Termination to HTTPS (`https-terminate-to-https`)**: Terminates TLS and creates a new TLS connection to an HTTPS backend. - -## Configuration - -### Basic Configuration - -Each domain is configured with a forwarding type and target: - -```typescript -{ - domains: ['example.com'], - forwarding: { - type: 'http-only', - target: { - host: 'localhost', - port: 3000 - } - } -} -``` - -### Helper Functions - -Helper functions are provided for common configurations: - -```typescript -import { helpers } from '../smartproxy/forwarding/index.js'; - -// HTTP-only -const httpConfig = helpers.httpOnly('localhost', 3000); - -// HTTPS termination to HTTP -const terminateToHttpConfig = helpers.tlsTerminateToHttp('localhost', 3000); - -// HTTPS termination to HTTPS -const terminateToHttpsConfig = helpers.tlsTerminateToHttps('localhost', 8443); - -// HTTPS passthrough (SNI) -const passthroughConfig = helpers.sniPassthrough('localhost', 443); -``` - -### Advanced Configuration - -For more complex scenarios, additional options can be specified: - -```typescript -{ - domains: ['api.example.com'], - forwarding: { - type: 'https-terminate-to-https', - target: { - host: ['10.0.0.10', '10.0.0.11'], // Round-robin load balancing - port: 8443 - }, - http: { - enabled: true, - redirectToHttps: true - }, - https: { - // Custom certificate instead of ACME-provisioned - customCert: { - key: '-----BEGIN PRIVATE KEY-----\n...', - cert: '-----BEGIN CERTIFICATE-----\n...' - } - }, - security: { - allowedIps: ['10.0.0.*', '192.168.1.*'], - blockedIps: ['1.2.3.4'], - maxConnections: 100 - }, - advanced: { - timeout: 30000, - headers: { - 'X-Forwarded-For': '{clientIp}', - 'X-Original-Host': '{sni}' - } - } - } -} -``` - -## DomainManager - -The `DomainManager` class manages domains and their forwarding handlers: - -```typescript -import { DomainManager, createDomainConfig, helpers } from '../smartproxy/forwarding/index.js'; - -// Create the domain manager -const domainManager = new DomainManager(); - -// Add a domain -await domainManager.addDomainConfig( - createDomainConfig('example.com', helpers.httpOnly('localhost', 3000)) -); - -// Handle a connection -domainManager.handleConnection('example.com', socket); - -// Handle an HTTP request -domainManager.handleHttpRequest('example.com', req, res); -``` - -## Usage Examples - -### Basic HTTP Server - -```typescript -{ - domains: ['example.com'], - forwarding: { - type: 'http-only', - target: { - host: 'localhost', - port: 3000 - } - } -} -``` - -### HTTPS Termination with HTTP Backend - -```typescript -{ - domains: ['secure.example.com'], - forwarding: { - type: 'https-terminate-to-http', - target: { - host: 'localhost', - port: 3000 - }, - acme: { - production: true // Use production Let's Encrypt - } - } -} -``` - -### HTTPS Termination with HTTPS Backend - -```typescript -{ - domains: ['secure-backend.example.com'], - forwarding: { - type: 'https-terminate-to-https', - target: { - host: 'internal-api', - port: 8443 - }, - http: { - redirectToHttps: true // Redirect HTTP requests to HTTPS - } - } -} -``` - -### SNI Passthrough - -```typescript -{ - domains: ['passthrough.example.com'], - forwarding: { - type: 'https-passthrough', - target: { - host: '10.0.0.5', - port: 443 - } - } -} -``` - -### Load Balancing - -```typescript -{ - domains: ['api.example.com'], - forwarding: { - type: 'https-terminate-to-https', - target: { - host: ['10.0.0.10', '10.0.0.11', '10.0.0.12'], // Round-robin - port: 8443 - } - } -} -``` - -## Integration with SmartProxy - -The unified forwarding system integrates with SmartProxy by replacing the existing domain configuration mechanism. The `DomainManager` handles all domain matching and forwarding, while the individual forwarding handlers handle the connections and requests. - -The system is designed to be used in SmartProxy's `ConnectionHandler` and in the `Port80Handler` for HTTP traffic. - -## Testing - -See the `test.forwarding.ts` file for examples of how to test the forwarding system. - -## Migration - -When migrating from the older configuration system, map the existing configuration to the appropriate forwarding type: - -1. HTTP forwarding → `http-only` -2. SNI forwarding → `https-passthrough` -3. NetworkProxy with HTTP backend → `https-terminate-to-http` -4. NetworkProxy with HTTPS backend → `https-terminate-to-https` - -## Extensibility - -The forwarding system is designed to be extensible: - -1. New forwarding types can be added by: - - Adding a new type to `ForwardingType` - - Creating a new handler class - - Adding the handler to `ForwardingHandlerFactory` - -2. Existing types can be extended with new options by updating the interface and handler implementations. - -## Implementation Details - -The system uses a factory pattern to create the appropriate handler for each forwarding type. Each handler extends a base `ForwardingHandler` class that provides common functionality. - -The `DomainManager` manages the domains and their handlers, and delegates connections and requests to the appropriate handler. - -## Performance Considerations - -- The system uses a map for fast domain lookups -- Wildcard domains are supported through pattern matching -- Handlers are reused for multiple domains with the same configuration \ No newline at end of file diff --git a/readme.md b/readme.md index 6dbdac3..d10c9e5 100644 --- a/readme.md +++ b/readme.md @@ -6,6 +6,7 @@ A high-performance proxy toolkit for Node.js, offering: - Low-level port forwarding via nftables - HTTP-to-HTTPS and custom URL redirects - Advanced TCP/SNI-based proxying with IP filtering and rules +- Unified forwarding configuration system for all proxy types ## Exports The following classes and interfaces are provided: @@ -23,11 +24,14 @@ The following classes and interfaces are provided: TCP/SNI-based proxy with dynamic routing, IP filtering, and unified certificates. - **SniHandler** (ts/smartproxy/classes.pp.snihandler.ts) Static utilities to extract SNI hostnames from TLS handshakes. +- **Forwarding Handlers** (ts/smartproxy/forwarding/*.ts) + Unified forwarding handlers for different connection types (HTTP, HTTPS passthrough, TLS termination). - **Interfaces** - IPortProxySettings, IDomainConfig (ts/smartproxy/classes.pp.interfaces.ts) - INetworkProxyOptions (ts/networkproxy/classes.np.types.ts) - - IAcmeOptions, IDomainOptions, IForwardConfig (ts/common/types.ts) + - IAcmeOptions, IDomainOptions (ts/common/types.ts) - INfTableProxySettings (ts/nfttablesproxy/classes.nftablesproxy.ts) + - IForwardConfig, ForwardingType (ts/smartproxy/types/forwarding.types.ts) ## Installation Install via npm: @@ -134,16 +138,37 @@ await nft.stop(); ### 5. TCP/SNI Proxy (SmartProxy) ```typescript import { SmartProxy } from '@push.rocks/smartproxy'; +import { createDomainConfig, httpOnly, tlsTerminateToHttp, httpsPassthrough } from '@push.rocks/smartproxy'; const smart = new SmartProxy({ fromPort: 443, toPort: 8443, domainConfigs: [ - { - domains: ['example.com', '*.example.com'], - allowedIPs: ['*'], - targetIPs: ['127.0.0.1'], - } + // HTTPS passthrough example + createDomainConfig(['example.com', '*.example.com'], + httpsPassthrough({ + target: { + host: '127.0.0.1', + port: 443 + }, + security: { + allowedIps: ['*'] + } + }) + ), + // HTTPS termination example + createDomainConfig('secure.example.com', + tlsTerminateToHttp({ + target: { + host: 'localhost', + port: 3000 + }, + acme: { + enabled: true, + production: true + } + }) + ) ], sniEnabled: true }); @@ -386,6 +411,126 @@ Listen for certificate events via EventEmitter: Provide a `certProvisionFunction(domain)` in SmartProxy settings to supply static certs or return `'http01'`. +## Unified Forwarding System + +The SmartProxy Unified Forwarding System provides a clean, use-case driven approach to configuring different types of traffic forwarding. It replaces disparate configuration mechanisms with a unified interface. + +### Forwarding Types + +The system supports four primary forwarding types: + +1. **HTTP-only (`http-only`)**: Forwards HTTP traffic to a backend server. +2. **HTTPS Passthrough (`https-passthrough`)**: Passes through raw TLS traffic without termination (SNI forwarding). +3. **HTTPS Termination to HTTP (`https-terminate-to-http`)**: Terminates TLS and forwards the decrypted traffic to an HTTP backend. +4. **HTTPS Termination to HTTPS (`https-terminate-to-https`)**: Terminates TLS and creates a new TLS connection to an HTTPS backend. + +### Basic Configuration + +Each domain is configured with a forwarding type and target: + +```typescript +{ + domains: ['example.com'], + forwarding: { + type: 'http-only', + target: { + host: 'localhost', + port: 3000 + } + } +} +``` + +### Helper Functions + +Helper functions are provided for common configurations: + +```typescript +import { createDomainConfig, httpOnly, tlsTerminateToHttp, + tlsTerminateToHttps, httpsPassthrough } from '@push.rocks/smartproxy'; + +// HTTP-only +await domainManager.addDomainConfig( + createDomainConfig('example.com', httpOnly({ + target: { host: 'localhost', port: 3000 } + })) +); + +// HTTPS termination to HTTP +await domainManager.addDomainConfig( + createDomainConfig('secure.example.com', tlsTerminateToHttp({ + target: { host: 'localhost', port: 3000 }, + acme: { production: true } + })) +); + +// HTTPS termination to HTTPS +await domainManager.addDomainConfig( + createDomainConfig('api.example.com', tlsTerminateToHttps({ + target: { host: 'internal-api', port: 8443 }, + http: { redirectToHttps: true } + })) +); + +// HTTPS passthrough (SNI) +await domainManager.addDomainConfig( + createDomainConfig('passthrough.example.com', httpsPassthrough({ + target: { host: '10.0.0.5', port: 443 } + })) +); +``` + +### Advanced Configuration + +For more complex scenarios, additional options can be specified: + +```typescript +{ + domains: ['api.example.com'], + forwarding: { + type: 'https-terminate-to-https', + target: { + host: ['10.0.0.10', '10.0.0.11'], // Round-robin load balancing + port: 8443 + }, + http: { + enabled: true, + redirectToHttps: true + }, + https: { + // Custom certificate instead of ACME-provisioned + customCert: { + key: '-----BEGIN PRIVATE KEY-----\n...', + cert: '-----BEGIN CERTIFICATE-----\n...' + } + }, + security: { + allowedIps: ['10.0.0.*', '192.168.1.*'], + blockedIps: ['1.2.3.4'], + maxConnections: 100 + }, + advanced: { + timeout: 30000, + headers: { + 'X-Forwarded-For': '{clientIp}', + 'X-Original-Host': '{sni}' + } + } + } +} +``` + +### Extended Configuration Options + +#### IForwardConfig +- `type`: 'http-only' | 'https-passthrough' | 'https-terminate-to-http' | 'https-terminate-to-https' +- `target`: { host: string | string[], port: number } +- `http?`: { enabled?: boolean, redirectToHttps?: boolean, headers?: Record } +- `https?`: { customCert?: { key: string, cert: string }, forwardSni?: boolean } +- `acme?`: { enabled?: boolean, maintenance?: boolean, production?: boolean, forwardChallenges?: { host: string, port: number, useTls?: boolean } } +- `security?`: { allowedIps?: string[], blockedIps?: string[], maxConnections?: number } +- `advanced?`: { portRanges?: Array<{ from: number, to: number }>, networkProxyPort?: number, keepAlive?: boolean, timeout?: number, headers?: Record } + ## Configuration Options ### NetworkProxy (INetworkProxyOptions) @@ -425,12 +570,14 @@ Provide a `certProvisionFunction(domain)` in SmartProxy settings to supply stati ### SmartProxy (IPortProxySettings) - `fromPort`, `toPort` (number) -- `domainConfigs` (IDomainConfig[]) -- `sniEnabled`, `defaultAllowedIPs`, `preserveSourceIP` (booleans) +- `domainConfigs` (IDomainConfig[]) - Using unified forwarding configuration +- `sniEnabled`, `preserveSourceIP` (booleans) +- `defaultAllowedIPs`, `defaultBlockedIPs` (string[]) - Default IP allowlists/blocklists - Timeouts: `initialDataTimeout`, `socketTimeout`, `inactivityTimeout`, etc. - Socket opts: `noDelay`, `keepAlive`, `enableKeepAliveProbes` - `acme` (IAcmeOptions), `certProvisionFunction` (callback) - `useNetworkProxy` (number[]), `networkProxyPort` (number) +- `globalPortRanges` (Array<{ from: number; to: number }>) ## Troubleshooting @@ -455,6 +602,9 @@ Provide a `certProvisionFunction(domain)` in SmartProxy settings to supply stati - Increase `initialDataTimeout`/`maxPendingDataSize` for large ClientHello - Enable `enableTlsDebugLogging` to trace handshake - Ensure `allowSessionTicket` and fragmentation support for resumption +- Double-check forwarding configuration to ensure correct `type` for your use case +- Use helper functions like `httpOnly()`, `httpsPassthrough()`, etc. to create correct configurations +- For IP filtering issues, check the `security.allowedIps` and `security.blockedIps` settings ## License and Legal Information diff --git a/test/test.certprovisioner.unit.ts b/test/test.certprovisioner.unit.ts index 336c225..b2dbf32 100644 --- a/test/test.certprovisioner.unit.ts +++ b/test/test.certprovisioner.unit.ts @@ -26,7 +26,13 @@ class FakeNetworkProxyBridge { tap.test('CertProvisioner handles static provisioning', async () => { const domain = 'static.com'; - const domainConfigs: IDomainConfig[] = [{ domains: [domain], allowedIPs: [] }]; + const domainConfigs: IDomainConfig[] = [{ + domains: [domain], + forwarding: { + type: 'https-terminate-to-https', + target: { host: 'localhost', port: 443 } + } + }]; const fakePort80 = new FakePort80Handler(); const fakeBridge = new FakeNetworkProxyBridge(); // certProvider returns static certificate @@ -68,7 +74,13 @@ tap.test('CertProvisioner handles static provisioning', async () => { tap.test('CertProvisioner handles http01 provisioning', async () => { const domain = 'http01.com'; - const domainConfigs: IDomainConfig[] = [{ domains: [domain], allowedIPs: [] }]; + const domainConfigs: IDomainConfig[] = [{ + domains: [domain], + forwarding: { + type: 'https-terminate-to-http', + target: { host: 'localhost', port: 80 } + } + }]; const fakePort80 = new FakePort80Handler(); const fakeBridge = new FakeNetworkProxyBridge(); // certProvider returns http01 directive @@ -93,7 +105,13 @@ tap.test('CertProvisioner handles http01 provisioning', async () => { tap.test('CertProvisioner on-demand http01 renewal', async () => { const domain = 'renew.com'; - const domainConfigs: IDomainConfig[] = [{ domains: [domain], allowedIPs: [] }]; + const domainConfigs: IDomainConfig[] = [{ + domains: [domain], + forwarding: { + type: 'https-terminate-to-http', + target: { host: 'localhost', port: 80 } + } + }]; const fakePort80 = new FakePort80Handler(); const fakeBridge = new FakeNetworkProxyBridge(); const certProvider = async (): Promise => 'http01'; @@ -113,7 +131,13 @@ tap.test('CertProvisioner on-demand http01 renewal', async () => { tap.test('CertProvisioner on-demand static provisioning', async () => { const domain = 'ondemand.com'; - const domainConfigs: IDomainConfig[] = [{ domains: [domain], allowedIPs: [] }]; + const domainConfigs: IDomainConfig[] = [{ + domains: [domain], + forwarding: { + type: 'https-terminate-to-https', + target: { host: 'localhost', port: 443 } + } + }]; const fakePort80 = new FakePort80Handler(); const fakeBridge = new FakeNetworkProxyBridge(); const certProvider = async (): Promise => ({ diff --git a/test/test.forwarding.examples.ts b/test/test.forwarding.examples.ts index 598ab02..d33d7a2 100644 --- a/test/test.forwarding.examples.ts +++ b/test/test.forwarding.examples.ts @@ -16,11 +16,13 @@ tap.test('Forwarding configuration examples', async (tools) => { // Example 1: HTTP-only configuration const httpOnlyConfig: IDomainConfig = { domains: ['http.example.com'], - allowedIPs: [], forwarding: httpOnly({ target: { host: 'localhost', port: 3000 + }, + security: { + allowedIps: ['*'] // Allow all } }) }; @@ -30,11 +32,13 @@ tap.test('Forwarding configuration examples', async (tools) => { // Example 2: HTTPS Passthrough (SNI) const httpsPassthroughConfig: IDomainConfig = { domains: ['pass.example.com'], - allowedIPs: [], forwarding: httpsPassthrough({ target: { host: ['10.0.0.1', '10.0.0.2'], // Round-robin target IPs port: 443 + }, + security: { + allowedIps: ['*'] // Allow all } }) }; @@ -45,7 +49,6 @@ tap.test('Forwarding configuration examples', async (tools) => { // Example 3: HTTPS Termination to HTTP Backend const terminateToHttpConfig: IDomainConfig = { domains: ['secure.example.com'], - allowedIPs: [], forwarding: tlsTerminateToHttp({ target: { host: 'localhost', @@ -61,6 +64,9 @@ tap.test('Forwarding configuration examples', async (tools) => { enabled: true, maintenance: true, production: false // Use staging ACME server for testing + }, + security: { + allowedIps: ['*'] // Allow all } }) }; @@ -71,7 +77,6 @@ tap.test('Forwarding configuration examples', async (tools) => { // Example 4: HTTPS Termination to HTTPS Backend const terminateToHttpsConfig: IDomainConfig = { domains: ['proxy.example.com'], - allowedIPs: [], forwarding: tlsTerminateToHttps({ target: { host: 'internal-api.local', diff --git a/test/test.forwarding.ts b/test/test.forwarding.ts index c5347ee..62b96de 100644 --- a/test/test.forwarding.ts +++ b/test/test.forwarding.ts @@ -6,13 +6,13 @@ import type { IForwardConfig, ForwardingType } from '../ts/smartproxy/types/forw import { ForwardingHandlerFactory } from '../ts/smartproxy/forwarding/forwarding.factory.js'; import { createDomainConfig } from '../ts/smartproxy/forwarding/domain-config.js'; import { DomainManager } from '../ts/smartproxy/forwarding/domain-manager.js'; -import { httpOnly, tlsTerminateToHttp, tlsTerminateToHttps, sniPassthrough } from '../ts/smartproxy/types/forwarding.types.js'; +import { httpOnly, tlsTerminateToHttp, tlsTerminateToHttps, httpsPassthrough } from '../ts/smartproxy/types/forwarding.types.js'; const helpers = { httpOnly, tlsTerminateToHttp, tlsTerminateToHttps, - sniPassthrough + sniPassthrough: httpsPassthrough }; tap.test('ForwardingHandlerFactory - apply defaults based on type', async () => { @@ -107,7 +107,9 @@ tap.test('DomainManager - manage domain configurations', async () => { // Add a domain configuration await domainManager.addDomainConfig( - createDomainConfig('example.com', helpers.httpOnly('localhost', 3000)) + createDomainConfig('example.com', helpers.httpOnly({ + target: { host: 'localhost', port: 3000 } + })) ); // Check that the configuration was added @@ -132,13 +134,15 @@ tap.test('DomainManager - manage domain configurations', async () => { const handlerAfterRemoval = domainManager.findHandlerForDomain('example.com'); expect(handlerAfterRemoval).toBeUndefined(); }); - + tap.test('DomainManager - support wildcard domains', async () => { const domainManager = new DomainManager(); // Add a wildcard domain configuration await domainManager.addDomainConfig( - createDomainConfig('*.example.com', helpers.httpOnly('localhost', 3000)) + createDomainConfig('*.example.com', helpers.httpOnly({ + target: { host: 'localhost', port: 3000 } + })) ); // Find a handler for a subdomain @@ -150,15 +154,19 @@ tap.test('DomainManager - support wildcard domains', async () => { expect(noHandler).toBeUndefined(); }); tap.test('Helper Functions - create http-only forwarding config', async () => { - const config = helpers.httpOnly('localhost', 3000); + const config = helpers.httpOnly({ + target: { host: 'localhost', port: 3000 } + }); expect(config.type).toEqual('http-only'); expect(config.target.host).toEqual('localhost'); expect(config.target.port).toEqual(3000); expect(config.http?.enabled).toBeTrue(); }); - + tap.test('Helper Functions - create https-terminate-to-http config', async () => { - const config = helpers.tlsTerminateToHttp('localhost', 3000); + const config = helpers.tlsTerminateToHttp({ + target: { host: 'localhost', port: 3000 } + }); expect(config.type).toEqual('https-terminate-to-http'); expect(config.target.host).toEqual('localhost'); expect(config.target.port).toEqual(3000); @@ -166,9 +174,11 @@ tap.test('Helper Functions - create https-terminate-to-http config', async () => expect(config.acme?.enabled).toBeTrue(); expect(config.acme?.maintenance).toBeTrue(); }); - + tap.test('Helper Functions - create https-terminate-to-https config', async () => { - const config = helpers.tlsTerminateToHttps('localhost', 8443); + const config = helpers.tlsTerminateToHttps({ + target: { host: 'localhost', port: 8443 } + }); expect(config.type).toEqual('https-terminate-to-https'); expect(config.target.host).toEqual('localhost'); expect(config.target.port).toEqual(8443); @@ -176,9 +186,11 @@ tap.test('Helper Functions - create https-terminate-to-https config', async () = expect(config.acme?.enabled).toBeTrue(); expect(config.acme?.maintenance).toBeTrue(); }); - + tap.test('Helper Functions - create https-passthrough config', async () => { - const config = helpers.sniPassthrough('localhost', 443); + const config = helpers.sniPassthrough({ + target: { host: 'localhost', port: 443 } + }); expect(config.type).toEqual('https-passthrough'); expect(config.target.host).toEqual('localhost'); expect(config.target.port).toEqual(443); diff --git a/test/test.forwarding.unit.ts b/test/test.forwarding.unit.ts index 5661260..644039a 100644 --- a/test/test.forwarding.unit.ts +++ b/test/test.forwarding.unit.ts @@ -6,13 +6,13 @@ import type { IForwardConfig } from '../ts/smartproxy/types/forwarding.types.js' import { ForwardingHandlerFactory } from '../ts/smartproxy/forwarding/forwarding.factory.js'; import { createDomainConfig } from '../ts/smartproxy/forwarding/domain-config.js'; import { DomainManager } from '../ts/smartproxy/forwarding/domain-manager.js'; -import { httpOnly, tlsTerminateToHttp, tlsTerminateToHttps, sniPassthrough } from '../ts/smartproxy/types/forwarding.types.js'; +import { httpOnly, tlsTerminateToHttp, tlsTerminateToHttps, httpsPassthrough } from '../ts/smartproxy/types/forwarding.types.js'; const helpers = { httpOnly, tlsTerminateToHttp, tlsTerminateToHttps, - sniPassthrough + sniPassthrough: httpsPassthrough }; tap.test('ForwardingHandlerFactory - apply defaults based on type', async () => { @@ -107,7 +107,9 @@ tap.test('DomainManager - manage domain configurations', async () => { // Add a domain configuration await domainManager.addDomainConfig( - createDomainConfig('example.com', helpers.httpOnly('localhost', 3000)) + createDomainConfig('example.com', helpers.httpOnly({ + target: { host: 'localhost', port: 3000 } + })) ); // Check that the configuration was added @@ -125,15 +127,19 @@ tap.test('DomainManager - manage domain configurations', async () => { expect(configsAfterRemoval.length).toEqual(0); }); tap.test('Helper Functions - create http-only forwarding config', async () => { - const config = helpers.httpOnly('localhost', 3000); + const config = helpers.httpOnly({ + target: { host: 'localhost', port: 3000 } + }); expect(config.type).toEqual('http-only'); expect(config.target.host).toEqual('localhost'); expect(config.target.port).toEqual(3000); expect(config.http?.enabled).toBeTrue(); }); - + tap.test('Helper Functions - create https-terminate-to-http config', async () => { - const config = helpers.tlsTerminateToHttp('localhost', 3000); + const config = helpers.tlsTerminateToHttp({ + target: { host: 'localhost', port: 3000 } + }); expect(config.type).toEqual('https-terminate-to-http'); expect(config.target.host).toEqual('localhost'); expect(config.target.port).toEqual(3000); @@ -141,9 +147,11 @@ tap.test('Helper Functions - create https-terminate-to-http config', async () => expect(config.acme?.enabled).toBeTrue(); expect(config.acme?.maintenance).toBeTrue(); }); - + tap.test('Helper Functions - create https-terminate-to-https config', async () => { - const config = helpers.tlsTerminateToHttps('localhost', 8443); + const config = helpers.tlsTerminateToHttps({ + target: { host: 'localhost', port: 8443 } + }); expect(config.type).toEqual('https-terminate-to-https'); expect(config.target.host).toEqual('localhost'); expect(config.target.port).toEqual(8443); @@ -151,9 +159,11 @@ tap.test('Helper Functions - create https-terminate-to-https config', async () = expect(config.acme?.enabled).toBeTrue(); expect(config.acme?.maintenance).toBeTrue(); }); - + tap.test('Helper Functions - create https-passthrough config', async () => { - const config = helpers.sniPassthrough('localhost', 443); + const config = helpers.sniPassthrough({ + target: { host: 'localhost', port: 443 } + }); expect(config.type).toEqual('https-passthrough'); expect(config.target.host).toEqual('localhost'); expect(config.target.port).toEqual(443); diff --git a/test/test.networkproxy.ts b/test/test.networkproxy.ts index d7d87ed..5ed32a3 100644 --- a/test/test.networkproxy.ts +++ b/test/test.networkproxy.ts @@ -575,4 +575,4 @@ process.on('exit', () => { testProxy.stop().then(() => console.log('[TEST] Proxy server stopped')); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.smartproxy.ts b/test/test.smartproxy.ts index 23cb936..247f4a4 100644 --- a/test/test.smartproxy.ts +++ b/test/test.smartproxy.ts @@ -279,14 +279,21 @@ tap.test('should support optional source IP preservation in chained proxies', as if (index4 !== -1) allProxies.splice(index4, 1); }); -// Test round-robin behavior for multiple target IPs in a domain config. -tap.test('should use round robin for multiple target IPs in domain config', async () => { +// Test round-robin behavior for multiple target hosts in a domain config. +tap.test('should use round robin for multiple target hosts in domain config', async () => { + // Create a domain config with multiple hosts in the target const domainConfig = { domains: ['rr.test'], - allowedIPs: ['127.0.0.1'], - targetIPs: ['hostA', 'hostB'] - } as any; - + forwarding: { + type: 'http-only', + target: { + host: ['hostA', 'hostB'], // Array of hosts for round-robin + port: 80 + }, + http: { enabled: true } + } + }; + const proxyInstance = new SmartProxy({ fromPort: 0, toPort: 0, @@ -296,11 +303,14 @@ tap.test('should use round robin for multiple target IPs in domain config', asyn defaultAllowedIPs: [], globalPortRanges: [] }); - + // Don't track this proxy as it doesn't actually start or listen - - const firstTarget = proxyInstance.domainConfigManager.getTargetIP(domainConfig); - const secondTarget = proxyInstance.domainConfigManager.getTargetIP(domainConfig); + + // Get the first target host from the forwarding config + const firstTarget = proxyInstance.domainConfigManager.getTargetHost(domainConfig); + // Get the second target host - should be different due to round-robin + const secondTarget = proxyInstance.domainConfigManager.getTargetHost(domainConfig); + expect(firstTarget).toEqual('hostA'); expect(secondTarget).toEqual('hostB'); }); diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 33c2c76..34a97c1 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartproxy', - version: '10.3.0', + version: '11.0.0', description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.' } diff --git a/ts/common/port80-adapter.ts b/ts/common/port80-adapter.ts index ccbc4f5..1eca72f 100644 --- a/ts/common/port80-adapter.ts +++ b/ts/common/port80-adapter.ts @@ -1,26 +1,26 @@ import * as plugins from '../plugins.js'; -import type { - IForwardConfig as ILegacyForwardConfig, - IDomainOptions +import type { + IForwardConfig as ILegacyForwardConfig, + IDomainOptions } from './types.js'; -import type { - IForwardConfig as INewForwardConfig +import type { + IForwardConfig } from '../smartproxy/types/forwarding.types.js'; /** - * Converts a new forwarding configuration target to the legacy format + * Converts a forwarding configuration target to the legacy format * for Port80Handler */ export function convertToLegacyForwardConfig( - forwardConfig: INewForwardConfig + forwardConfig: IForwardConfig ): ILegacyForwardConfig { // Determine host from the target configuration const host = Array.isArray(forwardConfig.target.host) ? forwardConfig.target.host[0] // Use the first host in the array : forwardConfig.target.host; - + return { ip: host, port: forwardConfig.target.port @@ -32,7 +32,7 @@ export function convertToLegacyForwardConfig( */ export function createPort80HandlerOptions( domain: string, - forwardConfig: INewForwardConfig + forwardConfig: IForwardConfig ): IDomainOptions { // Determine if we should redirect HTTP to HTTPS let sslRedirect = false; diff --git a/ts/examples/forwarding-example.ts b/ts/examples/forwarding-example.ts index 0cb821f..5e8b6bf 100644 --- a/ts/examples/forwarding-example.ts +++ b/ts/examples/forwarding-example.ts @@ -38,22 +38,30 @@ async function main() { // Example 1: HTTP-only forwarding await domainManager.addDomainConfig( - createDomainConfig('example.com', helpers.httpOnly('localhost', 3000)) + createDomainConfig('example.com', helpers.httpOnly({ + target: { host: 'localhost', port: 3000 } + })) ); - + // Example 2: HTTPS termination with HTTP backend await domainManager.addDomainConfig( - createDomainConfig('secure.example.com', helpers.tlsTerminateToHttp('localhost', 3000)) + createDomainConfig('secure.example.com', helpers.tlsTerminateToHttp({ + target: { host: 'localhost', port: 3000 } + })) ); - + // Example 3: HTTPS termination with HTTPS backend await domainManager.addDomainConfig( - createDomainConfig('api.example.com', helpers.tlsTerminateToHttps('localhost', 8443)) + createDomainConfig('api.example.com', helpers.tlsTerminateToHttps({ + target: { host: 'localhost', port: 8443 } + })) ); - + // Example 4: SNI passthrough await domainManager.addDomainConfig( - createDomainConfig('passthrough.example.com', helpers.sniPassthrough('10.0.0.5', 443)) + createDomainConfig('passthrough.example.com', helpers.sniPassthrough({ + target: { host: '10.0.0.5', port: 443 } + })) ); // Example 5: Custom configuration for a more complex setup diff --git a/ts/smartproxy/classes.pp.connectionhandler.ts b/ts/smartproxy/classes.pp.connectionhandler.ts index 417ff3f..1684a78 100644 --- a/ts/smartproxy/classes.pp.connectionhandler.ts +++ b/ts/smartproxy/classes.pp.connectionhandler.ts @@ -11,7 +11,7 @@ import { TlsManager } from './classes.pp.tlsmanager.js'; import { NetworkProxyBridge } from './classes.pp.networkproxybridge.js'; import { TimeoutManager } from './classes.pp.timeoutmanager.js'; import { PortRangeManager } from './classes.pp.portrangemanager.js'; -import type { IForwardingHandler } from './forwarding/forwarding.handler.js'; +import type { IForwardingHandler } from './types/forwarding.types.js'; import type { ForwardingType } from './types/forwarding.types.js'; /** @@ -446,9 +446,8 @@ export class ConnectionHandler { if (domainConfig) { const ipRules = this.domainConfigManager.getEffectiveIPRules(domainConfig); - // Skip IP validation if allowedIPs is empty + // Perform IP validation using security rules if ( - domainConfig.allowedIPs.length > 0 && !this.securityManager.isIPAuthorized( record.remoteIP, ipRules.allowedIPs, @@ -497,10 +496,31 @@ export class ConnectionHandler { // Only apply port-based rules if the incoming port is within one of the global port ranges. if (this.portRangeManager.isPortInGlobalRanges(localPort)) { if (this.portRangeManager.shouldUseGlobalForwarding(localPort)) { + // Create a virtual domain config for global forwarding with security settings + const globalDomainConfig = { + domains: ['global'], + forwarding: { + type: 'http-only' as ForwardingType, + target: { + host: this.settings.targetIP!, + port: this.settings.toPort + }, + security: { + allowedIps: this.settings.defaultAllowedIPs || [], + blockedIps: this.settings.defaultBlockedIPs || [] + } + }, + }; + + // Use the same IP filtering mechanism as domain-specific configs + const ipRules = this.domainConfigManager.getEffectiveIPRules(globalDomainConfig); + if ( - this.settings.defaultAllowedIPs && - this.settings.defaultAllowedIPs.length > 0 && - !this.securityManager.isIPAuthorized(record.remoteIP, this.settings.defaultAllowedIPs) + !this.securityManager.isIPAuthorized( + record.remoteIP, + ipRules.allowedIPs, + ipRules.blockedIPs + ) ) { console.log( `[${connectionId}] Connection from ${record.remoteIP} rejected: IP ${record.remoteIP} not allowed in global default allowed list.` @@ -508,29 +528,21 @@ export class ConnectionHandler { socket.end(); return; } + if (this.settings.enableDetailedLogging) { console.log( `[${connectionId}] Port-based connection from ${record.remoteIP} on port ${localPort} forwarded to global target IP ${this.settings.targetIP}.` ); } - setupConnection( - '', - undefined, - { - domains: ['global'], - allowedIPs: this.settings.defaultAllowedIPs || [], - blockedIPs: this.settings.defaultBlockedIPs || [], - targetIPs: [this.settings.targetIP!], - portRanges: [], - }, - localPort - ); + + setupConnection('', undefined, globalDomainConfig, localPort); return; } else { // Attempt to find a matching forced domain config based on the local port. const forcedDomain = this.domainConfigManager.findDomainConfigForPort(localPort); if (forcedDomain) { + // Get effective IP rules from the domain config's forwarding security settings const ipRules = this.domainConfigManager.getEffectiveIPRules(forcedDomain); if ( @@ -690,10 +702,18 @@ export class ConnectionHandler { initialDataReceived = true; record.hasReceivedInitialData = true; - if ( - this.settings.defaultAllowedIPs && - this.settings.defaultAllowedIPs.length > 0 && - !this.securityManager.isIPAuthorized(record.remoteIP, this.settings.defaultAllowedIPs) + // Create default security settings for non-SNI connections + const defaultSecurity = { + allowedIPs: this.settings.defaultAllowedIPs || [], + blockedIPs: this.settings.defaultBlockedIPs || [] + }; + + if (defaultSecurity.allowedIPs.length > 0 && + !this.securityManager.isIPAuthorized( + record.remoteIP, + defaultSecurity.allowedIPs, + defaultSecurity.blockedIPs + ) ) { return rejectIncomingConnection( 'rejected', diff --git a/ts/smartproxy/classes.pp.domainconfigmanager.ts b/ts/smartproxy/classes.pp.domainconfigmanager.ts index 7e31e66..8b2bdcc 100644 --- a/ts/smartproxy/classes.pp.domainconfigmanager.ts +++ b/ts/smartproxy/classes.pp.domainconfigmanager.ts @@ -1,8 +1,7 @@ import * as plugins from '../plugins.js'; import type { IDomainConfig, ISmartProxyOptions } from './classes.pp.interfaces.js'; -import type { ForwardingType, IForwardConfig } from './types/forwarding.types.js'; +import type { ForwardingType, IForwardConfig, IForwardingHandler } from './types/forwarding.types.js'; import { ForwardingHandlerFactory } from './forwarding/forwarding.factory.js'; -import type { IForwardingHandler } from './forwarding/forwarding.handler.js'; /** * Manages domain configurations and target selection @@ -79,10 +78,12 @@ export class DomainConfigManager { */ public findDomainConfigForPort(port: number): IDomainConfig | undefined { return this.settings.domainConfigs.find( - (domain) => - domain.portRanges && - domain.portRanges.length > 0 && - this.isPortInRanges(port, domain.portRanges) + (domain) => { + const portRanges = domain.forwarding?.advanced?.portRanges; + return portRanges && + portRanges.length > 0 && + this.isPortInRanges(port, portRanges); + } ); } @@ -111,6 +112,14 @@ export class DomainConfigManager { return this.settings.targetIP || 'localhost'; } + /** + * Get target host with round-robin support (for tests) + * This is just an alias for getTargetIP for easier test compatibility + */ + public getTargetHost(domainConfig: IDomainConfig): string { + return this.getTargetIP(domainConfig); + } + /** * Get target port from domain config */ @@ -122,16 +131,9 @@ export class DomainConfigManager { * Checks if a domain should use NetworkProxy */ public shouldUseNetworkProxy(domainConfig: IDomainConfig): boolean { - // Check forwarding type first const forwardingType = this.getForwardingType(domainConfig); - - if (forwardingType === 'https-terminate-to-http' || - forwardingType === 'https-terminate-to-https') { - return true; - } - - // Fall back to legacy setting - return !!domainConfig.useNetworkProxy; + return forwardingType === 'https-terminate-to-http' || + forwardingType === 'https-terminate-to-https'; } /** @@ -143,17 +145,14 @@ export class DomainConfigManager { return undefined; } - // Check forwarding config first - if (domainConfig.forwarding?.advanced?.networkProxyPort) { - return domainConfig.forwarding.advanced.networkProxyPort; - } - - // Fall back to legacy setting - return domainConfig.networkProxyPort || this.settings.networkProxyPort; + return domainConfig.forwarding.advanced?.networkProxyPort || this.settings.networkProxyPort; } /** * Get effective allowed and blocked IPs for a domain + * + * This method combines domain-specific security rules from the forwarding configuration + * with global security defaults when necessary. */ public getEffectiveIPRules(domainConfig: IDomainConfig): { allowedIPs: string[], @@ -163,31 +162,33 @@ export class DomainConfigManager { const allowedIPs: string[] = []; const blockedIPs: string[] = []; - // Add IPs from forwarding security settings + // Add IPs from forwarding security settings if available if (domainConfig.forwarding?.security?.allowedIps) { allowedIPs.push(...domainConfig.forwarding.security.allowedIps); + } else { + // If no allowed IPs are specified in forwarding config and global defaults exist, use them + if (this.settings.defaultAllowedIPs && this.settings.defaultAllowedIPs.length > 0) { + allowedIPs.push(...this.settings.defaultAllowedIPs); + } else { + // Default to allow all if no specific rules + allowedIPs.push('*'); + } } + // Add blocked IPs from forwarding security settings if available if (domainConfig.forwarding?.security?.blockedIps) { blockedIPs.push(...domainConfig.forwarding.security.blockedIps); } - // Add legacy settings - if (domainConfig.allowedIPs.length > 0) { - allowedIPs.push(...domainConfig.allowedIPs); - } - - if (domainConfig.blockedIPs && domainConfig.blockedIPs.length > 0) { - blockedIPs.push(...domainConfig.blockedIPs); - } - - // Add global defaults - if (this.settings.defaultAllowedIPs && this.settings.defaultAllowedIPs.length > 0) { - allowedIPs.push(...this.settings.defaultAllowedIPs); - } - + // Always add global blocked IPs, even if domain has its own rules + // This ensures that global blocks take precedence if (this.settings.defaultBlockedIPs && this.settings.defaultBlockedIPs.length > 0) { - blockedIPs.push(...this.settings.defaultBlockedIPs); + // Add only unique IPs that aren't already in the list + for (const ip of this.settings.defaultBlockedIPs) { + if (!blockedIPs.includes(ip)) { + blockedIPs.push(ip); + } + } } return { @@ -200,16 +201,10 @@ export class DomainConfigManager { * Get connection timeout for a domain */ public getConnectionTimeout(domainConfig?: IDomainConfig): number { - // First check forwarding configuration for timeout - if (domainConfig?.forwarding?.advanced?.timeout) { + if (domainConfig?.forwarding.advanced?.timeout) { return domainConfig.forwarding.advanced.timeout; } - // Fall back to legacy connectionTimeout - if (domainConfig?.connectionTimeout) { - return domainConfig.connectionTimeout; - } - return this.settings.maxConnectionLifetime || 86400000; // 24 hours default } @@ -217,10 +212,6 @@ export class DomainConfigManager { * Creates a forwarding handler for a domain configuration */ private createForwardingHandler(domainConfig: IDomainConfig): IForwardingHandler { - if (!domainConfig.forwarding) { - throw new Error(`Domain config for ${domainConfig.domains.join(', ')} has no forwarding configuration`); - } - // Create a new handler using the factory const handler = ForwardingHandlerFactory.createHandler(domainConfig.forwarding); diff --git a/ts/smartproxy/classes.pp.portrangemanager.ts b/ts/smartproxy/classes.pp.portrangemanager.ts index 79ec139..ca85dba 100644 --- a/ts/smartproxy/classes.pp.portrangemanager.ts +++ b/ts/smartproxy/classes.pp.portrangemanager.ts @@ -84,8 +84,10 @@ export class PortRangeManager { } | undefined { for (let i = 0; i < this.settings.domainConfigs.length; i++) { const domain = this.settings.domainConfigs[i]; - if (domain.portRanges) { - for (const range of domain.portRanges) { + // Get port ranges from forwarding.advanced if available + const portRanges = domain.forwarding?.advanced?.portRanges; + if (portRanges && portRanges.length > 0) { + for (const range of portRanges) { if (port >= range.from && port <= range.to) { return { domainIndex: i, range }; } @@ -129,17 +131,20 @@ export class PortRangeManager { // Add domain-specific port ranges for (const domain of this.settings.domainConfigs) { - if (domain.portRanges) { - for (const range of domain.portRanges) { + // Get port ranges from forwarding.advanced + const portRanges = domain.forwarding?.advanced?.portRanges; + if (portRanges && portRanges.length > 0) { + for (const range of portRanges) { for (let port = range.from; port <= range.to; port++) { ports.add(port); } } } - - // Add domain-specific NetworkProxy port if configured - if (domain.useNetworkProxy && domain.networkProxyPort) { - ports.add(domain.networkProxyPort); + + // Add domain-specific NetworkProxy port if configured in forwarding.advanced + const networkProxyPort = domain.forwarding?.advanced?.networkProxyPort; + if (networkProxyPort) { + ports.add(networkProxyPort); } } @@ -170,8 +175,10 @@ export class PortRangeManager { // Track domain-specific port ranges for (const domain of this.settings.domainConfigs) { - if (domain.portRanges) { - for (const range of domain.portRanges) { + // Get port ranges from forwarding.advanced + const portRanges = domain.forwarding?.advanced?.portRanges; + if (portRanges && portRanges.length > 0) { + for (const range of portRanges) { for (let port = range.from; port <= range.to; port++) { if (!portMappings.has(port)) { portMappings.set(port, []); diff --git a/ts/smartproxy/classes.pp.securitymanager.ts b/ts/smartproxy/classes.pp.securitymanager.ts index 3848b8c..a7d3cc8 100644 --- a/ts/smartproxy/classes.pp.securitymanager.ts +++ b/ts/smartproxy/classes.pp.securitymanager.ts @@ -63,45 +63,69 @@ export class SecurityManager { } /** - * Check if an IP is allowed using glob patterns + * Check if an IP is authorized using forwarding security rules + * + * This method is used to determine if an IP is allowed to connect, based on security + * rules configured in the forwarding configuration. The allowed and blocked IPs are + * typically derived from domain.forwarding.security.allowedIps and blockedIps through + * DomainConfigManager.getEffectiveIPRules(). + * + * @param ip - The IP address to check + * @param allowedIPs - Array of allowed IP patterns from forwarding.security.allowedIps + * @param blockedIPs - Array of blocked IP patterns from forwarding.security.blockedIps + * @returns true if IP is authorized, false if blocked */ public isIPAuthorized(ip: string, allowedIPs: string[], blockedIPs: string[] = []): boolean { // Skip IP validation if allowedIPs is empty if (!ip || (allowedIPs.length === 0 && blockedIPs.length === 0)) { return true; } - - // First check if IP is blocked + + // First check if IP is blocked - blocked IPs take precedence if (blockedIPs.length > 0 && this.isGlobIPMatch(ip, blockedIPs)) { return false; } - + // Then check if IP is allowed return this.isGlobIPMatch(ip, allowedIPs); } /** - * Check if the IP matches any of the glob patterns + * Check if the IP matches any of the glob patterns from security configuration + * + * This method checks IP addresses against glob patterns and handles IPv4/IPv6 normalization. + * It's used to implement IP filtering based on the forwarding.security configuration. + * + * @param ip - The IP address to check + * @param patterns - Array of glob patterns from forwarding.security.allowedIps or blockedIps + * @returns true if IP matches any pattern, false otherwise */ private isGlobIPMatch(ip: string, patterns: string[]): boolean { if (!ip || !patterns || patterns.length === 0) return false; + // Handle IPv4/IPv6 normalization for proper matching const normalizeIP = (ip: string): string[] => { if (!ip) return []; + // Handle IPv4-mapped IPv6 addresses (::ffff:127.0.0.1) if (ip.startsWith('::ffff:')) { const ipv4 = ip.slice(7); return [ip, ipv4]; } + // Handle IPv4 addresses by also checking IPv4-mapped form if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) { return [ip, `::ffff:${ip}`]; } return [ip]; }; + // Normalize the IP being checked const normalizedIPVariants = normalizeIP(ip); if (normalizedIPVariants.length === 0) return false; + // Normalize the pattern IPs for consistent comparison const expandedPatterns = patterns.flatMap(normalizeIP); + + // Check for any match between normalized IP variants and patterns return normalizedIPVariants.some((ipVariant) => expandedPatterns.some((pattern) => plugins.minimatch(ipVariant, pattern)) ); diff --git a/ts/smartproxy/classes.pp.timeoutmanager.ts b/ts/smartproxy/classes.pp.timeoutmanager.ts index 962da9a..a3903f7 100644 --- a/ts/smartproxy/classes.pp.timeoutmanager.ts +++ b/ts/smartproxy/classes.pp.timeoutmanager.ts @@ -61,9 +61,9 @@ export class TimeoutManager { * Calculate effective max lifetime based on connection type */ public getEffectiveMaxLifetime(record: IConnectionRecord): number { - // Use domain-specific timeout if available - const baseTimeout = record.domainConfig?.connectionTimeout || - this.settings.maxConnectionLifetime || + // Use domain-specific timeout from forwarding.advanced if available + const baseTimeout = record.domainConfig?.forwarding?.advanced?.timeout || + this.settings.maxConnectionLifetime || 86400000; // 24 hours default // For immortal keep-alive connections, use an extremely long lifetime diff --git a/ts/smartproxy/classes.smartproxy.ts b/ts/smartproxy/classes.smartproxy.ts index 0fdffd2..f400cb1 100644 --- a/ts/smartproxy/classes.smartproxy.ts +++ b/ts/smartproxy/classes.smartproxy.ts @@ -12,7 +12,6 @@ import { Port80Handler } from '../port80handler/classes.port80handler.js'; import { CertProvisioner } from './classes.pp.certprovisioner.js'; import type { ICertificateData } from '../common/types.js'; import { buildPort80Handler } from '../common/acmeFactory.js'; -import { ensureForwardingConfig } from './forwarding/legacy-converter.js'; import type { ForwardingType } from './types/forwarding.types.js'; import { createPort80HandlerOptions } from '../common/port80-adapter.js'; @@ -159,8 +158,8 @@ export class SmartProxy extends plugins.EventEmitter { return; } - // Pre-process domain configs to ensure they all have forwarding configurations - this.settings.domainConfigs = this.settings.domainConfigs.map(config => ensureForwardingConfig(config)); + // Process domain configs + // Note: ensureForwardingConfig is no longer needed since forwarding is now required // Initialize domain config manager with the processed configs this.domainConfigManager.updateDomainConfigs(this.settings.domainConfigs); @@ -412,11 +411,8 @@ export class SmartProxy extends plugins.EventEmitter { public async updateDomainConfigs(newDomainConfigs: IDomainConfig[]): Promise { console.log(`Updating domain configurations (${newDomainConfigs.length} configs)`); - // Ensure each domain config has a valid forwarding configuration - const processedConfigs = newDomainConfigs.map(config => ensureForwardingConfig(config)); - // Update domain configs in DomainConfigManager - this.domainConfigManager.updateDomainConfigs(processedConfigs); + this.domainConfigManager.updateDomainConfigs(newDomainConfigs); // If NetworkProxy is initialized, resync the configurations if (this.networkProxyBridge.getNetworkProxy()) { @@ -425,15 +421,15 @@ export class SmartProxy extends plugins.EventEmitter { // If Port80Handler is running, provision certificates based on forwarding type if (this.port80Handler && this.settings.acme?.enabled) { - for (const domainConfig of processedConfigs) { + for (const domainConfig of newDomainConfigs) { // Skip certificate provisioning for http-only or passthrough configs that don't need certs - const forwardingType = domainConfig.forwarding?.type as ForwardingType; + const forwardingType = domainConfig.forwarding.type; const needsCertificate = forwardingType === 'https-terminate-to-http' || forwardingType === 'https-terminate-to-https'; // Skip certificate provisioning if ACME is explicitly disabled for this domain - const acmeDisabled = domainConfig.forwarding?.acme?.enabled === false; + const acmeDisabled = domainConfig.forwarding.acme?.enabled === false; if (!needsCertificate || acmeDisabled) { if (this.settings.enableDetailedLogging) { @@ -447,7 +443,7 @@ export class SmartProxy extends plugins.EventEmitter { let provision: string | plugins.tsclass.network.ICert = 'http01'; // Check for ACME forwarding configuration in the domain - const forwardAcmeChallenges = domainConfig.forwarding?.acme?.forwardChallenges; + const forwardAcmeChallenges = domainConfig.forwarding.acme?.forwardChallenges; if (this.settings.certProvisionFunction) { try { @@ -467,7 +463,7 @@ export class SmartProxy extends plugins.EventEmitter { } // Create Port80Handler options from the forwarding configuration - const port80Config = createPort80HandlerOptions(domain, domainConfig.forwarding!); + const port80Config = createPort80HandlerOptions(domain, domainConfig.forwarding); this.port80Handler.addDomain(port80Config); console.log(`Registered domain ${domain} with Port80Handler for HTTP-01`); diff --git a/ts/smartproxy/forwarding/domain-config.ts b/ts/smartproxy/forwarding/domain-config.ts index d5c5d5b..650c329 100644 --- a/ts/smartproxy/forwarding/domain-config.ts +++ b/ts/smartproxy/forwarding/domain-config.ts @@ -1,25 +1,14 @@ import type { IForwardConfig } from '../types/forwarding.types.js'; /** - * Updated domain configuration with unified forwarding configuration + * Domain configuration with unified forwarding configuration */ export interface IDomainConfig { // Core properties - domain patterns domains: string[]; - + // Unified forwarding configuration forwarding: IForwardConfig; - - // Legacy security properties that will be migrated to forwarding.security - allowedIPs?: string[]; - blockedIPs?: string[]; - - // Legacy NetworkProxy properties - useNetworkProxy?: boolean; - networkProxyPort?: number; - - // Legacy timeout property - connectionTimeout?: number; } /** @@ -27,19 +16,13 @@ export interface IDomainConfig { */ export function createDomainConfig( domains: string | string[], - forwarding: IForwardConfig, - security?: { - allowedIPs?: string[]; - blockedIPs?: string[]; - } + forwarding: IForwardConfig ): IDomainConfig { // Normalize domains to an array const domainArray = Array.isArray(domains) ? domains : [domains]; - + return { domains: domainArray, - forwarding, - allowedIPs: security?.allowedIPs || ['*'], - blockedIPs: security?.blockedIPs + forwarding }; } \ No newline at end of file diff --git a/ts/smartproxy/forwarding/index.ts b/ts/smartproxy/forwarding/index.ts index 3a8ec16..5dccef7 100644 --- a/ts/smartproxy/forwarding/index.ts +++ b/ts/smartproxy/forwarding/index.ts @@ -17,7 +17,7 @@ export { httpOnly, tlsTerminateToHttp, tlsTerminateToHttps, - sniPassthrough + httpsPassthrough } from '../types/forwarding.types.js'; // Export domain configuration @@ -41,12 +41,12 @@ import { httpOnly, tlsTerminateToHttp, tlsTerminateToHttps, - sniPassthrough + httpsPassthrough } from '../types/forwarding.types.js'; export const helpers = { httpOnly, tlsTerminateToHttp, tlsTerminateToHttps, - sniPassthrough + sniPassthrough: httpsPassthrough // Alias for backward compatibility }; \ No newline at end of file