"use strict";
/**
 * Dashboard Authentication Middleware
 * Session-based auth with httpOnly cookies for CCS dashboard.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.requireLocalAccessWhenAuthDisabled = exports.getDashboardWebSocketRejectionStatus = exports.isDashboardWebSocketUpgradeAllowed = exports.isDashboardWebSocketOriginAllowed = exports.isLoopbackRemoteAddress = exports.authMiddleware = exports.createSessionMiddleware = exports.loginRateLimiter = void 0;
const express_session_1 = __importDefault(require("express-session"));
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
const crypto_1 = __importDefault(require("crypto"));
const net = __importStar(require("net"));
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const config_loader_facade_1 = require("../../config/config-loader-facade");
/** Public paths that bypass auth (lowercase for case-insensitive matching) */
const PUBLIC_PATHS = ['/api/auth/login', '/api/auth/check', '/api/auth/setup', '/api/health'];
/** Path to persistent session secret file */
function getSessionSecretPath() {
    return path_1.default.join((0, config_loader_facade_1.getCcsDir)(), '.session-secret');
}
/**
 * Generate or retrieve persistent session secret.
 * Priority: ENV var > persisted file > generate new
 */
function getSessionSecret() {
    // 1. Check ENV var first
    if (process.env.CCS_SESSION_SECRET) {
        return process.env.CCS_SESSION_SECRET;
    }
    const secretPath = getSessionSecretPath();
    // 2. Try to read persisted secret
    try {
        if (fs_1.default.existsSync(secretPath)) {
            const secret = fs_1.default.readFileSync(secretPath, 'utf-8').trim();
            if (secret.length >= 32) {
                return secret;
            }
        }
    }
    catch {
        // Ignore read errors, generate new secret
    }
    // 3. Generate and persist new random secret
    const newSecret = crypto_1.default.randomBytes(32).toString('hex');
    try {
        const dir = path_1.default.dirname(secretPath);
        if (!fs_1.default.existsSync(dir)) {
            fs_1.default.mkdirSync(dir, { recursive: true });
        }
        fs_1.default.writeFileSync(secretPath, newSecret, { mode: 0o600 });
    }
    catch (err) {
        // Log warning - sessions won't persist across restarts
        console.warn('[!] Failed to persist session secret:', err.message);
    }
    return newSecret;
}
/**
 * Rate limiter for login attempts.
 * 5 attempts per 15 minutes per IP.
 */
exports.loginRateLimiter = (0, express_rate_limit_1.default)({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 5, // 5 attempts
    message: { error: 'Too many login attempts. Please try again later.' },
    standardHeaders: true,
    legacyHeaders: false,
    skip: () => !(0, config_loader_facade_1.isDashboardAuthEnabled)(),
});
/**
 * Create session middleware configured for CCS dashboard.
 */
function createSessionMiddleware() {
    const authConfig = (0, config_loader_facade_1.getDashboardAuthConfig)();
    const maxAge = (authConfig.session_timeout_hours ?? 24) * 60 * 60 * 1000;
    return (0, express_session_1.default)({
        secret: getSessionSecret(),
        resave: false,
        saveUninitialized: false,
        cookie: {
            secure: false, // Local CLI uses HTTP
            httpOnly: true,
            maxAge,
            sameSite: 'strict',
        },
    });
}
exports.createSessionMiddleware = createSessionMiddleware;
/**
 * Auth middleware that protects all routes except public paths.
 * Only active when dashboard_auth.enabled = true.
 */
function authMiddleware(req, res, next) {
    // Skip auth if disabled
    if (!(0, config_loader_facade_1.isDashboardAuthEnabled)()) {
        return next();
    }
    // Allow public paths (case-insensitive)
    const pathLower = req.path.toLowerCase();
    if (PUBLIC_PATHS.some((p) => pathLower.startsWith(p))) {
        return next();
    }
    // Allow static assets and SPA routes (non-API)
    if (!req.path.startsWith('/api/')) {
        return next();
    }
    // Check session
    if (req.session?.authenticated) {
        return next();
    }
    // Unauthorized
    res.status(401).json({ error: 'Authentication required' });
}
exports.authMiddleware = authMiddleware;
function isLoopbackRemoteAddress(value) {
    if (!value)
        return false;
    const normalized = value.trim().replace(/^\[|\]$/g, '');
    return (normalized === '::1' ||
        normalized === '127.0.0.1' ||
        normalized.startsWith('127.') ||
        normalized === '::ffff:127.0.0.1' ||
        normalized.startsWith('::ffff:127.'));
}
exports.isLoopbackRemoteAddress = isLoopbackRemoteAddress;
function isLoopbackHostname(value) {
    if (!value)
        return false;
    const normalized = value
        .trim()
        .toLowerCase()
        .replace(/^\[|\]$/g, '');
    return (normalized === 'localhost' ||
        normalized.endsWith('.localhost') ||
        (net.isIP(normalized) !== 0 && isLoopbackRemoteAddress(normalized)));
}
function getSingleHeader(value) {
    return Array.isArray(value) ? value[0] : value;
}
function parseHostHeader(value) {
    if (!value)
        return null;
    try {
        return new URL(`http://${value}`);
    }
    catch {
        return null;
    }
}
function isHttpOrigin(origin) {
    return origin.protocol === 'http:' || origin.protocol === 'https:';
}
function isDashboardWebSocketOriginAllowed(req) {
    const originHeader = getSingleHeader(req.headers.origin);
    if (!originHeader)
        return true;
    let origin;
    try {
        origin = new URL(originHeader);
    }
    catch {
        return false;
    }
    if (!isHttpOrigin(origin)) {
        return false;
    }
    const host = parseHostHeader(getSingleHeader(req.headers.host));
    if (!host) {
        return false;
    }
    if (origin.host.toLowerCase() === host.host.toLowerCase()) {
        return true;
    }
    return (isLoopbackHostname(origin.hostname) &&
        isLoopbackHostname(host.hostname) &&
        origin.port === host.port);
}
exports.isDashboardWebSocketOriginAllowed = isDashboardWebSocketOriginAllowed;
function isDashboardWebSocketUpgradeAllowed(req) {
    if (!isDashboardWebSocketOriginAllowed(req)) {
        return false;
    }
    if (!(0, config_loader_facade_1.isDashboardAuthEnabled)()) {
        return isLoopbackRemoteAddress(req.socket.remoteAddress);
    }
    return Boolean(req.session?.authenticated);
}
exports.isDashboardWebSocketUpgradeAllowed = isDashboardWebSocketUpgradeAllowed;
function getDashboardWebSocketRejectionStatus(req) {
    if (req && !isDashboardWebSocketOriginAllowed(req)) {
        return 403;
    }
    if (!(0, config_loader_facade_1.isDashboardAuthEnabled)())
        return 403;
    return 401;
}
exports.getDashboardWebSocketRejectionStatus = getDashboardWebSocketRejectionStatus;
function requireLocalAccessWhenAuthDisabled(req, res, error = 'This endpoint requires localhost access when dashboard auth is disabled.') {
    if ((0, config_loader_facade_1.isDashboardAuthEnabled)()) {
        return true;
    }
    if (isLoopbackRemoteAddress(req.socket.remoteAddress)) {
        return true;
    }
    res.status(403).json({ error });
    return false;
}
exports.requireLocalAccessWhenAuthDisabled = requireLocalAccessWhenAuthDisabled;
//# sourceMappingURL=auth-middleware.js.map