feat: add Ivanti Queue redirect for completed items
This commit is contained in:
@@ -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
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user