feat: add Ivanti Queue redirect for completed items

This commit is contained in:
jramos
2026-04-09 16:01:36 -06:00
parent 1963faf9b8
commit 0a7a7c2827
7 changed files with 881 additions and 3 deletions

View File

@@ -293,7 +293,7 @@ function createIvantiTodoQueueRouter(db, requireAuth) {
return res.status(400).json({ error: 'vendor must be a non-empty string (max 200 chars).' });
}
if (workflow_type !== undefined && !VALID_WORKFLOW_TYPES.includes(workflow_type)) {
return res.status(400).json({ error: 'workflow_type must be FP or Archer.' });
return res.status(400).json({ error: 'workflow_type must be FP, Archer, or CARD.' });
}
if (status !== undefined && !VALID_STATUSES.includes(status)) {
return res.status(400).json({ error: 'status must be pending or complete.' });
@@ -358,6 +358,110 @@ function createIvantiTodoQueueRouter(db, requireAuth) {
);
});
/**
* POST /api/ivanti/todo-queue/:id/redirect
*
* Redirect a completed queue item to a different workflow type.
* Creates a new pending item copying finding data from the original.
*
* @param {string} id - Original queue item ID (URL parameter)
* @body {string} workflow_type - Target workflow type: 'FP', 'Archer', or 'CARD'
* @body {string} [vendor] - Required for FP/Archer (max 200 chars); ignored for CARD
*
* @returns {Object} 201 - Newly created queue item with parsed cves array
* @returns {Object} 400 - { error: string } on validation failure or item not complete
* @returns {Object} 404 - { error: string } if item not found for current user
* @returns {Object} 500 - { error: string } on database error
*/
router.post('/:id/redirect', requireAuth(db), requireGroup('Admin', 'Standard_User'), (req, res) => {
const { id } = req.params;
const { workflow_type, vendor } = req.body;
// --- Validation ---
if (!VALID_WORKFLOW_TYPES.includes(workflow_type)) {
return res.status(400).json({ error: 'workflow_type must be FP, Archer, or CARD.' });
}
if (workflow_type !== 'CARD') {
if (!isValidVendor(vendor)) {
return res.status(400).json({ error: 'vendor is required for FP and Archer workflows.' });
}
}
if (vendor !== undefined && vendor !== '' && typeof vendor === 'string' && vendor.trim().length > 200) {
return res.status(400).json({ error: 'vendor must be under 200 chars.' });
}
const vendorVal = workflow_type === 'CARD' ? '' : vendor.trim();
// --- Fetch original item scoped to current user ---
db.get(
'SELECT * FROM ivanti_todo_queue WHERE id = ? AND user_id = ?',
[id, req.user.id],
(err, original) => {
if (err) {
console.error('Error fetching queue item for redirect:', err);
return res.status(500).json({ error: 'Internal server error.' });
}
if (!original) {
return res.status(404).json({ error: 'Queue item not found.' });
}
if (original.status !== 'complete') {
return res.status(400).json({ error: 'Only completed queue items can be redirected.' });
}
// --- INSERT new row copying finding data from original ---
db.run(
`INSERT INTO ivanti_todo_queue
(user_id, finding_id, finding_title, cves_json, ip_address, hostname, vendor, workflow_type)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
[req.user.id, original.finding_id, original.finding_title, original.cves_json, original.ip_address, original.hostname, vendorVal, workflow_type],
function (insertErr) {
if (insertErr) {
console.error('Error inserting redirected queue item:', insertErr);
return res.status(500).json({ error: 'Internal server error.' });
}
const newId = this.lastID;
// --- Fetch the inserted row ---
db.get(
'SELECT * FROM ivanti_todo_queue WHERE id = ?',
[newId],
(fetchErr, row) => {
if (fetchErr || !row) {
console.error('Error fetching redirected queue item:', fetchErr);
return res.status(500).json({ error: 'Internal server error.' });
}
// Audit log (fire-and-forget)
logAudit(db, {
userId: req.user.id,
username: req.user.username,
action: 'queue_item_redirected',
entityType: 'ivanti_todo_queue',
entityId: String(original.id),
details: {
original_workflow_type: original.workflow_type,
target_workflow_type: workflow_type,
new_item_id: newId,
vendor: vendorVal,
},
ipAddress: req.ip,
});
return res.status(201).json({
...row,
cves: row.cves_json ? JSON.parse(row.cves_json) : [],
});
}
);
}
);
}
);
});
/**
* DELETE /api/ivanti/todo-queue/completed
*