Added tweaks to allow edits/deletes of cve and vendors or to fix typos

This commit is contained in:
2026-02-02 11:33:44 -07:00
parent da109a6f8b
commit d520c4ae41
4 changed files with 543 additions and 8 deletions

View File

@@ -341,6 +341,224 @@ app.post('/api/cves/nvd-sync', requireAuth(db), requireRole('editor', 'admin'),
}
});
// ========== CVE EDIT & DELETE ENDPOINTS ==========
// Edit single CVE entry (editor or admin)
app.put('/api/cves/:id', requireAuth(db), requireRole('editor', 'admin'), (req, res) => {
const { id } = req.params;
const { cve_id, vendor, severity, description, published_date, status } = req.body;
// Fetch existing row first
db.get('SELECT * FROM cves WHERE id = ?', [id], (err, existing) => {
if (err) return res.status(500).json({ error: err.message });
if (!existing) return res.status(404).json({ error: 'CVE entry not found' });
const before = { cve_id: existing.cve_id, vendor: existing.vendor, severity: existing.severity, description: existing.description, published_date: existing.published_date, status: existing.status };
const newCveId = cve_id !== undefined ? cve_id : existing.cve_id;
const newVendor = vendor !== undefined ? vendor : existing.vendor;
const cveIdChanged = newCveId !== existing.cve_id;
const vendorChanged = newVendor !== existing.vendor;
const doUpdate = () => {
// Build dynamic SET clause
const fields = [];
const values = [];
if (cve_id !== undefined) { fields.push('cve_id = ?'); values.push(cve_id); }
if (vendor !== undefined) { fields.push('vendor = ?'); values.push(vendor); }
if (severity !== undefined) { fields.push('severity = ?'); values.push(severity); }
if (description !== undefined) { fields.push('description = ?'); values.push(description); }
if (published_date !== undefined) { fields.push('published_date = ?'); values.push(published_date); }
if (status !== undefined) { fields.push('status = ?'); values.push(status); }
if (fields.length === 0) return res.status(400).json({ error: 'No fields to update' });
fields.push('updated_at = CURRENT_TIMESTAMP');
values.push(id);
db.run(`UPDATE cves SET ${fields.join(', ')} WHERE id = ?`, values, function(updateErr) {
if (updateErr) return res.status(500).json({ error: updateErr.message });
const after = {
cve_id: newCveId, vendor: newVendor,
severity: severity !== undefined ? severity : existing.severity,
description: description !== undefined ? description : existing.description,
published_date: published_date !== undefined ? published_date : existing.published_date,
status: status !== undefined ? status : existing.status
};
logAudit(db, {
userId: req.user.id,
username: req.user.username,
action: 'cve_edit',
entityType: 'cve',
entityId: newCveId,
details: { before, after },
ipAddress: req.ip
});
res.json({ message: 'CVE updated successfully', changes: this.changes });
});
};
if (cveIdChanged || vendorChanged) {
// Check UNIQUE constraint
db.get('SELECT id FROM cves WHERE cve_id = ? AND vendor = ? AND id != ?', [newCveId, newVendor, id], (checkErr, conflict) => {
if (checkErr) return res.status(500).json({ error: checkErr.message });
if (conflict) return res.status(409).json({ error: 'A CVE entry with this CVE ID and vendor already exists.' });
// Rename document directory
const oldDir = path.join('uploads', existing.cve_id, existing.vendor);
const newDir = path.join('uploads', newCveId, newVendor);
if (fs.existsSync(oldDir)) {
const newParent = path.join('uploads', newCveId);
if (!fs.existsSync(newParent)) {
fs.mkdirSync(newParent, { recursive: true });
}
fs.renameSync(oldDir, newDir);
// Clean up old cve_id directory if empty
const oldParent = path.join('uploads', existing.cve_id);
if (fs.existsSync(oldParent)) {
const remaining = fs.readdirSync(oldParent);
if (remaining.length === 0) fs.rmdirSync(oldParent);
}
}
// Update documents table - file paths
db.all('SELECT id, file_path FROM documents WHERE cve_id = ? AND vendor = ?', [existing.cve_id, existing.vendor], (docErr, docs) => {
if (docErr) return res.status(500).json({ error: docErr.message });
const oldPrefix = path.join('uploads', existing.cve_id, existing.vendor);
const newPrefix = path.join('uploads', newCveId, newVendor);
let docUpdated = 0;
const totalDocs = docs.length;
const finishDocUpdate = () => {
if (docUpdated >= totalDocs) doUpdate();
};
if (totalDocs === 0) {
doUpdate();
} else {
docs.forEach((doc) => {
const newFilePath = doc.file_path.replace(oldPrefix, newPrefix);
db.run('UPDATE documents SET cve_id = ?, vendor = ?, file_path = ? WHERE id = ?',
[newCveId, newVendor, newFilePath, doc.id],
(docUpdateErr) => {
if (docUpdateErr) console.error('Error updating document:', docUpdateErr);
docUpdated++;
finishDocUpdate();
}
);
});
}
});
});
} else {
doUpdate();
}
});
});
// Delete entire CVE - all vendors (editor or admin) - MUST be before /:id route
app.delete('/api/cves/by-cve-id/:cveId', requireAuth(db), requireRole('editor', 'admin'), (req, res) => {
const { cveId } = req.params;
// Get all rows for this CVE ID to know what we're deleting
db.all('SELECT * FROM cves WHERE cve_id = ?', [cveId], (err, rows) => {
if (err) return res.status(500).json({ error: err.message });
if (!rows || rows.length === 0) return res.status(404).json({ error: 'No CVE entries found for this CVE ID' });
// Delete all documents from DB
db.run('DELETE FROM documents WHERE cve_id = ?', [cveId], (docErr) => {
if (docErr) console.error('Error deleting documents:', docErr);
// Delete all CVE rows
db.run('DELETE FROM cves WHERE cve_id = ?', [cveId], function(cveErr) {
if (cveErr) return res.status(500).json({ error: cveErr.message });
// Remove upload directory
const cveDir = path.join('uploads', cveId);
if (fs.existsSync(cveDir)) {
fs.rmSync(cveDir, { recursive: true, force: true });
}
logAudit(db, {
userId: req.user.id,
username: req.user.username,
action: 'cve_delete',
entityType: 'cve',
entityId: cveId,
details: { type: 'all_vendors', vendors: rows.map(r => r.vendor), count: rows.length },
ipAddress: req.ip
});
res.json({ message: `Deleted CVE ${cveId} and all ${rows.length} vendor entries`, deleted: this.changes });
});
});
});
});
// Delete single CVE vendor entry (editor or admin)
app.delete('/api/cves/:id', requireAuth(db), requireRole('editor', 'admin'), (req, res) => {
const { id } = req.params;
db.get('SELECT * FROM cves WHERE id = ?', [id], (err, cve) => {
if (err) return res.status(500).json({ error: err.message });
if (!cve) return res.status(404).json({ error: 'CVE entry not found' });
// Delete associated documents from DB
db.all('SELECT id, file_path FROM documents WHERE cve_id = ? AND vendor = ?', [cve.cve_id, cve.vendor], (docErr, docs) => {
if (docErr) console.error('Error fetching documents:', docErr);
// Delete document files from disk
if (docs && docs.length > 0) {
docs.forEach(doc => {
if (doc.file_path && fs.existsSync(doc.file_path)) {
fs.unlinkSync(doc.file_path);
}
});
}
// Delete documents from DB
db.run('DELETE FROM documents WHERE cve_id = ? AND vendor = ?', [cve.cve_id, cve.vendor], (delDocErr) => {
if (delDocErr) console.error('Error deleting documents from DB:', delDocErr);
// Delete CVE row
db.run('DELETE FROM cves WHERE id = ?', [id], function(delErr) {
if (delErr) return res.status(500).json({ error: delErr.message });
// Clean up directories
const vendorDir = path.join('uploads', cve.cve_id, cve.vendor);
if (fs.existsSync(vendorDir)) {
fs.rmSync(vendorDir, { recursive: true, force: true });
}
const cveDir = path.join('uploads', cve.cve_id);
if (fs.existsSync(cveDir)) {
const remaining = fs.readdirSync(cveDir);
if (remaining.length === 0) fs.rmdirSync(cveDir);
}
logAudit(db, {
userId: req.user.id,
username: req.user.username,
action: 'cve_delete',
entityType: 'cve',
entityId: cve.cve_id,
details: { type: 'single_vendor', vendor: cve.vendor, severity: cve.severity },
ipAddress: req.ip
});
res.json({ message: `Deleted ${cve.vendor} entry for ${cve.cve_id}` });
});
});
});
});
});
// ========== DOCUMENT ENDPOINTS ==========
// Get documents for a CVE - FILTER BY VENDOR (authenticated users)