feat: add Ivanti Queue redirect for completed items
This commit is contained in:
220
.kiro/specs/ivanti-queue-redirect/design.md
Normal file
220
.kiro/specs/ivanti-queue-redirect/design.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# Design Document: Ivanti Queue Redirect
|
||||
|
||||
## Overview
|
||||
|
||||
The Ivanti Queue Redirect feature adds an optional redirect action to completed queue items, allowing users to create a new pending queue item under a different workflow type from an existing completed item. This supports the common scenario where a CARD inventory fix is done but the finding still needs FP or Archer processing, or where an item was assigned to the wrong workflow initially.
|
||||
|
||||
The feature consists of three parts:
|
||||
1. A new backend API endpoint (`POST /api/ivanti/todo-queue/:id/redirect`) added to the existing `ivantiTodoQueue.js` route module
|
||||
2. A redirect modal component in the frontend for collecting target workflow type and vendor
|
||||
3. A redirect button on completed queue items in the existing QueuePanel
|
||||
|
||||
The redirect operation creates a new row in `ivanti_todo_queue` — it does not modify or delete the original completed item. This preserves the audit trail and allows the original item to remain visible as completed.
|
||||
|
||||
## Architecture
|
||||
|
||||
The feature follows the existing patterns in the codebase:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as User
|
||||
participant QP as QueuePanel
|
||||
participant RM as RedirectModal
|
||||
participant API as POST /todo-queue/:id/redirect
|
||||
participant DB as SQLite (ivanti_todo_queue)
|
||||
participant AL as Audit Log
|
||||
|
||||
U->>QP: Clicks redirect button on completed item
|
||||
QP->>RM: Opens modal with item context
|
||||
U->>RM: Selects target workflow type + vendor
|
||||
RM->>API: POST /api/ivanti/todo-queue/:id/redirect
|
||||
API->>DB: SELECT original item (verify ownership + complete status)
|
||||
API->>DB: INSERT new pending item with target workflow_type
|
||||
API->>AL: logAudit (fire-and-forget)
|
||||
API-->>RM: 201 + new item JSON
|
||||
RM->>QP: Adds new item to list, closes modal, shows success
|
||||
```
|
||||
|
||||
No new database tables or schema changes are required. The redirect creates a standard `ivanti_todo_queue` row using the existing schema. The only backend change outside the new endpoint is fixing the PUT validation message (Requirement 5).
|
||||
|
||||
## Components and Interfaces
|
||||
|
||||
### Backend: Redirect Endpoint
|
||||
|
||||
Added to `backend/routes/ivantiTodoQueue.js` inside the existing `createIvantiTodoQueueRouter` factory function.
|
||||
|
||||
```
|
||||
POST /api/ivanti/todo-queue/:id/redirect
|
||||
```
|
||||
|
||||
**Request body:**
|
||||
```json
|
||||
{
|
||||
"workflow_type": "FP" | "Archer" | "CARD",
|
||||
"vendor": "string (required for FP/Archer, omitted for CARD)"
|
||||
}
|
||||
```
|
||||
|
||||
**Success response (201):**
|
||||
```json
|
||||
{
|
||||
"id": 42,
|
||||
"user_id": 1,
|
||||
"finding_id": "12345",
|
||||
"finding_title": "...",
|
||||
"cves_json": "[...]",
|
||||
"ip_address": "10.0.0.1",
|
||||
"hostname": "host.example.com",
|
||||
"vendor": "Cisco",
|
||||
"workflow_type": "FP",
|
||||
"status": "pending",
|
||||
"created_at": "...",
|
||||
"updated_at": "...",
|
||||
"cves": ["CVE-2024-1234"]
|
||||
}
|
||||
```
|
||||
|
||||
**Error responses:**
|
||||
| Status | Condition |
|
||||
|--------|-----------|
|
||||
| 400 | Item not in "complete" status |
|
||||
| 400 | Invalid workflow_type |
|
||||
| 400 | Missing/invalid vendor for FP/Archer |
|
||||
| 404 | Item not found or belongs to different user |
|
||||
| 500 | Database error |
|
||||
|
||||
**Auth:** `requireAuth(db)`, `requireGroup('Admin', 'Standard_User')`
|
||||
|
||||
### Backend: PUT Validation Fix
|
||||
|
||||
In the existing PUT `/:id` handler, the error message for invalid `workflow_type` currently says `"workflow_type must be FP or Archer."` — this will be updated to `"workflow_type must be FP, Archer, or CARD."`.
|
||||
|
||||
### Frontend: RedirectModal Component
|
||||
|
||||
A new modal component rendered inside the QueuePanel. It receives the item being redirected and collects:
|
||||
- Target workflow type (radio buttons or select: FP, Archer, CARD)
|
||||
- Vendor (text input, shown only when FP or Archer is selected)
|
||||
|
||||
The modal displays read-only context: finding title, finding ID, and current workflow type.
|
||||
|
||||
Props:
|
||||
```js
|
||||
{
|
||||
item: Object, // The completed queue item being redirected
|
||||
onClose: Function, // Close the modal
|
||||
onRedirect: Function // Callback with the new item after successful redirect
|
||||
}
|
||||
```
|
||||
|
||||
### Frontend: QueuePanel Changes
|
||||
|
||||
- Add a redirect button (e.g., `CornerUpRight` or `ArrowRightLeft` icon from lucide-react) on each completed item row, next to the existing delete button
|
||||
- Track `redirectItem` state — when set, render the RedirectModal
|
||||
- On successful redirect, append the new item to the queue items list
|
||||
|
||||
### Frontend: API Call
|
||||
|
||||
```js
|
||||
const res = await fetch(`${API_BASE}/ivanti/todo-queue/${itemId}/redirect`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ workflow_type, vendor })
|
||||
});
|
||||
```
|
||||
|
||||
## Data Models
|
||||
|
||||
No schema changes. The redirect creates a standard `ivanti_todo_queue` row:
|
||||
|
||||
| Column | Source |
|
||||
|--------|--------|
|
||||
| user_id | `req.user.id` (current user) |
|
||||
| finding_id | Copied from original item |
|
||||
| finding_title | Copied from original item |
|
||||
| cves_json | Copied from original item |
|
||||
| ip_address | Copied from original item |
|
||||
| hostname | Copied from original item |
|
||||
| vendor | From request body (FP/Archer) or empty string (CARD) |
|
||||
| workflow_type | From request body |
|
||||
| status | `'pending'` (always) |
|
||||
|
||||
The original completed item remains unchanged.
|
||||
|
||||
|
||||
## Correctness Properties
|
||||
|
||||
*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
|
||||
|
||||
### Property 1: Redirect preserves finding data
|
||||
|
||||
*For any* completed queue item with arbitrary finding_id, finding_title, cves_json, ip_address, and hostname values, and *for any* valid target workflow type, redirecting that item SHALL produce a new queue item where finding_id, finding_title, cves_json, ip_address, and hostname are identical to the original, status is "pending", and workflow_type matches the requested target.
|
||||
|
||||
**Validates: Requirements 1.1, 1.7**
|
||||
|
||||
### Property 2: Vendor requirement is conditional on workflow type
|
||||
|
||||
*For any* redirect request, if the target workflow_type is "FP" or "Archer", the request SHALL be accepted if and only if vendor is a non-empty string of 200 characters or fewer. If the target workflow_type is "CARD", the request SHALL be accepted regardless of whether vendor is provided.
|
||||
|
||||
**Validates: Requirements 1.2, 1.3**
|
||||
|
||||
### Property 3: Successful redirect produces correct audit entry
|
||||
|
||||
*For any* successful redirect operation, the audit log entry SHALL contain action "queue_item_redirected", entityType "ivanti_todo_queue", the original item's ID as entityId, and details including the original workflow_type, the target workflow_type, the new item's ID, and the vendor.
|
||||
|
||||
**Validates: Requirements 2.1, 2.2**
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | HTTP Status | Error Message | Behavior |
|
||||
|----------|-------------|---------------|----------|
|
||||
| Item not found or belongs to another user | 404 | "Queue item not found." | Consistent with existing DELETE/PUT pattern |
|
||||
| Item status is not "complete" | 400 | "Only completed queue items can be redirected." | Prevents redirecting pending items |
|
||||
| Invalid workflow_type | 400 | "workflow_type must be FP, Archer, or CARD." | Same message as batch/single add |
|
||||
| Missing/invalid vendor for FP/Archer | 400 | "vendor is required for FP and Archer workflows." | Same message as existing endpoints |
|
||||
| Vendor exceeds 200 chars | 400 | "vendor must be under 200 chars." | Same message as existing endpoints |
|
||||
| Database insert failure | 500 | "Internal server error." | Consistent with existing error pattern |
|
||||
| Frontend API error | — | Display error message from API in modal | Modal stays open so user can retry or cancel |
|
||||
|
||||
The redirect endpoint reuses the existing `isValidVendor()` helper and `VALID_WORKFLOW_TYPES` constant from `ivantiTodoQueue.js` for consistent validation.
|
||||
|
||||
Audit logging uses the existing fire-and-forget pattern — a failed audit log write does not block or fail the redirect response.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests (Example-Based)
|
||||
|
||||
Backend:
|
||||
- Redirect a completed CARD item to FP with vendor → 201, new item returned
|
||||
- Redirect a completed FP item to CARD without vendor → 201, new item returned
|
||||
- Redirect a pending item → 400
|
||||
- Redirect another user's item → 404
|
||||
- Redirect with invalid workflow_type → 400
|
||||
- Redirect to FP without vendor → 400
|
||||
- Redirect to FP with vendor > 200 chars → 400
|
||||
- Redirect non-existent item → 404
|
||||
- PUT with invalid workflow_type returns updated error message text
|
||||
- Verify audit log is called with correct fields on successful redirect
|
||||
|
||||
Frontend:
|
||||
- Redirect button visible on completed items, hidden on pending items
|
||||
- Clicking redirect button opens modal with correct item context
|
||||
- Modal shows vendor field for FP/Archer, hides for CARD
|
||||
- Modal displays finding title, finding ID, current workflow type
|
||||
- Successful redirect closes modal, adds new item to list, shows notification
|
||||
- Failed redirect shows error message, modal stays open
|
||||
|
||||
### Property-Based Tests
|
||||
|
||||
Library: `fast-check` (JavaScript property-based testing library)
|
||||
|
||||
Each property test runs a minimum of 100 iterations.
|
||||
|
||||
- **Property 1**: Generate random queue item data (finding_id, finding_title, cves_json, ip_address, hostname with varying lengths, special characters, null optionals) and random valid workflow_type. Mock the database layer. Verify the INSERT parameters preserve all finding fields and set status to "pending".
|
||||
- Tag: `Feature: ivanti-queue-redirect, Property 1: Redirect preserves finding data`
|
||||
|
||||
- **Property 2**: Generate random (workflow_type, vendor) pairs where workflow_type is drawn from valid types and vendor is drawn from a mix of valid strings, empty strings, whitespace, strings of length 200, and strings of length 201. Verify that the validation logic accepts/rejects correctly based on the conditional rule.
|
||||
- Tag: `Feature: ivanti-queue-redirect, Property 2: Vendor requirement is conditional on workflow type`
|
||||
|
||||
- **Property 3**: Generate random successful redirect scenarios with varying item data and workflow types. Mock logAudit. Verify the audit call contains the correct action, entityType, entityId, and all required detail fields.
|
||||
- Tag: `Feature: ivanti-queue-redirect, Property 3: Successful redirect produces correct audit entry`
|
||||
Reference in New Issue
Block a user