Skip to main content
Glama
intecrel

Industrial MCP Server

by intecrel
audit-logger.ts14.6 kB
/** * Comprehensive Audit Logging for OAuth and Security Events * Provides structured logging for compliance and security monitoring */ export interface AuditEvent { timestamp: string; event_type: string; user_id?: string; user_email?: string | null; client_id?: string; ip_address?: string; user_agent?: string; session_id?: string; resource?: string; action: string; result: 'success' | 'failure' | 'warning'; details?: Record<string, any>; risk_level: 'low' | 'medium' | 'high' | 'critical'; } /** * Specialized audit event interface for database operations */ export interface DatabaseAuditEvent extends AuditEvent { database_type: 'neo4j' | 'mysql'; operation_type: 'CREATE' | 'MERGE' | 'SET' | 'READ'; query_hash: string; // SHA256 hash of the executed query affected_nodes: number; affected_relationships: number; execution_time_ms: number; before_state?: any; // State before operation (for SET operations) after_state?: any; // State after operation complexity_score: number; // Query complexity rating (0-100) transaction_id?: string; // Transaction identifier if part of transaction query_parameters?: Record<string, any>; // Sanitized query parameters } export enum AuditEventType { // Authentication Events AUTH_LOGIN_SUCCESS = 'auth.login.success', AUTH_LOGIN_FAILURE = 'auth.login.failure', AUTH_LOGOUT = 'auth.logout', AUTH_SESSION_EXPIRED = 'auth.session.expired', // OAuth Events OAUTH_AUTHORIZE_REQUEST = 'oauth.authorize.request', OAUTH_CONSENT_GRANTED = 'oauth.consent.granted', OAUTH_CONSENT_DENIED = 'oauth.consent.denied', OAUTH_TOKEN_ISSUED = 'oauth.token.issued', OAUTH_TOKEN_REVOKED = 'oauth.token.revoked', // Security Events SECURITY_CSRF_VALIDATION_FAILED = 'security.csrf.validation_failed', SECURITY_RATE_LIMIT_EXCEEDED = 'security.rate_limit.exceeded', SECURITY_UNAUTHORIZED_ACCESS = 'security.unauthorized.access', SECURITY_SUSPICIOUS_ACTIVITY = 'security.suspicious.activity', // Database Events - Neo4j Operations DB_NEO4J_CREATE_NODE = 'database.neo4j.create_node', DB_NEO4J_CREATE_RELATIONSHIP = 'database.neo4j.create_relationship', DB_NEO4J_MERGE_OPERATION = 'database.neo4j.merge_operation', DB_NEO4J_SET_PROPERTY = 'database.neo4j.set_property', DB_NEO4J_QUERY_EXECUTED = 'database.neo4j.query_executed', DB_NEO4J_TRANSACTION_STARTED = 'database.neo4j.transaction_started', DB_NEO4J_TRANSACTION_COMMITTED = 'database.neo4j.transaction_committed', DB_NEO4J_TRANSACTION_ROLLED_BACK = 'database.neo4j.transaction_rolled_back', DB_NEO4J_DANGEROUS_OPERATION_BLOCKED = 'database.neo4j.dangerous_operation_blocked', // System Events SYSTEM_ERROR = 'system.error', SYSTEM_CONFIG_CHANGE = 'system.config.change', } /** * Extract client information from request */ function extractClientInfo(request: Request): { ip_address?: string; user_agent?: string; } { const forwardedFor = request.headers.get('x-forwarded-for'); const realIp = request.headers.get('x-real-ip'); const cfConnectingIp = request.headers.get('cf-connecting-ip'); const ip_address = forwardedFor?.split(',')[0] || realIp || cfConnectingIp; const user_agent = request.headers.get('user-agent'); return { ip_address: ip_address || undefined, user_agent: user_agent || undefined }; } /** * Log an audit event with configurable storage */ export async function logAuditEvent(event: Omit<AuditEvent, 'timestamp'>): Promise<void> { const auditEvent: AuditEvent = { timestamp: new Date().toISOString(), ...event }; try { // Use configurable audit storage const { storeAuditEvent } = await import('../database/audit-storage'); await storeAuditEvent(auditEvent); } catch (error) { // Fallback to console logging if storage fails console.error('❌ Audit storage failed, falling back to console:', error); const logLevel = event.risk_level === 'critical' || event.risk_level === 'high' ? 'error' : event.risk_level === 'medium' ? 'warn' : 'info'; const logMessage = { audit: true, storage_fallback: true, ...auditEvent }; console[logLevel](`[AUDIT] ${JSON.stringify(logMessage)}`); // Alert on high-risk events if (event.risk_level === 'critical' || event.risk_level === 'high') { console.error(`🚨 HIGH RISK AUDIT EVENT: ${event.event_type} - ${event.action}`); } } } /** * Create audit logger for specific context */ export function createAuditLogger( request: Request, user?: { id?: string; email?: string | null }, sessionId?: string ) { const clientInfo = extractClientInfo(request); return { log: async (eventType: AuditEventType, action: string, result: AuditEvent['result'], details?: Record<string, any>, riskLevel: AuditEvent['risk_level'] = 'low') => { await logAuditEvent({ event_type: eventType, user_id: user?.id || user?.email || undefined, user_email: user?.email || undefined, session_id: sessionId, action, result, details, risk_level: riskLevel, ...clientInfo }); }, logOAuthEvent: async (eventType: AuditEventType, action: string, clientId: string, result: AuditEvent['result'], details?: Record<string, any>, riskLevel: AuditEvent['risk_level'] = 'medium') => { await logAuditEvent({ event_type: eventType, user_id: user?.id || user?.email || undefined, user_email: user?.email || undefined, client_id: clientId, session_id: sessionId, action, result, details, risk_level: riskLevel, ...clientInfo }); }, logSecurityEvent: async (eventType: AuditEventType, action: string, result: AuditEvent['result'], details?: Record<string, any>, riskLevel: AuditEvent['risk_level'] = 'high') => { await logAuditEvent({ event_type: eventType, user_id: user?.id || user?.email || undefined, user_email: user?.email || undefined, session_id: sessionId, action, result, details, risk_level: riskLevel, ...clientInfo }); } }; } /** * Log OAuth consent decision */ export async function logOAuthConsent( request: Request, user: { id?: string; email?: string | null }, clientId: string, approved: boolean, scopes: string[], sessionId?: string ): Promise<void> { const logger = createAuditLogger(request, user, sessionId); await logger.logOAuthEvent( approved ? AuditEventType.OAUTH_CONSENT_GRANTED : AuditEventType.OAUTH_CONSENT_DENIED, `OAuth consent ${approved ? 'granted' : 'denied'} for client ${clientId}`, clientId, 'success', { scopes, approved, consent_timestamp: new Date().toISOString() }, approved ? 'medium' : 'low' ); } /** * Log security violation */ export async function logSecurityViolation( request: Request, violation: string, details?: Record<string, any>, user?: { id?: string; email?: string | null } ): Promise<void> { const logger = createAuditLogger(request, user); await logger.logSecurityEvent( AuditEventType.SECURITY_SUSPICIOUS_ACTIVITY, `Security violation detected: ${violation}`, 'failure', details, 'high' ); } /** * Create a SHA256 hash of query for audit logging */ function createQueryHash(query: string): string { // Simple hash implementation for audit purposes // In production, use crypto.createHash('sha256') let hash = 0; for (let i = 0; i < query.length; i++) { const char = query.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32bit integer } return Math.abs(hash).toString(16); } /** * Sanitize query parameters for audit logging (remove sensitive data) */ function sanitizeParameters(params: Record<string, any>): Record<string, any> { const sanitized: Record<string, any> = {}; for (const [key, value] of Object.entries(params)) { // Mask potentially sensitive fields const sensitiveFields = ['password', 'token', 'secret', 'key', 'auth']; const isSensitive = sensitiveFields.some(field => key.toLowerCase().includes(field) ); if (isSensitive) { sanitized[key] = '[REDACTED]'; } else if (typeof value === 'string' && value.length > 100) { sanitized[key] = value.substring(0, 100) + '...[TRUNCATED]'; } else { sanitized[key] = value; } } return sanitized; } /** * Log database operation audit event with configurable storage */ export async function logDatabaseOperation( eventType: AuditEventType, action: string, result: DatabaseAuditEvent['result'], databaseType: 'neo4j' | 'mysql', operationType: 'CREATE' | 'MERGE' | 'SET' | 'READ', query: string, executionTimeMs: number, affectedNodes: number = 0, affectedRelationships: number = 0, complexityScore: number = 0, beforeState?: any, afterState?: any, parameters?: Record<string, any>, transactionId?: string, user?: { id?: string; email?: string | null }, clientInfo?: { ip_address?: string; user_agent?: string } ): Promise<void> { const databaseAuditEvent: Omit<DatabaseAuditEvent, 'timestamp'> = { event_type: eventType, user_id: user?.id || user?.email || undefined, user_email: user?.email || undefined, action, result, database_type: databaseType, operation_type: operationType, query_hash: createQueryHash(query), affected_nodes: affectedNodes, affected_relationships: affectedRelationships, execution_time_ms: executionTimeMs, complexity_score: complexityScore, before_state: beforeState, after_state: afterState, query_parameters: parameters ? sanitizeParameters(parameters) : undefined, transaction_id: transactionId, risk_level: determineRiskLevel(operationType, affectedNodes, affectedRelationships, complexityScore), ip_address: clientInfo?.ip_address, user_agent: clientInfo?.user_agent, details: { query_length: query.length, has_transaction: !!transactionId, parameter_count: parameters ? Object.keys(parameters).length : 0 } }; try { // Use configurable database audit storage const { storeDatabaseAuditEvent } = await import('../database/audit-storage'); await storeDatabaseAuditEvent(databaseAuditEvent as DatabaseAuditEvent); } catch (error) { // Fallback to regular audit logging console.error('❌ Database audit storage failed, using fallback:', error); await logAuditEvent(databaseAuditEvent); } } /** * Determine risk level for database operations */ function determineRiskLevel( operationType: string, affectedNodes: number, affectedRelationships: number, complexityScore: number ): DatabaseAuditEvent['risk_level'] { const totalAffected = affectedNodes + affectedRelationships; // High risk: Large operations or high complexity if (totalAffected > 1000 || complexityScore > 80) { return 'high'; } // Medium risk: Write operations or moderate size if (operationType !== 'READ' || totalAffected > 100 || complexityScore > 50) { return 'medium'; } // Low risk: Small read operations return 'low'; } /** * Create database audit logger for specific context */ export function createDatabaseAuditLogger( databaseType: 'neo4j' | 'mysql', user?: { id?: string; email?: string | null }, clientInfo?: { ip_address?: string; user_agent?: string }, transactionId?: string ) { return { logQuery: async ( query: string, operationType: 'CREATE' | 'MERGE' | 'SET' | 'READ', result: DatabaseAuditEvent['result'], executionTimeMs: number, affectedNodes: number = 0, affectedRelationships: number = 0, complexityScore: number = 0, beforeState?: any, afterState?: any, parameters?: Record<string, any> ) => { const eventType = getEventTypeForOperation(databaseType, operationType); await logDatabaseOperation( eventType, `${operationType} operation on ${databaseType}`, result, databaseType, operationType, query, executionTimeMs, affectedNodes, affectedRelationships, complexityScore, beforeState, afterState, parameters, transactionId, user, clientInfo ); }, logTransaction: async (action: 'START' | 'COMMIT' | 'ROLLBACK', result: DatabaseAuditEvent['result']) => { const eventType = action === 'START' ? AuditEventType.DB_NEO4J_TRANSACTION_STARTED : action === 'COMMIT' ? AuditEventType.DB_NEO4J_TRANSACTION_COMMITTED : AuditEventType.DB_NEO4J_TRANSACTION_ROLLED_BACK; await logAuditEvent({ event_type: eventType, user_id: user?.id || user?.email || undefined, user_email: user?.email || undefined, action: `Transaction ${action.toLowerCase()}`, result, risk_level: action === 'ROLLBACK' ? 'medium' : 'low', ip_address: clientInfo?.ip_address, user_agent: clientInfo?.user_agent, details: { transaction_id: transactionId, database_type: databaseType } }); }, logDangerousOperationBlocked: async (query: string, reason: string) => { await logAuditEvent({ event_type: AuditEventType.DB_NEO4J_DANGEROUS_OPERATION_BLOCKED, user_id: user?.id || user?.email || undefined, user_email: user?.email || undefined, action: `Blocked dangerous database operation: ${reason}`, result: 'warning', risk_level: 'high', ip_address: clientInfo?.ip_address, user_agent: clientInfo?.user_agent, details: { blocked_query: query.substring(0, 200) + (query.length > 200 ? '...' : ''), block_reason: reason, database_type: databaseType } }); } }; } /** * Get appropriate event type for database operation */ function getEventTypeForOperation(databaseType: string, operationType: string): AuditEventType { if (databaseType === 'neo4j') { switch (operationType) { case 'CREATE': return AuditEventType.DB_NEO4J_CREATE_NODE; case 'MERGE': return AuditEventType.DB_NEO4J_MERGE_OPERATION; case 'SET': return AuditEventType.DB_NEO4J_SET_PROPERTY; default: return AuditEventType.DB_NEO4J_QUERY_EXECUTED; } } return AuditEventType.DB_NEO4J_QUERY_EXECUTED; // Fallback }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/intecrel/industrial-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server