@chinchillaenterprises/mcp-amplify
Version:
AWS Amplify MCP server with intelligent deployment automation, specialized logging suite, and recursive resource discovery
234 lines • 9.85 kB
JavaScript
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