"use strict";
/**
 * Cursor IDE Authentication Handler
 *
 * Handles token import and authentication for Cursor IDE integration.
 * Supports auto-detection from Cursor's SQLite database.
 *
 * Token Location:
 * - Linux: ~/.config/Cursor/User/globalStorage/state.vscdb
 * - macOS: ~/Library/Application Support/Cursor/User/globalStorage/state.vscdb
 * - Windows: %APPDATA%\Cursor\User\globalStorage\state.vscdb
 *
 * Database Keys:
 * - cursorAuth/accessToken: Access token
 * - storage.serviceMachineId: Machine ID for checksum
 */
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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.deleteCredentials = exports.checkAuthStatus = exports.loadCredentials = exports.saveCredentials = exports.getCredentialsPath = exports.extractUserInfo = exports.validateToken = exports.autoDetectTokens = exports.getTokenStoragePath = exports.getTokenStorageCandidates = void 0;
const child_process_1 = require("child_process");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const config_loader_facade_1 = require("../config/config-loader-facade");
const ACCESS_TOKEN_KEYS = ['cursorAuth/accessToken', 'cursorAuth/token'];
const MACHINE_ID_KEYS = [
    'storage.serviceMachineId',
    'storage.machineId',
    'telemetry.machineId',
];
/**
 * Resolve home directory from environment first for deterministic testability,
 * then fall back to os.homedir() when env vars are unavailable.
 */
function resolveHomeDir() {
    if (process.platform === 'win32') {
        return process.env.USERPROFILE || process.env.HOME || os.homedir();
    }
    return process.env.HOME || os.homedir();
}
/**
 * Get platform-specific path to Cursor's state.vscdb
 */
function getTokenStorageCandidates() {
    const platform = process.platform;
    const home = resolveHomeDir();
    if (platform === 'win32') {
        const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
        const localAppData = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
        return [
            path.join(appData, 'Cursor', 'User', 'globalStorage', 'state.vscdb'),
            path.join(appData, 'Cursor - Insiders', 'User', 'globalStorage', 'state.vscdb'),
            path.join(localAppData, 'Cursor', 'User', 'globalStorage', 'state.vscdb'),
            path.join(localAppData, 'Programs', 'Cursor', 'User', 'globalStorage', 'state.vscdb'),
        ];
    }
    if (platform === 'darwin') {
        return [
            path.join(home, 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'state.vscdb'),
            path.join(home, 'Library', 'Application Support', 'Cursor - Insiders', 'User', 'globalStorage', 'state.vscdb'),
        ];
    }
    return [
        path.join(home, '.config', 'Cursor', 'User', 'globalStorage', 'state.vscdb'),
        path.join(home, '.config', 'cursor', 'User', 'globalStorage', 'state.vscdb'),
    ];
}
exports.getTokenStorageCandidates = getTokenStorageCandidates;
function getTokenStoragePath() {
    return getTokenStorageCandidates()[0];
}
exports.getTokenStoragePath = getTokenStoragePath;
function normalizeStateValue(value) {
    const trimmed = value.trim();
    if (!trimmed)
        return trimmed;
    try {
        const parsed = JSON.parse(trimmed);
        return typeof parsed === 'string' ? parsed : trimmed;
    }
    catch {
        return trimmed;
    }
}
function getSqliteBinary() {
    return process.env.CCS_CURSOR_SQLITE_BIN || 'sqlite3';
}
/**
 * Query Cursor's SQLite database using sqlite3 CLI
 */
function queryStateDb(dbPath, key) {
    try {
        // Escape single quotes to prevent SQL injection
        const sanitizedKey = key.replace(/'/g, "''");
        const result = (0, child_process_1.execFileSync)(getSqliteBinary(), [dbPath, `SELECT value FROM itemTable WHERE key='${sanitizedKey}'`], { encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'ignore'] });
        return {
            value: normalizeStateValue(result) || null,
            sqliteAvailable: true,
            queryFailed: false,
        };
    }
    catch (err) {
        // Check if sqlite3 is not installed
        if (err.code === 'ENOENT') {
            return { value: null, sqliteAvailable: false, queryFailed: false };
        }
        return { value: null, sqliteAvailable: true, queryFailed: true };
    }
}
function queryStateDbKeys(dbPath, keys) {
    for (const key of keys) {
        const result = queryStateDb(dbPath, key);
        if (!result.sqliteAvailable || result.queryFailed)
            return result;
        if (result.value)
            return result;
    }
    return { value: null, sqliteAvailable: true, queryFailed: false };
}
/**
 * Auto-detect tokens from Cursor's SQLite database
 */
function autoDetectTokens() {
    const checkedPaths = getTokenStorageCandidates();
    const existingPaths = checkedPaths.filter((candidate) => fs.existsSync(candidate));
    if (existingPaths.length === 0) {
        return {
            found: false,
            checkedPaths,
            reason: 'db_not_found',
            error: `Cursor state database not found. Checked:\n${checkedPaths.join('\n')}`,
        };
    }
    let sawSqliteUnavailable = false;
    let sawQueryFailure = false;
    let sawTokenMissing = false;
    let sawMachineIdMissing = false;
    let sawInvalidCredentials = false;
    let firstQueryFailurePath;
    let firstInvalidCredentialsPath;
    for (const dbPath of existingPaths) {
        const accessTokenResult = queryStateDbKeys(dbPath, ACCESS_TOKEN_KEYS);
        if (!accessTokenResult.sqliteAvailable) {
            sawSqliteUnavailable = true;
            continue;
        }
        if (accessTokenResult.queryFailed) {
            sawQueryFailure = true;
            firstQueryFailurePath ?? (firstQueryFailurePath = dbPath);
            continue;
        }
        if (!accessTokenResult.value) {
            sawTokenMissing = true;
            continue;
        }
        const machineIdResult = queryStateDbKeys(dbPath, MACHINE_ID_KEYS);
        if (!machineIdResult.sqliteAvailable) {
            sawSqliteUnavailable = true;
            continue;
        }
        if (machineIdResult.queryFailed) {
            sawQueryFailure = true;
            firstQueryFailurePath ?? (firstQueryFailurePath = dbPath);
            continue;
        }
        if (!machineIdResult.value) {
            sawMachineIdMissing = true;
            continue;
        }
        if (!validateToken(accessTokenResult.value, machineIdResult.value)) {
            sawInvalidCredentials = true;
            firstInvalidCredentialsPath ?? (firstInvalidCredentialsPath = dbPath);
            continue;
        }
        return {
            found: true,
            accessToken: accessTokenResult.value,
            machineId: machineIdResult.value,
            dbPath,
            checkedPaths,
        };
    }
    if (sawSqliteUnavailable) {
        return {
            found: false,
            checkedPaths,
            dbPath: existingPaths[0],
            reason: 'sqlite_unavailable',
            error: 'Cursor state database was found, but sqlite3 is not available in PATH. Install sqlite3 or use manual import.',
        };
    }
    if (sawQueryFailure) {
        return {
            found: false,
            checkedPaths,
            dbPath: firstQueryFailurePath ?? existingPaths[0],
            reason: 'db_query_failed',
            error: 'Cursor state database was found, but CCS could not query it. The database may be locked, corrupted, or use an unexpected schema.',
        };
    }
    if (sawInvalidCredentials) {
        return {
            found: false,
            checkedPaths,
            dbPath: firstInvalidCredentialsPath ?? existingPaths[0],
            reason: 'invalid_token_format',
            error: 'Cursor credentials were found, but the access token or machine ID format was invalid. Re-authenticate in Cursor IDE or use manual import.',
        };
    }
    if (sawMachineIdMissing) {
        return {
            found: false,
            checkedPaths,
            dbPath: existingPaths[0],
            reason: 'machine_id_not_found',
            error: 'Cursor access token was found, but the machine ID was not present in the database. Re-open Cursor IDE or use manual import.',
        };
    }
    if (sawTokenMissing) {
        return {
            found: false,
            checkedPaths,
            dbPath: existingPaths[0],
            reason: 'access_token_not_found',
            error: 'Access token not found in Cursor state database. Make sure you are logged in to Cursor IDE first.',
        };
    }
    return {
        found: false,
        checkedPaths,
        dbPath: existingPaths[0],
        reason: 'db_not_found',
        error: 'Cursor credentials could not be detected from the discovered database paths.',
    };
}
exports.autoDetectTokens = autoDetectTokens;
/**
 * Validate token and machine ID format
 */
function validateToken(accessToken, machineId) {
    // Basic validation
    if (!accessToken || typeof accessToken !== 'string') {
        return false;
    }
    if (!machineId || typeof machineId !== 'string') {
        return false;
    }
    // Token format validation (Cursor tokens are typically long strings)
    if (accessToken.length < 50) {
        return false;
    }
    // Machine ID format validation (UUID without hyphens = exactly 32 hex chars)
    const hexRegex = /^[a-f0-9]{32}$/i;
    if (!hexRegex.test(machineId.replace(/-/g, ''))) {
        return false;
    }
    return true;
}
exports.validateToken = validateToken;
/**
 * Extract user info from token if possible
 * Cursor tokens may contain encoded user info as JWT
 */
function extractUserInfo(accessToken) {
    try {
        // Try to decode as JWT
        const parts = accessToken.split('.');
        if (parts.length === 3) {
            let payload = parts[1];
            // Add padding if needed
            while (payload.length % 4) {
                payload += '=';
            }
            const decoded = JSON.parse(Buffer.from(payload.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString());
            const email = typeof decoded.email === 'string' ? decoded.email : undefined;
            const userId = typeof decoded.sub === 'string'
                ? decoded.sub
                : typeof decoded.user_id === 'string'
                    ? decoded.user_id
                    : undefined;
            const exp = typeof decoded.exp === 'number' ? decoded.exp : undefined;
            // If all claims are undefined, treat as if JWT parsing failed
            if (!email && !userId && exp === undefined)
                return null;
            return { email, userId, exp };
        }
    }
    catch {
        // Token is not a JWT, that's okay
    }
    return null;
}
exports.extractUserInfo = extractUserInfo;
/**
 * Get path to credentials file
 */
function getCredentialsPath() {
    return path.join((0, config_loader_facade_1.getCcsDir)(), 'cursor', 'credentials.json');
}
exports.getCredentialsPath = getCredentialsPath;
/**
 * Save credentials to CCS config directory
 */
function saveCredentials(credentials) {
    const credPath = getCredentialsPath();
    const dir = path.dirname(credPath);
    // Ensure directory exists with restrictive permissions
    if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
    }
    // Write credentials with restrictive permissions
    fs.writeFileSync(credPath, JSON.stringify(credentials, null, 2), {
        encoding: 'utf8',
        mode: 0o600,
    });
}
exports.saveCredentials = saveCredentials;
/**
 * Load credentials from CCS config directory
 */
function loadCredentials() {
    const credPath = getCredentialsPath();
    if (!fs.existsSync(credPath)) {
        return null;
    }
    try {
        const raw = fs.readFileSync(credPath, 'utf8');
        const parsed = JSON.parse(raw);
        // Basic validation
        if (typeof parsed === 'object' &&
            parsed !== null &&
            'accessToken' in parsed &&
            'machineId' in parsed &&
            'authMethod' in parsed &&
            'importedAt' in parsed) {
            // Type validation
            if (typeof parsed.accessToken !== 'string' ||
                typeof parsed.machineId !== 'string' ||
                typeof parsed.importedAt !== 'string' ||
                (parsed.authMethod !== 'auto-detect' && parsed.authMethod !== 'manual')) {
                return null;
            }
            return parsed;
        }
        return null;
    }
    catch {
        return null;
    }
}
exports.loadCredentials = loadCredentials;
/**
 * Check authentication status
 */
function checkAuthStatus() {
    const credentials = loadCredentials();
    if (!credentials) {
        return { authenticated: false };
    }
    // Validate credentials are still valid format
    if (!validateToken(credentials.accessToken, credentials.machineId)) {
        return { authenticated: false };
    }
    // Try to get token expiry from JWT exp claim
    let tokenAge;
    let expired = false;
    const userInfo = extractUserInfo(credentials.accessToken);
    if (userInfo?.exp) {
        // Use JWT exp claim for expiry detection
        const now = Math.floor(Date.now() / 1000);
        expired = now >= userInfo.exp;
    }
    // Always use importedAt for tokenAge (more reliable than reverse-engineering JWT lifetime)
    const TOKEN_EXPIRY_HOURS = 24;
    const importedDate = new Date(credentials.importedAt);
    if (!isNaN(importedDate.getTime())) {
        const now = new Date();
        tokenAge = Math.floor((now.getTime() - importedDate.getTime()) / (1000 * 60 * 60));
        // Only set expired from importedAt if JWT exp was not available
        if (userInfo?.exp === undefined) {
            expired = tokenAge >= TOKEN_EXPIRY_HOURS;
        }
    }
    return {
        authenticated: true,
        credentials,
        tokenAge,
        expired,
    };
}
exports.checkAuthStatus = checkAuthStatus;
/**
 * Delete credentials file
 */
function deleteCredentials() {
    try {
        fs.unlinkSync(getCredentialsPath());
        return true;
    }
    catch {
        return false;
    }
}
exports.deleteCredentials = deleteCredentials;
//# sourceMappingURL=cursor-auth.js.map