const PUBLIC_RUNTIME_ALLOWED_PATH = '/'; const PUBLIC_RUNTIME_ENTITY_FIELDS = { projects: [ 'id', 'name', 'slug', 'description', 'phase', 'logo_url', 'favicon_url', 'og_image_url', 'theme_config_json', 'custom_css_json', 'cdn_base_url', 'entry_page_slug', ], tour_pages: [ 'id', 'projectId', 'environment', 'source_key', 'name', 'slug', 'sort_order', 'background_image_url', 'background_video_url', 'background_audio_url', 'background_loop', 'requires_auth', 'ui_schema_json', ], page_elements: [ 'id', 'pageId', 'element_type', 'name', 'sort_order', 'is_visible', 'x_percent', 'y_percent', 'width_percent', 'height_percent', 'rotation_deg', 'style_json', 'content_json', ], page_links: [ 'id', 'from_pageId', 'to_pageId', 'transitionId', 'direction', 'external_url', 'is_active', 'trigger_selector', ], transitions: [ 'id', 'projectId', 'environment', 'source_key', 'name', 'slug', 'video_url', 'audio_url', 'supports_reverse', 'duration_sec', ], project_audio_tracks: [ 'id', 'projectId', 'environment', 'source_key', 'name', 'slug', 'url', 'loop', 'volume', 'sort_order', 'is_enabled', ], }; const pickFields = (record, fields) => { if (!record || typeof record !== 'object') { return record; } return fields.reduce((acc, field) => { if (Object.prototype.hasOwnProperty.call(record, field)) { acc[field] = record[field]; } return acc; }, {}); }; const isPublicRuntimeReadRequest = (req) => { return req.isRuntimePublicRequest === true && req.method === 'GET'; }; const blockNonPublicRuntimeListEndpoints = (req, res, next) => { if (!isPublicRuntimeReadRequest(req)) { return next(); } if (req.path !== PUBLIC_RUNTIME_ALLOWED_PATH) { return res.status(404).send({ message: 'Not found' }); } if (req.query.filetype === 'csv') { return res.status(404).send({ message: 'Not found' }); } return next(); }; const sanitizePublicRuntimeListResponse = (entityName) => { const fields = PUBLIC_RUNTIME_ENTITY_FIELDS[entityName] || []; return (req, res, next) => { if (!isPublicRuntimeReadRequest(req) || req.path !== PUBLIC_RUNTIME_ALLOWED_PATH || fields.length === 0) { return next(); } const originalSend = res.send.bind(res); res.send = (body) => { if (!body || typeof body !== 'object') { return originalSend(body); } if (!Array.isArray(body.rows)) { return originalSend(body); } const sanitizedRows = body.rows.map((row) => pickFields(row, fields)); return originalSend({ ...body, rows: sanitizedRows, }); }; return next(); }; }; module.exports = { blockNonPublicRuntimeListEndpoints, sanitizePublicRuntimeListResponse, };