← back to Dear Bubbe Admin

backend/middleware/MessageInterceptor.js

272 lines

const axios = require('axios')
const AlertService = require('../services/AlertService')

/**
 * Message Interceptor Middleware for Dear Bubbe AI
 * 
 * This middleware can be integrated into the main Dear Bubbe application
 * to automatically process all messages through the admin alert system
 */
class MessageInterceptor {
  constructor(alertService) {
    this.alertService = alertService || new AlertService(null, console)
    this.processedMessages = new Set() // Prevent duplicate processing
    this.apiEndpoint = process.env.ADMIN_API_URL || 'http://localhost:3015'
  }

  /**
   * Process a message and check for violations
   * This should be called from the Dear Bubbe chat API
   */
  async processMessage(messageData) {
    try {
      const {
        message,
        userId,
        sessionId,
        ip,
        userAgent,
        timestamp,
        userProfile
      } = messageData

      // Create unique message ID to prevent duplicate processing
      const messageId = this.createMessageId(message, userId, timestamp)
      if (this.processedMessages.has(messageId)) {
        return { processed: true, violations: [] }
      }

      console.log(`[INTERCEPTOR] Processing message from user ${userId}`)

      // Prepare user context
      const userContext = {
        userId: userId || 'unknown',
        ip: ip || 'unknown',
        sessionId: sessionId || this.generateSessionId(),
        userAgent: userAgent || 'unknown',
        userProfile: userProfile || {}
      }

      // Process through alert service
      const result = await this.alertService.processMessage(message, userContext)

      // Mark as processed
      this.processedMessages.add(messageId)
      
      // Clean up old processed messages (keep last 1000)
      if (this.processedMessages.size > 1000) {
        const messagesArray = Array.from(this.processedMessages)
        this.processedMessages.clear()
        messagesArray.slice(-500).forEach(id => this.processedMessages.add(id))
      }

      console.log(`[INTERCEPTOR] Message processed: ${result.violations.length} violations found`)

      return {
        processed: true,
        blocked: result.blocked,
        violations: result.violations,
        alertsCreated: result.alertsCreated,
        shouldBlock: result.blocked,
        threatResponse: result.violations.some(v => v.severity === 'high' || v.severity === 'critical')
      }

    } catch (error) {
      console.error('[INTERCEPTOR] Error processing message:', error)
      return {
        processed: false,
        error: error.message,
        violations: [],
        alertsCreated: 0
      }
    }
  }

  /**
   * Express middleware function for automatic message processing
   * Add this to the Dear Bubbe API routes
   */
  middleware() {
    return async (req, res, next) => {
      try {
        // Only process chat messages
        if (req.path !== '/api/chat' || req.method !== 'POST') {
          return next()
        }

        const { message, userId } = req.body
        if (!message || message === '__init__') {
          return next() // Skip init messages
        }

        // Extract request context
        const messageData = {
          message: message,
          userId: userId || 'anonymous',
          sessionId: req.sessionId || req.sessionID,
          ip: this.extractIP(req),
          userAgent: req.get('User-Agent'),
          timestamp: new Date(),
          userProfile: req.body.userProfile || {}
        }

        // Process the message
        const result = await this.processMessage(messageData)

        // Add result to request for use by the main handler
        req.securityScan = result

        // If message should be blocked, return early
        if (result.blocked) {
          console.log(`[INTERCEPTOR] Blocking message from ${userId}: ${result.violations.length} violations`)
          return res.status(400).json({
            error: 'Message blocked due to policy violation',
            blocked: true,
            violations: result.violations.map(v => ({
              type: v.type,
              severity: v.severity,
              category: v.category || v.type
            }))
          })
        }

        // Continue to main handler
        next()

      } catch (error) {
        console.error('[INTERCEPTOR] Middleware error:', error)
        // Don't block the request on errors, just log
        req.securityScan = { 
          processed: false, 
          error: error.message 
        }
        next()
      }
    }
  }

  /**
   * Webhook endpoint for real-time message processing
   * Call this from Dear Bubbe application
   */
  async webhook(req, res) {
    try {
      const messageData = req.body
      const result = await this.processMessage(messageData)
      
      res.json({
        success: true,
        result
      })
    } catch (error) {
      console.error('[INTERCEPTOR] Webhook error:', error)
      res.status(500).json({
        success: false,
        error: error.message
      })
    }
  }

  /**
   * Integration helper for Dear Bubbe
   */
  static createIntegrationCode() {
    return `
// Add this to your Dear Bubbe chat API route (app/api/chat/route.ts)

// Import the admin integration
const axios = require('axios')

// Function to scan message for violations
async function scanMessageForViolations(messageData) {
  try {
    const response = await axios.post('http://localhost:3015/api/scan-message', {
      message: messageData.message,
      userId: messageData.userId,
      sessionId: messageData.sessionId,
      ip: messageData.ip,
      userAgent: messageData.userAgent,
      userProfile: messageData.userProfile
    }, {
      timeout: 5000 // Don't wait too long
    })
    
    return response.data.result || {}
  } catch (error) {
    console.warn('Admin scan failed:', error.message)
    return { processed: false, violations: [] }
  }
}

// In your chat handler, add this before processing:
export async function POST(request: NextRequest) {
  try {
    const body = await request.json()
    const { message, userId, userProfile } = body
    
    // Skip scanning for init messages
    if (message !== '__init__') {
      // Scan for violations
      const scanResult = await scanMessageForViolations({
        message,
        userId: userId || 'anonymous',
        sessionId: request.headers.get('x-session-id'),
        ip: request.headers.get('x-forwarded-for')?.split(',')[0] || 
            request.headers.get('x-real-ip') || 'unknown',
        userAgent: request.headers.get('user-agent'),
        userProfile
      })
      
      // Block if necessary
      if (scanResult.blocked) {
        return NextResponse.json({
          error: 'Message blocked due to policy violation',
          blocked: true,
          threatResponse: scanResult.threatResponse
        }, { status: 400 })
      }
      
      // Log violations for monitoring
      if (scanResult.violations?.length > 0) {
        console.warn(\`🚨 Security violations detected: \${scanResult.violations.length}\`)
      }
    }
    
    // Continue with normal processing...
    
  } catch (error) {
    // Handle errors...
  }
}
`
  }

  // Helper methods
  createMessageId(message, userId, timestamp) {
    const content = `${message}-${userId}-${timestamp.getTime()}`
    return require('crypto').createHash('md5').update(content).digest('hex')
  }

  generateSessionId() {
    return 'sess_' + Math.random().toString(36).substr(2, 9)
  }

  extractIP(req) {
    const forwarded = req.headers['x-forwarded-for']
    const realIp = req.headers['x-real-ip']
    const connectionRemote = req.connection?.remoteAddress
    const socketRemote = req.socket?.remoteAddress
    const connectionSocket = req.connection?.socket?.remoteAddress
    
    const ip = forwarded?.split(',')[0] || 
               realIp || 
               connectionRemote || 
               socketRemote || 
               connectionSocket || 
               'unknown'
    
    return ip.replace('::ffff:', '') // Clean IPv6 mapped IPv4
  }
}

module.exports = MessageInterceptor