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
JavaScript
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