UNPKG

@chinchillaenterprises/mcp-amplify

Version:

AWS Amplify MCP server with intelligent deployment automation, specialized logging suite, and recursive resource discovery

234 lines 9.85 kB
import * as fs from 'fs/promises'; import * as path from 'path'; import * as os from 'os'; import * as crypto from 'crypto'; export class CredentialManager { SERVICE_NAME = 'mcp-amplify'; FILE_NAME = '.mcp-amplify-accounts.json'; keytar = null; isKeytarAvailable = false; keytarChecked = false; encryptionKey; constructor() { } async ensureInitialized() { if (!this.keytarChecked) { this.keytarChecked = true; try { const keytarModule = await import('keytar'); this.keytar = keytarModule.default || keytarModule; this.isKeytarAvailable = true; console.error('[CredentialManager] Keytar available for secure credential storage'); } catch (error) { console.error('[CredentialManager] Keytar not available, using encrypted file storage'); this.isKeytarAvailable = false; } } if (!this.encryptionKey) { // Derive a key from machine-specific information const machineId = `${os.hostname()}-${os.userInfo().username}-${os.platform()}`; this.encryptionKey = crypto.scryptSync(machineId, 'mcp-amplify-salt', 32); } } encrypt(text) { const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv('aes-256-cbc', this.encryptionKey, iv); let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex'); return iv.toString('hex') + ':' + encrypted; } decrypt(text) { const parts = text.split(':'); const iv = Buffer.from(parts[0], 'hex'); const encrypted = parts[1]; const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, iv); let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } getFilePath() { return path.join(os.homedir(), this.FILE_NAME); } async loadFromFile() { try { const filePath = this.getFilePath(); const data = await fs.readFile(filePath, 'utf8'); return JSON.parse(data); } catch (error) { return {}; } } async saveToFile(accounts) { const filePath = this.getFilePath(); await fs.writeFile(filePath, JSON.stringify(accounts, null, 2), { mode: 0o600 }); } async saveAccount(account) { await this.ensureInitialized(); // Encrypt sensitive data const encryptedAccount = { ...account, accessKeyId: this.encrypt(account.accessKeyId), secretAccessKey: this.encrypt(account.secretAccessKey), sessionToken: account.sessionToken ? this.encrypt(account.sessionToken) : undefined, githubToken: account.githubToken ? this.encrypt(account.githubToken) : undefined }; // Try keytar first if (this.isKeytarAvailable && this.keytar) { try { await this.keytar.setPassword(this.SERVICE_NAME, `aws-${account.id}`, JSON.stringify(account)); console.error(`[CredentialManager] Saved account ${account.id} to keychain`); } catch (error) { console.error('[CredentialManager] Failed to save to keychain, falling back to file:', error); } } // Also save to file as backup const accounts = await this.loadFromFile(); accounts[account.id] = encryptedAccount; await this.saveToFile(accounts); console.error(`[CredentialManager] Saved account ${account.id} to encrypted file`); } async loadAccount(accountId) { await this.ensureInitialized(); // Try keytar first if (this.isKeytarAvailable && this.keytar) { try { const stored = await this.keytar.getPassword(this.SERVICE_NAME, `aws-${accountId}`); if (stored) { return JSON.parse(stored); } } catch (error) { console.error('[CredentialManager] Failed to load account from keychain:', error); } } // Fallback to file try { const accounts = await this.loadFromFile(); const encryptedAccount = accounts[accountId]; if (encryptedAccount) { // Decrypt sensitive data const account = { ...encryptedAccount, accessKeyId: this.decrypt(encryptedAccount.accessKeyId), secretAccessKey: this.decrypt(encryptedAccount.secretAccessKey), sessionToken: encryptedAccount.sessionToken ? this.decrypt(encryptedAccount.sessionToken) : undefined, githubToken: encryptedAccount.githubToken ? this.decrypt(encryptedAccount.githubToken) : undefined }; return account; } } catch (error) { console.error('[CredentialManager] Failed to load account from file:', error); } return null; } async loadAllAccounts() { await this.ensureInitialized(); const accounts = []; // Try keytar first if (this.isKeytarAvailable && this.keytar) { try { const credentials = await this.keytar.findCredentials(this.SERVICE_NAME); for (const cred of credentials) { if (cred.account.startsWith('aws-')) { try { const account = JSON.parse(cred.password); accounts.push(account); } catch (e) { console.error(`[CredentialManager] Failed to parse account ${cred.account}:`, e); } } } if (accounts.length > 0) { console.error(`[CredentialManager] Loaded ${accounts.length} accounts from keychain`); return accounts; } } catch (error) { console.error('[CredentialManager] Failed to load accounts from keychain:', error); } } // Fallback to file const fileAccounts = await this.loadFromFile(); for (const [id, encryptedAccount] of Object.entries(fileAccounts)) { try { const account = { ...encryptedAccount, accessKeyId: this.decrypt(encryptedAccount.accessKeyId), secretAccessKey: this.decrypt(encryptedAccount.secretAccessKey), sessionToken: encryptedAccount.sessionToken ? this.decrypt(encryptedAccount.sessionToken) : undefined, githubToken: encryptedAccount.githubToken ? this.decrypt(encryptedAccount.githubToken) : undefined }; accounts.push(account); } catch (error) { console.error(`[CredentialManager] Failed to decrypt account ${id}:`, error); } } console.error(`[CredentialManager] Loaded ${accounts.length} accounts from encrypted file`); return accounts; } async deleteAccount(accountId) { await this.ensureInitialized(); // Try keytar first if (this.isKeytarAvailable && this.keytar) { try { await this.keytar.deletePassword(this.SERVICE_NAME, `aws-${accountId}`); console.error(`[CredentialManager] Deleted account ${accountId} from keychain`); } catch (error) { console.error('[CredentialManager] Failed to delete from keychain:', error); } } // Also delete from file const accounts = await this.loadFromFile(); delete accounts[accountId]; await this.saveToFile(accounts); console.error(`[CredentialManager] Deleted account ${accountId} from encrypted file`); } async getDefaultAccount() { await this.ensureInitialized(); // Try keytar first if (this.isKeytarAvailable && this.keytar) { try { const defaultId = await this.keytar.getPassword(this.SERVICE_NAME, 'default-account'); if (defaultId) { return defaultId; } } catch (error) { console.error('[CredentialManager] Failed to get default account from keychain:', error); } } // Fallback to file try { const filePath = path.join(os.homedir(), '.mcp-amplify-default'); const defaultId = await fs.readFile(filePath, 'utf8'); return defaultId.trim(); } catch (error) { return null; } } async setDefaultAccount(accountId) { await this.ensureInitialized(); // Try keytar first if (this.isKeytarAvailable && this.keytar) { try { await this.keytar.setPassword(this.SERVICE_NAME, 'default-account', accountId); console.error(`[CredentialManager] Set default account to ${accountId} in keychain`); } catch (error) { console.error('[CredentialManager] Failed to set default account in keychain:', error); } } // Also save to file const filePath = path.join(os.homedir(), '.mcp-amplify-default'); await fs.writeFile(filePath, accountId, { mode: 0o600 }); console.error(`[CredentialManager] Set default account to ${accountId} in file`); } } //# sourceMappingURL=credential-manager.js.map