feat(smartproxy): Update documentation and route helper functions; add createPortRange, createSecurityConfig, createStaticFileRoute, and createTestRoute helpers to the readme and tests. Refactor test examples to use the new helper API and remove legacy connection handling files (including the old connection handler and PortRangeManager) to fully embrace the unified route‐based configuration.
This commit is contained in:
parent
36bea96ac7
commit
b4a0e4be6b
@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-05-10 - 15.1.0 - feat(smartproxy)
|
||||||
|
Update documentation and route helper functions; add createPortRange, createSecurityConfig, createStaticFileRoute, and createTestRoute helpers to the readme and tests. Refactor test examples to use the new helper API and remove legacy connection handling files (including the old connection handler and PortRangeManager) to fully embrace the unified route‐based configuration.
|
||||||
|
|
||||||
|
- Added new helper functions (createPortRange, createSecurityConfig, createStaticFileRoute, createTestRoute) in readme and route helpers.
|
||||||
|
- Refactored tests (test.forwarding.examples.ts, test.forwarding.unit.ts, etc.) to update references to the new API.
|
||||||
|
- Removed legacy connection handler and PortRangeManager files to simplify code and align with route‐based configuration.
|
||||||
|
|
||||||
## 2025-05-10 - 15.0.0 - BREAKING CHANGE(documentation)
|
## 2025-05-10 - 15.0.0 - BREAKING CHANGE(documentation)
|
||||||
Update readme documentation to comprehensively describe the new unified route-based configuration system in v14.0.0
|
Update readme documentation to comprehensively describe the new unified route-based configuration system in v14.0.0
|
||||||
|
|
||||||
|
@ -463,6 +463,10 @@ Available helper functions:
|
|||||||
- `createBlockRoute()` - Create a route to block specific traffic
|
- `createBlockRoute()` - Create a route to block specific traffic
|
||||||
- `createLoadBalancerRoute()` - Create a route for load balancing
|
- `createLoadBalancerRoute()` - Create a route for load balancing
|
||||||
- `createHttpsServer()` - Create a complete HTTPS server setup with HTTP redirect
|
- `createHttpsServer()` - Create a complete HTTPS server setup with HTTP redirect
|
||||||
|
- `createPortRange()` - Helper to create port range configurations from various formats
|
||||||
|
- `createSecurityConfig()` - Helper to create security configuration objects
|
||||||
|
- `createStaticFileRoute()` - Create a route for serving static files
|
||||||
|
- `createTestRoute()` - Create a test route for debugging and testing purposes
|
||||||
|
|
||||||
## What You Can Do with SmartProxy
|
## What You Can Do with SmartProxy
|
||||||
|
|
||||||
|
@ -1,112 +1,197 @@
|
|||||||
import * as plugins from '../ts/plugins.js';
|
import * as path from 'path';
|
||||||
import { tap, expect } from '@push.rocks/tapbundle';
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
|
|
||||||
import { SmartProxy } from '../ts/proxies/smart-proxy/index.js';
|
import { SmartProxy } from '../ts/proxies/smart-proxy/index.js';
|
||||||
import type { TForwardingType } from '../ts/forwarding/config/forwarding-types.js';
|
|
||||||
import type { IDomainConfig } from '../ts/forwarding/config/domain-config.js';
|
|
||||||
import {
|
import {
|
||||||
httpOnly,
|
createHttpRoute,
|
||||||
httpsPassthrough,
|
createHttpsRoute,
|
||||||
tlsTerminateToHttp,
|
createPassthroughRoute,
|
||||||
tlsTerminateToHttps
|
createRedirectRoute,
|
||||||
} from '../ts/forwarding/config/forwarding-types.js';
|
createHttpToHttpsRedirect,
|
||||||
|
createBlockRoute,
|
||||||
|
createLoadBalancerRoute,
|
||||||
|
createHttpsServer,
|
||||||
|
createPortRange,
|
||||||
|
createSecurityConfig,
|
||||||
|
createStaticFileRoute,
|
||||||
|
createTestRoute
|
||||||
|
} from '../ts/proxies/smart-proxy/route-helpers/index.js';
|
||||||
|
import type { IRouteConfig } from '../ts/proxies/smart-proxy/models/route-types.js';
|
||||||
|
|
||||||
// Test to demonstrate various forwarding configurations
|
// Test to demonstrate various route configurations using the new helpers
|
||||||
tap.test('Forwarding configuration examples', async (tools) => {
|
tap.test('Route-based configuration examples', async (tools) => {
|
||||||
// Example 1: HTTP-only configuration
|
// Example 1: HTTP-only configuration
|
||||||
const httpOnlyConfig: IDomainConfig = {
|
const httpOnlyRoute = createHttpRoute({
|
||||||
domains: ['http.example.com'],
|
domains: 'http.example.com',
|
||||||
forwarding: httpOnly({
|
target: {
|
||||||
target: {
|
host: 'localhost',
|
||||||
host: 'localhost',
|
port: 3000
|
||||||
port: 3000
|
},
|
||||||
},
|
security: {
|
||||||
security: {
|
allowedIps: ['*'] // Allow all
|
||||||
allowedIps: ['*'] // Allow all
|
},
|
||||||
}
|
name: 'Basic HTTP Route'
|
||||||
})
|
});
|
||||||
};
|
|
||||||
console.log(httpOnlyConfig.forwarding, 'HTTP-only configuration created successfully');
|
|
||||||
expect(httpOnlyConfig.forwarding.type).toEqual('http-only');
|
|
||||||
|
|
||||||
// Example 2: HTTPS Passthrough (SNI)
|
console.log('HTTP-only route created successfully:', httpOnlyRoute.name);
|
||||||
const httpsPassthroughConfig: IDomainConfig = {
|
expect(httpOnlyRoute.action.type).toEqual('forward');
|
||||||
domains: ['pass.example.com'],
|
expect(httpOnlyRoute.match.domains).toEqual('http.example.com');
|
||||||
forwarding: httpsPassthrough({
|
|
||||||
target: {
|
// Example 2: HTTPS Passthrough (SNI) configuration
|
||||||
host: ['10.0.0.1', '10.0.0.2'], // Round-robin target IPs
|
const httpsPassthroughRoute = createPassthroughRoute({
|
||||||
port: 443
|
domains: 'pass.example.com',
|
||||||
},
|
target: {
|
||||||
security: {
|
host: ['10.0.0.1', '10.0.0.2'], // Round-robin target IPs
|
||||||
allowedIps: ['*'] // Allow all
|
port: 443
|
||||||
}
|
},
|
||||||
})
|
security: {
|
||||||
};
|
allowedIps: ['*'] // Allow all
|
||||||
expect(httpsPassthroughConfig.forwarding).toBeTruthy();
|
},
|
||||||
expect(httpsPassthroughConfig.forwarding.type).toEqual('https-passthrough');
|
name: 'HTTPS Passthrough Route'
|
||||||
expect(Array.isArray(httpsPassthroughConfig.forwarding.target.host)).toBeTrue();
|
});
|
||||||
|
|
||||||
|
expect(httpsPassthroughRoute).toBeTruthy();
|
||||||
|
expect(httpsPassthroughRoute.action.tls?.mode).toEqual('passthrough');
|
||||||
|
expect(Array.isArray(httpsPassthroughRoute.action.target?.host)).toBeTrue();
|
||||||
|
|
||||||
// Example 3: HTTPS Termination to HTTP Backend
|
// Example 3: HTTPS Termination to HTTP Backend
|
||||||
const terminateToHttpConfig: IDomainConfig = {
|
const terminateToHttpRoute = createHttpsRoute({
|
||||||
domains: ['secure.example.com'],
|
domains: 'secure.example.com',
|
||||||
forwarding: tlsTerminateToHttp({
|
target: {
|
||||||
target: {
|
host: 'localhost',
|
||||||
host: 'localhost',
|
port: 8080
|
||||||
port: 8080
|
},
|
||||||
},
|
tlsMode: 'terminate',
|
||||||
http: {
|
certificate: 'auto',
|
||||||
redirectToHttps: true, // Redirect HTTP requests to HTTPS
|
headers: {
|
||||||
headers: {
|
'X-Forwarded-Proto': 'https'
|
||||||
'X-Forwarded-Proto': 'https'
|
},
|
||||||
}
|
security: {
|
||||||
},
|
allowedIps: ['*'] // Allow all
|
||||||
acme: {
|
},
|
||||||
enabled: true,
|
name: 'HTTPS Termination to HTTP Backend'
|
||||||
maintenance: true,
|
});
|
||||||
production: false // Use staging ACME server for testing
|
|
||||||
},
|
|
||||||
security: {
|
|
||||||
allowedIps: ['*'] // Allow all
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
expect(terminateToHttpConfig.forwarding).toBeTruthy();
|
|
||||||
expect(terminateToHttpConfig.forwarding.type).toEqual('https-terminate-to-http');
|
|
||||||
expect(terminateToHttpConfig.forwarding.http?.redirectToHttps).toBeTrue();
|
|
||||||
|
|
||||||
// Example 4: HTTPS Termination to HTTPS Backend
|
// Create the HTTP to HTTPS redirect for this domain
|
||||||
const terminateToHttpsConfig: IDomainConfig = {
|
const httpToHttpsRedirect = createHttpToHttpsRedirect({
|
||||||
domains: ['proxy.example.com'],
|
domains: 'secure.example.com',
|
||||||
forwarding: tlsTerminateToHttps({
|
name: 'HTTP to HTTPS Redirect for secure.example.com'
|
||||||
target: {
|
});
|
||||||
host: 'internal-api.local',
|
|
||||||
port: 8443
|
|
||||||
},
|
|
||||||
https: {
|
|
||||||
forwardSni: true // Forward original SNI info
|
|
||||||
},
|
|
||||||
security: {
|
|
||||||
allowedIps: ['10.0.0.0/24', '192.168.1.0/24'],
|
|
||||||
maxConnections: 1000
|
|
||||||
},
|
|
||||||
advanced: {
|
|
||||||
timeout: 3600000, // 1 hour in ms
|
|
||||||
headers: {
|
|
||||||
'X-Original-Host': '{sni}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
expect(terminateToHttpsConfig.forwarding).toBeTruthy();
|
|
||||||
expect(terminateToHttpsConfig.forwarding.type).toEqual('https-terminate-to-https');
|
|
||||||
expect(terminateToHttpsConfig.forwarding.https?.forwardSni).toBeTrue();
|
|
||||||
expect(terminateToHttpsConfig.forwarding.security?.allowedIps?.length).toEqual(2);
|
|
||||||
|
|
||||||
// Skip the SmartProxy integration test for now and just verify our configuration objects work
|
expect(terminateToHttpRoute).toBeTruthy();
|
||||||
console.log('All forwarding configurations were created successfully');
|
expect(terminateToHttpRoute.action.tls?.mode).toEqual('terminate');
|
||||||
|
expect(terminateToHttpRoute.action.advanced?.headers?.['X-Forwarded-Proto']).toEqual('https');
|
||||||
|
expect(httpToHttpsRedirect.action.type).toEqual('redirect');
|
||||||
|
|
||||||
// This is just to verify that our test passes
|
// Example 4: Load Balancer with HTTPS
|
||||||
expect(true).toBeTrue();
|
const loadBalancerRoute = createLoadBalancerRoute({
|
||||||
|
domains: 'proxy.example.com',
|
||||||
|
targets: ['internal-api-1.local', 'internal-api-2.local'],
|
||||||
|
targetPort: 8443,
|
||||||
|
tlsMode: 'terminate-and-reencrypt',
|
||||||
|
certificate: 'auto',
|
||||||
|
headers: {
|
||||||
|
'X-Original-Host': '{domain}'
|
||||||
|
},
|
||||||
|
security: {
|
||||||
|
allowedIps: ['10.0.0.0/24', '192.168.1.0/24'],
|
||||||
|
maxConnections: 1000
|
||||||
|
},
|
||||||
|
name: 'Load Balanced HTTPS Route'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(loadBalancerRoute).toBeTruthy();
|
||||||
|
expect(loadBalancerRoute.action.tls?.mode).toEqual('terminate-and-reencrypt');
|
||||||
|
expect(Array.isArray(loadBalancerRoute.action.target?.host)).toBeTrue();
|
||||||
|
expect(loadBalancerRoute.action.security?.allowedIps?.length).toEqual(2);
|
||||||
|
|
||||||
|
// Example 5: Block specific IPs
|
||||||
|
const blockRoute = createBlockRoute({
|
||||||
|
ports: [80, 443],
|
||||||
|
clientIp: ['192.168.5.0/24'],
|
||||||
|
name: 'Block Suspicious IPs',
|
||||||
|
priority: 1000 // High priority to ensure it's evaluated first
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(blockRoute.action.type).toEqual('block');
|
||||||
|
expect(blockRoute.match.clientIp?.length).toEqual(1);
|
||||||
|
expect(blockRoute.priority).toEqual(1000);
|
||||||
|
|
||||||
|
// Example 6: Complete HTTPS Server with HTTP Redirect
|
||||||
|
const httpsServerRoutes = createHttpsServer({
|
||||||
|
domains: 'complete.example.com',
|
||||||
|
target: {
|
||||||
|
host: 'localhost',
|
||||||
|
port: 8080
|
||||||
|
},
|
||||||
|
certificate: 'auto',
|
||||||
|
name: 'Complete HTTPS Server'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(Array.isArray(httpsServerRoutes)).toBeTrue();
|
||||||
|
expect(httpsServerRoutes.length).toEqual(2); // HTTPS route and HTTP redirect
|
||||||
|
expect(httpsServerRoutes[0].action.tls?.mode).toEqual('terminate');
|
||||||
|
expect(httpsServerRoutes[1].action.type).toEqual('redirect');
|
||||||
|
|
||||||
|
// Example 7: Static File Server
|
||||||
|
const staticFileRoute = createStaticFileRoute({
|
||||||
|
domains: 'static.example.com',
|
||||||
|
targetDirectory: '/var/www/static',
|
||||||
|
tlsMode: 'terminate',
|
||||||
|
certificate: 'auto',
|
||||||
|
headers: {
|
||||||
|
'Cache-Control': 'public, max-age=86400'
|
||||||
|
},
|
||||||
|
name: 'Static File Server'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(staticFileRoute.action.advanced?.staticFiles?.directory).toEqual('/var/www/static');
|
||||||
|
expect(staticFileRoute.action.advanced?.headers?.['Cache-Control']).toEqual('public, max-age=86400');
|
||||||
|
|
||||||
|
// Example 8: Test Route for Debugging
|
||||||
|
const testRoute = createTestRoute({
|
||||||
|
ports: 8000,
|
||||||
|
domains: 'test.example.com',
|
||||||
|
response: {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ status: 'ok', message: 'API is working!' })
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(testRoute.match.ports).toEqual(8000);
|
||||||
|
expect(testRoute.action.advanced?.testResponse?.status).toEqual(200);
|
||||||
|
|
||||||
|
// Create a SmartProxy instance with all routes
|
||||||
|
const allRoutes: IRouteConfig[] = [
|
||||||
|
httpOnlyRoute,
|
||||||
|
httpsPassthroughRoute,
|
||||||
|
terminateToHttpRoute,
|
||||||
|
httpToHttpsRedirect,
|
||||||
|
loadBalancerRoute,
|
||||||
|
blockRoute,
|
||||||
|
...httpsServerRoutes,
|
||||||
|
staticFileRoute,
|
||||||
|
testRoute
|
||||||
|
];
|
||||||
|
|
||||||
|
// We're not actually starting the SmartProxy in this test,
|
||||||
|
// just verifying that the configuration is valid
|
||||||
|
const smartProxy = new SmartProxy({
|
||||||
|
routes: allRoutes,
|
||||||
|
acme: {
|
||||||
|
email: 'admin@example.com',
|
||||||
|
termsOfServiceAgreed: true,
|
||||||
|
directoryUrl: 'https://acme-staging-v02.api.letsencrypt.org/directory'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Smart Proxy configured with ${allRoutes.length} routes`);
|
||||||
|
|
||||||
|
// Verify our example proxy was created correctly
|
||||||
|
expect(smartProxy).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
export default tap.start();
|
export default tap.start();
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartproxy',
|
name: '@push.rocks/smartproxy',
|
||||||
version: '15.0.0',
|
version: '15.1.0',
|
||||||
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.'
|
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.'
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,25 @@ export interface IRouteRedirect {
|
|||||||
status: 301 | 302 | 307 | 308;
|
status: 301 | 302 | 307 | 308;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication options
|
||||||
|
*/
|
||||||
|
export interface IRouteAuthentication {
|
||||||
|
type: 'basic' | 'digest' | 'oauth' | 'jwt';
|
||||||
|
credentials?: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}[];
|
||||||
|
realm?: string;
|
||||||
|
jwtSecret?: string;
|
||||||
|
jwtIssuer?: string;
|
||||||
|
oauthProvider?: string;
|
||||||
|
oauthClientId?: string;
|
||||||
|
oauthClientSecret?: string;
|
||||||
|
oauthRedirectUri?: string;
|
||||||
|
[key: string]: any; // Allow additional auth-specific options
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Security options for route actions
|
* Security options for route actions
|
||||||
*/
|
*/
|
||||||
@ -68,10 +87,28 @@ export interface IRouteSecurity {
|
|||||||
allowedIps?: string[];
|
allowedIps?: string[];
|
||||||
blockedIps?: string[];
|
blockedIps?: string[];
|
||||||
maxConnections?: number;
|
maxConnections?: number;
|
||||||
authentication?: {
|
authentication?: IRouteAuthentication;
|
||||||
type: 'basic' | 'digest' | 'oauth';
|
}
|
||||||
// Auth-specific options would go here
|
|
||||||
};
|
/**
|
||||||
|
* Static file server configuration
|
||||||
|
*/
|
||||||
|
export interface IRouteStaticFiles {
|
||||||
|
directory: string;
|
||||||
|
indexFiles?: string[];
|
||||||
|
cacheControl?: string;
|
||||||
|
expires?: number;
|
||||||
|
followSymlinks?: boolean;
|
||||||
|
disableDirectoryListing?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test route response configuration
|
||||||
|
*/
|
||||||
|
export interface IRouteTestResponse {
|
||||||
|
status: number;
|
||||||
|
headers: Record<string, string>;
|
||||||
|
body: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -81,6 +118,8 @@ export interface IRouteAdvanced {
|
|||||||
timeout?: number;
|
timeout?: number;
|
||||||
headers?: Record<string, string>;
|
headers?: Record<string, string>;
|
||||||
keepAlive?: boolean;
|
keepAlive?: boolean;
|
||||||
|
staticFiles?: IRouteStaticFiles;
|
||||||
|
testResponse?: IRouteTestResponse;
|
||||||
// Additional advanced options would go here
|
// Additional advanced options would go here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,16 +377,10 @@ export class NetworkProxyBridge {
|
|||||||
publicKey: certCert,
|
publicKey: certCert,
|
||||||
destinationIps: targetHosts,
|
destinationIps: targetHosts,
|
||||||
destinationPorts: [targetPort],
|
destinationPorts: [targetPort],
|
||||||
proxyConfig: {
|
// Use backendProtocol for TLS re-encryption:
|
||||||
targetIsTls: route.action.tls.mode === 'terminate-and-reencrypt',
|
backendProtocol: route.action.tls.mode === 'terminate-and-reencrypt' ? 'http2' : 'http1',
|
||||||
allowHTTP1: true,
|
// Add rewriteHostHeader for host header handling:
|
||||||
// Apply any other NetworkProxy-specific settings
|
rewriteHostHeader: route.action.advanced?.headers ? true : false
|
||||||
...(route.action.advanced ? {
|
|
||||||
preserveHost: true,
|
|
||||||
timeout: route.action.advanced.timeout,
|
|
||||||
headers: route.action.advanced.headers
|
|
||||||
} : {})
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
configs.push(config);
|
configs.push(config);
|
||||||
|
@ -741,20 +741,7 @@ export class RouteConnectionHandler {
|
|||||||
this.connectionManager.incrementTerminationStat('outgoing', 'connection_failed');
|
this.connectionManager.incrementTerminationStat('outgoing', 'connection_failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a forwarding handler for this domain, let it handle the error
|
// Route-based configuration doesn't use domain handlers
|
||||||
if (domainConfig) {
|
|
||||||
try {
|
|
||||||
const forwardingHandler = this.domainConfigManager.getForwardingHandler(domainConfig);
|
|
||||||
forwardingHandler.emit('connection_error', {
|
|
||||||
socket,
|
|
||||||
error: err,
|
|
||||||
connectionId
|
|
||||||
});
|
|
||||||
} catch (handlerErr) {
|
|
||||||
// If getting the handler fails, just log and continue with normal cleanup
|
|
||||||
console.log(`Error getting forwarding handler for error handling: ${handlerErr}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up the connection
|
// Clean up the connection
|
||||||
this.connectionManager.initiateCleanupOnce(record, `connection_failed_${code}`);
|
this.connectionManager.initiateCleanupOnce(record, `connection_failed_${code}`);
|
||||||
@ -887,8 +874,8 @@ export class RouteConnectionHandler {
|
|||||||
`${
|
`${
|
||||||
serverName
|
serverName
|
||||||
? ` (SNI: ${serverName})`
|
? ` (SNI: ${serverName})`
|
||||||
: domainConfig
|
: record.lockedDomain
|
||||||
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
? ` (Domain: ${record.lockedDomain})`
|
||||||
: ''
|
: ''
|
||||||
}` +
|
}` +
|
||||||
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${
|
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${
|
||||||
@ -901,8 +888,8 @@ export class RouteConnectionHandler {
|
|||||||
`${
|
`${
|
||||||
serverName
|
serverName
|
||||||
? ` (SNI: ${serverName})`
|
? ` (SNI: ${serverName})`
|
||||||
: domainConfig
|
: record.lockedDomain
|
||||||
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
? ` (Domain: ${record.lockedDomain})`
|
||||||
: ''
|
: ''
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
|
@ -6,7 +6,8 @@ import type {
|
|||||||
IRouteTls,
|
IRouteTls,
|
||||||
IRouteRedirect,
|
IRouteRedirect,
|
||||||
IRouteSecurity,
|
IRouteSecurity,
|
||||||
IRouteAdvanced
|
IRouteAdvanced,
|
||||||
|
TPortRange
|
||||||
} from './models/route-types.js';
|
} from './models/route-types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -342,3 +343,155 @@ export function createHttpsServer(
|
|||||||
|
|
||||||
return routes;
|
return routes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a port range configuration from various input formats
|
||||||
|
*/
|
||||||
|
export function createPortRange(
|
||||||
|
ports: number | number[] | string | Array<{ from: number; to: number }>
|
||||||
|
): TPortRange {
|
||||||
|
// If it's a string like "80,443" or "8000-9000", parse it
|
||||||
|
if (typeof ports === 'string') {
|
||||||
|
if (ports.includes('-')) {
|
||||||
|
// Handle range like "8000-9000"
|
||||||
|
const [start, end] = ports.split('-').map(p => parseInt(p.trim(), 10));
|
||||||
|
return [{ from: start, to: end }];
|
||||||
|
} else if (ports.includes(',')) {
|
||||||
|
// Handle comma-separated list like "80,443,8080"
|
||||||
|
return ports.split(',').map(p => parseInt(p.trim(), 10));
|
||||||
|
} else {
|
||||||
|
// Handle single port as string
|
||||||
|
return parseInt(ports.trim(), 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise return as is
|
||||||
|
return ports;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a security configuration object
|
||||||
|
*/
|
||||||
|
export function createSecurityConfig(
|
||||||
|
options: {
|
||||||
|
allowedIps?: string[];
|
||||||
|
blockedIps?: string[];
|
||||||
|
maxConnections?: number;
|
||||||
|
authentication?: {
|
||||||
|
type: 'basic' | 'digest' | 'oauth';
|
||||||
|
// Auth-specific options
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
): IRouteSecurity {
|
||||||
|
return {
|
||||||
|
...(options.allowedIps ? { allowedIps: options.allowedIps } : {}),
|
||||||
|
...(options.blockedIps ? { blockedIps: options.blockedIps } : {}),
|
||||||
|
...(options.maxConnections ? { maxConnections: options.maxConnections } : {}),
|
||||||
|
...(options.authentication ? { authentication: options.authentication } : {})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a static file server route
|
||||||
|
*/
|
||||||
|
export function createStaticFileRoute(
|
||||||
|
options: {
|
||||||
|
ports?: number | number[]; // Default: 80
|
||||||
|
domains: string | string[];
|
||||||
|
path?: string;
|
||||||
|
targetDirectory: string;
|
||||||
|
tlsMode?: 'terminate' | 'terminate-and-reencrypt';
|
||||||
|
certificate?: 'auto' | { key: string; cert: string };
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
security?: IRouteSecurity;
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
priority?: number;
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
|
): IRouteConfig {
|
||||||
|
const useTls = options.tlsMode !== undefined;
|
||||||
|
const defaultPort = useTls ? 443 : 80;
|
||||||
|
|
||||||
|
return createRoute(
|
||||||
|
{
|
||||||
|
ports: options.ports || defaultPort,
|
||||||
|
domains: options.domains,
|
||||||
|
...(options.path ? { path: options.path } : {})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'forward',
|
||||||
|
target: {
|
||||||
|
host: 'localhost', // Static file serving is typically handled locally
|
||||||
|
port: 0, // Special value indicating a static file server
|
||||||
|
preservePort: false
|
||||||
|
},
|
||||||
|
...(useTls ? {
|
||||||
|
tls: {
|
||||||
|
mode: options.tlsMode!,
|
||||||
|
certificate: options.certificate || 'auto'
|
||||||
|
}
|
||||||
|
} : {}),
|
||||||
|
advanced: {
|
||||||
|
...(options.headers ? { headers: options.headers } : {}),
|
||||||
|
staticFiles: {
|
||||||
|
directory: options.targetDirectory,
|
||||||
|
indexFiles: ['index.html', 'index.htm']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...(options.security ? { security: options.security } : {})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: options.name || 'Static File Server',
|
||||||
|
description: options.description || `Serving static files from ${options.targetDirectory}`,
|
||||||
|
priority: options.priority,
|
||||||
|
tags: options.tags
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a test route for debugging purposes
|
||||||
|
*/
|
||||||
|
export function createTestRoute(
|
||||||
|
options: {
|
||||||
|
ports?: number | number[]; // Default: 8000
|
||||||
|
domains?: string | string[];
|
||||||
|
path?: string;
|
||||||
|
response?: {
|
||||||
|
status?: number;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
body?: string;
|
||||||
|
};
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
): IRouteConfig {
|
||||||
|
return createRoute(
|
||||||
|
{
|
||||||
|
ports: options.ports || 8000,
|
||||||
|
...(options.domains ? { domains: options.domains } : {}),
|
||||||
|
...(options.path ? { path: options.path } : {})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'forward',
|
||||||
|
target: {
|
||||||
|
host: 'test', // Special value indicating a test route
|
||||||
|
port: 0
|
||||||
|
},
|
||||||
|
advanced: {
|
||||||
|
testResponse: {
|
||||||
|
status: options.response?.status || 200,
|
||||||
|
headers: options.response?.headers || { 'Content-Type': 'text/plain' },
|
||||||
|
body: options.response?.body || 'Test route is working!'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: options.name || 'Test Route',
|
||||||
|
description: 'Route for testing and debugging',
|
||||||
|
priority: 500,
|
||||||
|
tags: ['test', 'debug']
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
9
ts/proxies/smart-proxy/route-helpers/index.ts
Normal file
9
ts/proxies/smart-proxy/route-helpers/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Route helpers for SmartProxy
|
||||||
|
*
|
||||||
|
* This module provides helper functions for creating various types of route configurations
|
||||||
|
* to be used with the SmartProxy system.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Re-export all functions from the route-helpers.ts file
|
||||||
|
export * from '../route-helpers.js';
|
@ -7,8 +7,7 @@ import type {
|
|||||||
} from './models/route-types.js';
|
} from './models/route-types.js';
|
||||||
import type {
|
import type {
|
||||||
ISmartProxyOptions,
|
ISmartProxyOptions,
|
||||||
IRoutedSmartProxyOptions,
|
IRoutedSmartProxyOptions
|
||||||
IDomainConfig
|
|
||||||
} from './models/interfaces.js';
|
} from './models/interfaces.js';
|
||||||
import {
|
import {
|
||||||
isRoutedOptions,
|
isRoutedOptions,
|
||||||
|
@ -6,7 +6,7 @@ import { SecurityManager } from './security-manager.js';
|
|||||||
import { TlsManager } from './tls-manager.js';
|
import { TlsManager } from './tls-manager.js';
|
||||||
import { NetworkProxyBridge } from './network-proxy-bridge.js';
|
import { NetworkProxyBridge } from './network-proxy-bridge.js';
|
||||||
import { TimeoutManager } from './timeout-manager.js';
|
import { TimeoutManager } from './timeout-manager.js';
|
||||||
import { PortRangeManager } from './port-range-manager.js';
|
// import { PortRangeManager } from './port-range-manager.js';
|
||||||
import { RouteManager } from './route-manager.js';
|
import { RouteManager } from './route-manager.js';
|
||||||
import { RouteConnectionHandler } from './route-connection-handler.js';
|
import { RouteConnectionHandler } from './route-connection-handler.js';
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ import type {
|
|||||||
ISmartProxyOptions,
|
ISmartProxyOptions,
|
||||||
IRoutedSmartProxyOptions
|
IRoutedSmartProxyOptions
|
||||||
} from './models/interfaces.js';
|
} from './models/interfaces.js';
|
||||||
import { isRoutedOptions } from './models/interfaces.js';
|
import { isRoutedOptions, isLegacyOptions } from './models/interfaces.js';
|
||||||
import type { IRouteConfig } from './models/route-types.js';
|
import type { IRouteConfig } from './models/route-types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,7 +49,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
private tlsManager: TlsManager;
|
private tlsManager: TlsManager;
|
||||||
private networkProxyBridge: NetworkProxyBridge;
|
private networkProxyBridge: NetworkProxyBridge;
|
||||||
private timeoutManager: TimeoutManager;
|
private timeoutManager: TimeoutManager;
|
||||||
private portRangeManager: PortRangeManager;
|
// private portRangeManager: PortRangeManager;
|
||||||
private routeManager: RouteManager;
|
private routeManager: RouteManager;
|
||||||
private routeConnectionHandler: RouteConnectionHandler;
|
private routeConnectionHandler: RouteConnectionHandler;
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
autoRenew: true,
|
autoRenew: true,
|
||||||
certificateStore: './certs',
|
certificateStore: './certs',
|
||||||
skipConfiguredCerts: false,
|
skipConfiguredCerts: false,
|
||||||
httpsRedirectPort: this.settings.fromPort || 443,
|
httpsRedirectPort: 443,
|
||||||
renewCheckIntervalHours: 24,
|
renewCheckIntervalHours: 24,
|
||||||
domainForwards: []
|
domainForwards: []
|
||||||
};
|
};
|
||||||
@ -152,7 +152,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
this.routeManager = new RouteManager(this.settings);
|
this.routeManager = new RouteManager(this.settings);
|
||||||
|
|
||||||
// Create port range manager
|
// Create port range manager
|
||||||
this.portRangeManager = new PortRangeManager(this.settings);
|
// this.portRangeManager = new PortRangeManager(this.settings);
|
||||||
|
|
||||||
// Create other required components
|
// Create other required components
|
||||||
this.tlsManager = new TlsManager(this.settings);
|
this.tlsManager = new TlsManager(this.settings);
|
||||||
@ -220,22 +220,22 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
if (this.port80Handler) {
|
if (this.port80Handler) {
|
||||||
const acme = this.settings.acme!;
|
const acme = this.settings.acme!;
|
||||||
|
|
||||||
// Setup domain forwards based on configuration type
|
// Setup domain forwards
|
||||||
const domainForwards = acme.domainForwards?.map(f => {
|
const domainForwards = acme.domainForwards?.map(f => {
|
||||||
if (isLegacyOptions(this.settings)) {
|
// Check if a matching route exists
|
||||||
// If using legacy mode, check if domain config exists
|
const matchingRoute = this.settings.routes.find(
|
||||||
const domainConfig = this.settings.domainConfigs.find(
|
route => Array.isArray(route.match.domains)
|
||||||
dc => dc.domains.some(d => d === f.domain)
|
? route.match.domains.some(d => d === f.domain)
|
||||||
);
|
: route.match.domains === f.domain
|
||||||
|
);
|
||||||
|
|
||||||
if (domainConfig?.forwarding) {
|
if (matchingRoute) {
|
||||||
return {
|
return {
|
||||||
domain: f.domain,
|
domain: f.domain,
|
||||||
forwardConfig: f.forwardConfig,
|
forwardConfig: f.forwardConfig,
|
||||||
acmeForwardConfig: f.acmeForwardConfig,
|
acmeForwardConfig: f.acmeForwardConfig,
|
||||||
sslRedirect: f.sslRedirect || domainConfig.forwarding.http?.redirectToHttps || false
|
sslRedirect: f.sslRedirect || false
|
||||||
};
|
};
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// In route mode, look for matching route
|
// In route mode, look for matching route
|
||||||
const route = this.routeManager.findMatchingRoute({
|
const route = this.routeManager.findMatchingRoute({
|
||||||
@ -332,10 +332,8 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
const isNetworkProxyPort = this.settings.useNetworkProxy?.includes(port);
|
const isNetworkProxyPort = this.settings.useNetworkProxy?.includes(port);
|
||||||
console.log(
|
console.log(
|
||||||
`SmartProxy -> OK: Now listening on port ${port}${
|
`SmartProxy -> OK: Now listening on port ${port}${
|
||||||
isLegacyOptions(this.settings) && this.settings.sniEnabled && !isNetworkProxyPort ?
|
isNetworkProxyPort ? ' (NetworkProxy forwarding enabled)' : ''
|
||||||
' (SNI passthrough enabled)' :
|
}`
|
||||||
''
|
|
||||||
}${isNetworkProxyPort ? ' (NetworkProxy forwarding enabled)' : ''}`
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -723,17 +721,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
domains.push(...eligibleDomains);
|
domains.push(...eligibleDomains);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For legacy mode, also get domains from domain configs
|
// Legacy mode is no longer supported
|
||||||
if (isLegacyOptions(this.settings)) {
|
|
||||||
for (const config of this.settings.domainConfigs) {
|
|
||||||
// Skip domains that can't be used with ACME
|
|
||||||
const eligibleDomains = config.domains.filter(domain =>
|
|
||||||
!domain.includes('*') && this.isValidDomain(domain)
|
|
||||||
);
|
|
||||||
|
|
||||||
domains.push(...eligibleDomains);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return domains;
|
return domains;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user