@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
JavaScript
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;