UNPKG

@aiondadotcom/mcp-salesforce

Version:

Model Context Protocol (MCP) server for Salesforce integration using OAuth authentication

187 lines (165 loc) 5.62 kB
import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; import { logger } from '../utils/debug.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Token file path in cache directory const TOKEN_FILE_PATH = path.join(__dirname, '../../cache/salesforce-tokens.json'); export class FileStorageManager { constructor() { this.tokenFilePath = TOKEN_FILE_PATH; } /** * Store tokens securely in cache directory * @param {Object} tokens - Object containing access_token and refresh_token */ async storeTokens(tokens) { try { // Ensure cache directory exists const cacheDir = path.dirname(this.tokenFilePath); await fs.mkdir(cacheDir, { recursive: true }); const tokenData = { access_token: tokens.access_token, refresh_token: tokens.refresh_token, expires_at: tokens.expires_at || null, instance_url: tokens.instance_url, stored_at: new Date().toISOString() }; // Write tokens to file with restricted permissions (600 = rw-------) await fs.writeFile(this.tokenFilePath, JSON.stringify(tokenData, null, 2), { mode: 0o600 }); // Explicitly set file permissions to ensure security await fs.chmod(this.tokenFilePath, 0o600); // Verify file permissions for security const stats = await fs.stat(this.tokenFilePath); const permissions = stats.mode & parseInt('777', 8); if (permissions !== parseInt('600', 8)) { logger.warn(`⚠️ Warning: Token file permissions are ${permissions.toString(8)}, expected 600`); } logger.log('✅ Tokens stored securely in cache directory (permissions: 600)'); } catch (error) { throw new Error(`Failed to store tokens in file: ${error.message}`); } } /** * Retrieve tokens from cache file * @returns {Object} Token data or null if not found */ async getTokens() { try { const data = await fs.readFile(this.tokenFilePath, 'utf8'); const tokenData = JSON.parse(data); // Validate token structure if (!tokenData.access_token || !tokenData.refresh_token) { logger.warn('⚠️ Invalid token structure found, removing file'); await this.clearTokens(); return null; } return tokenData; } catch (error) { if (error.code === 'ENOENT') { // File not found - no tokens stored return null; } throw new Error(`Failed to retrieve tokens from file: ${error.message}`); } } /** * Clear stored tokens */ async clearTokens() { try { await fs.unlink(this.tokenFilePath); logger.log('✅ Tokens cleared successfully'); } catch (error) { if (error.code !== 'ENOENT') { throw new Error(`Failed to clear tokens: ${error.message}`); } } } /** * Check if tokens exist * @returns {boolean} True if tokens exist */ async hasTokens() { try { await fs.access(this.tokenFilePath); return true; } catch { return false; } } /** * Get token file information for debugging */ async getTokenFileInfo() { try { const stats = await fs.stat(this.tokenFilePath); const data = await this.getTokens(); const permissions = stats.mode & parseInt('777', 8); const isSecure = permissions === parseInt('600', 8); return { exists: true, size: stats.size, modified: stats.mtime.toISOString(), permissions: '0' + permissions.toString(8), isSecure: isSecure, securityWarning: !isSecure ? 'File permissions are not secure (should be 600)' : null, hasValidStructure: !!(data?.access_token && data?.refresh_token), storedAt: data?.stored_at, instanceUrl: data?.instance_url }; } catch (error) { return { exists: false, error: error.message }; } } /** * Verify and fix token file security */ async ensureTokenSecurity() { try { const hasTokens = await this.hasTokens(); if (!hasTokens) { return { status: 'no_tokens', message: 'No token file exists' }; } const stats = await fs.stat(this.tokenFilePath); const permissions = stats.mode & parseInt('777', 8); const expectedPermissions = parseInt('600', 8); if (permissions !== expectedPermissions) { logger.log(`🔒 Fixing token file permissions from ${permissions.toString(8)} to 600`); await fs.chmod(this.tokenFilePath, 0o600); // Verify the change const newStats = await fs.stat(this.tokenFilePath); const newPermissions = newStats.mode & parseInt('777', 8); if (newPermissions === expectedPermissions) { return { status: 'fixed', message: 'Token file permissions fixed to 600', oldPermissions: permissions.toString(8), newPermissions: newPermissions.toString(8) }; } else { return { status: 'error', message: 'Failed to fix permissions', permissions: newPermissions.toString(8) }; } } else { return { status: 'secure', message: 'Token file permissions are correct (600)', permissions: permissions.toString(8) }; } } catch (error) { return { status: 'error', message: `Error checking token security: ${error.message}` }; } } }