"use strict";
/**
 * Token Authentication Core Module
 * Issue #331: Token authentication and HTTPS support
 *
 * CONSTRAINTS:
 * - C001: No Next.js module dependencies (next/headers, next/server, etc.)
 *   This module must be compatible with CLI build (tsconfig.cli.json)
 * - S001: Token verification uses crypto.timingSafeEqual() (timing-safe comparison)
 * - S002: AUTH_EXCLUDED_PATHS matching uses === (exact match, no startsWith)
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_COOKIE_MAX_AGE_SECONDS = exports.RATE_LIMIT_CONFIG = exports.isValidTokenHash = exports.DEFAULT_EXPIRE_DURATION_MS = exports.computeExpireAt = exports.parseDuration = exports.AUTH_EXCLUDED_PATHS = exports.AUTH_COOKIE_NAME = void 0;
exports.generateToken = generateToken;
exports.hashToken = hashToken;
exports.verifyToken = verifyToken;
exports.parseCookies = parseCookies;
exports.isAuthEnabled = isAuthEnabled;
exports.getTokenMaxAge = getTokenMaxAge;
exports.isHttpsEnabled = isHttpsEnabled;
exports.buildAuthCookieOptions = buildAuthCookieOptions;
exports.createRateLimiter = createRateLimiter;
const crypto_1 = __importDefault(require("crypto"));
// Import and re-export shared constants and functions from Edge Runtime-compatible config
const auth_config_1 = require("../../config/auth-config");
Object.defineProperty(exports, "AUTH_COOKIE_NAME", { enumerable: true, get: function () { return auth_config_1.AUTH_COOKIE_NAME; } });
Object.defineProperty(exports, "AUTH_EXCLUDED_PATHS", { enumerable: true, get: function () { return auth_config_1.AUTH_EXCLUDED_PATHS; } });
Object.defineProperty(exports, "parseDuration", { enumerable: true, get: function () { return auth_config_1.parseDuration; } });
Object.defineProperty(exports, "computeExpireAt", { enumerable: true, get: function () { return auth_config_1.computeExpireAt; } });
Object.defineProperty(exports, "DEFAULT_EXPIRE_DURATION_MS", { enumerable: true, get: function () { return auth_config_1.DEFAULT_EXPIRE_DURATION_MS; } });
Object.defineProperty(exports, "isValidTokenHash", { enumerable: true, get: function () { return auth_config_1.isValidTokenHash; } });
/** Rate limiting configuration for brute-force protection */
exports.RATE_LIMIT_CONFIG = {
    /** Maximum failed attempts before lockout */
    maxAttempts: 5,
    /** Lockout duration in ms (15 minutes) */
    lockoutDuration: 15 * 60 * 1000,
    /** Cleanup interval in ms (1 hour) */
    cleanupInterval: 60 * 60 * 1000,
};
/** Fallback cookie maxAge in seconds when no explicit expiry is set (24 hours) */
exports.DEFAULT_COOKIE_MAX_AGE_SECONDS = 24 * 60 * 60;
// ============================================================
// Module-level state (initialized from env at import time)
// ============================================================
/** The stored hash of the authentication token */
const storedTokenHash = (() => {
    const hash = process.env.CM_AUTH_TOKEN_HASH || undefined;
    if (!hash)
        return undefined;
    // Store validation result to avoid type predicate narrowing to 'never' in else branch
    const valid = (0, auth_config_1.isValidTokenHash)(hash);
    if (!valid) {
        // C001: Cannot use logger (CLI build compatibility) - use console.error
        console.error('[auth] CM_AUTH_TOKEN_HASH is not a valid SHA-256 hash');
        return undefined;
    }
    return hash;
})();
/** Token expiration timestamp (ms since epoch) */
const expireAt = (0, auth_config_1.computeExpireAt)();
// ============================================================
// Token Functions
// ============================================================
/**
 * Generate a cryptographically secure random token
 * @returns 64-character hex string (32 bytes of entropy)
 */
function generateToken() {
    return crypto_1.default.randomBytes(32).toString('hex');
}
/**
 * Hash a token using SHA-256
 * @param token - The plain text token to hash
 * @returns 64-character hex string (SHA-256 hash)
 */
function hashToken(token) {
    return crypto_1.default.createHash('sha256').update(token).digest('hex');
}
/**
 * Verify a token against the stored hash
 * S001: Uses crypto.timingSafeEqual() for timing-safe comparison
 *
 * @param token - The plain text token to verify
 * @returns true if the token is valid and not expired
 */
function verifyToken(token) {
    if (!storedTokenHash) {
        return false;
    }
    // Guard against undefined/null/empty token
    if (!token || typeof token !== 'string') {
        return false;
    }
    // Check expiration
    if (expireAt !== null && Date.now() > expireAt) {
        return false;
    }
    const tokenHash = hashToken(token);
    // S001: timing-safe comparison to prevent timing attacks
    const hashBuffer = Buffer.from(tokenHash, 'hex');
    const storedBuffer = Buffer.from(storedTokenHash, 'hex');
    if (hashBuffer.length !== storedBuffer.length) {
        return false;
    }
    return crypto_1.default.timingSafeEqual(hashBuffer, storedBuffer);
}
// ============================================================
// Cookie Parsing (for WebSocket authentication)
// ============================================================
/**
 * Parse a Cookie header string into key-value pairs
 * Used by WebSocket upgrade handler where next/headers is not available
 *
 * @param cookieHeader - Raw Cookie header string
 * @returns Parsed cookies as Record<string, string>
 */
function parseCookies(cookieHeader) {
    const cookies = {};
    if (!cookieHeader) {
        return cookies;
    }
    const pairs = cookieHeader.split(';');
    for (const pair of pairs) {
        const eqIndex = pair.indexOf('=');
        if (eqIndex === -1) {
            continue;
        }
        const name = pair.substring(0, eqIndex).trim();
        const value = pair.substring(eqIndex + 1).trim();
        if (name) {
            cookies[name] = value;
        }
    }
    return cookies;
}
// ============================================================
// Auth State Functions
// ============================================================
/**
 * Check if authentication is enabled.
 * Returns true only when CM_AUTH_TOKEN_HASH is set AND passes format validation.
 * This prevents the state where auth appears enabled but login is impossible
 * (e.g., when the hash value is malformed).
 */
function isAuthEnabled() {
    return !!storedTokenHash;
}
/**
 * Calculate the Cookie maxAge in seconds (remaining token lifetime)
 * @returns maxAge in seconds, or 0 if expired/no expiry
 */
function getTokenMaxAge() {
    if (expireAt === null) {
        return 0;
    }
    const remaining = expireAt - Date.now();
    if (remaining <= 0) {
        return 0;
    }
    return Math.floor(remaining / 1000);
}
/**
 * Check if HTTPS is enabled based on certificate environment variable
 * @returns true if CM_HTTPS_CERT is set (indicating TLS certificates are configured)
 */
function isHttpsEnabled() {
    return !!process.env.CM_HTTPS_CERT;
}
/**
 * Build authentication cookie options with consistent security settings.
 * Centralizes cookie configuration to enforce HttpOnly, SameSite, and Secure flags.
 *
 * @param maxAge - Cookie max age in seconds. Use 0 to clear the cookie.
 * @returns Cookie options object compatible with Next.js response.cookies.set()
 */
function buildAuthCookieOptions(maxAge) {
    return {
        httpOnly: true,
        sameSite: 'strict',
        secure: isHttpsEnabled(),
        maxAge,
        path: '/',
    };
}
/**
 * Create a rate limiter for brute-force protection
 * Uses in-memory Map with periodic cleanup
 *
 * @returns RateLimiter instance with checkLimit, recordFailure, recordSuccess, destroy
 */
function createRateLimiter() {
    const entries = new Map();
    // Periodic cleanup of expired entries
    const cleanupTimer = setInterval(() => {
        const now = Date.now();
        for (const [ip, entry] of entries) {
            // Remove entries whose lockout has expired (or was never set)
            // and whose last attempt is older than the lockout duration
            const isLockoutExpired = entry.lockedUntil === null || now > entry.lockedUntil;
            const isStale = now - entry.lastAttempt > exports.RATE_LIMIT_CONFIG.lockoutDuration;
            if (isLockoutExpired && isStale) {
                entries.delete(ip);
            }
        }
    }, exports.RATE_LIMIT_CONFIG.cleanupInterval);
    // Ensure timer doesn't prevent process exit
    if (cleanupTimer.unref) {
        cleanupTimer.unref();
    }
    return {
        checkLimit(ip) {
            const entry = entries.get(ip);
            if (!entry) {
                return { allowed: true };
            }
            // Check if lockout has expired
            if (entry.lockedUntil !== null) {
                const now = Date.now();
                if (now < entry.lockedUntil) {
                    const retryAfter = Math.ceil((entry.lockedUntil - now) / 1000);
                    return { allowed: false, retryAfter };
                }
                // Lockout expired - reset entry
                entry.attempts = 0;
                entry.lockedUntil = null;
            }
            return { allowed: true };
        },
        recordFailure(ip) {
            const now = Date.now();
            const entry = entries.get(ip) || { attempts: 0, lockedUntil: null, lastAttempt: now };
            entry.attempts++;
            entry.lastAttempt = now;
            if (entry.attempts >= exports.RATE_LIMIT_CONFIG.maxAttempts) {
                entry.lockedUntil = now + exports.RATE_LIMIT_CONFIG.lockoutDuration;
            }
            entries.set(ip, entry);
        },
        recordSuccess(ip) {
            entries.delete(ip);
        },
        destroy() {
            clearInterval(cleanupTimer);
            entries.clear();
        },
    };
}
