import * as plugins from '../../plugins.js';
import { isIPAuthorized, checkMaxConnections, checkConnectionRate, trackConnection, removeConnection, cleanupExpiredRateLimits, parseBasicAuthHeader, normalizeIP } from './security-utils.js';
/**
 * Shared SecurityManager for use across proxy components
 * Handles IP tracking, rate limiting, and authentication
 */
export class SharedSecurityManager {
    /**
     * Create a new SharedSecurityManager
     *
     * @param options - Configuration options
     * @param logger - Logger instance
     */
    constructor(options, logger) {
        this.logger = logger;
        // IP connection tracking
        this.connectionsByIP = new Map();
        // Route-specific rate limiting
        this.rateLimits = new Map();
        // Cache IP filtering results to avoid constant regex matching
        this.ipFilterCache = new Map();
        // Cache cleanup interval
        this.cleanupInterval = null;
        this.maxConnectionsPerIP = options.maxConnectionsPerIP || 100;
        this.connectionRateLimitPerMinute = options.connectionRateLimitPerMinute || 300;
        // Set up logger with defaults if not provided
        this.logger = logger || {
            info: console.log,
            warn: console.warn,
            error: console.error
        };
        // Set up cache cleanup interval
        const cleanupInterval = options.cleanupIntervalMs || 60000; // Default: 1 minute
        this.cleanupInterval = setInterval(() => {
            this.cleanupCaches();
        }, cleanupInterval);
        // Don't keep the process alive just for cleanup
        if (this.cleanupInterval.unref) {
            this.cleanupInterval.unref();
        }
    }
    /**
     * Get connections count by IP
     *
     * @param ip - The IP address to check
     * @returns Number of connections from this IP
     */
    getConnectionCountByIP(ip) {
        // Check all normalized variants of the IP
        const variants = normalizeIP(ip);
        for (const variant of variants) {
            const info = this.connectionsByIP.get(variant);
            if (info) {
                return info.connections.size;
            }
        }
        return 0;
    }
    /**
     * Track connection by IP
     *
     * @param ip - The IP address to track
     * @param connectionId - The connection ID to associate
     */
    trackConnectionByIP(ip, connectionId) {
        // Check if any variant already exists
        const variants = normalizeIP(ip);
        let existingKey = null;
        for (const variant of variants) {
            if (this.connectionsByIP.has(variant)) {
                existingKey = variant;
                break;
            }
        }
        // Use existing key or the original IP
        trackConnection(existingKey || ip, connectionId, this.connectionsByIP);
    }
    /**
     * Remove connection tracking for an IP
     *
     * @param ip - The IP address to update
     * @param connectionId - The connection ID to remove
     */
    removeConnectionByIP(ip, connectionId) {
        // Check all variants to find where the connection is tracked
        const variants = normalizeIP(ip);
        for (const variant of variants) {
            if (this.connectionsByIP.has(variant)) {
                removeConnection(variant, connectionId, this.connectionsByIP);
                break;
            }
        }
    }
    /**
     * Check if IP is authorized based on route security settings
     *
     * @param ip - The IP address to check
     * @param allowedIPs - List of allowed IP patterns
     * @param blockedIPs - List of blocked IP patterns
     * @returns Whether the IP is authorized
     */
    isIPAuthorized(ip, allowedIPs = ['*'], blockedIPs = []) {
        return isIPAuthorized(ip, allowedIPs, blockedIPs);
    }
    /**
     * Validate IP against rate limits and connection limits
     *
     * @param ip - The IP address to validate
     * @returns Result with allowed status and reason if blocked
     */
    validateIP(ip) {
        // Check connection count limit
        const connectionResult = checkMaxConnections(ip, this.connectionsByIP, this.maxConnectionsPerIP);
        if (!connectionResult.allowed) {
            return connectionResult;
        }
        // Check connection rate limit
        const rateResult = checkConnectionRate(ip, this.connectionsByIP, this.connectionRateLimitPerMinute);
        if (!rateResult.allowed) {
            return rateResult;
        }
        return { allowed: true };
    }
    /**
     * Atomically validate an IP and track the connection if allowed.
     * This prevents race conditions where concurrent connections could bypass per-IP limits.
     *
     * @param ip - The IP address to validate
     * @param connectionId - The connection ID to track if validation passes
     * @returns Object with validation result and reason
     */
    validateAndTrackIP(ip, connectionId) {
        // Check connection count limit BEFORE tracking
        const connectionResult = checkMaxConnections(ip, this.connectionsByIP, this.maxConnectionsPerIP);
        if (!connectionResult.allowed) {
            return connectionResult;
        }
        // Check connection rate limit
        const rateResult = checkConnectionRate(ip, this.connectionsByIP, this.connectionRateLimitPerMinute);
        if (!rateResult.allowed) {
            return rateResult;
        }
        // Validation passed - immediately track to prevent race conditions
        this.trackConnectionByIP(ip, connectionId);
        return { allowed: true };
    }
    /**
     * Check if a client is allowed to access a specific route
     *
     * @param route - The route to check
     * @param context - The request context
     * @param routeConnectionCount - Current connection count for this route (optional)
     * @returns Whether access is allowed
     */
    isAllowed(route, context, routeConnectionCount) {
        if (!route.security) {
            return true; // No security restrictions
        }
        // --- IP filtering ---
        if (!this.isClientIpAllowed(route, context.clientIp)) {
            this.logger?.debug?.(`IP ${context.clientIp} is blocked for route ${route.name || 'unnamed'}`);
            return false;
        }
        // --- Route-level connection limit ---
        if (route.security.maxConnections !== undefined && routeConnectionCount !== undefined) {
            if (routeConnectionCount >= route.security.maxConnections) {
                this.logger?.debug?.(`Route connection limit (${route.security.maxConnections}) exceeded for route ${route.name || 'unnamed'}`);
                return false;
            }
        }
        // --- Rate limiting ---
        if (route.security.rateLimit?.enabled && !this.isWithinRateLimit(route, context)) {
            this.logger?.debug?.(`Rate limit exceeded for route ${route.name || 'unnamed'}`);
            return false;
        }
        return true;
    }
    /**
     * Check if a client IP is allowed for a route
     *
     * @param route - The route to check
     * @param clientIp - The client IP
     * @returns Whether the IP is allowed
     */
    isClientIpAllowed(route, clientIp) {
        if (!route.security) {
            return true; // No security restrictions
        }
        const routeId = route.id || route.name || 'unnamed';
        // Check cache first
        if (!this.ipFilterCache.has(routeId)) {
            this.ipFilterCache.set(routeId, new Map());
        }
        const routeCache = this.ipFilterCache.get(routeId);
        if (routeCache.has(clientIp)) {
            return routeCache.get(clientIp);
        }
        // Check IP against route security settings
        const ipAllowList = route.security.ipAllowList;
        const ipBlockList = route.security.ipBlockList;
        const allowed = this.isIPAuthorized(clientIp, ipAllowList, ipBlockList);
        // Cache the result
        routeCache.set(clientIp, allowed);
        return allowed;
    }
    /**
     * Check if request is within rate limit
     *
     * @param route - The route to check
     * @param context - The request context
     * @returns Whether the request is within rate limit
     */
    isWithinRateLimit(route, context) {
        if (!route.security?.rateLimit?.enabled) {
            return true;
        }
        const rateLimit = route.security.rateLimit;
        const routeId = route.id || route.name || 'unnamed';
        // Determine rate limit key (by IP, path, or header)
        let key = context.clientIp; // Default to IP
        if (rateLimit.keyBy === 'path' && context.path) {
            key = `${context.clientIp}:${context.path}`;
        }
        else if (rateLimit.keyBy === 'header' && rateLimit.headerName && context.headers) {
            const headerValue = context.headers[rateLimit.headerName.toLowerCase()];
            if (headerValue) {
                key = `${context.clientIp}:${headerValue}`;
            }
        }
        // Get or create rate limit tracking for this route
        if (!this.rateLimits.has(routeId)) {
            this.rateLimits.set(routeId, new Map());
        }
        const routeLimits = this.rateLimits.get(routeId);
        const now = Date.now();
        // Get or create rate limit tracking for this key
        let limit = routeLimits.get(key);
        if (!limit || limit.expiry < now) {
            // Create new rate limit or reset expired one
            limit = {
                count: 1,
                expiry: now + (rateLimit.window * 1000)
            };
            routeLimits.set(key, limit);
            return true;
        }
        // Increment the counter
        limit.count++;
        // Check if rate limit is exceeded
        return limit.count <= rateLimit.maxRequests;
    }
    /**
     * Validate HTTP Basic Authentication
     *
     * @param route - The route to check
     * @param authHeader - The Authorization header
     * @returns Whether authentication is valid
     */
    validateBasicAuth(route, authHeader) {
        // Skip if basic auth not enabled for route
        if (!route.security?.basicAuth?.enabled) {
            return true;
        }
        // No auth header means auth failed
        if (!authHeader) {
            return false;
        }
        // Parse auth header
        const credentials = parseBasicAuthHeader(authHeader);
        if (!credentials) {
            return false;
        }
        // Check credentials against configured users
        const { username, password } = credentials;
        const users = route.security.basicAuth.users;
        return users.some(user => user.username === username && user.password === password);
    }
    /**
     * Verify a JWT token against route configuration
     *
     * @param route - The route to verify the token for
     * @param token - The JWT token to verify
     * @returns True if the token is valid, false otherwise
     */
    verifyJwtToken(route, token) {
        if (!route.security?.jwtAuth?.enabled) {
            return true;
        }
        try {
            const jwtAuth = route.security.jwtAuth;
            // Verify structure (header.payload.signature)
            const parts = token.split('.');
            if (parts.length !== 3) {
                return false;
            }
            // Decode payload
            const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
            // Check expiration
            if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {
                return false;
            }
            // Check issuer
            if (jwtAuth.issuer && payload.iss !== jwtAuth.issuer) {
                return false;
            }
            // Check audience
            if (jwtAuth.audience && payload.aud !== jwtAuth.audience) {
                return false;
            }
            // Note: In a real implementation, you'd also verify the signature
            // using the secret and algorithm specified in jwtAuth.
            // This requires a proper JWT library for cryptographic verification.
            return true;
        }
        catch (err) {
            this.logger?.error?.(`Error verifying JWT: ${err}`);
            return false;
        }
    }
    /**
     * Clean up caches to prevent memory leaks
     */
    cleanupCaches() {
        // Clean up rate limits
        cleanupExpiredRateLimits(this.rateLimits, this.logger);
        // Clean up IP connection tracking
        let cleanedIPs = 0;
        for (const [ip, info] of this.connectionsByIP.entries()) {
            // Remove IPs with no active connections and no recent timestamps
            if (info.connections.size === 0 && info.timestamps.length === 0) {
                this.connectionsByIP.delete(ip);
                cleanedIPs++;
            }
        }
        if (cleanedIPs > 0 && this.logger?.debug) {
            this.logger.debug(`Cleaned up ${cleanedIPs} IPs with no active connections`);
        }
        // IP filter cache doesn't need cleanup (tied to routes)
    }
    /**
     * Clear all IP tracking data (for shutdown)
     */
    clearIPTracking() {
        this.connectionsByIP.clear();
        this.rateLimits.clear();
        this.ipFilterCache.clear();
        if (this.cleanupInterval) {
            clearInterval(this.cleanupInterval);
            this.cleanupInterval = null;
        }
    }
    /**
     * Update routes for security checking
     *
     * @param routes - New routes to use
     */
    setRoutes(routes) {
        // Only clear the IP filter cache - route-specific
        this.ipFilterCache.clear();
    }
}
//# sourceMappingURL=data:application/json;base64, 