// Shared Ivanti / RiskSense API helpers // Centralizes HTTP calls so ivantiWorkflows.js, ivantiFindings.js, and // ivantiFpWorkflow.js all use the same implementation. const https = require('https'); const IVANTI_URL_BASE = 'https://platform4.risksense.com/api/v1'; // --------------------------------------------------------------------------- // JSON POST — used for search, workflow creation, etc. // --------------------------------------------------------------------------- function ivantiPost(urlPath, body, apiKey, skipTls) { const bodyStr = JSON.stringify(body); const fullUrl = new URL(IVANTI_URL_BASE + urlPath); return new Promise((resolve, reject) => { const options = { hostname: fullUrl.hostname, path: fullUrl.pathname + fullUrl.search, method: 'POST', headers: { 'accept': '*/*', 'content-type': 'application/json', 'x-api-key': apiKey, 'x-http-client-type': 'browser', 'content-length': Buffer.byteLength(bodyStr) }, rejectUnauthorized: !skipTls, timeout: 15000 }; const req = https.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => resolve({ status: res.statusCode, body: data })); }); req.on('timeout', () => req.destroy(new Error('Request timed out'))); req.on('error', reject); req.write(bodyStr); req.end(); }); } // --------------------------------------------------------------------------- // Multipart POST — used for file attachment uploads. // Constructs multipart/form-data manually using Node's https module. // --------------------------------------------------------------------------- function ivantiMultipartPost(urlPath, fileBuffer, fileName, apiKey, skipTls) { const boundary = '----IvantiUpload' + Date.now().toString(36) + Math.random().toString(36).slice(2); const fullUrl = new URL(IVANTI_URL_BASE + urlPath); // Build multipart body const preamble = Buffer.from( `--${boundary}\r\n` + `Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n` + `Content-Type: application/octet-stream\r\n\r\n` ); const epilogue = Buffer.from(`\r\n--${boundary}--\r\n`); const bodyBuffer = Buffer.concat([preamble, fileBuffer, epilogue]); return new Promise((resolve, reject) => { const options = { hostname: fullUrl.hostname, path: fullUrl.pathname + fullUrl.search, method: 'POST', headers: { 'accept': '*/*', 'content-type': `multipart/form-data; boundary=${boundary}`, 'x-api-key': apiKey, 'x-http-client-type': 'browser', 'content-length': bodyBuffer.length }, rejectUnauthorized: !skipTls, timeout: 30000 }; const req = https.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => resolve({ status: res.statusCode, body: data })); }); req.on('timeout', () => req.destroy(new Error('Request timed out'))); req.on('error', reject); req.write(bodyBuffer); req.end(); }); } module.exports = { IVANTI_URL_BASE, ivantiPost, ivantiMultipartPost };