@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
JavaScript
/**
* 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;
}