UNPKG

@coretext-ai/qa-google-contacts-a58500d5-8331-4ce9-a140-d204a9fae815

Version:
201 lines 7.33 kB
import fs from 'fs-extra'; import path from 'path'; import os from 'os'; import crypto from 'crypto'; export class TokenManager { constructor() { this.tokenPath = path.resolve('~/.mcp/google-tokens/'.replace(/^~/, os.homedir())); this.ensureTokenDirectory(); } /** * Store OAuth tokens securely */ async storeTokens(tokens, userId = 'default') { const tokenFile = path.join(this.tokenPath, `${userId}.json`); try { // Try platform keychain first const encryptedData = await this.encryptTokenData(tokens); // Write with secure permissions await fs.writeFile(tokenFile, encryptedData, { mode: 0o600 }); console.error(`[TOKEN_MANAGER] Tokens stored securely for user: ${userId}`); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error(`Failed to store tokens: ${errorMessage}`); throw new Error(`Token storage failed: ${errorMessage}`); } } /** * Retrieve stored OAuth tokens */ async getTokens(userId = 'default') { const tokenFile = path.join(this.tokenPath, `${userId}.json`); if (!await fs.pathExists(tokenFile)) { return null; } try { const encryptedData = await fs.readFile(tokenFile, 'utf8'); return await this.decryptTokenData(encryptedData); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error(`Failed to retrieve tokens: ${errorMessage}`); return null; } } /** * Delete stored tokens */ async deleteTokens(userId = 'default') { const tokenFile = path.join(this.tokenPath, `${userId}.json`); try { if (await fs.pathExists(tokenFile)) { await fs.remove(tokenFile); console.error(`[TOKEN_MANAGER] Tokens deleted for user: ${userId}`); } // Also try to delete from keychain if used await this.deleteFromKeychain(userId); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error(`Failed to delete tokens: ${errorMessage}`); throw new Error(`Token deletion failed: ${errorMessage}`); } } /** * Encrypt token data using platform keychain or file-based encryption */ async encryptTokenData(tokens) { try { // Try platform keychain first return await this.encryptWithKeychain(tokens); } catch (error) { console.warn('Keychain not available, using file-based encryption'); return this.encryptTokenDataFile(tokens); } } /** * Decrypt token data from platform keychain or file */ async decryptTokenData(encryptedData) { try { const reference = JSON.parse(encryptedData); if (reference.type === 'keychain') { return await this.decryptFromKeychain(reference); } else { return this.decryptTokenDataFile(encryptedData); } } catch (error) { // Fallback to file decryption return this.decryptTokenDataFile(encryptedData); } } /** * Platform keychain encryption (macOS/Windows/Linux) */ async encryptWithKeychain(tokens) { try { const keytar = await import('keytar'); const serviceName = 'mcp-oauth-tokens'; const accountName = 'google-default'; const serializedData = JSON.stringify(tokens); await keytar.setPassword(serviceName, accountName, serializedData); // Return encrypted reference return JSON.stringify({ type: 'keychain', service: serviceName, account: accountName }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Keychain encryption failed: ${errorMessage}`); } } /** * Platform keychain decryption */ async decryptFromKeychain(reference) { try { const keytar = await import('keytar'); const tokenData = await keytar.getPassword(reference.service, reference.account); if (!tokenData) { throw new Error('No data found in keychain'); } return JSON.parse(tokenData); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Keychain decryption failed: ${errorMessage}`); } } /** * Delete from keychain */ async deleteFromKeychain(userId) { try { const keytar = await import('keytar'); const serviceName = 'mcp-oauth-tokens'; const accountName = `google-${userId}`; await keytar.deletePassword(serviceName, accountName); } catch (error) { // Ignore errors, keychain might not be available } } /** * Fallback file-based encryption */ encryptTokenDataFile(tokens) { const algorithm = 'aes-256-gcm'; const key = this.getDerivedKey(); const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv(algorithm, key, iv); let encrypted = cipher.update(JSON.stringify(tokens), 'utf8', 'hex'); encrypted += cipher.final('hex'); const authTag = cipher.getAuthTag(); return JSON.stringify({ type: 'file', iv: iv.toString('hex'), authTag: authTag.toString('hex'), data: encrypted }); } /** * Fallback file-based decryption */ decryptTokenDataFile(encryptedData) { const algorithm = 'aes-256-gcm'; const key = this.getDerivedKey(); const encrypted = JSON.parse(encryptedData); const decipher = crypto.createDecipheriv(algorithm, key, Buffer.from(encrypted.iv, 'hex')); decipher.setAuthTag(Buffer.from(encrypted.authTag, 'hex')); let decrypted = decipher.update(encrypted.data, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return JSON.parse(decrypted); } /** * Derive encryption key from system-specific data */ getDerivedKey() { const systemInfo = `${os.hostname()}-${os.userInfo().username}`; return crypto.pbkdf2Sync('mcp-oauth', systemInfo, 100000, 32, 'sha512'); } /** * Ensure token directory exists with secure permissions */ async ensureTokenDirectory() { try { await fs.ensureDir(this.tokenPath, { mode: 0o700 }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error(`Failed to create token directory: ${errorMessage}`); throw new Error(`Token directory creation failed: ${errorMessage}`); } } } //# sourceMappingURL=token-manager.js.map