UNPKG

@ufdevsllc/authme2.0

Version:

SDK for license management and remote monitoring with automatic system tracking, license validation, and remote control capabilities

676 lines (595 loc) 24.9 kB
import DatabaseManager from './database-manager.js'; import License from './models/license.js'; import UsageLog from './models/usage-log.js'; import os from 'os'; import crypto from 'crypto'; class LicenseValidator { constructor(errorHandler = null) { this.errorHandler = errorHandler; this.dbManager = new DatabaseManager(this.errorHandler); this.systemId = this.generateSystemId(); this.validationCache = new Map(); this.cacheTimeout = 5 * 60 * 1000; // 5 minutes cache } /** * Generate a unique system ID based on hardware characteristics * @returns {string} */ generateSystemId() { const hostname = os.hostname(); const platform = os.platform(); const arch = os.arch(); const networkInterfaces = os.networkInterfaces(); // Get MAC addresses for uniqueness const macAddresses = []; for (const interfaceName in networkInterfaces) { const interfaces = networkInterfaces[interfaceName]; for (const iface of interfaces) { if (iface.mac && iface.mac !== '00:00:00:00:00:00') { macAddresses.push(iface.mac); } } } const systemString = `${hostname}-${platform}-${arch}-${macAddresses.join(',')}`; return crypto.createHash('sha256').update(systemString).digest('hex').substring(0, 32); } /** * Initialize the license validator by connecting to the monitoring database * @returns {Promise<void>} */ async initialize() { const context = { component: 'license-validator', operation: 'initialization', systemId: this.systemId }; try { if (this.errorHandler) { await this.errorHandler.initialize(); } await this.dbManager.initMonitoringConnection(); if (this.errorHandler) { await this.errorHandler.logInfo('License validator initialized successfully', context); } else { console.log('License validator initialized successfully'); } } catch (error) { if (this.errorHandler) { await this.errorHandler.handleError(error, context); } else { console.error('Failed to initialize license validator:', error.message); } throw new Error(`License validator initialization failed: ${error.message}`); } } /** * Validate a license key against the database * @param {string} licenseKey - The license key to validate * @param {boolean} useCache - Whether to use cached validation results * @returns {Promise<Object>} Validation result object */ async validateLicense(licenseKey, useCache = true) { // Validate input if (this.errorHandler) { const validation = this.errorHandler.validateInput(licenseKey, { type: 'string', required: true, minLength: 8, maxLength: 200 }, { field: 'licenseKey' }); if (!validation.isValid) { const error = new Error(`Invalid license key: ${validation.errors.join(', ')}`); const errorResult = await this.errorHandler.handleLicenseError(error, licenseKey, { validationErrors: validation.errors }); return { isValid: false, reason: 'INVALID_LICENSE_KEY', message: errorResult.userMessage, canStartApplication: false, error: errorResult.technicalMessage }; } } else { // Basic validation without error handler if (!licenseKey || typeof licenseKey !== 'string' || licenseKey.length < 8) { return { isValid: false, reason: 'INVALID_LICENSE_KEY', message: 'License key is required and must be a valid string', canStartApplication: false, error: 'Invalid license key format' }; } } const startTime = Date.now(); const context = { component: 'license-validator', operation: 'validate-license', licenseKey, systemId: this.systemId }; try { // Check cache first if enabled if (useCache && this.validationCache.has(licenseKey)) { const cached = this.validationCache.get(licenseKey); if (Date.now() - cached.timestamp < this.cacheTimeout) { await this.logValidationAttempt(licenseKey, 'validation', true, { source: 'cache', ...cached.result }, Date.now() - startTime); return cached.result; } } // Ensure database connection if (!this.dbManager.isMonitoringConnected()) { await this.initialize(); } const result = await this._executeValidationWithRetry(licenseKey, context, startTime); return result; } catch (error) { const errorResult = this.errorHandler ? await this.errorHandler.handleLicenseError(error, licenseKey, context) : { errorType: 'VALIDATION_ERROR', userMessage: `Validation failed: ${error.message}`, technicalMessage: error.message }; await this.logValidationAttempt(licenseKey, 'validation', false, { reason: 'error', error: error.message }, Date.now() - startTime); return { isValid: false, reason: errorResult.errorType, message: errorResult.userMessage, canStartApplication: false, error: errorResult.technicalMessage }; } } /** * Execute validation with retry logic * @private */ async _executeValidationWithRetry(licenseKey, context, startTime) { const executeValidation = async () => { const db = this.dbManager.getMonitoringDB(); const LicenseModel = db.model('License', License.schema); // Find the license in the database const license = await LicenseModel.findOne({ licenseKey }).lean(); if (!license) { const result = { isValid: false, reason: 'LICENSE_NOT_FOUND', message: 'License key not found in database', canStartApplication: false }; await this.logValidationAttempt(licenseKey, 'validation', false, { reason: 'not_found' }, Date.now() - startTime); return result; } // Check if license is active if (!license.isActive) { const result = { isValid: false, reason: 'LICENSE_INACTIVE', message: 'License has been deactivated', canStartApplication: false, license: this.sanitizeLicenseData(license) }; await this.logValidationAttempt(licenseKey, 'validation', false, { reason: 'inactive' }, Date.now() - startTime); return result; } // Check if license is expired const now = new Date(); if (now > new Date(license.expiresAt)) { const result = { isValid: false, reason: 'LICENSE_EXPIRED', message: `License expired on ${new Date(license.expiresAt).toISOString()}`, canStartApplication: false, license: this.sanitizeLicenseData(license) }; await this.logValidationAttempt(licenseKey, 'validation', false, { reason: 'expired', expiresAt: license.expiresAt }, Date.now() - startTime); return result; } // Check distribution limit const distributionCheck = await this.checkDistributionLimit(licenseKey, license); if (!distributionCheck.canAcceptNewDistribution) { const result = { isValid: false, reason: 'DISTRIBUTION_LIMIT_EXCEEDED', message: `Distribution limit of ${license.distributionLimit} has been reached`, canStartApplication: false, license: this.sanitizeLicenseData(license), distributionInfo: distributionCheck }; await this.logValidationAttempt(licenseKey, 'validation', false, { reason: 'distribution_limit', ...distributionCheck }, Date.now() - startTime); return result; } // License is valid const result = { isValid: true, reason: 'LICENSE_VALID', message: 'License is valid and active', canStartApplication: true, license: this.sanitizeLicenseData(license), distributionInfo: distributionCheck }; // Cache the result this.validationCache.set(licenseKey, { result, timestamp: Date.now() }); await this.logValidationAttempt(licenseKey, 'validation', true, { reason: 'valid' }, Date.now() - startTime); return result; }; if (this.errorHandler) { return await this.errorHandler.executeWithRetry(executeValidation, context, { maxRetries: 2 }); } else { return await executeValidation(); } } /** * Check if a license can accept new distributions * @param {string} licenseKey - The license key to check * @param {Object} license - Optional license object to avoid database query * @returns {Promise<Object>} Distribution check result */ async checkDistributionLimit(licenseKey, license = null) { try { if (!license) { const db = this.dbManager.getMonitoringDB(); const LicenseModel = db.model('License', License.schema); license = await LicenseModel.findOne({ licenseKey }).lean(); if (!license) { throw new Error('License not found'); } } const canAcceptNewDistribution = license.currentDistributions < license.distributionLimit; const remainingDistributions = license.distributionLimit - license.currentDistributions; return { canAcceptNewDistribution, currentDistributions: license.currentDistributions, distributionLimit: license.distributionLimit, remainingDistributions: Math.max(0, remainingDistributions), utilizationPercentage: Math.round((license.currentDistributions / license.distributionLimit) * 100) }; } catch (error) { if (this.errorHandler) { await this.errorHandler.handleError(error, { component: 'license-validator', operation: 'check-distribution-limit', licenseKey }); } else { console.error('Distribution limit check error:', error); } throw new Error(`Failed to check distribution limit: ${error.message}`); } } /** * Track license usage by incrementing distribution count * @param {string} licenseKey - The license key to track * @param {Object} systemInfo - System information for tracking * @returns {Promise<Object>} Tracking result */ async trackUsage(licenseKey, systemInfo = {}) { try { const db = this.dbManager.getMonitoringDB(); const LicenseModel = db.model('License', License.schema); const license = await LicenseModel.findOne({ licenseKey }); if (!license) { throw new Error('License not found'); } // Check if this system is already tracked const existingUsage = await this.getExistingUsage(licenseKey, this.systemId); if (existingUsage) { // Update existing usage await this.logValidationAttempt(licenseKey, 'startup', true, { reason: 'existing_system', systemId: this.systemId, ...systemInfo }); return { success: true, message: 'Usage tracked for existing system', isNewDistribution: false, distributionInfo: await this.checkDistributionLimit(licenseKey, license) }; } // Check if we can accept a new distribution const distributionCheck = await this.checkDistributionLimit(licenseKey, license); if (!distributionCheck.canAcceptNewDistribution) { throw new Error('Distribution limit exceeded'); } // Increment distribution count license.currentDistributions += 1; await license.save(); // Log the new usage await this.logValidationAttempt(licenseKey, 'startup', true, { reason: 'new_distribution', systemId: this.systemId, distributionCount: license.currentDistributions, ...systemInfo }); return { success: true, message: 'New distribution tracked successfully', isNewDistribution: true, distributionInfo: { ...distributionCheck, currentDistributions: license.currentDistributions, remainingDistributions: distributionCheck.remainingDistributions - 1 } }; } catch (error) { if (this.errorHandler) { await this.errorHandler.handleError(error, { component: 'license-validator', operation: 'track-usage', licenseKey, systemId: this.systemId }); } else { console.error('Usage tracking error:', error); } await this.logValidationAttempt(licenseKey, 'startup', false, { reason: 'tracking_error', error: error.message, systemId: this.systemId }); throw new Error(`Failed to track usage: ${error.message}`); } } /** * Check if there's existing usage for this system * @param {string} licenseKey - The license key * @param {string} systemId - The system ID * @returns {Promise<Object|null>} Existing usage log or null */ async getExistingUsage(licenseKey, systemId) { try { const db = this.dbManager.getMonitoringDB(); const UsageLogModel = db.model('UsageLog', UsageLog.schema); const existingUsage = await UsageLogModel.findOne({ licenseKey, systemId, action: 'startup', success: true }).sort({ timestamp: -1 }); return existingUsage; } catch (error) { if (this.errorHandler) { await this.errorHandler.handleError(error, { component: 'license-validator', operation: 'get-existing-usage', licenseKey, systemId }); } else { console.error('Error checking existing usage:', error); } return null; } } /** * Check if a license is currently active and valid * @param {string} licenseKey - The license key to check * @returns {Promise<boolean>} True if license is active */ async isLicenseActive(licenseKey) { try { const validation = await this.validateLicense(licenseKey); return validation.isValid && validation.canStartApplication; } catch (error) { if (this.errorHandler) { await this.errorHandler.handleError(error, { component: 'license-validator', operation: 'is-license-active', licenseKey }); } else { console.error('License activity check error:', error); } return false; } } /** * Prevent application startup if license is invalid * @param {string} licenseKey - The license key to validate * @param {Object} options - Validation options * @returns {Promise<void>} Throws error if license is invalid */ async preventStartupOnInvalidLicense(licenseKey, options = {}) { const { exitOnFailure = true, logToConsole = true, customErrorHandler = null } = options; const context = { component: 'license-validator', operation: 'prevent-startup', licenseKey, systemId: this.systemId }; try { const validation = await this.validateLicense(licenseKey); if (!validation.canStartApplication) { const error = new Error(`APPLICATION STARTUP BLOCKED: ${validation.message}`); if (this.errorHandler) { const errorResult = await this.errorHandler.handleLicenseError(error, licenseKey, { ...context, validationReason: validation.reason, validationResult: validation }); } if (logToConsole) { console.error('\n' + '='.repeat(80)); console.error('LICENSE VALIDATION FAILED'); console.error('='.repeat(80)); console.error(`Reason: ${validation.reason}`); console.error(`Message: ${validation.message}`); console.error(`License Key: ${licenseKey.substring(0, 8)}...`); console.error('='.repeat(80) + '\n'); } // Log the startup prevention await this.logValidationAttempt(licenseKey, 'startup', false, { reason: 'startup_blocked', validationReason: validation.reason, systemId: this.systemId }); if (customErrorHandler && typeof customErrorHandler === 'function') { customErrorHandler(validation); } else if (exitOnFailure) { process.exit(1); } else { throw error; } } else { // Track successful startup await this.trackUsage(licenseKey, { startupTime: new Date(), nodeVersion: process.version, platform: os.platform(), arch: os.arch() }); if (logToConsole) { console.log(`License validated successfully for system ${this.systemId.substring(0, 8)}...`); } if (this.errorHandler) { await this.errorHandler.logInfo('License validation successful, application startup allowed', context); } } } catch (error) { const errorResult = this.errorHandler ? await this.errorHandler.handleLicenseError(error, licenseKey, context) : { userMessage: `LICENSE VALIDATION ERROR: ${error.message}` }; if (logToConsole) { console.error('\n' + '='.repeat(80)); console.error('LICENSE VALIDATION ERROR'); console.error('='.repeat(80)); console.error(`Error: ${errorResult.userMessage}`); console.error('='.repeat(80) + '\n'); } if (exitOnFailure) { process.exit(1); } else { throw new Error(errorResult.userMessage); } } } /** * Log a validation attempt to the usage logs * @param {string} licenseKey - The license key * @param {string} action - The action being performed * @param {boolean} success - Whether the action was successful * @param {Object} details - Additional details * @param {number} duration - Duration in milliseconds * @returns {Promise<void>} */ async logValidationAttempt(licenseKey, action, success, details = {}, duration = null) { try { const db = this.dbManager.getMonitoringDB(); const UsageLogModel = db.model('UsageLog', UsageLog.schema); const logData = { licenseKey, action, success, systemId: this.systemId, details: { ...details, timestamp: new Date(), userAgent: process.env.npm_config_user_agent || 'unknown', nodeVersion: process.version }, severity: success ? 'info' : 'error', category: 'license_validation' }; if (duration !== null) { logData.duration = duration; } if (!success && details.error) { logData.errorMessage = details.error; } await UsageLogModel.create(logData); } catch (error) { if (this.errorHandler) { await this.errorHandler.handleError(error, { component: 'license-validator', operation: 'log-validation-attempt' }); } else { console.error('Failed to log validation attempt:', error); } // Don't throw here to avoid breaking the main validation flow } } /** * Sanitize license data for safe return to client * @param {Object} license - The license object * @returns {Object} Sanitized license data */ sanitizeLicenseData(license) { return { licenseKey: license.licenseKey.substring(0, 8) + '...', distributionLimit: license.distributionLimit, currentDistributions: license.currentDistributions, isActive: license.isActive, expiresAt: license.expiresAt, vendorId: license.vendorId, productId: license.productId }; } /** * Clear the validation cache */ clearCache() { this.validationCache.clear(); } /** * Get system information for tracking * @returns {Object} System information */ getSystemInfo() { return { systemId: this.systemId, hostname: os.hostname(), platform: os.platform(), arch: os.arch(), nodeVersion: process.version, totalMemory: os.totalmem(), freeMemory: os.freemem(), cpus: os.cpus().length, uptime: os.uptime() }; } /** * Close database connections and cleanup * @returns {Promise<void>} */ async cleanup() { const context = { component: 'license-validator', operation: 'cleanup', systemId: this.systemId }; try { this.clearCache(); await this.dbManager.closeConnection(); if (this.errorHandler) { await this.errorHandler.logInfo('License validator cleanup completed', context); } else { console.log('License validator cleanup completed'); } } catch (error) { if (this.errorHandler) { await this.errorHandler.handleError(error, context); } else { console.error('Error during license validator cleanup:', error); } } finally { if (this.errorHandler) { await this.errorHandler.cleanup(); } } } } export default LicenseValidator;