@coretext-ai/custom-test-google-contacts-a58500d5-8331-4ce9-a140-d204a9fae815
Version:
MCP server with google-contacts integration
232 lines • 9.96 kB
JavaScript
import fs from 'fs-extra';
import path from 'path';
import os from 'os';
import crypto from 'crypto';
export class TokenManager {
constructor(provider) {
// Use provided provider or extract from template context
this.provider = provider || 'google';
// Dynamic token path based on provider
this.tokenPath = path.join(os.homedir(), '.mcp', `${this.provider}-tokens`);
this.ensureTokenDirectory();
}
/**
* Store OAuth tokens securely
*/
async storeTokens(tokens, userId = 'default') {
const tokenFile = path.join(this.tokenPath, `${userId}.json`);
console.error(`[TOKEN_STORAGE] 🔐 Starting token storage process...`);
console.error(`[TOKEN_STORAGE] 🏷️ Provider: ${this.provider}`);
console.error(`[TOKEN_STORAGE] 📁 Storage directory: ${this.tokenPath}`);
console.error(`[TOKEN_STORAGE] 📄 Token file path: ${tokenFile}`);
console.error(`[TOKEN_STORAGE] 👤 User ID: ${userId}`);
console.error(`[TOKEN_STORAGE] 🔑 Token preview: ${tokens.access_token ? tokens.access_token.substring(0, 12) + '...' : 'null'}`);
try {
// Try platform keychain first
console.error(`[TOKEN_STORAGE] 🔐 Attempting encryption...`);
const encryptedData = await this.encryptTokenData(tokens);
// Write with secure permissions
console.error(`[TOKEN_STORAGE] 💾 Writing encrypted tokens to file system...`);
await fs.writeFile(tokenFile, encryptedData, { mode: 0o600 });
console.error(`[TOKEN_STORAGE] ✅ Tokens stored successfully!`);
console.error(`[TOKEN_STORAGE] 📍 Final storage location: ${tokenFile}`);
console.error(`[TOKEN_STORAGE] 🔒 File permissions: 0o600 (owner read/write only)`);
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(`[TOKEN_STORAGE] ❌ Token storage failed: ${errorMessage}`);
throw new Error(`Token storage failed: ${errorMessage}`);
}
}
/**
* Retrieve stored OAuth tokens
*/
async getTokens(userId = 'default') {
const tokenFile = path.join(this.tokenPath, `${userId}.json`);
console.error(`[TOKEN_RETRIEVAL] 🔍 Looking for stored tokens...`);
console.error(`[TOKEN_RETRIEVAL] 🏷️ Provider: ${this.provider}`);
console.error(`[TOKEN_RETRIEVAL] 📁 Search directory: ${this.tokenPath}`);
console.error(`[TOKEN_RETRIEVAL] 📄 Token file path: ${tokenFile}`);
console.error(`[TOKEN_RETRIEVAL] 👤 User ID: ${userId}`);
if (!await fs.pathExists(tokenFile)) {
console.error(`[TOKEN_RETRIEVAL] ❌ No token file found at: ${tokenFile}`);
console.error(`[TOKEN_RETRIEVAL] 🔄 This will trigger new OAuth authentication flow`);
return null;
}
try {
console.error(`[TOKEN_RETRIEVAL] ✅ Token file found! Reading encrypted data...`);
const encryptedData = await fs.readFile(tokenFile, 'utf8');
console.error(`[TOKEN_RETRIEVAL] 🔐 Decrypting stored tokens...`);
const tokens = await this.decryptTokenData(encryptedData);
console.error(`[TOKEN_RETRIEVAL] 🎉 Tokens successfully retrieved from storage!`);
console.error(`[TOKEN_RETRIEVAL] 🔑 Token preview: ${tokens.access_token ? tokens.access_token.substring(0, 12) + '...' : 'null'}`);
console.error(`[TOKEN_RETRIEVAL] 📍 Retrieved from: ${tokenFile}`);
return tokens;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(`[TOKEN_RETRIEVAL] ❌ Failed to retrieve tokens: ${errorMessage}`);
console.error(`[TOKEN_RETRIEVAL] 📄 File location: ${tokenFile}`);
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
console.error(`[TOKEN_ENCRYPTION] 🔐 Attempting keychain storage...`);
const result = await this.encryptWithKeychain(tokens);
console.error(`[TOKEN_ENCRYPTION] ✅ Using KEYCHAIN storage (more secure)`);
return result;
}
catch (error) {
console.error(`[TOKEN_ENCRYPTION] ⚠️ Keychain not available, falling back to file encryption`);
console.error(`[TOKEN_ENCRYPTION] 🔐 Using FILE-BASED encryption with AES-256-GCM`);
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 = `${this.provider}-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 = `${this.provider}-${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