UNPKG

@handit.ai/cli

Version:

AI-Powered Agent Instrumentation & Monitoring CLI Tool

184 lines (159 loc) 5.12 kB
const fs = require('fs-extra'); const path = require('path'); const os = require('os'); const crypto = require('crypto'); class TokenStorage { constructor() { this.configDir = path.join(os.homedir(), '.handit'); this.tokensFile = path.join(this.configDir, 'tokens.json'); this.encryptionKey = this.getEncryptionKey(); } /** * Get or generate encryption key */ getEncryptionKey() { const keyFile = path.join(this.configDir, '.key'); try { if (fs.existsSync(keyFile)) { return fs.readFileSync(keyFile, 'utf8'); } } catch (error) { // If we can't read the key, we'll generate a new one } // Generate a new encryption key const key = crypto.randomBytes(32).toString('hex'); try { this.ensureConfigDir(); fs.writeFileSync(keyFile, key, { mode: 0o600 }); // Read/write for owner only return key; } catch (error) { console.warn('Warning: Could not save encryption key, using fallback'); return 'fallback-key-for-development-only'; } } /** * Ensure config directory exists */ ensureConfigDir() { if (!fs.existsSync(this.configDir)) { fs.mkdirSync(this.configDir, { mode: 0o700 }); // Read/write/execute for owner only } } /** * Simple encryption (for development - in production use proper encryption) */ encrypt(text) { try { const algorithm = 'aes-256-cbc'; const key = crypto.scryptSync(this.encryptionKey, 'salt', 32); const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv(algorithm, key, iv); let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex'); // Return IV + encrypted data return iv.toString('hex') + ':' + encrypted; } catch (error) { throw new Error(`Encryption failed: ${error.message}`); } } /** * Simple decryption (for development - in production use proper encryption) */ decrypt(encryptedText) { try { const algorithm = 'aes-256-cbc'; const key = crypto.scryptSync(this.encryptionKey, 'salt', 32); // Split IV and encrypted data const parts = encryptedText.split(':'); if (parts.length !== 2) { throw new Error('Invalid encrypted data format'); } const iv = Buffer.from(parts[0], 'hex'); const encrypted = parts[1]; const decipher = crypto.createDecipheriv(algorithm, key, iv); let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } catch (error) { throw new Error('Failed to decrypt tokens'); } } /** * Store authentication tokens */ async storeTokens(tokens) { try { this.ensureConfigDir(); const encryptedTokens = { authToken: tokens.authToken ? this.encrypt(tokens.authToken) : null, apiToken: tokens.apiToken ? this.encrypt(tokens.apiToken) : null, stagingApiToken: tokens.stagingApiToken ? this.encrypt(tokens.stagingApiToken) : null, user: tokens.user, company: tokens.company, timestamp: Date.now() }; await fs.writeFile(this.tokensFile, JSON.stringify(encryptedTokens, null, 2), { mode: 0o600 }); return true; } catch (error) { throw new Error(`Failed to store tokens: ${error.message}`); } } /** * Load authentication tokens */ async loadTokens() { try { if (!fs.existsSync(this.tokensFile)) { return null; } const encryptedData = await fs.readFile(this.tokensFile, 'utf8'); const encryptedTokens = JSON.parse(encryptedData); // Check if tokens are expired (24 hours) const tokenAge = Date.now() - encryptedTokens.timestamp; const maxAge = 24 * 60 * 60 * 1000; // 24 hours if (tokenAge > maxAge) { await this.clearTokens(); return null; } return { authToken: encryptedTokens.authToken ? this.decrypt(encryptedTokens.authToken) : null, apiToken: encryptedTokens.apiToken ? this.decrypt(encryptedTokens.apiToken) : null, stagingApiToken: encryptedTokens.stagingApiToken ? this.decrypt(encryptedTokens.stagingApiToken) : null, user: encryptedTokens.user, company: encryptedTokens.company }; } catch (error) { // If we can't decrypt, clear the tokens await this.clearTokens(); return null; } } /** * Clear stored tokens */ async clearTokens() { try { if (fs.existsSync(this.tokensFile)) { await fs.unlink(this.tokensFile); } return true; } catch (error) { console.warn('Warning: Could not clear tokens:', error.message); return false; } } /** * Check if tokens exist and are valid */ async hasValidTokens() { const tokens = await this.loadTokens(); return tokens !== null; } /** * Get tokens file path (for debugging) */ getTokensFilePath() { return this.tokensFile; } } module.exports = { TokenStorage };