feat: add Ivanti FP workflow submission from Queue
- Add shared ivantiApi.js helper (ivantiPost + ivantiMultipartPost) - Add ivantiFpWorkflow.js backend route with validation, Ivanti API workflow creation, attachment uploads, submission tracking, and audit - Add add_fp_submissions_table.js migration - Wire route into server.js at /api/ivanti/fp-workflow - Add FpWorkflowModal component in ReportingPage.js with form fields, drag-and-drop file upload, progress indicator, and result views - Add Create FP Workflow button to QueuePanel footer (editor/admin only) - Refactor ivantiWorkflows.js and ivantiFindings.js to use shared helper
This commit is contained in:
91
backend/helpers/ivantiApi.js
Normal file
91
backend/helpers/ivantiApi.js
Normal file
@@ -0,0 +1,91 @@
|
||||
// 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 };
|
||||
Reference in New Issue
Block a user