feat: add GRANITE as fourth workflow type in Ivanti queue
- Add GRANITE to VALID_WORKFLOW_TYPES in backend (no vendor required, same as CARD) - Update vendor validation and error messages across all endpoints (single add, batch, PUT, redirect) - Add GRANITE option to RedirectModal with warm slate color (#A1887F) - Rename QueuePanel CARD section to Inventory, group CARD + GRANITE with sub-divider - Add GRANITE to AddToQueuePopover and SelectionToolbar - Update spec docs (requirements, design, tasks)
This commit is contained in:
@@ -3,7 +3,7 @@ const express = require('express');
|
||||
const { requireGroup } = require('../middleware/auth');
|
||||
const logAudit = require('../helpers/auditLog');
|
||||
|
||||
const VALID_WORKFLOW_TYPES = ['FP', 'Archer', 'CARD'];
|
||||
const VALID_WORKFLOW_TYPES = ['FP', 'Archer', 'CARD', 'GRANITE'];
|
||||
const VALID_STATUSES = ['pending', 'complete'];
|
||||
|
||||
function isValidVendor(vendor) {
|
||||
@@ -64,8 +64,8 @@ function createIvantiTodoQueueRouter(db, requireAuth) {
|
||||
* @body {string[]} [findings[].cves] - Optional array of CVE identifiers
|
||||
* @body {string} [findings[].ip_address] - Optional IP address (max 64 chars)
|
||||
* @body {string} [findings[].hostname] - Optional hostname (max 255 chars)
|
||||
* @body {string} workflow_type - One of 'FP', 'Archer', 'CARD'
|
||||
* @body {string} vendor - Required for FP/Archer (max 200 chars); optional for CARD
|
||||
* @body {string} workflow_type - One of 'FP', 'Archer', 'CARD', 'GRANITE'
|
||||
* @body {string} vendor - Required for FP/Archer (max 200 chars); optional for CARD/GRANITE
|
||||
*
|
||||
* @returns {Object} 201 - { items: Array<Object> } array of created queue items,
|
||||
* each with: id, user_id, finding_id, finding_title, cves_json, ip_address,
|
||||
@@ -89,10 +89,10 @@ function createIvantiTodoQueueRouter(db, requireAuth) {
|
||||
}
|
||||
|
||||
if (!VALID_WORKFLOW_TYPES.includes(workflow_type)) {
|
||||
return res.status(400).json({ error: 'workflow_type must be FP, Archer, or CARD.' });
|
||||
return res.status(400).json({ error: 'workflow_type must be FP, Archer, CARD, or GRANITE.' });
|
||||
}
|
||||
|
||||
if (workflow_type !== 'CARD') {
|
||||
if (!['CARD', 'GRANITE'].includes(workflow_type)) {
|
||||
if (!isValidVendor(vendor)) {
|
||||
return res.status(400).json({ error: 'vendor is required for FP and Archer workflows.' });
|
||||
}
|
||||
@@ -102,7 +102,7 @@ function createIvantiTodoQueueRouter(db, requireAuth) {
|
||||
return res.status(400).json({ error: 'vendor must be under 200 chars.' });
|
||||
}
|
||||
|
||||
const vendorVal = workflow_type === 'CARD' ? '' : vendor.trim();
|
||||
const vendorVal = ['CARD', 'GRANITE'].includes(workflow_type) ? '' : vendor.trim();
|
||||
const userId = req.user.id;
|
||||
|
||||
// --- Transactional batch insert ---
|
||||
@@ -219,8 +219,9 @@ function createIvantiTodoQueueRouter(db, requireAuth) {
|
||||
* @body {string} [finding_title] - Optional finding title (max 500 chars)
|
||||
* @body {string[]} [cves] - Optional array of CVE identifiers
|
||||
* @body {string} [ip_address] - Optional IP address (max 64 chars)
|
||||
* @body {string} [hostname] - Optional hostname (max 255 chars) * @body {string} vendor - Required for FP/Archer (max 200 chars); optional for CARD
|
||||
* @body {string} workflow_type - One of 'FP', 'Archer', 'CARD'
|
||||
* @body {string} [hostname] - Optional hostname (max 255 chars)
|
||||
* @body {string} workflow_type - One of 'FP', 'Archer', 'CARD', 'GRANITE'
|
||||
* @body {string} vendor - Required for FP/Archer (max 200 chars); optional for CARD/GRANITE
|
||||
*
|
||||
* @returns {Object} 201 - Created queue item with parsed cves array:
|
||||
* id, user_id, finding_id, finding_title, cves_json, ip_address,
|
||||
@@ -235,17 +236,17 @@ function createIvantiTodoQueueRouter(db, requireAuth) {
|
||||
return res.status(400).json({ error: 'finding_id is required.' });
|
||||
}
|
||||
if (!VALID_WORKFLOW_TYPES.includes(workflow_type)) {
|
||||
return res.status(400).json({ error: 'workflow_type must be FP, Archer, or CARD.' });
|
||||
return res.status(400).json({ error: 'workflow_type must be FP, Archer, CARD, or GRANITE.' });
|
||||
}
|
||||
// Vendor is required for FP and Archer, optional for CARD
|
||||
if (workflow_type !== 'CARD' && !isValidVendor(vendor)) {
|
||||
// Vendor is required for FP and Archer, optional for CARD/GRANITE
|
||||
if (!['CARD', 'GRANITE'].includes(workflow_type) && !isValidVendor(vendor)) {
|
||||
return res.status(400).json({ error: 'vendor is required for FP and Archer workflows.' });
|
||||
}
|
||||
if (vendor !== undefined && vendor !== '' && !isValidVendor(vendor)) {
|
||||
return res.status(400).json({ error: 'vendor must be under 200 chars.' });
|
||||
}
|
||||
|
||||
const vendorVal = workflow_type === 'CARD' ? '' : vendor.trim();
|
||||
const vendorVal = ['CARD', 'GRANITE'].includes(workflow_type) ? '' : vendor.trim();
|
||||
const cvesJson = Array.isArray(cves) ? JSON.stringify(cves) : null;
|
||||
const ipVal = ip_address && typeof ip_address === 'string' ? ip_address.trim().slice(0, 64) : null;
|
||||
const hostVal = hostname && typeof hostname === 'string' ? hostname.trim().slice(0, 255) : null;
|
||||
@@ -294,7 +295,7 @@ function createIvantiTodoQueueRouter(db, requireAuth) {
|
||||
*
|
||||
* @param {string} id - Queue item ID (URL parameter)
|
||||
* @body {string} [vendor] - New vendor string (max 200 chars)
|
||||
* @body {string} [workflow_type] - One of 'FP', 'Archer', 'CARD'
|
||||
* @body {string} [workflow_type] - One of 'FP', 'Archer', 'CARD', 'GRANITE'
|
||||
* @body {string} [status] - One of 'pending', 'complete'
|
||||
*
|
||||
* @returns {Object} 200 - Updated queue item with parsed cves array:
|
||||
@@ -312,7 +313,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, Archer, or CARD.' });
|
||||
return res.status(400).json({ error: 'workflow_type must be FP, Archer, CARD, or GRANITE.' });
|
||||
}
|
||||
if (status !== undefined && !VALID_STATUSES.includes(status)) {
|
||||
return res.status(400).json({ error: 'status must be pending or complete.' });
|
||||
@@ -394,8 +395,8 @@ function createIvantiTodoQueueRouter(db, requireAuth) {
|
||||
* 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
|
||||
* @body {string} workflow_type - Target workflow type: 'FP', 'Archer', 'CARD', or 'GRANITE'
|
||||
* @body {string} [vendor] - Required for FP/Archer (max 200 chars); ignored for CARD/GRANITE
|
||||
*
|
||||
* @returns {Object} 201 - Newly created queue item with parsed cves array
|
||||
* @returns {Object} 400 - { error: string } on validation failure or item not complete
|
||||
@@ -408,10 +409,10 @@ function createIvantiTodoQueueRouter(db, requireAuth) {
|
||||
|
||||
// --- Validation ---
|
||||
if (!VALID_WORKFLOW_TYPES.includes(workflow_type)) {
|
||||
return res.status(400).json({ error: 'workflow_type must be FP, Archer, or CARD.' });
|
||||
return res.status(400).json({ error: 'workflow_type must be FP, Archer, CARD, or GRANITE.' });
|
||||
}
|
||||
|
||||
if (workflow_type !== 'CARD') {
|
||||
if (!['CARD', 'GRANITE'].includes(workflow_type)) {
|
||||
if (!isValidVendor(vendor)) {
|
||||
return res.status(400).json({ error: 'vendor is required for FP and Archer workflows.' });
|
||||
}
|
||||
@@ -421,7 +422,7 @@ function createIvantiTodoQueueRouter(db, requireAuth) {
|
||||
return res.status(400).json({ error: 'vendor must be under 200 chars.' });
|
||||
}
|
||||
|
||||
const vendorVal = workflow_type === 'CARD' ? '' : vendor.trim();
|
||||
const vendorVal = ['CARD', 'GRANITE'].includes(workflow_type) ? '' : vendor.trim();
|
||||
|
||||
// --- Fetch original item scoped to current user ---
|
||||
db.get(
|
||||
|
||||
Reference in New Issue
Block a user