@push.rocks/smartproxy
Version:
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.
920 lines • 62.3 kB
JavaScript
/**
* Route Helper Functions
*
* This file provides utility functions for creating route configurations for common scenarios.
* These functions aim to simplify the creation of route configurations for typical use cases.
*
* This module includes helper functions for creating:
* - HTTP routes (createHttpRoute)
* - HTTPS routes with TLS termination (createHttpsTerminateRoute)
* - HTTP to HTTPS redirects (createHttpToHttpsRedirect)
* - HTTPS passthrough routes (createHttpsPassthroughRoute)
* - Complete HTTPS servers with redirects (createCompleteHttpsServer)
* - Load balancer routes (createLoadBalancerRoute)
* - API routes (createApiRoute)
* - WebSocket routes (createWebSocketRoute)
* - Port mapping routes (createPortMappingRoute, createOffsetPortMappingRoute)
* - Dynamic routing (createDynamicRoute, createSmartLoadBalancer)
* - NFTables routes (createNfTablesRoute, createNfTablesTerminateRoute)
*/
import * as plugins from '../../../plugins.js';
import { mergeRouteConfigs } from './route-utils.js';
import { ProtocolDetector, HttpDetector } from '../../../detection/index.js';
/**
* Create an HTTP-only route configuration
* @param domains Domain(s) to match
* @param target Target host and port
* @param options Additional route options
* @returns Route configuration object
*/
export function createHttpRoute(domains, target, options = {}) {
// Create route match
const match = {
ports: options.match?.ports || 80,
domains
};
// Create route action
const action = {
type: 'forward',
targets: [target]
};
// Create the route config
return {
match,
action,
name: options.name || `HTTP Route for ${Array.isArray(domains) ? domains.join(', ') : domains}`,
...options
};
}
/**
* Create an HTTPS route with TLS termination (including HTTP redirect to HTTPS)
* @param domains Domain(s) to match
* @param target Target host and port
* @param options Additional route options
* @returns Route configuration object
*/
export function createHttpsTerminateRoute(domains, target, options = {}) {
// Create route match
const match = {
ports: options.httpsPort || 443,
domains
};
// Create route action
const action = {
type: 'forward',
targets: [target],
tls: {
mode: options.reencrypt ? 'terminate-and-reencrypt' : 'terminate',
certificate: options.certificate || 'auto'
}
};
// Create the route config
return {
match,
action,
name: options.name || `HTTPS Route for ${Array.isArray(domains) ? domains.join(', ') : domains}`,
...options
};
}
/**
* Create an HTTP to HTTPS redirect route
* @param domains Domain(s) to match
* @param httpsPort HTTPS port to redirect to (default: 443)
* @param options Additional route options
* @returns Route configuration object
*/
export function createHttpToHttpsRedirect(domains, httpsPort = 443, options = {}) {
// Create route match
const match = {
ports: options.match?.ports || 80,
domains
};
// Create route action
const action = {
type: 'socket-handler',
socketHandler: SocketHandlers.httpRedirect(`https://{domain}:${httpsPort}{path}`, 301)
};
// Create the route config
return {
match,
action,
name: options.name || `HTTP to HTTPS Redirect for ${Array.isArray(domains) ? domains.join(', ') : domains}`,
...options
};
}
/**
* Create an HTTPS passthrough route (SNI-based forwarding without TLS termination)
* @param domains Domain(s) to match
* @param target Target host and port
* @param options Additional route options
* @returns Route configuration object
*/
export function createHttpsPassthroughRoute(domains, target, options = {}) {
// Create route match
const match = {
ports: options.match?.ports || 443,
domains
};
// Create route action
const action = {
type: 'forward',
targets: [target],
tls: {
mode: 'passthrough'
}
};
// Create the route config
return {
match,
action,
name: options.name || `HTTPS Passthrough for ${Array.isArray(domains) ? domains.join(', ') : domains}`,
...options
};
}
/**
* Create a complete HTTPS server with HTTP to HTTPS redirects
* @param domains Domain(s) to match
* @param target Target host and port
* @param options Additional configuration options
* @returns Array of two route configurations (HTTPS and HTTP redirect)
*/
export function createCompleteHttpsServer(domains, target, options = {}) {
// Create the HTTPS route
const httpsRoute = createHttpsTerminateRoute(domains, target, options);
// Create the HTTP redirect route
const httpRedirectRoute = createHttpToHttpsRedirect(domains,
// Extract the HTTPS port from the HTTPS route - ensure it's a number
typeof options.httpsPort === 'number' ? options.httpsPort :
Array.isArray(options.httpsPort) ? options.httpsPort[0] : 443, {
// Set the HTTP port
match: {
ports: options.httpPort || 80,
domains
},
name: `HTTP to HTTPS Redirect for ${Array.isArray(domains) ? domains.join(', ') : domains}`
});
return [httpsRoute, httpRedirectRoute];
}
/**
* Create a load balancer route (round-robin between multiple backend hosts)
* @param domains Domain(s) to match
* @param backendsOrHosts Array of backend servers OR array of host strings (legacy)
* @param portOrOptions Port number (legacy) OR options object
* @param options Additional route options (legacy)
* @returns Route configuration object
*/
export function createLoadBalancerRoute(domains, backendsOrHosts, portOrOptions, options) {
// Handle legacy signature: (domains, hosts[], port, options)
let backends;
let finalOptions;
if (Array.isArray(backendsOrHosts) && backendsOrHosts.length > 0 && typeof backendsOrHosts[0] === 'string') {
// Legacy signature
const hosts = backendsOrHosts;
const port = portOrOptions;
backends = hosts.map(host => ({ host, port }));
finalOptions = options || {};
}
else {
// New signature
backends = backendsOrHosts;
finalOptions = portOrOptions || {};
}
// Extract hosts and ensure all backends use the same port
const port = backends[0].port;
const hosts = backends.map(backend => backend.host);
// Create route match
const match = {
ports: finalOptions.match?.ports || (finalOptions.tls || finalOptions.useTls ? 443 : 80),
domains
};
// Create route target
const target = {
host: hosts,
port
};
// Create route action
const action = {
type: 'forward',
targets: [target]
};
// Add TLS configuration if provided
if (finalOptions.tls || finalOptions.useTls) {
action.tls = {
mode: finalOptions.tls?.mode || 'terminate',
certificate: finalOptions.tls?.certificate || finalOptions.certificate || 'auto'
};
}
// Add load balancing options
if (finalOptions.algorithm || finalOptions.healthCheck) {
action.loadBalancing = {
algorithm: finalOptions.algorithm || 'round-robin',
healthCheck: finalOptions.healthCheck
};
}
// Create the route config
return {
match,
action,
name: finalOptions.name || `Load Balancer for ${Array.isArray(domains) ? domains.join(', ') : domains}`,
...finalOptions
};
}
/**
* Create an API route configuration
* @param domains Domain(s) to match
* @param apiPath API base path (e.g., "/api")
* @param target Target host and port
* @param options Additional route options
* @returns Route configuration object
*/
export function createApiRoute(domains, apiPath, target, options = {}) {
// Normalize API path
const normalizedPath = apiPath.startsWith('/') ? apiPath : `/${apiPath}`;
const pathWithWildcard = normalizedPath.endsWith('/')
? `${normalizedPath}*`
: `${normalizedPath}/*`;
// Create route match
const match = {
ports: options.useTls
? (options.httpsPort || 443)
: (options.httpPort || 80),
domains,
path: pathWithWildcard
};
// Create route action
const action = {
type: 'forward',
targets: [target]
};
// Add TLS configuration if using HTTPS
if (options.useTls) {
action.tls = {
mode: 'terminate',
certificate: options.certificate || 'auto'
};
}
// Add CORS headers if requested
const headers = {};
if (options.addCorsHeaders) {
headers.response = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400'
};
}
// Create the route config
return {
match,
action,
headers: Object.keys(headers).length > 0 ? headers : undefined,
name: options.name || `API Route ${normalizedPath} for ${Array.isArray(domains) ? domains.join(', ') : domains}`,
priority: options.priority || 100, // Higher priority for specific path matches
...options
};
}
/**
* Create a WebSocket route configuration
* @param domains Domain(s) to match
* @param targetOrPath Target server OR WebSocket path (legacy)
* @param targetOrOptions Target server (legacy) OR options
* @param options Additional route options (legacy)
* @returns Route configuration object
*/
export function createWebSocketRoute(domains, targetOrPath, targetOrOptions, options) {
// Handle different signatures
let target;
let wsPath;
let finalOptions;
if (typeof targetOrPath === 'string') {
// Legacy signature: (domains, path, target, options)
wsPath = targetOrPath;
target = targetOrOptions;
finalOptions = options || {};
}
else {
// New signature: (domains, target, options)
target = targetOrPath;
finalOptions = targetOrOptions || {};
wsPath = finalOptions.path || '/ws';
}
// Normalize WebSocket path
const normalizedPath = wsPath.startsWith('/') ? wsPath : `/${wsPath}`;
// Create route match
const match = {
ports: finalOptions.useTls
? (finalOptions.httpsPort || 443)
: (finalOptions.httpPort || 80),
domains,
path: normalizedPath
};
// Create route action
const action = {
type: 'forward',
targets: [target],
websocket: {
enabled: true,
pingInterval: finalOptions.pingInterval || 30000, // 30 seconds
pingTimeout: finalOptions.pingTimeout || 5000 // 5 seconds
}
};
// Add TLS configuration if using HTTPS
if (finalOptions.useTls) {
action.tls = {
mode: 'terminate',
certificate: finalOptions.certificate || 'auto'
};
}
// Create the route config
return {
match,
action,
name: finalOptions.name || `WebSocket Route ${normalizedPath} for ${Array.isArray(domains) ? domains.join(', ') : domains}`,
priority: finalOptions.priority || 100, // Higher priority for WebSocket routes
...finalOptions
};
}
/**
* Create a helper function that applies a port offset
* @param offset The offset to apply to the matched port
* @returns A function that adds the offset to the matched port
*/
export function createPortOffset(offset) {
return (context) => context.port + offset;
}
/**
* Create a port mapping route with context-based port function
* @param options Port mapping route options
* @returns Route configuration object
*/
export function createPortMappingRoute(options) {
// Create route match
const match = {
ports: options.sourcePortRange,
domains: options.domains
};
// Create route action
const action = {
type: 'forward',
targets: [{
host: options.targetHost,
port: options.portMapper
}]
};
// Create the route config
return {
match,
action,
name: options.name || `Port Mapping Route for ${options.domains || 'all domains'}`,
priority: options.priority,
...options
};
}
/**
* Create a simple offset port mapping route
* @param options Offset port mapping route options
* @returns Route configuration object
*/
export function createOffsetPortMappingRoute(options) {
return createPortMappingRoute({
sourcePortRange: options.ports,
targetHost: options.targetHost,
portMapper: (context) => context.port + options.offset,
name: options.name || `Offset Mapping (${options.offset > 0 ? '+' : ''}${options.offset}) for ${options.domains || 'all domains'}`,
domains: options.domains,
priority: options.priority,
...options
});
}
/**
* Create a dynamic route with context-based host and port mapping
* @param options Dynamic route options
* @returns Route configuration object
*/
export function createDynamicRoute(options) {
// Create route match
const match = {
ports: options.ports,
domains: options.domains,
path: options.path,
clientIp: options.clientIp
};
// Create route action
const action = {
type: 'forward',
targets: [{
host: options.targetHost,
port: options.portMapper
}]
};
// Create the route config
return {
match,
action,
name: options.name || `Dynamic Route for ${options.domains || 'all domains'}`,
priority: options.priority,
...options
};
}
/**
* Create a smart load balancer with dynamic domain-based backend selection
* @param options Smart load balancer options
* @returns Route configuration object
*/
export function createSmartLoadBalancer(options) {
// Extract all domain keys to create the match criteria
const domains = Object.keys(options.domainTargets);
// Create the smart host selector function
const hostSelector = (context) => {
const domain = context.domain || '';
return options.domainTargets[domain] || options.defaultTarget || 'localhost';
};
// Create route match
const match = {
ports: options.ports,
domains
};
// Create route action
const action = {
type: 'forward',
targets: [{
host: hostSelector,
port: options.portMapper
}]
};
// Create the route config
return {
match,
action,
name: options.name || `Smart Load Balancer for ${domains.join(', ')}`,
priority: options.priority,
...options
};
}
/**
* Create an NFTables-based route for high-performance packet forwarding
* @param nameOrDomains Name or domain(s) to match
* @param target Target host and port
* @param options Additional route options
* @returns Route configuration object
*/
export function createNfTablesRoute(nameOrDomains, target, options = {}) {
// Determine if this is a name or domain
let name;
let domains;
if (Array.isArray(nameOrDomains) || (typeof nameOrDomains === 'string' && nameOrDomains.includes('.'))) {
domains = nameOrDomains;
name = Array.isArray(nameOrDomains) ? nameOrDomains[0] : nameOrDomains;
}
else {
name = nameOrDomains;
domains = undefined; // No domains
}
// Create route match
const match = {
domains,
ports: options.ports || 80
};
// Create route action
const action = {
type: 'forward',
targets: [{
host: target.host,
port: target.port
}],
forwardingEngine: 'nftables',
nftables: {
protocol: options.protocol || 'tcp',
preserveSourceIP: options.preserveSourceIP,
maxRate: options.maxRate,
priority: options.priority,
tableName: options.tableName,
useIPSets: options.useIPSets,
useAdvancedNAT: options.useAdvancedNAT
}
};
// Add TLS options if needed
if (options.useTls) {
action.tls = {
mode: 'passthrough'
};
}
// Create the route config
const routeConfig = {
name,
match,
action
};
// Add security if allowed or blocked IPs are specified
if (options.ipAllowList?.length || options.ipBlockList?.length) {
routeConfig.security = {
ipAllowList: options.ipAllowList,
ipBlockList: options.ipBlockList
};
}
return routeConfig;
}
/**
* Create an NFTables-based TLS termination route
* @param nameOrDomains Name or domain(s) to match
* @param target Target host and port
* @param options Additional route options
* @returns Route configuration object
*/
export function createNfTablesTerminateRoute(nameOrDomains, target, options = {}) {
// Create basic NFTables route
const route = createNfTablesRoute(nameOrDomains, target, {
...options,
ports: options.ports || 443,
useTls: false
});
// Set TLS termination
route.action.tls = {
mode: 'terminate',
certificate: options.certificate || 'auto'
};
return route;
}
/**
* Create a complete NFTables-based HTTPS setup with HTTP redirect
* @param nameOrDomains Name or domain(s) to match
* @param target Target host and port
* @param options Additional route options
* @returns Array of two route configurations (HTTPS and HTTP redirect)
*/
export function createCompleteNfTablesHttpsServer(nameOrDomains, target, options = {}) {
// Create the HTTPS route using NFTables
const httpsRoute = createNfTablesTerminateRoute(nameOrDomains, target, {
...options,
ports: options.httpsPort || 443
});
// Determine the domain(s) for HTTP redirect
const domains = typeof nameOrDomains === 'string' && !nameOrDomains.includes('.')
? undefined
: nameOrDomains;
// Extract the HTTPS port for the redirect destination
const httpsPort = typeof options.httpsPort === 'number'
? options.httpsPort
: Array.isArray(options.httpsPort) && typeof options.httpsPort[0] === 'number'
? options.httpsPort[0]
: 443;
// Create the HTTP redirect route (this uses standard forwarding, not NFTables)
const httpRedirectRoute = createHttpToHttpsRedirect(domains, // Type cast needed since domains can be undefined now
httpsPort, {
match: {
ports: options.httpPort || 80,
domains: domains // Type cast needed since domains can be undefined now
},
name: `HTTP to HTTPS Redirect for ${Array.isArray(domains) ? domains.join(', ') : domains || 'all domains'}`
});
return [httpsRoute, httpRedirectRoute];
}
/**
* Create a socket handler route configuration
* @param domains Domain(s) to match
* @param ports Port(s) to listen on
* @param handler Socket handler function
* @param options Additional route options
* @returns Route configuration object
*/
export function createSocketHandlerRoute(domains, ports, handler, options = {}) {
return {
name: options.name || 'socket-handler-route',
priority: options.priority !== undefined ? options.priority : 50,
match: {
domains,
ports,
...(options.path && { path: options.path })
},
action: {
type: 'socket-handler',
socketHandler: handler
}
};
}
/**
* Pre-built socket handlers for common use cases
*/
export const SocketHandlers = {
/**
* Simple echo server handler
*/
echo: (socket, context) => {
socket.write('ECHO SERVER READY\n');
socket.on('data', data => socket.write(data));
},
/**
* TCP proxy handler
*/
proxy: (targetHost, targetPort) => (socket, context) => {
const target = plugins.net.connect(targetPort, targetHost);
socket.pipe(target);
target.pipe(socket);
socket.on('close', () => target.destroy());
target.on('close', () => socket.destroy());
target.on('error', (err) => {
console.error('Proxy target error:', err);
socket.destroy();
});
},
/**
* Line-based protocol handler
*/
lineProtocol: (handler) => (socket, context) => {
let buffer = '';
socket.on('data', (data) => {
buffer += data.toString();
const lines = buffer.split('\n');
buffer = lines.pop() || '';
lines.forEach(line => {
if (line.trim()) {
handler(line.trim(), socket);
}
});
});
},
/**
* Simple HTTP response handler (for testing)
*/
httpResponse: (statusCode, body) => (socket, context) => {
const response = [
`HTTP/1.1 ${statusCode} ${statusCode === 200 ? 'OK' : 'Error'}`,
'Content-Type: text/plain',
`Content-Length: ${body.length}`,
'Connection: close',
'',
body
].join('\r\n');
socket.write(response);
socket.end();
},
/**
* Block connection immediately
*/
block: (message) => (socket, context) => {
const finalMessage = message || `Connection blocked from ${context.clientIp}`;
if (finalMessage) {
socket.write(finalMessage);
}
socket.end();
},
/**
* HTTP block response
*/
httpBlock: (statusCode = 403, message) => (socket, context) => {
const defaultMessage = `Access forbidden for ${context.domain || context.clientIp}`;
const finalMessage = message || defaultMessage;
const response = [
`HTTP/1.1 ${statusCode} ${finalMessage}`,
'Content-Type: text/plain',
`Content-Length: ${finalMessage.length}`,
'Connection: close',
'',
finalMessage
].join('\r\n');
socket.write(response);
socket.end();
},
/**
* HTTP redirect handler
* Now uses the centralized detection module for HTTP parsing
*/
httpRedirect: (locationTemplate, statusCode = 301) => (socket, context) => {
const connectionId = ProtocolDetector.createConnectionId({
socketId: context.connectionId || `${Date.now()}-${Math.random()}`
});
socket.once('data', async (data) => {
// Use detection module for parsing
const detectionResult = await ProtocolDetector.detectWithConnectionTracking(data, connectionId, { extractFullHeaders: false } // We only need method and path
);
if (detectionResult.protocol === 'http' && detectionResult.connectionInfo.path) {
const method = detectionResult.connectionInfo.method || 'GET';
const path = detectionResult.connectionInfo.path || '/';
const domain = context.domain || 'localhost';
const port = context.port;
let finalLocation = locationTemplate
.replace('{domain}', domain)
.replace('{port}', String(port))
.replace('{path}', path)
.replace('{clientIp}', context.clientIp);
const message = `Redirecting to ${finalLocation}`;
const response = [
`HTTP/1.1 ${statusCode} ${statusCode === 301 ? 'Moved Permanently' : 'Found'}`,
`Location: ${finalLocation}`,
'Content-Type: text/plain',
`Content-Length: ${message.length}`,
'Connection: close',
'',
message
].join('\r\n');
socket.write(response);
}
else {
// Not a valid HTTP request, close connection
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
}
socket.end();
// Clean up detection state
ProtocolDetector.cleanupConnections();
});
},
/**
* HTTP server handler for ACME challenges and other HTTP needs
* Now uses the centralized detection module for HTTP parsing
*/
httpServer: (handler) => (socket, context) => {
let requestParsed = false;
const connectionId = ProtocolDetector.createConnectionId({
socketId: context.connectionId || `${Date.now()}-${Math.random()}`
});
const processData = async (data) => {
if (requestParsed)
return; // Only handle the first request
// Use HttpDetector for parsing
const detectionResult = await ProtocolDetector.detectWithConnectionTracking(data, connectionId, { extractFullHeaders: true });
if (detectionResult.protocol !== 'http' || !detectionResult.isComplete) {
// Not a complete HTTP request yet
return;
}
requestParsed = true;
const connInfo = detectionResult.connectionInfo;
// Create request object from detection result
const req = {
method: connInfo.method || 'GET',
url: connInfo.path || '/',
headers: connInfo.headers || {},
body: detectionResult.remainingBuffer?.toString() || ''
};
// Create response object
let statusCode = 200;
const responseHeaders = {};
let ended = false;
const res = {
status: (code) => {
statusCode = code;
},
header: (name, value) => {
responseHeaders[name] = value;
},
send: (data) => {
if (ended)
return;
ended = true;
if (!responseHeaders['content-type']) {
responseHeaders['content-type'] = 'text/plain';
}
responseHeaders['content-length'] = String(data.length);
responseHeaders['connection'] = 'close';
const statusText = statusCode === 200 ? 'OK' :
statusCode === 404 ? 'Not Found' :
statusCode === 500 ? 'Internal Server Error' : 'Response';
let response = `HTTP/1.1 ${statusCode} ${statusText}\r\n`;
for (const [name, value] of Object.entries(responseHeaders)) {
response += `${name}: ${value}\r\n`;
}
response += '\r\n';
response += data;
socket.write(response);
socket.end();
},
end: () => {
if (ended)
return;
ended = true;
socket.write('HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n');
socket.end();
}
};
try {
handler(req, res);
// Ensure response is sent even if handler doesn't call send()
setTimeout(() => {
if (!ended) {
res.send('');
}
}, 1000);
}
catch (error) {
if (!ended) {
res.status(500);
res.send('Internal Server Error');
}
}
};
socket.on('data', processData);
socket.on('error', () => {
if (!requestParsed) {
socket.end();
}
});
socket.on('close', () => {
// Clean up detection state
ProtocolDetector.cleanupConnections();
});
}
};
/**
* Create an API Gateway route pattern
* @param domains Domain(s) to match
* @param apiBasePath Base path for API endpoints (e.g., '/api')
* @param target Target host and port
* @param options Additional route options
* @returns API route configuration
*/
export function createApiGatewayRoute(domains, apiBasePath, target, options = {}) {
// Normalize apiBasePath to ensure it starts with / and doesn't end with /
const normalizedPath = apiBasePath.startsWith('/')
? apiBasePath
: `/${apiBasePath}`;
// Add wildcard to path to match all API endpoints
const apiPath = normalizedPath.endsWith('/')
? `${normalizedPath}*`
: `${normalizedPath}/*`;
// Create base route
const baseRoute = options.useTls
? createHttpsTerminateRoute(domains, target, {
certificate: options.certificate || 'auto'
})
: createHttpRoute(domains, target);
// Add API-specific configurations
const apiRoute = {
match: {
...baseRoute.match,
path: apiPath
},
name: options.name || `API Gateway: ${apiPath} -> ${Array.isArray(target.host) ? target.host.join(', ') : target.host}:${target.port}`,
priority: options.priority || 100 // Higher priority for specific path matching
};
// Add CORS headers if requested
if (options.addCorsHeaders) {
apiRoute.headers = {
response: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400'
}
};
}
return mergeRouteConfigs(baseRoute, apiRoute);
}
/**
* Create a rate limiting route pattern
* @param baseRoute Base route to add rate limiting to
* @param rateLimit Rate limiting configuration
* @returns Route with rate limiting
*/
export function addRateLimiting(baseRoute, rateLimit) {
return mergeRouteConfigs(baseRoute, {
security: {
rateLimit: {
enabled: true,
maxRequests: rateLimit.maxRequests,
window: rateLimit.window,
keyBy: rateLimit.keyBy || 'ip',
headerName: rateLimit.headerName,
errorMessage: rateLimit.errorMessage || 'Rate limit exceeded. Please try again later.'
}
}
});
}
/**
* Create a basic authentication route pattern
* @param baseRoute Base route to add authentication to
* @param auth Authentication configuration
* @returns Route with basic authentication
*/
export function addBasicAuth(baseRoute, auth) {
return mergeRouteConfigs(baseRoute, {
security: {
basicAuth: {
enabled: true,
users: auth.users,
realm: auth.realm || 'Restricted Area',
excludePaths: auth.excludePaths || []
}
}
});
}
/**
* Create a JWT authentication route pattern
* @param baseRoute Base route to add JWT authentication to
* @param jwt JWT authentication configuration
* @returns Route with JWT authentication
*/
export function addJwtAuth(baseRoute, jwt) {
return mergeRouteConfigs(baseRoute, {
security: {
jwtAuth: {
enabled: true,
secret: jwt.secret,
algorithm: jwt.algorithm || 'HS256',
issuer: jwt.issuer,
audience: jwt.audience,
expiresIn: jwt.expiresIn,
excludePaths: jwt.excludePaths || []
}
}
});
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUtaGVscGVycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3RzL3Byb3hpZXMvc21hcnQtcHJveHkvdXRpbHMvcm91dGUtaGVscGVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBa0JHO0FBRUgsT0FBTyxLQUFLLE9BQU8sTUFBTSxxQkFBcUIsQ0FBQztBQUUvQyxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUNyRCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsWUFBWSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFFN0U7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLGVBQWUsQ0FDN0IsT0FBMEIsRUFDMUIsTUFBaUQsRUFDakQsVUFBaUMsRUFBRTtJQUVuQyxxQkFBcUI7SUFDckIsTUFBTSxLQUFLLEdBQWdCO1FBQ3pCLEtBQUssRUFBRSxPQUFPLENBQUMsS0FBSyxFQUFFLEtBQUssSUFBSSxFQUFFO1FBQ2pDLE9BQU87S0FDUixDQUFDO0lBRUYsc0JBQXNCO0lBQ3RCLE1BQU0sTUFBTSxHQUFpQjtRQUMzQixJQUFJLEVBQUUsU0FBUztRQUNmLE9BQU8sRUFBRSxDQUFDLE1BQU0sQ0FBQztLQUNsQixDQUFDO0lBRUYsMEJBQTBCO0lBQzFCLE9BQU87UUFDTCxLQUFLO1FBQ0wsTUFBTTtRQUNOLElBQUksRUFBRSxPQUFPLENBQUMsSUFBSSxJQUFJLGtCQUFrQixLQUFLLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLEVBQUU7UUFDL0YsR0FBRyxPQUFPO0tBQ1gsQ0FBQztBQUNKLENBQUM7QUFFRDs7Ozs7O0dBTUc7QUFDSCxNQUFNLFVBQVUseUJBQXlCLENBQ3ZDLE9BQTBCLEVBQzFCLE1BQWlELEVBQ2pELFVBT0ksRUFBRTtJQUVOLHFC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