fix: map findings one at a time via JSON POST, only mark successfully mapped queue items as complete

This commit is contained in:
jramos
2026-04-13 15:59:55 -06:00
parent 4583d09750
commit fa3b045a2f

View File

@@ -887,36 +887,39 @@ function createIvantiFpWorkflowRouter(db, requireAuth) {
const mapUrl = `/client/${encodeURIComponent(clientId)}/workflowBatch/falsePositive/${encodeURIComponent(mapUuid)}/map`;
// Use multipart form (same format as the create endpoint)
const formFields = [{ name: 'subjectFilterRequest', value: buildSubjectFilterRequest(findingIds) }];
let mapResult;
try {
mapResult = await ivantiFormPost(mapUrl, formFields, [], apiKey, skipTls);
} catch (networkErr) {
logAudit(db, {
userId: req.user.id, username: req.user.username,
action: 'ivanti_fp_findings_add_failed', entityType: 'ivanti_workflow',
details: { error: networkErr.message, submissionId, findingIds },
ipAddress: req.ip
});
return res.status(502).json({ success: false, error: 'Failed to connect to Ivanti API.', details: networkErr.message });
// Map each finding individually via JSON POST (Ivanti map endpoint only accepts one finding per call)
const mappedIds = [];
const failedIds = [];
for (const fid of findingIds) {
const mapBody = {
subject: 'hostFinding',
filterRequest: {
filters: [{
field: 'id',
exclusive: false,
operator: 'EXACT',
value: String(fid)
}]
}
if (mapResult.status !== 200 && mapResult.status !== 201 && mapResult.status !== 202) {
const errorMap = {
401: 'Ivanti API key is invalid or missing. Contact your administrator.',
419: 'API key lacks permissions for this operation.',
429: 'Ivanti API rate limit reached. Please try again in a few minutes.'
};
const errorMsg = mapResult.status >= 500
? 'Ivanti API is temporarily unavailable. Please try again later.'
: (errorMap[mapResult.status] || `Operation failed: ${mapResult.status}`);
return res.status(mapResult.status === 429 ? 429 : 502).json({ success: false, error: errorMsg });
try {
const result = await ivantiPost(mapUrl, mapBody, apiKey, skipTls);
if (result.status === 200 || result.status === 201 || result.status === 202) {
mappedIds.push(fid);
} else {
failedIds.push({ id: fid, status: result.status });
}
} catch (err) {
failedIds.push({ id: fid, error: err.message });
}
}
// 5. Merge finding IDs
const mergedJson = mergeFindings(submission.finding_ids_json, findingIds);
if (mappedIds.length === 0) {
return res.status(502).json({ success: false, error: 'Failed to map any findings to the workflow.' });
}
// 5. Merge only successfully mapped finding IDs
const mergedJson = mergeFindings(submission.finding_ids_json, mappedIds);
const now = new Date().toISOString();
try {
@@ -931,25 +934,34 @@ function createIvantiFpWorkflowRouter(db, requireAuth) {
console.error('Failed to update finding_ids_json:', dbErr);
}
// 6. Mark queue items complete
// 6. Mark only successfully mapped queue items as complete
let queueItemsUpdated = 0;
// Build a set of successfully mapped finding IDs to match against queue items
const mappedSet = new Set(mappedIds.map(String));
const successQueueIds = queueItemIds.filter((qid, idx) => {
const queueItem = queueRows.find(r => r.id === qid);
return queueItem && mappedSet.has(String(findingIds[idx]));
});
if (successQueueIds.length > 0) {
try {
const queuePlaceholders = queueItemIds.map(() => '?').join(',');
const queuePlaceholders = successQueueIds.map(() => '?').join(',');
queueItemsUpdated = await new Promise((resolve, reject) => {
db.run(
`UPDATE ivanti_todo_queue SET status='complete', updated_at=CURRENT_TIMESTAMP WHERE id IN (${queuePlaceholders}) AND user_id=?`,
[...queueItemIds, req.user.id],
[...successQueueIds, req.user.id],
function (err) { if (err) reject(err); else resolve(this.changes); }
);
});
} catch (queueErr) {
console.error('Failed to update queue items:', queueErr);
}
}
// 7. Insert history row
const historyEntry = buildSubmissionHistoryEntry('findings_added', {
addedFindingIds: findingIds,
queueItemIds: queueItemIds
addedFindingIds: mappedIds,
failedFindingIds: failedIds.map(f => f.id || f),
queueItemIds: successQueueIds
}, req.user.id, req.user.username);
try {
@@ -974,7 +986,7 @@ function createIvantiFpWorkflowRouter(db, requireAuth) {
ipAddress: req.ip
});
res.json({ success: true, addedFindings: findingIds, queueItemsUpdated });
res.json({ success: true, addedFindings: mappedIds, failedFindings: failedIds, queueItemsUpdated });
})().catch((unexpectedErr) => {
console.error('Unexpected error in POST /submissions/:id/findings:', unexpectedErr);
res.status(500).json({ success: false, error: 'Internal server error.' });