Added tweaks to allow edits/deletes of cve and vendors or to fix typos
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user