Added NVD lookup features and optional NVD API key in .env file

This commit is contained in:
2026-02-02 10:50:38 -07:00
parent 260ae48f77
commit da109a6f8b
7 changed files with 855 additions and 11 deletions

View File

@@ -1,10 +1,11 @@
import React, { useState, useEffect } from 'react';
import { Search, FileText, AlertCircle, Download, Upload, Eye, Filter, CheckCircle, XCircle, Loader, Trash2, Plus } from 'lucide-react';
import { Search, FileText, AlertCircle, Download, Upload, Eye, Filter, CheckCircle, XCircle, Loader, Trash2, Plus, RefreshCw } from 'lucide-react';
import { useAuth } from './contexts/AuthContext';
import LoginForm from './components/LoginForm';
import UserMenu from './components/UserMenu';
import UserManagement from './components/UserManagement';
import AuditLog from './components/AuditLog';
import NvdSyncModal from './components/NvdSyncModal';
const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api';
const API_HOST = process.env.REACT_APP_API_HOST || 'http://localhost:3001';
@@ -29,6 +30,7 @@ export default function App() {
const [showAddCVE, setShowAddCVE] = useState(false);
const [showUserManagement, setShowUserManagement] = useState(false);
const [showAuditLog, setShowAuditLog] = useState(false);
const [showNvdSync, setShowNvdSync] = useState(false);
const [newCVE, setNewCVE] = useState({
cve_id: '',
vendor: '',
@@ -37,6 +39,42 @@ export default function App() {
published_date: new Date().toISOString().split('T')[0]
});
const [uploadingFile, setUploadingFile] = useState(false);
const [nvdLoading, setNvdLoading] = useState(false);
const [nvdError, setNvdError] = useState(null);
const [nvdAutoFilled, setNvdAutoFilled] = useState(false);
const lookupNVD = async (cveId) => {
const trimmed = cveId.trim();
if (!/^CVE-\d{4}-\d{4,}$/.test(trimmed)) return;
setNvdLoading(true);
setNvdError(null);
setNvdAutoFilled(false);
try {
const response = await fetch(`${API_BASE}/nvd/lookup/${encodeURIComponent(trimmed)}`, {
credentials: 'include'
});
if (!response.ok) {
const data = await response.json();
throw new Error(data.error || 'NVD lookup failed');
}
const data = await response.json();
setNewCVE(prev => ({
...prev,
description: prev.description || data.description,
severity: data.severity,
published_date: data.published_date || prev.published_date
}));
setNvdAutoFilled(true);
} catch (err) {
setNvdError(err.message);
} finally {
setNvdLoading(false);
}
};
const fetchCVEs = async () => {
setLoading(true);
@@ -163,6 +201,9 @@ export default function App() {
description: '',
published_date: new Date().toISOString().split('T')[0]
});
setNvdLoading(false);
setNvdError(null);
setNvdAutoFilled(false);
fetchCVEs();
} catch (err) {
alert(`Error: ${err.message}`);
@@ -297,6 +338,15 @@ export default function App() {
<p className="text-gray-600">Query vulnerabilities, manage vendors, and attach documentation</p>
</div>
<div className="flex items-center gap-4">
{canWrite() && (
<button
onClick={() => setShowNvdSync(true)}
className="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors flex items-center gap-2 shadow-md"
>
<RefreshCw className="w-5 h-5" />
Sync with NVD
</button>
)}
{canWrite() && (
<button
onClick={() => setShowAddCVE(true)}
@@ -320,6 +370,11 @@ export default function App() {
<AuditLog onClose={() => setShowAuditLog(false)} />
)}
{/* NVD Sync Modal */}
{showNvdSync && (
<NvdSyncModal onClose={() => setShowNvdSync(false)} onSyncComplete={() => fetchCVEs()} />
)}
{/* Add CVE Modal */}
{showAddCVE && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
@@ -328,7 +383,7 @@ export default function App() {
<div className="flex justify-between items-center mb-4">
<h2 className="text-2xl font-bold text-gray-900">Add CVE Entry</h2>
<button
onClick={() => setShowAddCVE(false)}
onClick={() => { setShowAddCVE(false); setNvdLoading(false); setNvdError(null); setNvdAutoFilled(false); }}
className="text-gray-400 hover:text-gray-600"
>
<XCircle className="w-6 h-6" />
@@ -347,15 +402,33 @@ export default function App() {
<label className="block text-sm font-medium text-gray-700 mb-1">
CVE ID *
</label>
<input
type="text"
required
placeholder="CVE-2024-1234"
value={newCVE.cve_id}
onChange={(e) => setNewCVE({...newCVE, cve_id: e.target.value.toUpperCase()})}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#0476D9] focus:border-transparent"
/>
<div className="relative">
<input
type="text"
required
placeholder="CVE-2024-1234"
value={newCVE.cve_id}
onChange={(e) => { setNewCVE({...newCVE, cve_id: e.target.value.toUpperCase()}); setNvdAutoFilled(false); setNvdError(null); }}
onBlur={(e) => lookupNVD(e.target.value)}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#0476D9] focus:border-transparent"
/>
{nvdLoading && (
<Loader className="absolute right-3 top-2.5 w-5 h-5 text-[#0476D9] animate-spin" />
)}
</div>
<p className="text-xs text-gray-500 mt-1">Can be the same as existing CVE if adding another vendor</p>
{nvdAutoFilled && (
<p className="text-xs text-green-600 mt-1 flex items-center gap-1">
<CheckCircle className="w-3 h-3" />
Auto-filled from NVD
</p>
)}
{nvdError && (
<p className="text-xs text-amber-600 mt-1 flex items-center gap-1">
<AlertCircle className="w-3 h-3" />
{nvdError}
</p>
)}
</div>
<div>
@@ -425,7 +498,7 @@ export default function App() {
</button>
<button
type="button"
onClick={() => setShowAddCVE(false)}
onClick={() => { setShowAddCVE(false); setNvdLoading(false); setNvdError(null); setNvdAutoFilled(false); }}
className="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors"
>
Cancel