diff --git a/backend/migrate_multivendor.js b/backend/migrate_multivendor.js new file mode 100644 index 0000000..aa476d0 --- /dev/null +++ b/backend/migrate_multivendor.js @@ -0,0 +1,128 @@ +const sqlite3 = require('sqlite3').verbose(); +const db = new sqlite3.Database('./cve_database.db'); + +console.log('š Starting database migration for multi-vendor support...\n'); + +db.serialize(() => { + // Backup existing data + console.log('š¦ Creating backup tables...'); + db.run(`CREATE TABLE IF NOT EXISTS cves_backup AS SELECT * FROM cves`, (err) => { + if (err) console.error('Backup error:', err); + else console.log('ā CVEs backed up'); + }); + + db.run(`CREATE TABLE IF NOT EXISTS documents_backup AS SELECT * FROM documents`, (err) => { + if (err) console.error('Backup error:', err); + else console.log('ā Documents backed up'); + }); + + // Drop old table + console.log('\nšļø Dropping old cves table...'); + db.run(`DROP TABLE IF EXISTS cves`, (err) => { + if (err) { + console.error('Drop error:', err); + return; + } + console.log('ā Old table dropped'); + + // Create new table with UNIQUE(cve_id, vendor) instead of UNIQUE(cve_id) + console.log('\nšļø Creating new cves table with multi-vendor support...'); + db.run(` + CREATE TABLE cves ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + cve_id VARCHAR(20) NOT NULL, + vendor VARCHAR(100) NOT NULL, + severity VARCHAR(20) NOT NULL, + description TEXT, + published_date DATE, + status VARCHAR(50) DEFAULT 'Open', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(cve_id, vendor) + ) + `, (err) => { + if (err) { + console.error('Create error:', err); + return; + } + console.log('ā New table created with UNIQUE(cve_id, vendor)'); + + // Restore data + console.log('\nš„ Restoring data...'); + db.run(`INSERT INTO cves SELECT * FROM cves_backup`, (err) => { + if (err) { + console.error('Restore error:', err); + return; + } + console.log('ā Data restored'); + + // Recreate indexes + console.log('\nš Creating indexes...'); + db.run(`CREATE INDEX idx_cve_id ON cves(cve_id)`, () => { + console.log('ā Index: idx_cve_id'); + }); + db.run(`CREATE INDEX idx_vendor ON cves(vendor)`, () => { + console.log('ā Index: idx_vendor'); + }); + db.run(`CREATE INDEX idx_severity ON cves(severity)`, () => { + console.log('ā Index: idx_severity'); + }); + db.run(`CREATE INDEX idx_status ON cves(status)`, () => { + console.log('ā Index: idx_status'); + }); + + // Update view + console.log('\nšļø Updating cve_document_status view...'); + db.run(`DROP VIEW IF EXISTS cve_document_status`, (err) => { + if (err) console.error('Drop view error:', err); + + db.run(` + CREATE VIEW cve_document_status AS + SELECT + c.id as record_id, + c.cve_id, + c.vendor, + c.severity, + c.status, + COUNT(DISTINCT d.id) as total_documents, + COUNT(DISTINCT CASE WHEN d.type = 'advisory' THEN d.id END) as advisory_count, + COUNT(DISTINCT CASE WHEN d.type = 'email' THEN d.id END) as email_count, + COUNT(DISTINCT CASE WHEN d.type = 'screenshot' THEN d.id END) as screenshot_count, + CASE + WHEN COUNT(DISTINCT CASE WHEN d.type = 'advisory' THEN d.id END) > 0 + THEN 'Complete' + ELSE 'Missing Required Docs' + END as compliance_status + FROM cves c + LEFT JOIN documents d ON c.cve_id = d.cve_id AND c.vendor = d.vendor + GROUP BY c.id, c.cve_id, c.vendor, c.severity, c.status + `, (err) => { + if (err) { + console.error('Create view error:', err); + } else { + console.log('ā View recreated'); + } + + console.log('\nā Migration complete!'); + console.log('\nš Summary:'); + + db.get('SELECT COUNT(*) as count FROM cves', (err, row) => { + if (!err) console.log(` Total CVE entries: ${row.count}`); + + db.get('SELECT COUNT(DISTINCT cve_id) as count FROM cves', (err, row) => { + if (!err) console.log(` Unique CVE IDs: ${row.count}`); + + console.log('\nš” Next steps:'); + console.log(' 1. Restart backend: pkill -f "node server.js" && node server.js &'); + console.log(' 2. Replace frontend/src/App.js with multi-vendor version'); + console.log(' 3. Test by adding same CVE with multiple vendors\n'); + + db.close(); + }); + }); + }); + }); + }); + }); + }); +}); diff --git a/backend/server.js b/backend/server.js index 22f8448..7bb1ebd 100644 --- a/backend/server.js +++ b/backend/server.js @@ -13,7 +13,7 @@ const PORT = 3001; // Middleware app.use(cors({ - origin: ['http://localhost:3000', 'http://192.168.2.117:3000'], + origin: ['http://localhost:3000', 'http://71.85.90.6:3000'], credentials: true })); app.use(express.json()); @@ -94,7 +94,7 @@ app.get('/api/cves', (req, res) => { }); }); -// Check if CVE exists and get its status +// Check if CVE exists and get its status - UPDATED FOR MULTI-VENDOR app.get('/api/cves/check/:cveId', (req, res) => { const { cveId } = req.params; @@ -105,37 +105,63 @@ app.get('/api/cves/check/:cveId', (req, res) => { COUNT(CASE WHEN d.type = 'email' THEN 1 END) as has_email, COUNT(CASE WHEN d.type = 'screenshot' THEN 1 END) as has_screenshot FROM cves c - LEFT JOIN documents d ON c.cve_id = d.cve_id + LEFT JOIN documents d ON c.cve_id = d.cve_id AND c.vendor = d.vendor WHERE c.cve_id = ? GROUP BY c.id `; - db.get(query, [cveId], (err, row) => { + db.all(query, [cveId], (err, rows) => { if (err) { return res.status(500).json({ error: err.message }); } - if (!row) { + if (!rows || rows.length === 0) { return res.json({ exists: false, message: 'CVE not found - not yet addressed' }); } + // Return all vendor entries for this CVE res.json({ exists: true, - cve: row, + vendors: rows.map(row => ({ + vendor: row.vendor, + severity: row.severity, + status: row.status, + total_documents: row.total_documents, + compliance: { + advisory: row.has_advisory > 0, + email: row.has_email > 0, + screenshot: row.has_screenshot > 0 + } + })), addressed: true, - has_required_docs: row.has_advisory > 0, - compliance: { - advisory: row.has_advisory > 0, - email: row.has_email > 0, - screenshot: row.has_screenshot > 0 - } + has_required_docs: rows.some(row => row.has_advisory > 0) }); }); }); -// Create new CVE entry +// NEW ENDPOINT: Get all vendors for a specific CVE +app.get('/api/cves/:cveId/vendors', (req, res) => { + const { cveId } = req.params; + + const query = ` + SELECT vendor, severity, status, description, published_date + FROM cves + WHERE cve_id = ? + ORDER BY vendor + `; + + db.all(query, [cveId], (err, rows) => { + if (err) { + return res.status(500).json({ error: err.message }); + } + res.json(rows); + }); +}); + + +// Create new CVE entry - ALLOW MULTIPLE VENDORS app.post('/api/cves', (req, res) => { const { cve_id, vendor, severity, description, published_date } = req.body; @@ -146,16 +172,23 @@ app.post('/api/cves', (req, res) => { db.run(query, [cve_id, vendor, severity, description, published_date], function(err) { if (err) { + // Check if it's a duplicate CVE_ID + Vendor combination + if (err.message.includes('UNIQUE constraint failed')) { + return res.status(409).json({ + error: 'This CVE already exists for this vendor. Choose a different vendor or update the existing entry.' + }); + } return res.status(500).json({ error: err.message }); } res.json({ id: this.lastID, cve_id, - message: 'CVE created successfully' + message: `CVE created successfully for vendor: ${vendor}` }); }); }); + // Update CVE status app.patch('/api/cves/:cveId/status', (req, res) => { const { cveId } = req.params; @@ -173,13 +206,22 @@ app.patch('/api/cves/:cveId/status', (req, res) => { // ========== DOCUMENT ENDPOINTS ========== -// Get documents for a CVE +// Get documents for a CVE - FILTER BY VENDOR app.get('/api/cves/:cveId/documents', (req, res) => { const { cveId } = req.params; + const { vendor } = req.query; // NEW: Optional vendor filter - const query = `SELECT * FROM documents WHERE cve_id = ? ORDER BY uploaded_at DESC`; + let query = `SELECT * FROM documents WHERE cve_id = ?`; + let params = [cveId]; - db.all(query, [cveId], (err, rows) => { + if (vendor) { + query += ` AND vendor = ?`; + params.push(vendor); + } + + query += ` ORDER BY uploaded_at DESC`; + + db.all(query, params, (err, rows) => { if (err) { return res.status(500).json({ error: err.message }); } diff --git a/frontend/src/App.js b/frontend/src/App.js index 41542a6..3cef783 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; -import { Search, FileText, AlertCircle, Download, Upload, Eye, Filter, CheckCircle, XCircle, Loader, Trash2 } from 'lucide-react'; +import { Search, FileText, AlertCircle, Download, Upload, Eye, Filter, CheckCircle, XCircle, Loader, Trash2, Plus } from 'lucide-react'; -const API_BASE = 'http://192.168.2.117:3001/api'; +const API_BASE = 'http://71.85.90.6:3001/api'; const severityLevels = ['All Severities', 'Critical', 'High', 'Medium', 'Low']; @@ -10,6 +10,7 @@ export default function App() { const [selectedVendor, setSelectedVendor] = useState('All Vendors'); const [selectedSeverity, setSelectedSeverity] = useState('All Severities'); const [selectedCVE, setSelectedCVE] = useState(null); + const [selectedVendorView, setSelectedVendorView] = useState(null); const [selectedDocuments, setSelectedDocuments] = useState([]); const [cves, setCves] = useState([]); const [vendors, setVendors] = useState(['All Vendors']); @@ -73,14 +74,15 @@ export default function App() { } }; - const fetchDocuments = async (cveId) => { - if (cveDocuments[cveId]) return; + const fetchDocuments = async (cveId, vendor) => { + const key = `${cveId}-${vendor}`; + if (cveDocuments[key]) return; try { - const response = await fetch(`${API_BASE}/cves/${cveId}/documents`); + const response = await fetch(`${API_BASE}/cves/${cveId}/documents?vendor=${vendor}`); if (!response.ok) throw new Error('Failed to fetch documents'); const data = await response.json(); - setCveDocuments(prev => ({ ...prev, [cveId]: data })); + setCveDocuments(prev => ({ ...prev, [key]: data })); } catch (err) { console.error('Error fetching documents:', err); } @@ -100,12 +102,15 @@ export default function App() { } }; - const handleViewDocuments = async (cveId) => { - if (selectedCVE === cveId) { + const handleViewDocuments = async (cveId, vendor) => { + const key = `${cveId}-${vendor}`; + if (selectedCVE === cveId && selectedVendorView === vendor) { setSelectedCVE(null); + setSelectedVendorView(null); } else { setSelectedCVE(cveId); - await fetchDocuments(cveId); + setSelectedVendorView(vendor); + await fetchDocuments(cveId, vendor); } }; @@ -140,9 +145,12 @@ export default function App() { body: JSON.stringify(newCVE) }); - if (!response.ok) throw new Error('Failed to add CVE'); + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to add CVE'); + } - alert(`CVE ${newCVE.cve_id} added successfully!`); + alert(`CVE ${newCVE.cve_id} added successfully for vendor: ${newCVE.vendor}!`); setShowAddCVE(false); setNewCVE({ cve_id: '', @@ -192,8 +200,9 @@ export default function App() { if (!response.ok) throw new Error('Failed to upload document'); alert(`Document uploaded successfully!`); - delete cveDocuments[cveId]; - await fetchDocuments(cveId); + const key = `${cveId}-${vendor}`; + delete cveDocuments[key]; + await fetchDocuments(cveId, vendor); fetchCVEs(); } catch (err) { alert(`Error: ${err.message}`); @@ -205,7 +214,7 @@ export default function App() { fileInput.click(); }; - const handleDeleteDocument = async (docId, cveId) => { + const handleDeleteDocument = async (docId, cveId, vendor) => { if (!window.confirm('Are you sure you want to delete this document?')) { return; } @@ -218,20 +227,30 @@ export default function App() { if (!response.ok) throw new Error('Failed to delete document'); alert('Document deleted successfully!'); - delete cveDocuments[cveId]; - await fetchDocuments(cveId); + const key = `${cveId}-${vendor}`; + delete cveDocuments[key]; + await fetchDocuments(cveId, vendor); fetchCVEs(); } catch (err) { alert(`Error: ${err.message}`); } }; - const filteredCVEs = cves; + // Group CVEs by CVE ID + const groupedCVEs = cves.reduce((acc, cve) => { + if (!acc[cve.cve_id]) { + acc[cve.cve_id] = []; + } + acc[cve.cve_id].push(cve); + return acc; + }, {}); + + const filteredGroupedCVEs = groupedCVEs; return (
+ Tip: You can add the same CVE-ID multiple times with different vendors. + Each vendor will have its own documents folder. +
+