"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.needsSetup = needsSetup;
exports.setupPassword = setupPassword;
exports.verifyPassword = verifyPassword;
exports.deletePasswordFile = deletePasswordFile;
exports.createSession = createSession;
exports.validateSession = validateSession;
exports.destroySession = destroySession;
exports.extractTokenFromRequest = extractTokenFromRequest;
exports.extractTokenFromQuery = extractTokenFromQuery;
exports.isAuthenticated = isAuthenticated;
exports.setSessionCookie = setSessionCookie;
exports.clearSessionCookie = clearSessionCookie;
const node_crypto_1 = __importDefault(require("node:crypto"));
const node_fs_1 = require("node:fs");
const promises_1 = require("node:fs/promises");
const node_os_1 = require("node:os");
const node_path_1 = __importDefault(require("node:path"));
// ---------------------------------------------------------------------------
// Config
// ---------------------------------------------------------------------------
const COCKPIT_DIR = node_path_1.default.join((0, node_os_1.homedir)(), ".cockpit");
const PASSWORD_FILE = node_path_1.default.join(COCKPIT_DIR, "password.json");
const SCRYPT_KEYLEN = 64;
const SALT_BYTES = 32;
// ---------------------------------------------------------------------------
// Password hashing
// ---------------------------------------------------------------------------
function scryptHash(password, salt) {
    return new Promise((resolve, reject) => {
        node_crypto_1.default.scrypt(password, salt, SCRYPT_KEYLEN, (err, key) => {
            if (err)
                reject(err);
            else
                resolve(key);
        });
    });
}
function readPasswordFile() {
    if (!(0, node_fs_1.existsSync)(PASSWORD_FILE))
        return null;
    try {
        const raw = require("node:fs").readFileSync(PASSWORD_FILE, "utf-8");
        const data = JSON.parse(raw);
        if (data.hash && data.salt)
            return data;
        return null;
    }
    catch {
        return null;
    }
}
let cachedPassword;
function getStoredPassword() {
    if (cachedPassword === undefined || cachedPassword === null) {
        cachedPassword = readPasswordFile();
    }
    return cachedPassword;
}
function needsSetup() {
    return getStoredPassword() === null;
}
async function setupPassword(password) {
    await (0, promises_1.mkdir)(COCKPIT_DIR, { recursive: true });
    const salt = node_crypto_1.default.randomBytes(SALT_BYTES);
    const hash = await scryptHash(password, salt);
    const data = {
        hash: hash.toString("hex"),
        salt: salt.toString("hex"),
    };
    await (0, promises_1.writeFile)(PASSWORD_FILE, JSON.stringify(data), "utf-8");
    cachedPassword = data;
}
async function verifyPassword(password) {
    const stored = getStoredPassword();
    if (!stored)
        return false;
    const salt = Buffer.from(stored.salt, "hex");
    const hash = await scryptHash(password, salt);
    const storedHash = Buffer.from(stored.hash, "hex");
    if (hash.length !== storedHash.length)
        return false;
    return node_crypto_1.default.timingSafeEqual(hash, storedHash);
}
async function deletePasswordFile() {
    if ((0, node_fs_1.existsSync)(PASSWORD_FILE)) {
        await (0, promises_1.unlink)(PASSWORD_FILE);
    }
    cachedPassword = undefined;
}
// ---------------------------------------------------------------------------
// Signed session tokens (stateless, survives server restarts)
//
// Token format: <timestamp_hex>.<hmac_hex>
// The HMAC is computed over the timestamp using the stored password hash
// as the key. Changing the password invalidates all existing tokens.
// ---------------------------------------------------------------------------
function getSigningKey() {
    const stored = getStoredPassword();
    return stored?.hash ?? null;
}
function createSession() {
    const key = getSigningKey();
    if (!key)
        return "";
    const timestamp = Date.now().toString(16);
    const hmac = node_crypto_1.default.createHmac("sha256", key).update(timestamp).digest("hex");
    return `${timestamp}.${hmac}`;
}
function validateSession(token) {
    const key = getSigningKey();
    if (!key)
        return false;
    const dot = token.indexOf(".");
    if (dot === -1)
        return false;
    const timestamp = token.slice(0, dot);
    const signature = token.slice(dot + 1);
    const expected = node_crypto_1.default.createHmac("sha256", key).update(timestamp).digest("hex");
    if (signature.length !== expected.length)
        return false;
    return node_crypto_1.default.timingSafeEqual(Buffer.from(signature, "hex"), Buffer.from(expected, "hex"));
}
function destroySession(_token) {
    // No-op: signed tokens are stateless.
    // Token becomes invalid when the password changes.
}
// ---------------------------------------------------------------------------
// Request helpers
// ---------------------------------------------------------------------------
function extractTokenFromRequest(req) {
    const authHeader = req.headers.authorization;
    if (authHeader?.startsWith("Bearer ")) {
        return authHeader.slice(7);
    }
    const cookie = req.headers.cookie;
    if (cookie) {
        const match = cookie.match(/(?:^|;\s*)cockpit_session=([^;]+)/);
        if (match)
            return match[1];
    }
    return null;
}
function extractTokenFromQuery(url) {
    try {
        const parsed = new URL(url, "http://localhost");
        return parsed.searchParams.get("token");
    }
    catch {
        return null;
    }
}
function isAuthenticated(req) {
    const token = extractTokenFromRequest(req);
    return token !== null && validateSession(token);
}
function setSessionCookie(res, token) {
    res.setHeader("Set-Cookie", `cockpit_session=${token}; HttpOnly; Path=/; SameSite=Strict; Max-Age=31536000`);
}
function clearSessionCookie(res) {
    res.setHeader("Set-Cookie", `cockpit_session=; HttpOnly; Path=/; SameSite=Strict; Max-Age=0`);
}
