UNPKG

ailock

Version:

AI-Proof File Guard - Protect sensitive files from accidental AI modifications

485 lines 16.1 kB
import { readFile, writeFile, mkdir } from 'fs/promises'; import { existsSync } from 'fs'; import { join } from 'path'; import { getAilockConfigDir, getMachineFingerprint, isValidMachineUuid, sanitizeForLogging } from './machine-id.js'; /** * Default user configuration */ export const DEFAULT_USER_CONFIG = { machineUuid: '', directoryQuota: 2, // Default free tier: 2 directories lockedDirectories: [], analyticsEnabled: true, offlineMode: false, version: '1.0.0', privacyLevel: 'standard', telemetryOptOut: false }; /** * Get the user config file path */ export function getUserConfigPath() { return join(getAilockConfigDir(), 'user-config.json'); } /** * Load user configuration from disk * Creates default config if file doesn't exist */ export async function loadUserConfig() { const configPath = getUserConfigPath(); const configDir = getAilockConfigDir(); try { // Create config directory if it doesn't exist if (!existsSync(configDir)) { await mkdir(configDir, { recursive: true }); } // Load existing config if available if (existsSync(configPath)) { const configContent = await readFile(configPath, 'utf-8'); const config = JSON.parse(configContent); // Merge with defaults to handle version upgrades const mergedConfig = { ...DEFAULT_USER_CONFIG, ...config }; // Convert date strings back to Date objects if (config.lastSyncAt && typeof config.lastSyncAt === 'string') { mergedConfig.lastSyncAt = new Date(config.lastSyncAt); } return mergedConfig; } // Return default config if file doesn't exist return { ...DEFAULT_USER_CONFIG }; } catch (error) { console.warn('Warning: Could not load user config, using defaults:', sanitizeForLogging(error instanceof Error ? error.message : String(error))); return { ...DEFAULT_USER_CONFIG }; } } /** * Save user configuration to disk */ export async function saveUserConfig(config) { const configPath = getUserConfigPath(); const configDir = getAilockConfigDir(); try { // Ensure config directory exists if (!existsSync(configDir)) { await mkdir(configDir, { recursive: true }); } // Write config with secure permissions await writeFile(configPath, JSON.stringify(config, null, 2), { encoding: 'utf-8', mode: 0o600 // Read/write for owner only }); } catch (error) { throw new Error(`Failed to save user config: ${error instanceof Error ? error.message : String(error)}`); } } /** * Get current user's directory quota */ export async function getUserQuota() { const config = await loadUserConfig(); return config.directoryQuota; } /** * Get locked directories count */ export async function getLockedDirectoryCount() { const config = await loadUserConfig(); return config.lockedDirectories.length; } /** * Check if user is within their directory quota */ export async function isWithinQuota() { const [quota, lockedCount] = await Promise.all([ getUserQuota(), getLockedDirectoryCount() ]); return lockedCount < quota; } /** * Add a directory to the locked directories list */ export async function addLockedDirectory(dirPath) { const config = await loadUserConfig(); // Add directory if not already in list if (!config.lockedDirectories.includes(dirPath)) { config.lockedDirectories.push(dirPath); await saveUserConfig(config); } } /** * Remove a directory from the locked directories list */ export async function removeLockedDirectory(dirPath) { const config = await loadUserConfig(); // Remove directory from list config.lockedDirectories = config.lockedDirectories.filter(dir => dir !== dirPath); await saveUserConfig(config); } /** * Update user's directory quota (typically after auth code redemption) */ export async function updateDirectoryQuota(newQuota) { const config = await loadUserConfig(); config.directoryQuota = Math.max(0, newQuota); // Ensure non-negative config.lastSyncAt = new Date(); await saveUserConfig(config); } /** * Set user's auth token */ export async function setAuthToken(token) { const config = await loadUserConfig(); config.authToken = token; config.lastSyncAt = new Date(); await saveUserConfig(config); } /** * Clear user's auth token */ export async function clearAuthToken() { const config = await loadUserConfig(); delete config.authToken; config.lastSyncAt = new Date(); await saveUserConfig(config); } /** * Update user's machine UUID in config */ export async function updateMachineUuid(uuid) { const config = await loadUserConfig(); config.machineUuid = uuid; await saveUserConfig(config); } /** * Set analytics preference */ export async function setAnalyticsEnabled(enabled) { const config = await loadUserConfig(); config.analyticsEnabled = enabled; await saveUserConfig(config); } /** * Get analytics preference */ export async function getAnalyticsEnabled() { const config = await loadUserConfig(); return config.analyticsEnabled; } /** * Set offline mode */ export async function setOfflineMode(offline) { const config = await loadUserConfig(); config.offlineMode = offline; await saveUserConfig(config); } /** * Get offline mode status */ export async function getOfflineMode() { const config = await loadUserConfig(); return config.offlineMode || false; } /** * Set privacy level */ export async function setPrivacyLevel(level) { const config = await loadUserConfig(); config.privacyLevel = level; // Adjust other settings based on privacy level switch (level) { case 'minimal': // Most permissive config.analyticsEnabled = true; config.telemetryOptOut = false; break; case 'standard': // Default settings config.analyticsEnabled = true; config.telemetryOptOut = false; break; case 'enhanced': // Most private config.analyticsEnabled = false; config.telemetryOptOut = true; break; } await saveUserConfig(config); } /** * Get privacy level */ export async function getPrivacyLevel() { const config = await loadUserConfig(); return config.privacyLevel || 'standard'; } /** * Set telemetry opt-out */ export async function setTelemetryOptOut(optOut) { const config = await loadUserConfig(); config.telemetryOptOut = optOut; await saveUserConfig(config); } /** * Get telemetry opt-out status */ export async function getTelemetryOptOut() { const config = await loadUserConfig(); return config.telemetryOptOut || false; } /** * Validate machine fingerprint for security * Returns true if the configuration appears to be on the same machine */ export async function validateMachineFingerprint() { const config = await loadUserConfig(); if (!config.machineFingerprint) { // First time - set the fingerprint config.machineFingerprint = getMachineFingerprint(); await saveUserConfig(config); return true; } const currentFingerprint = getMachineFingerprint(); return config.machineFingerprint === currentFingerprint; } /** * Check if analytics should be collected based on privacy settings */ export async function shouldCollectAnalytics() { const config = await loadUserConfig(); // Check telemetry opt-out first if (config.telemetryOptOut) { return false; } // Check analytics enabled if (!config.analyticsEnabled) { return false; } // Check privacy level const privacyLevel = config.privacyLevel || 'standard'; if (privacyLevel === 'enhanced') { return false; } return true; } /** * Sanitize user config for safe logging/transmission */ export function sanitizeUserConfig(config) { const sanitized = { directoryQuota: config.directoryQuota, analyticsEnabled: config.analyticsEnabled, offlineMode: config.offlineMode, version: config.version, privacyLevel: config.privacyLevel, telemetryOptOut: config.telemetryOptOut }; // Only include machine UUID if it's valid format and user allows analytics if (isValidMachineUuid(config.machineUuid) && config.analyticsEnabled && !config.telemetryOptOut) { sanitized.machineUuid = config.machineUuid; } // Don't include sensitive information // authToken, lockedDirectories, machineFingerprint are excluded return sanitized; } /** * Get user configuration for debugging */ export async function getUserConfigDebugInfo() { const config = await loadUserConfig(); // Remove sensitive information for debug output const { authToken, ...debugConfig } = config; return debugConfig; } /** * Reset user configuration to defaults (useful for testing) */ export async function resetUserConfig() { const config = { ...DEFAULT_USER_CONFIG }; await saveUserConfig(config); } /** * Validate user configuration integrity */ export function validateUserConfig(config) { const errors = []; if (!config.machineUuid) { errors.push('Machine UUID is required'); } if (config.directoryQuota < 0) { errors.push('Directory quota must be non-negative'); } if (!Array.isArray(config.lockedDirectories)) { errors.push('Locked directories must be an array'); } if (typeof config.analyticsEnabled !== 'boolean') { errors.push('Analytics enabled must be a boolean'); } if (config.authToken && typeof config.authToken !== 'string') { errors.push('Auth token must be a string if provided'); } if (!config.version) { errors.push('Configuration version is required'); } return errors; } /** * Create backup of user configuration */ export async function backupUserConfig() { try { const config = await loadUserConfig(); const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupPath = join(getAilockConfigDir(), `user-config-backup-${timestamp}.json`); await writeFile(backupPath, JSON.stringify(config, null, 2), { encoding: 'utf-8', mode: 0o600 }); return backupPath; } catch (error) { throw new Error(`Failed to backup user config: ${error instanceof Error ? error.message : String(error)}`); } } /** * Restore user configuration from backup */ export async function restoreUserConfig(backupPath) { try { const backupContent = await readFile(backupPath, 'utf-8'); const backupConfig = JSON.parse(backupContent); // Validate backup before restoring const validationErrors = validateUserConfig(backupConfig); if (validationErrors.length > 0) { throw new Error(`Invalid backup configuration: ${validationErrors.join(', ')}`); } await saveUserConfig(backupConfig); } catch (error) { throw new Error(`Failed to restore user config: ${error instanceof Error ? error.message : String(error)}`); } } /** * Repair corrupted user configuration * Attempts to fix issues automatically or reset to defaults */ export async function repairUserConfig() { const issuesFixed = []; let repaired = false; let hadToReset = false; try { // Try to load the config first let config; try { config = await loadUserConfig(); } catch (loadError) { // If config can't be loaded at all, create a backup of the corrupted file and reset const configPath = getUserConfigPath(); if (existsSync(configPath)) { try { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const corruptedBackupPath = join(getAilockConfigDir(), `corrupted-config-${timestamp}.json`); await writeFile(corruptedBackupPath, await readFile(configPath, 'utf-8')); issuesFixed.push(`Backed up corrupted config to ${corruptedBackupPath}`); } catch { // Ignore backup errors } } config = { ...DEFAULT_USER_CONFIG }; await saveUserConfig(config); issuesFixed.push('Reset to default configuration due to corruption'); hadToReset = true; repaired = true; return { repaired, issuesFixed, hadToReset }; } // Validate the loaded config const validationErrors = validateUserConfig(config); if (validationErrors.length === 0) { return { repaired: false, issuesFixed: [], hadToReset: false }; } // Attempt to fix validation errors let configChanged = false; // Fix missing machine UUID if (!config.machineUuid) { const { getMachineUuid } = await import('./machine-id.js'); config.machineUuid = await getMachineUuid(); issuesFixed.push('Generated missing machine UUID'); configChanged = true; repaired = true; } // Fix negative quota if (config.directoryQuota < 0) { config.directoryQuota = DEFAULT_USER_CONFIG.directoryQuota; issuesFixed.push('Reset negative directory quota to default'); configChanged = true; repaired = true; } // Fix invalid locked directories array if (!Array.isArray(config.lockedDirectories)) { config.lockedDirectories = []; issuesFixed.push('Reset invalid locked directories array'); configChanged = true; repaired = true; } // Fix invalid analytics setting if (typeof config.analyticsEnabled !== 'boolean') { config.analyticsEnabled = DEFAULT_USER_CONFIG.analyticsEnabled; issuesFixed.push('Reset invalid analytics setting'); configChanged = true; repaired = true; } // Fix missing version if (!config.version) { config.version = DEFAULT_USER_CONFIG.version; issuesFixed.push('Added missing configuration version'); configChanged = true; repaired = true; } // Save repaired configuration if (configChanged) { await saveUserConfig(config); } return { repaired, issuesFixed, hadToReset }; } catch (error) { // If repair fails, try to reset to defaults as last resort try { await resetUserConfig(); return { repaired: true, issuesFixed: [`Failed to repair config, reset to defaults: ${error instanceof Error ? error.message : String(error)}`], hadToReset: true }; } catch (resetError) { throw new Error(`Critical error: Cannot repair or reset user config: ${resetError instanceof Error ? resetError.message : String(resetError)}`); } } } /** * Safe configuration operation wrapper */ export async function safeConfigOperation(operation, operationName, fallbackValue) { try { const result = await operation(); return { success: true, result }; } catch (error) { const errorMessage = `Config operation '${operationName}' failed: ${error instanceof Error ? error.message : String(error)}`; if (process.env.AILOCK_DEBUG === 'true') { console.log(`Debug: ${errorMessage}`); } return { success: false, error: errorMessage, result: fallbackValue }; } } //# sourceMappingURL=user-config.js.map