← 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