UNPKG

@hivetechs/hive-ai

Version:

Real-time streaming AI consensus platform with HTTP+SSE MCP integration for Claude Code, VS Code, Cursor, and Windsurf - powered by OpenRouter's unified API

493 lines (492 loc) 17.3 kB
/** * Modern License Gate System (2025 Best Practices) * * Features: * - Pure ES Module design * - Async-first architecture * - Encrypted local storage * - Multiple fallback strategies * - Circuit breaker pattern * - Comprehensive error handling */ import { createRequire } from 'node:module'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; import { homedir } from 'node:os'; import { createHash, randomBytes, createCipheriv, createDecipheriv } from 'node:crypto'; // Create require for compatibility const require = createRequire(import.meta.url); const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); export var FeatureTier; (function (FeatureTier) { FeatureTier["FREE"] = "free"; FeatureTier["PREMIUM"] = "premium"; })(FeatureTier || (FeatureTier = {})); export class ModernLicenseGate { constructor() { this.licenseCache = null; this.CACHE_TTL = 60 * 60 * 1000; // 1 hour this.CONFIG_DIR = join(homedir(), '.hive-ai'); this.LICENSE_FILE = join(this.CONFIG_DIR, 'license.enc'); this.CIRCUIT_BREAKER_THRESHOLD = 3; this.CIRCUIT_BREAKER_TIMEOUT = 30000; // 30 seconds this.quietMode = false; this.circuitBreaker = { failures: 0, lastFailure: 0, state: 'closed' }; this.freeFeatures = [ 'setup_wizard', 'configure_provider', 'test_providers', 'help', 'browse_models', 'single_model_query', 'basic_analytics' ]; this.premiumFeatures = [ 'consensus_query', 'multi_model_query', 'dashboard', 'analytics', 'export_data', 'cost_tracking', 'benchmarking', 'advanced_features' ]; } static getInstance() { if (!ModernLicenseGate.instance) { ModernLicenseGate.instance = new ModernLicenseGate(); } return ModernLicenseGate.instance; } /** * Enable quiet mode to suppress debug messages during interactive sessions */ setQuietMode(quiet) { this.quietMode = quiet; } /** * Force refresh license cache (useful after plan upgrades) */ async refreshLicense() { if (!this.quietMode) { console.log('🔄 Refreshing license information...'); } // Store previous license info for comparison const previousLicense = this.licenseCache?.license; // Clear local cache this.licenseCache = null; // Delete encrypted file to force re-validation try { const fs = await import('node:fs/promises'); await fs.unlink(this.LICENSE_FILE); } catch (error) { // File might not exist, which is fine } // Reset circuit breaker this.circuitBreaker = { failures: 0, lastFailure: 0, state: 'closed' }; // Validate fresh from server const license = await this.validateLicense(); // Log details about the refresh const tierDisplayName = license.tier === FeatureTier.FREE ? 'free' : (license.dailyLimit || 0) >= 999999 ? 'unlimited' : 'premium'; if (!this.quietMode) { console.log(`✅ License refreshed - ${tierDisplayName} tier (${(license.dailyLimit || 0) >= 999999 ? 'unlimited' : license.dailyLimit || 0} daily conversations)`); // Check if plan changed if (previousLicense && (previousLicense.tier !== license.tier || previousLicense.dailyLimit !== license.dailyLimit)) { console.log(`🔄 Plan change detected: ${previousLicense.tier} (${previousLicense.dailyLimit}) → ${license.tier} (${license.dailyLimit})`); } } return license; } /** * Clear stored license and cache (for logout) */ async clearLicense() { try { // Clear in-memory cache this.licenseCache = null; // Clear cached license file if it exists try { const fs = await this.getFS(); if (fs.existsSync(this.LICENSE_FILE)) { fs.unlinkSync(this.LICENSE_FILE); } } catch (error) { // Ignore file system errors during cleanup } console.log('🔓 License cache cleared successfully'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.warn(`[LicenseGate] Error clearing license cache: ${errorMessage}`); } } /** * Primary entry point for feature access validation */ async checkFeatureAccess(feature) { try { const license = await this.validateLicense(); if (!license.valid) { throw new NoLicenseError(feature); } // Check feature tier requirements if (this.freeFeatures.includes(feature)) { return license; } else if (this.premiumFeatures.includes(feature)) { if (license.tier === FeatureTier.FREE) { throw new PremiumFeatureError(feature); } return license; } return license; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (!this.quietMode) { console.error(`[LicenseGate] Feature access check failed for ${feature}:`, errorMessage); } throw error; } } /** * Validate license using multiple strategies */ async validateLicense() { // Check cache first if (this.isCacheValid()) { return this.licenseCache.license; } // Try to get license key using multiple strategies const licenseKey = await this.getLicenseKey(); if (!licenseKey) { return this.createInvalidLicense(); } // Validate with API (with circuit breaker) try { const license = await this.validateWithAPI(licenseKey); await this.cacheLicense(license); return license; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.warn(`[LicenseGate] API validation failed: ${errorMessage}`); // Try cached license as fallback const cachedLicense = await this.getCachedLicense(); if (cachedLicense) { console.info('[LicenseGate] Using cached license due to API failure'); return cachedLicense; } return this.createInvalidLicense(); } } /** * Get license key from unified database storage */ async getLicenseKey() { // Strategy 1: Environment variable (highest priority for CI/CD) if (process.env.HIVE_TOOLS_LICENSE) { if (!this.quietMode) { console.debug('[LicenseGate] Using environment variable license'); } return process.env.HIVE_TOOLS_LICENSE; } // Strategy 2: Unified Database (primary storage for new product) try { const { getConfig } = await import('../storage/unified-database.js'); const licenseKey = await getConfig('license_key'); if (licenseKey && licenseKey.startsWith('HIVE-')) { if (!this.quietMode) { console.debug('[LicenseGate] Found license in unified database'); } return licenseKey; } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (!this.quietMode) { console.debug(`[LicenseGate] Database license read failed: ${errorMessage}`); } } if (!this.quietMode) { console.debug('[LicenseGate] No license found'); } return null; } /** * Validate license with API using circuit breaker pattern */ async validateWithAPI(licenseKey) { // Check circuit breaker if (this.circuitBreaker.state === 'open') { const timeSinceLastFailure = Date.now() - this.circuitBreaker.lastFailure; if (timeSinceLastFailure < this.CIRCUIT_BREAKER_TIMEOUT) { throw new Error('Circuit breaker is open - API temporarily unavailable'); } else { this.circuitBreaker.state = 'half-open'; } } try { const response = await this.makeAPICall(licenseKey); // Reset circuit breaker on success this.circuitBreaker.failures = 0; this.circuitBreaker.state = 'closed'; return response; } catch (error) { // Handle circuit breaker this.circuitBreaker.failures++; this.circuitBreaker.lastFailure = Date.now(); if (this.circuitBreaker.failures >= this.CIRCUIT_BREAKER_THRESHOLD) { this.circuitBreaker.state = 'open'; } throw error; } } /** * Make API call with proper error handling */ async makeAPICall(licenseKey) { const { getApiEndpoint } = require('../config/endpoints.js'); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout try { const response = await fetch(`${getApiEndpoint('general')}/v1/session/validate`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'User-Agent': 'HiveClient/2.0' }, body: JSON.stringify({ client_id: 'hive-tools', session_token: licenseKey, fingerprint: await this.getDeviceFingerprint(), version: await this.getVersion(), nonce: Date.now() }), signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`API returned ${response.status}: ${response.statusText}`); } const license = await response.json(); // Ensure valid response structure license.valid = true; license.tier = license.tier === 'free' ? FeatureTier.FREE : FeatureTier.PREMIUM; return license; } catch (error) { clearTimeout(timeoutId); if (error instanceof Error && error.name === 'AbortError') { throw new Error('License validation timeout'); } const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`License validation failed: ${errorMessage}`); } } /** * Store license key securely with encryption */ async storeLicenseKey(licenseKey) { try { const fs = await this.getFS(); // Ensure config directory exists if (!fs.existsSync(this.CONFIG_DIR)) { fs.mkdirSync(this.CONFIG_DIR, { recursive: true, mode: 0o700 }); } // Create encrypted license data const licenseData = { licenseKey, createdAt: new Date().toISOString(), version: '2.0' }; const encrypted = this.encryptData(JSON.stringify(licenseData)); // Write encrypted file fs.writeFileSync(this.LICENSE_FILE, encrypted, { mode: 0o600 }); // Clear cache to force revalidation this.licenseCache = null; console.info('[LicenseGate] License key stored securely'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Failed to store license key: ${errorMessage}`); } } /** * Encrypt sensitive data */ encryptData(data) { const algorithm = 'aes-256-gcm'; const key = this.getDerivedKey(); const iv = randomBytes(16); const cipher = createCipheriv(algorithm, key, iv); let encrypted = cipher.update(data, 'utf8', 'hex'); encrypted += cipher.final('hex'); const authTag = cipher.getAuthTag(); return JSON.stringify({ encrypted, iv: iv.toString('hex'), authTag: authTag.toString('hex'), algorithm }); } /** * Decrypt sensitive data */ decryptData(encryptedData) { const { encrypted, iv, authTag, algorithm } = JSON.parse(encryptedData); const key = this.getDerivedKey(); const decipher = createDecipheriv(algorithm, key, Buffer.from(iv, 'hex')); decipher.setAuthTag(Buffer.from(authTag, 'hex')); let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } /** * Derive encryption key from system info */ getDerivedKey() { const machineId = this.getMachineId(); return createHash('sha256').update(machineId + 'hive-license-salt-2025').digest(); } /** * Get stable machine identifier */ getMachineId() { try { const { platform, arch, hostname } = require('node:os'); const id = `${platform()}-${arch()}-${hostname()}`; return createHash('sha256').update(id).digest('hex').substring(0, 16); } catch { return 'unknown-machine'; } } /** * Get device fingerprint for API calls */ async getDeviceFingerprint() { try { const os = require('node:os'); const fingerprint = { platform: os.platform(), arch: os.arch(), hostname: os.hostname(), user: os.userInfo().username }; return createHash('sha256') .update(JSON.stringify(fingerprint)) .digest('hex') .substring(0, 16); } catch { return 'unknown-device'; } } /** * Get version information */ async getVersion() { try { const fs = await this.getFS(); const packagePath = join(__dirname, '..', '..', 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8')); return packageJson.version; } catch { return '1.0.0'; } } /** * Get filesystem module with proper error handling */ async getFS() { try { // Try multiple approaches to get fs return require('node:fs'); } catch { try { return require('fs'); } catch { throw new Error('Unable to access filesystem module'); } } } /** * Check if cache is valid */ isCacheValid() { if (!this.licenseCache) return false; return Date.now() - this.licenseCache.timestamp < this.CACHE_TTL; } /** * Cache license with encryption */ async cacheLicense(license) { this.licenseCache = { license, timestamp: Date.now(), encryptionKey: randomBytes(32).toString('hex') }; } /** * Get cached license */ async getCachedLicense() { return this.licenseCache?.license || null; } /** * Create invalid license response */ createInvalidLicense() { return { valid: false, tier: FeatureTier.FREE, features: [], message: 'License key required. Get your FREE license at https://hivetechs.io/pricing' }; } } export class NoLicenseError extends Error { constructor(feature) { super(`🔑 License required to use '${feature}'. Get your FREE license at hivetechs.io/pricing`); this.name = 'NoLicenseError'; } } export class PremiumFeatureError extends Error { constructor(feature) { super(`🔒 Premium feature '${feature}' requires a premium license. Upgrade at hivetechs.io/pricing`); this.name = 'PremiumFeatureError'; } } /** * Decorator for protecting premium MCP tools */ export function requiresPremium(target, propertyName, descriptor) { const method = descriptor.value; descriptor.value = async function (...args) { const licenseGate = ModernLicenseGate.getInstance(); await licenseGate.checkFeatureAccess(propertyName); return method.apply(this, args); }; return descriptor; }