UNPKG

survey-mcp-server

Version:

Survey management server handling survey creation, response collection, analysis, and reporting with database access for data management

211 lines 7.68 kB
import { getCompanyImoNumbers, shouldBypassImoFiltering } from './imoUtils.js'; import { config } from './config.js'; import { logger } from './logger.js'; /** * Universal response filtering utility for IMO-based access control * Recursively scans response objects and filters out content with unauthorized IMO numbers */ // Common IMO field names to check in responses const IMO_FIELD_NAMES = ['imo', 'vesselImo', 'imoNumber', 'vessel_imo']; /** * Check if a value is a valid IMO number (digits only) */ function isValidImoNumber(value) { if (typeof value === 'number') { return Number.isInteger(value) && value > 0; } if (typeof value === 'string') { return /^\d+$/.test(value.trim()); } return false; } /** * Check if an IMO number is authorized for the current company */ function isAuthorizedImo(imoValue) { if (!isValidImoNumber(imoValue)) { return true; // Not an IMO number, allow through } const companyName = config.companyName; // Admin companies bypass all filtering if (!companyName || shouldBypassImoFiltering(companyName)) { return true; } const companyImos = getCompanyImoNumbers(); // If no IMO restrictions, allow all if (companyImos.length === 0) { return true; } // Check if IMO is in company's authorized list const imoNum = Number(imoValue); const companyImosNum = companyImos.map(imo => Number(imo)); return companyImosNum.includes(imoNum); } /** * Recursively scan an object for IMO numbers and check authorization */ function scanObjectForUnauthorizedImos(obj, path = '') { const unauthorizedImos = []; if (obj === null || obj === undefined) { return unauthorizedImos; } if (typeof obj === 'object') { // Check if this is an array if (Array.isArray(obj)) { obj.forEach((item, index) => { const itemPath = `${path}[${index}]`; unauthorizedImos.push(...scanObjectForUnauthorizedImos(item, itemPath)); }); } else { // Check each property of the object Object.keys(obj).forEach(key => { const propertyPath = path ? `${path}.${key}` : key; const value = obj[key]; // Check if this property is an IMO field if (IMO_FIELD_NAMES.includes(key.toLowerCase())) { if (!isAuthorizedImo(value)) { unauthorizedImos.push(`${propertyPath}: ${value}`); } } else { // Recursively check nested objects unauthorizedImos.push(...scanObjectForUnauthorizedImos(value, propertyPath)); } }); } } return unauthorizedImos; } /** * Filter array items based on IMO authorization */ function filterArrayByImo(arr) { const filtered = []; const removedItems = []; let removedCount = 0; arr.forEach(item => { const unauthorizedImos = scanObjectForUnauthorizedImos(item); if (unauthorizedImos.length === 0) { filtered.push(item); } else { removedItems.push(item); removedCount++; } }); return { filtered, removedCount, removedItems }; } /** * Recursively filter response content based on IMO authorization */ function filterResponseContent(content) { const stats = { itemsRemoved: 0, unauthorizedImos: [], filteredPaths: [] }; if (content === null || content === undefined) { return { filtered: content, stats }; } if (Array.isArray(content)) { const result = filterArrayByImo(content); stats.itemsRemoved = result.removedCount; // Log details about removed items result.removedItems.forEach(item => { const unauthorizedImos = scanObjectForUnauthorizedImos(item); stats.unauthorizedImos.push(...unauthorizedImos); }); return { filtered: result.filtered, stats }; } if (typeof content === 'object') { const filtered = { ...content }; Object.keys(content).forEach(key => { const result = filterResponseContent(content[key]); filtered[key] = result.filtered; // Merge stats stats.itemsRemoved += result.stats.itemsRemoved; stats.unauthorizedImos.push(...result.stats.unauthorizedImos); stats.filteredPaths.push(...result.stats.filteredPaths.map(path => `${key}.${path}`)); }); return { filtered, stats }; } return { filtered: content, stats }; } /** * Main function to filter tool responses based on company IMO authorization */ export async function filterResponseByCompanyImos(response) { const companyName = config.companyName; // Skip filtering for admin companies if (!companyName || shouldBypassImoFiltering(companyName)) { return response; } const companyImos = getCompanyImoNumbers(); // If no IMO restrictions, allow all if (companyImos.length === 0) { return response; } try { const filteredResponse = []; let totalItemsRemoved = 0; let totalUnauthorizedImos = []; for (const item of response) { if (item.type === 'text') { // For text responses, try to parse as JSON and filter try { const parsedContent = JSON.parse(item.text); const result = filterResponseContent(parsedContent); // Update stats totalItemsRemoved += result.stats.itemsRemoved; totalUnauthorizedImos.push(...result.stats.unauthorizedImos); // Update the response with filtered content filteredResponse.push({ ...item, text: JSON.stringify(result.filtered, null, 2) }); } catch (parseError) { // If it's not JSON, pass through as-is filteredResponse.push(item); } } else { // For non-text responses, pass through as-is filteredResponse.push(item); } } // Log filtering results if (totalItemsRemoved > 0) { logger.warn(`Filtered ${totalItemsRemoved} unauthorized items from response`, { companyName, itemsRemoved: totalItemsRemoved, unauthorizedImos: totalUnauthorizedImos, companyImoCount: companyImos.length }); } return filteredResponse; } catch (error) { logger.error('Error filtering response by company IMOs:', error); // Return original response if filtering fails return response; } } /** * Filter specific content object (used for nested filtering) */ export function filterContentByCompanyImos(content) { const companyName = config.companyName; // Skip filtering for admin companies if (!companyName || shouldBypassImoFiltering(companyName)) { return { filtered: content, stats: { itemsRemoved: 0, unauthorizedImos: [], filteredPaths: [] } }; } const companyImos = getCompanyImoNumbers(); // If no IMO restrictions, allow all if (companyImos.length === 0) { return { filtered: content, stats: { itemsRemoved: 0, unauthorizedImos: [], filteredPaths: [] } }; } return filterResponseContent(content); } //# sourceMappingURL=responseFilter.js.map