UNPKG

@chinchillaenterprises/mcp-discord

Version:

Modular Discord MCP server with 45+ tools for comprehensive server management, featuring clean architecture and discord_ prefixed tools

229 lines 19.4 kB
import { promises as fs } from "fs"; import path from "path"; import os from "os"; import crypto from "crypto"; /** * Manages Discord account credentials with fallback storage options * Tries keytar first, falls back to encrypted file storage */ export class CredentialManager { SERVICE_NAME = 'mcp-discord'; CONFIG_DIR = path.join(os.homedir(), '.mcp-discord'); CONFIG_FILE = path.join(this.CONFIG_DIR, 'accounts.json'); ENCRYPTION_KEY_FILE = path.join(this.CONFIG_DIR, '.key'); keytar = null; isKeytarAvailable = false; initPromise; encryptionKey = null; constructor() { this.initPromise = this.initialize(); } async initialize() { // Initialize keytar try { this.keytar = await import('keytar'); this.isKeytarAvailable = true; console.error('[CredentialManager] Keytar initialized successfully'); } catch (error) { console.error('[CredentialManager] Keytar not available, using file-based storage:', error); this.isKeytarAvailable = false; } // Ensure config directory exists try { await fs.mkdir(this.CONFIG_DIR, { recursive: true }); // Set directory permissions to be readable only by the user await fs.chmod(this.CONFIG_DIR, 0o700); } catch (error) { console.error('[CredentialManager] Failed to create config directory:', error); } // Initialize or load encryption key for file-based storage await this.initializeEncryptionKey(); } async initializeEncryptionKey() { try { // Try to read existing key this.encryptionKey = await fs.readFile(this.ENCRYPTION_KEY_FILE); } catch (error) { // Generate new key if doesn't exist this.encryptionKey = crypto.randomBytes(32); try { await fs.writeFile(this.ENCRYPTION_KEY_FILE, this.encryptionKey); await fs.chmod(this.ENCRYPTION_KEY_FILE, 0o600); // Read/write for owner only } catch (writeError) { console.error('[CredentialManager] Failed to save encryption key:', writeError); } } } encrypt(text) { if (!this.encryptionKey) { throw new Error('Encryption key not initialized'); } 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(encryptedText) { if (!this.encryptionKey) { throw new Error('Encryption key not initialized'); } const [ivHex, encrypted] = encryptedText.split(':'); const iv = Buffer.from(ivHex, 'hex'); const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, iv); let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } async waitForInitialization() { await this.initPromise; } async saveAccount(account) { await this.waitForInitialization(); const persistedAccount = { ...account, addedAt: new Date().toISOString(), lastUsed: new Date().toISOString() }; if (this.isKeytarAvailable && this.keytar) { try { await this.keytar.setPassword(this.SERVICE_NAME, account.id, JSON.stringify(persistedAccount)); console.error(`[CredentialManager] Account ${account.id} saved to keychain`); return; } catch (error) { console.error('[CredentialManager] Failed to save to keychain, falling back to file:', error); } } // Fallback to encrypted file storage try { const allAccounts = await this.loadAllAccountsFromFile(); allAccounts[account.id] = persistedAccount; const encryptedData = this.encrypt(JSON.stringify(allAccounts)); await fs.writeFile(this.CONFIG_FILE, encryptedData); await fs.chmod(this.CONFIG_FILE, 0o600); console.error(`[CredentialManager] Account ${account.id} saved to encrypted file`); } catch (error) { console.error('[CredentialManager] Failed to save account:', error); throw new Error(`Failed to save account: ${error.message}`); } } async loadAllAccounts() { await this.waitForInitialization(); 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) { try { const account = JSON.parse(cred.password); accounts.push(account); } catch (error) { console.error(`[CredentialManager] Failed to parse account ${cred.account}:`, error); } } if (accounts.length > 0) { console.error(`[CredentialManager] Loaded ${accounts.length} accounts from keychain`); return accounts; } } catch (error) { console.error('[CredentialManager] Failed to load from keychain:', error); } } // Fallback to file storage try { const fileAccounts = await this.loadAllAccountsFromFile(); const accountList = Object.values(fileAccounts); console.error(`[CredentialManager] Loaded ${accountList.length} accounts from encrypted file`); return accountList; } catch (error) { console.error('[CredentialManager] Failed to load accounts from file:', error); return []; } } async loadAllAccountsFromFile() { try { const encryptedData = await fs.readFile(this.CONFIG_FILE, 'utf8'); const decryptedData = this.decrypt(encryptedData); return JSON.parse(decryptedData); } catch (error) { if (error.code === 'ENOENT') { return {}; } throw error; } } async getAccount(accountId) { await this.waitForInitialization(); // Try keytar first if (this.isKeytarAvailable && this.keytar) { try { const accountData = await this.keytar.getPassword(this.SERVICE_NAME, accountId); if (accountData) { return JSON.parse(accountData); } } catch (error) { console.error(`[CredentialManager] Failed to get account ${accountId} from keychain:`, error); } } // Fallback to file storage try { const allAccounts = await this.loadAllAccountsFromFile(); return allAccounts[accountId] || null; } catch (error) { console.error(`[CredentialManager] Failed to get account ${accountId} from file:`, error); return null; } } async updateAccount(account) { const existingAccount = await this.getAccount(account.id); if (!existingAccount) { throw new Error(`Account ${account.id} not found`); } const updatedAccount = { ...account, addedAt: existingAccount.addedAt, lastUsed: new Date().toISOString() }; await this.saveAccount(updatedAccount); } async deleteAccount(accountId) { await this.waitForInitialization(); // Try keytar first if (this.isKeytarAvailable && this.keytar) { try { await this.keytar.deletePassword(this.SERVICE_NAME, accountId); console.error(`[CredentialManager] Account ${accountId} deleted from keychain`); return; } catch (error) { console.error(`[CredentialManager] Failed to delete account ${accountId} from keychain:`, error); } } // Fallback to file storage try { const allAccounts = await this.loadAllAccountsFromFile(); delete allAccounts[accountId]; const encryptedData = this.encrypt(JSON.stringify(allAccounts)); await fs.writeFile(this.CONFIG_FILE, encryptedData); console.error(`[CredentialManager] Account ${accountId} deleted from encrypted file`); } catch (error) { console.error(`[CredentialManager] Failed to delete account ${accountId}:`, error); throw new Error(`Failed to delete account: ${error.message}`); } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlZGVudGlhbC1tYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlcnZpY2VzL2NyZWRlbnRpYWwtbWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsUUFBUSxJQUFJLEVBQUUsRUFBRSxNQUFNLElBQUksQ0FBQztBQUNwQyxPQUFPLElBQUksTUFBTSxNQUFNLENBQUM7QUFDeEIsT0FBTyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3BCLE9BQU8sTUFBTSxNQUFNLFFBQVEsQ0FBQztBQUc1Qjs7O0dBR0c7QUFDSCxNQUFNLE9BQU8saUJBQWlCO0lBQ1gsWUFBWSxHQUFHLGFBQWEsQ0FBQztJQUM3QixVQUFVLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLEVBQUUsY0FBYyxDQUFDLENBQUM7SUFDckQsV0FBVyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxlQUFlLENBQUMsQ0FBQztJQUMxRCxtQkFBbUIsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDbEUsTUFBTSxHQUFRLElBQUksQ0FBQztJQUNuQixpQkFBaUIsR0FBRyxLQUFLLENBQUM7SUFDMUIsV0FBVyxDQUFnQjtJQUMzQixhQUFhLEdBQWtCLElBQUksQ0FBQztJQUU1QztRQUNFLElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO0lBQ3ZDLENBQUM7SUFFTyxLQUFLLENBQUMsVUFBVTtRQUN0QixvQkFBb0I7UUFDcEIsSUFBSSxDQUFDO1lBQ0gsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQztZQUNyQyxJQUFJLENBQUMsaUJBQWlCLEdBQUcsSUFBSSxDQUFDO1lBQzlCLE9BQU8sQ0FBQyxLQUFLLENBQUMscURBQXFE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