feat: consolidate setup.js with complete v1.0.0 schema — all tables, indexes, triggers for fresh deployments

This commit is contained in:
Jordan Ramos
2026-05-01 20:13:52 +00:00
parent 8e377bb85f
commit c8b3626ac5

View File

@@ -1,5 +1,12 @@
// Setup Script for CVE Database // Setup Script for CVE Dashboard v1.0.0
// This creates a fresh database with multi-vendor support built-in // Creates a fresh database with the complete schema including all tables,
// indexes, triggers, and views needed for a new deployment.
//
// Usage: node backend/setup.js
//
// This consolidates the original schema plus all migration scripts into a
// single idempotent setup. Migration scripts in backend/migrations/ are
// retained for reference but are NOT needed on fresh deployments.
const sqlite3 = require('sqlite3').verbose(); const sqlite3 = require('sqlite3').verbose();
const bcrypt = require('bcryptjs'); const bcrypt = require('bcryptjs');
@@ -7,17 +14,49 @@ const crypto = require('crypto');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const DB_FILE = './cve_database.db'; const DB_FILE = path.join(__dirname, 'cve_database.db');
const UPLOADS_DIR = './uploads'; const UPLOADS_DIR = path.join(__dirname, 'uploads');
// Initialize database with schema // ---------------------------------------------------------------------------
function initializeDatabase() { // Database helpers
// ---------------------------------------------------------------------------
function dbRun(db, sql, params = []) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const db = new sqlite3.Database(DB_FILE, (err) => { db.run(sql, params, function (err) {
if (err) reject(err); if (err) reject(err);
else resolve(this);
}); });
});
}
function dbGet(db, sql, params = []) {
return new Promise((resolve, reject) => {
db.get(sql, params, (err, row) => {
if (err) reject(err);
else resolve(row);
});
});
}
function dbExec(db, sql) {
return new Promise((resolve, reject) => {
db.exec(sql, (err) => {
if (err) reject(err);
else resolve();
});
});
}
// ---------------------------------------------------------------------------
// Schema — complete v1.0.0 database structure
// ---------------------------------------------------------------------------
async function initializeDatabase(db) {
await dbExec(db, `
-- =================================================================
-- Core CVE tracking tables
-- =================================================================
const schema = `
CREATE TABLE IF NOT EXISTS cves ( CREATE TABLE IF NOT EXISTS cves (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
cve_id VARCHAR(20) NOT NULL, cve_id VARCHAR(20) NOT NULL,
@@ -61,7 +100,10 @@ function initializeDatabase() {
CREATE INDEX IF NOT EXISTS idx_doc_vendor ON documents(vendor); CREATE INDEX IF NOT EXISTS idx_doc_vendor ON documents(vendor);
CREATE INDEX IF NOT EXISTS idx_doc_type ON documents(type); CREATE INDEX IF NOT EXISTS idx_doc_type ON documents(type);
-- Users table for authentication -- =================================================================
-- Authentication and session management
-- =================================================================
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
username VARCHAR(50) UNIQUE NOT NULL, username VARCHAR(50) UNIQUE NOT NULL,
@@ -71,10 +113,10 @@ function initializeDatabase() {
is_active BOOLEAN DEFAULT 1, is_active BOOLEAN DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login TIMESTAMP, last_login TIMESTAMP,
user_group VARCHAR(20) NOT NULL DEFAULT 'Read_Only',
CHECK (role IN ('admin', 'editor', 'viewer')) CHECK (role IN ('admin', 'editor', 'viewer'))
); );
-- Sessions table for session management
CREATE TABLE IF NOT EXISTS sessions ( CREATE TABLE IF NOT EXISTS sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id VARCHAR(255) UNIQUE NOT NULL, session_id VARCHAR(255) UNIQUE NOT NULL,
@@ -88,8 +130,12 @@ function initializeDatabase() {
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id); CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at); CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username); CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
CREATE INDEX IF NOT EXISTS idx_users_user_group ON users(user_group);
-- =================================================================
-- Audit logging
-- =================================================================
-- Audit log table for tracking user actions
CREATE TABLE IF NOT EXISTS audit_logs ( CREATE TABLE IF NOT EXISTS audit_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER, user_id INTEGER,
@@ -107,13 +153,332 @@ function initializeDatabase() {
CREATE INDEX IF NOT EXISTS idx_audit_entity_type ON audit_logs(entity_type); CREATE INDEX IF NOT EXISTS idx_audit_entity_type ON audit_logs(entity_type);
CREATE INDEX IF NOT EXISTS idx_audit_created_at ON audit_logs(created_at); CREATE INDEX IF NOT EXISTS idx_audit_created_at ON audit_logs(created_at);
INSERT OR IGNORE INTO required_documents (vendor, document_type, is_mandatory, description) VALUES -- =================================================================
('Microsoft', 'advisory', 1, 'Official Microsoft Security Advisory'), -- Jira integration
('Microsoft', 'screenshot', 0, 'Proof of patch application'), -- =================================================================
('Cisco', 'advisory', 1, 'Cisco Security Advisory'),
('Oracle', 'advisory', 1, 'Oracle Security Alert'), CREATE TABLE IF NOT EXISTS jira_tickets (
('VMware', 'advisory', 1, 'VMware Security Advisory'), id INTEGER PRIMARY KEY AUTOINCREMENT,
('Adobe', 'advisory', 1, 'Adobe Security Bulletin'); cve_id TEXT NOT NULL,
vendor TEXT NOT NULL,
ticket_key TEXT NOT NULL,
url TEXT,
summary TEXT,
status TEXT DEFAULT 'Open' CHECK(status IN ('Open', 'In Progress', 'Closed')),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (cve_id, vendor) REFERENCES cves(cve_id, vendor) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_jira_tickets_cve ON jira_tickets(cve_id, vendor);
CREATE INDEX IF NOT EXISTS idx_jira_tickets_status ON jira_tickets(status);
-- =================================================================
-- Archer integration
-- =================================================================
CREATE TABLE IF NOT EXISTS archer_tickets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
exc_number TEXT NOT NULL UNIQUE,
archer_url TEXT,
status TEXT DEFAULT 'Draft' CHECK(status IN ('Draft', 'Open', 'Under Review', 'Accepted')),
cve_id TEXT NOT NULL,
vendor TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (cve_id, vendor) REFERENCES cves(cve_id, vendor) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_archer_tickets_cve ON archer_tickets(cve_id, vendor);
CREATE INDEX IF NOT EXISTS idx_archer_tickets_status ON archer_tickets(status);
CREATE INDEX IF NOT EXISTS idx_archer_tickets_exc ON archer_tickets(exc_number);
-- =================================================================
-- Knowledge base
-- =================================================================
CREATE TABLE IF NOT EXISTS knowledge_base (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title VARCHAR(255) NOT NULL,
slug VARCHAR(255) UNIQUE NOT NULL,
description TEXT,
category VARCHAR(100),
file_path VARCHAR(500),
file_name VARCHAR(255),
file_type VARCHAR(50),
file_size INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
FOREIGN KEY (created_by) REFERENCES users(id)
);
CREATE INDEX IF NOT EXISTS idx_knowledge_base_slug ON knowledge_base(slug);
CREATE INDEX IF NOT EXISTS idx_knowledge_base_category ON knowledge_base(category);
CREATE INDEX IF NOT EXISTS idx_knowledge_base_created_at ON knowledge_base(created_at DESC);
-- =================================================================
-- Ivanti findings sync and cache
-- =================================================================
CREATE TABLE IF NOT EXISTS ivanti_sync_state (
id INTEGER PRIMARY KEY CHECK (id = 1),
total INTEGER DEFAULT 0,
workflows_json TEXT DEFAULT '[]',
synced_at DATETIME,
sync_status TEXT DEFAULT 'never',
error_message TEXT
);
CREATE TABLE IF NOT EXISTS ivanti_findings_cache (
id INTEGER PRIMARY KEY CHECK (id = 1),
total INTEGER DEFAULT 0,
findings_json TEXT DEFAULT '[]',
synced_at DATETIME,
sync_status TEXT DEFAULT 'never',
error_message TEXT
);
CREATE TABLE IF NOT EXISTS ivanti_finding_notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
finding_id TEXT NOT NULL UNIQUE,
note TEXT NOT NULL DEFAULT '',
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_finding_notes_finding_id ON ivanti_finding_notes(finding_id);
CREATE TABLE IF NOT EXISTS ivanti_counts_cache (
id INTEGER PRIMARY KEY CHECK (id = 1),
open_count INTEGER DEFAULT 0,
closed_count INTEGER DEFAULT 0,
synced_at DATETIME,
fp_workflow_counts_json TEXT DEFAULT '{}',
fp_id_counts_json TEXT DEFAULT '{}'
);
CREATE TABLE IF NOT EXISTS ivanti_finding_overrides (
id INTEGER PRIMARY KEY AUTOINCREMENT,
finding_id TEXT NOT NULL,
field TEXT NOT NULL,
value TEXT NOT NULL DEFAULT '',
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(finding_id, field)
);
CREATE INDEX IF NOT EXISTS idx_finding_overrides_finding_id ON ivanti_finding_overrides(finding_id);
CREATE TABLE IF NOT EXISTS ivanti_counts_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
open_count INTEGER NOT NULL,
closed_count INTEGER NOT NULL,
recorded_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- =================================================================
-- Ivanti FP (False Positive) submissions
-- =================================================================
CREATE TABLE IF NOT EXISTS ivanti_fp_submissions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
username TEXT NOT NULL,
ivanti_workflow_batch_id INTEGER,
ivanti_generated_id TEXT,
ivanti_workflow_batch_uuid TEXT,
workflow_name TEXT NOT NULL,
reason TEXT NOT NULL,
description TEXT,
expiration_date TEXT NOT NULL,
scope_override TEXT NOT NULL DEFAULT 'Authorized',
finding_ids_json TEXT NOT NULL,
queue_item_ids_json TEXT NOT NULL,
attachment_count INTEGER DEFAULT 0,
attachment_results_json TEXT,
status TEXT NOT NULL DEFAULT 'success' CHECK(status IN ('success', 'partial', 'failed')),
lifecycle_status TEXT NOT NULL DEFAULT 'submitted' CHECK(lifecycle_status IN ('submitted', 'approved', 'rejected', 'rework', 'resubmitted')),
error_message TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT NULL
);
CREATE INDEX IF NOT EXISTS idx_fp_submissions_user ON ivanti_fp_submissions(user_id);
CREATE INDEX IF NOT EXISTS idx_fp_submissions_ivanti_id ON ivanti_fp_submissions(ivanti_generated_id);
CREATE TABLE IF NOT EXISTS ivanti_fp_submission_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
submission_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
username TEXT NOT NULL,
change_type TEXT NOT NULL CHECK(change_type IN (
'created', 'fields_updated', 'findings_added',
'attachments_added', 'status_changed'
)),
change_details_json TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (submission_id) REFERENCES ivanti_fp_submissions(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_fp_history_submission ON ivanti_fp_submission_history(submission_id);
-- =================================================================
-- Ivanti todo queue (FP, Archer, CARD, GRANITE workflows)
-- =================================================================
CREATE TABLE IF NOT EXISTS ivanti_todo_queue (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
finding_id TEXT NOT NULL,
finding_title TEXT,
cves_json TEXT,
ip_address TEXT,
hostname TEXT,
vendor TEXT NOT NULL,
workflow_type TEXT NOT NULL CHECK(workflow_type IN ('FP', 'Archer', 'CARD', 'GRANITE')),
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'complete')),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_todo_queue_user ON ivanti_todo_queue(user_id, status);
-- =================================================================
-- Ivanti archive detection and anomaly tracking
-- =================================================================
CREATE TABLE IF NOT EXISTS ivanti_finding_archives (
id INTEGER PRIMARY KEY AUTOINCREMENT,
finding_id TEXT NOT NULL UNIQUE,
finding_title TEXT NOT NULL DEFAULT '',
host_name TEXT NOT NULL DEFAULT '',
ip_address TEXT NOT NULL DEFAULT '',
current_state TEXT NOT NULL CHECK(current_state IN ('ARCHIVED','RETURNED','CLOSED','CLOSED_GONE')),
last_severity REAL NOT NULL DEFAULT 0,
first_archived_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_transition_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_archive_finding_id ON ivanti_finding_archives(finding_id);
CREATE INDEX IF NOT EXISTS idx_archive_current_state ON ivanti_finding_archives(current_state);
CREATE TABLE IF NOT EXISTS ivanti_archive_transitions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
archive_id INTEGER NOT NULL,
from_state TEXT NOT NULL,
to_state TEXT NOT NULL,
severity_at_transition REAL NOT NULL DEFAULT 0,
reason TEXT NOT NULL DEFAULT '',
transitioned_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (archive_id) REFERENCES ivanti_finding_archives(id)
);
CREATE INDEX IF NOT EXISTS idx_transition_archive_id ON ivanti_archive_transitions(archive_id);
CREATE TABLE IF NOT EXISTS ivanti_sync_anomaly_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sync_timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
open_count_delta INTEGER NOT NULL DEFAULT 0,
closed_count_delta INTEGER NOT NULL DEFAULT 0,
newly_archived_count INTEGER NOT NULL DEFAULT 0,
returned_count INTEGER NOT NULL DEFAULT 0,
classification_json TEXT NOT NULL DEFAULT '{}',
return_classification_json TEXT NOT NULL DEFAULT '{}',
is_significant INTEGER NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_anomaly_sync_timestamp ON ivanti_sync_anomaly_log(sync_timestamp);
CREATE TABLE IF NOT EXISTS ivanti_finding_bu_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
finding_id TEXT NOT NULL,
finding_title TEXT NOT NULL DEFAULT '',
host_name TEXT NOT NULL DEFAULT '',
previous_bu TEXT NOT NULL,
new_bu TEXT NOT NULL,
detected_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_bu_history_finding_id ON ivanti_finding_bu_history(finding_id);
CREATE INDEX IF NOT EXISTS idx_bu_history_detected_at ON ivanti_finding_bu_history(detected_at);
-- =================================================================
-- Atlas action plans cache
-- =================================================================
CREATE TABLE IF NOT EXISTS atlas_action_plans_cache (
id INTEGER PRIMARY KEY AUTOINCREMENT,
host_id INTEGER NOT NULL UNIQUE,
has_action_plan INTEGER NOT NULL DEFAULT 0,
plan_count INTEGER NOT NULL DEFAULT 0,
plans_json TEXT NOT NULL DEFAULT '[]',
synced_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_atlas_cache_host_id ON atlas_action_plans_cache(host_id);
-- =================================================================
-- Compliance (NTS AEO) tracking
-- =================================================================
CREATE TABLE IF NOT EXISTS compliance_uploads (
id INTEGER PRIMARY KEY AUTOINCREMENT,
filename TEXT NOT NULL,
report_date TEXT,
uploaded_by INTEGER,
uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP,
new_count INTEGER DEFAULT 0,
resolved_count INTEGER DEFAULT 0,
recurring_count INTEGER DEFAULT 0,
summary_json TEXT,
FOREIGN KEY (uploaded_by) REFERENCES users(id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS compliance_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
upload_id INTEGER NOT NULL,
hostname TEXT NOT NULL,
ip_address TEXT,
device_type TEXT,
team TEXT,
metric_id TEXT NOT NULL,
metric_desc TEXT,
category TEXT,
extra_json TEXT,
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'resolved')),
first_seen_upload_id INTEGER,
resolved_upload_id INTEGER,
seen_count INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (upload_id) REFERENCES compliance_uploads(id) ON DELETE CASCADE,
FOREIGN KEY (first_seen_upload_id) REFERENCES compliance_uploads(id) ON DELETE SET NULL,
FOREIGN KEY (resolved_upload_id) REFERENCES compliance_uploads(id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_compliance_items_upload ON compliance_items(upload_id);
CREATE INDEX IF NOT EXISTS idx_compliance_items_identity ON compliance_items(hostname, metric_id);
CREATE INDEX IF NOT EXISTS idx_compliance_items_team_status ON compliance_items(team, status);
CREATE TABLE IF NOT EXISTS compliance_notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hostname TEXT NOT NULL,
metric_id TEXT NOT NULL,
note TEXT NOT NULL,
group_id TEXT,
created_by INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_compliance_notes_identity ON compliance_notes(hostname, metric_id);
CREATE INDEX IF NOT EXISTS idx_compliance_notes_group ON compliance_notes(group_id);
-- =================================================================
-- Document compliance view
-- =================================================================
CREATE VIEW IF NOT EXISTS cve_document_status AS CREATE VIEW IF NOT EXISTS cve_document_status AS
SELECT SELECT
@@ -134,207 +499,143 @@ function initializeDatabase() {
FROM cves c FROM cves c
LEFT JOIN documents d ON c.cve_id = d.cve_id AND c.vendor = d.vendor 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; GROUP BY c.id, c.cve_id, c.vendor, c.severity, c.status;
`;
db.exec(schema, (err) => { -- =================================================================
if (err) { -- Seed data
reject(err); -- =================================================================
} else {
console.log('✓ Database initialized successfully'); INSERT OR IGNORE INTO required_documents (vendor, document_type, is_mandatory, description) VALUES
resolve(db); ('Microsoft', 'advisory', 1, 'Official Microsoft Security Advisory'),
} ('Microsoft', 'screenshot', 0, 'Proof of patch application'),
}); ('Cisco', 'advisory', 1, 'Cisco Security Advisory'),
}); ('Oracle', 'advisory', 1, 'Oracle Security Alert'),
('VMware', 'advisory', 1, 'VMware Security Advisory'),
('Adobe', 'advisory', 1, 'Adobe Security Bulletin');
`);
console.log('✓ Database schema initialized');
// User group validation triggers (cannot be in db.exec multi-statement)
await dbRun(db, `
CREATE TRIGGER IF NOT EXISTS check_user_group_insert
BEFORE INSERT ON users
FOR EACH ROW
WHEN NEW.user_group NOT IN ('Admin', 'Standard_User', 'Leadership', 'Read_Only')
BEGIN
SELECT RAISE(ABORT, 'Invalid user_group value. Must be Admin, Standard_User, Leadership, or Read_Only');
END
`);
await dbRun(db, `
CREATE TRIGGER IF NOT EXISTS check_user_group_update
BEFORE UPDATE OF user_group ON users
FOR EACH ROW
WHEN NEW.user_group NOT IN ('Admin', 'Standard_User', 'Leadership', 'Read_Only')
BEGIN
SELECT RAISE(ABORT, 'Invalid user_group value. Must be Admin, Standard_User, Leadership, or Read_Only');
END
`);
console.log('✓ Triggers created');
} }
// Create uploads directory structure // ---------------------------------------------------------------------------
function createUploadsDirectory() { // Directory setup
if (!fs.existsSync(UPLOADS_DIR)) { // ---------------------------------------------------------------------------
fs.mkdirSync(UPLOADS_DIR, { recursive: true }); function createDirectories() {
console.log('✓ Created uploads directory'); const dirs = [
} else { UPLOADS_DIR,
console.log('✓ Uploads directory already exists'); path.join(UPLOADS_DIR, 'temp'),
path.join(UPLOADS_DIR, 'knowledge_base'),
];
for (const dir of dirs) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
console.log(`✓ Created directory: ${path.relative(__dirname, dir)}`);
}
} }
} }
// Create default admin user // ---------------------------------------------------------------------------
// Default admin user
// ---------------------------------------------------------------------------
async function createDefaultAdmin(db) { async function createDefaultAdmin(db) {
return new Promise((resolve, reject) => { const existing = await dbGet(db, 'SELECT id FROM users WHERE username = ?', ['admin']);
// Check if admin already exists if (existing) {
db.get('SELECT id FROM users WHERE username = ?', ['admin'], async (err, row) => {
if (err) {
reject(err);
return;
}
if (row) {
console.log('✓ Default admin user already exists'); console.log('✓ Default admin user already exists');
resolve();
return; return;
} }
// Generate a random admin password on first run
const generatedPassword = crypto.randomBytes(12).toString('base64url'); const generatedPassword = crypto.randomBytes(12).toString('base64url');
const passwordHash = await bcrypt.hash(generatedPassword, 10); const passwordHash = await bcrypt.hash(generatedPassword, 10);
db.run( await dbRun(db,
`INSERT INTO users (username, email, password_hash, role, is_active) `INSERT INTO users (username, email, password_hash, role, user_group, is_active)
VALUES (?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?)`,
['admin', 'admin@localhost', passwordHash, 'admin', 1], ['admin', 'admin@localhost', passwordHash, 'admin', 'Admin', 1]
(err) => { );
if (err) {
reject(err);
} else {
console.log('✓ Created default admin user'); console.log('✓ Created default admin user');
console.log(`\n ╔══════════════════════════════════════════╗`); console.log(`\n ╔══════════════════════════════════════════╗`);
console.log(` ║ Admin credentials (save these now!) ║`); console.log(` ║ Admin credentials (save these now!) ║`);
console.log(` ║ Username: admin ║`); console.log(` ║ Username: admin ║`);
console.log(` ║ Password: ${generatedPassword.padEnd(29)}`); console.log(` ║ Password: ${generatedPassword.padEnd(29)}`);
console.log(` ╚══════════════════════════════════════════╝\n`); console.log(` ╚══════════════════════════════════════════╝\n`);
resolve();
}
}
);
});
});
} }
// Add sample CVE data (optional - for testing) // ---------------------------------------------------------------------------
async function addSampleData(db) { // Setup summary
console.log('\n📝 Adding sample CVE data for testing...'); // ---------------------------------------------------------------------------
const sampleCVEs = [
{
cve_id: 'CVE-2024-SAMPLE-1',
vendor: 'Microsoft',
severity: 'Critical',
description: 'Sample remote code execution vulnerability',
published_date: '2024-01-15'
},
{
cve_id: 'CVE-2024-SAMPLE-1',
vendor: 'Cisco',
severity: 'High',
description: 'Sample remote code execution vulnerability',
published_date: '2024-01-15'
}
];
for (const cve of sampleCVEs) {
await new Promise((resolve, reject) => {
db.run(
`INSERT OR IGNORE INTO cves (cve_id, vendor, severity, description, published_date)
VALUES (?, ?, ?, ?, ?)`,
[cve.cve_id, cve.vendor, cve.severity, cve.description, cve.published_date],
(err) => {
if (err) reject(err);
else {
console.log(` ✓ Added sample: ${cve.cve_id} / ${cve.vendor}`);
resolve();
}
}
);
});
}
console.log(' Sample data added - demonstrates multi-vendor support');
}
// Verify database structure
async function verifySetup(db) {
return new Promise((resolve) => {
db.get('SELECT sql FROM sqlite_master WHERE type="table" AND name="cves"', (err, row) => {
if (err) {
console.error('Warning: Could not verify setup:', err);
} else {
console.log('\n📋 CVEs table structure:');
console.log(row.sql);
// Check if UNIQUE constraint is correct
if (row.sql.includes('UNIQUE(cve_id, vendor)')) {
console.log('\n✅ Multi-vendor support: ENABLED');
} else {
console.log('\n⚠ Warning: Multi-vendor constraint may not be set correctly');
}
}
resolve();
});
});
}
// Display setup summary
function displaySummary() { function displaySummary() {
console.log('\n╔════════════════════════════════════════════════════════╗'); console.log('\n╔════════════════════════════════════════════════════════╗');
console.log('║ CVE DATABASE SETUP COMPLETE! ║'); console.log('║ CVE DASHBOARD v1.0.0 — SETUP COMPLETE ║');
console.log('╚════════════════════════════════════════════════════════╝'); console.log('╚════════════════════════════════════════════════════════╝');
console.log('\n📊 What was created:'); console.log('\n📊 Tables created:');
console.log(' ✓ SQLite database (cve_database.db)'); console.log(' Core: cves, documents, required_documents');
console.log(' ✓ Tables: cves, documents, required_documents, users, sessions, audit_logs'); console.log(' Auth: users, sessions');
console.log(' ✓ Multi-vendor support with UNIQUE(cve_id, vendor)'); console.log(' Audit: audit_logs');
console.log(' ✓ Vendor column in documents table'); console.log(' Jira: jira_tickets');
console.log(' ✓ User authentication with session-based auth'); console.log(' Archer: archer_tickets');
console.log(' ✓ Indexes for fast queries'); console.log(' KB: knowledge_base');
console.log(' ✓ Document compliance view'); console.log(' Ivanti: ivanti_sync_state, ivanti_findings_cache,');
console.log(' ✓ Uploads directory for file storage'); console.log(' ivanti_finding_notes, ivanti_counts_cache,');
console.log(' ✓ Default admin user (see credentials above)'); console.log(' ivanti_finding_overrides, ivanti_counts_history,');
console.log('\n📁 File structure will be:'); console.log(' ivanti_fp_submissions, ivanti_fp_submission_history,');
console.log(' uploads/'); console.log(' ivanti_todo_queue');
console.log(' └── CVE-XXXX-XXXX/'); console.log(' Archives: ivanti_finding_archives, ivanti_archive_transitions,');
console.log(' ├── Vendor1/'); console.log(' ivanti_sync_anomaly_log, ivanti_finding_bu_history');
console.log(' │ ├── advisory.pdf'); console.log(' Atlas: atlas_action_plans_cache');
console.log(' │ └── screenshot.png'); console.log(' Compliance: compliance_uploads, compliance_items, compliance_notes');
console.log(' └── Vendor2/');
console.log(' └── advisory.pdf');
console.log('\n🚀 Next steps:'); console.log('\n🚀 Next steps:');
console.log(' 1. Start the backend API:'); console.log(' 1. Copy .env.example to .env and configure API keys');
console.log(' → cd backend && node server.js'); console.log(' 2. Start the backend: node backend/server.js');
console.log(' 2. Start the frontend:'); console.log(' 3. Build the frontend: cd frontend && npm run build');
console.log(' → cd frontend && npm start'); console.log(' 4. Open the dashboard and log in with the admin credentials above\n');
console.log(' 3. Open http://localhost:3000');
console.log(' 4. Start adding CVEs with multiple vendors!');
console.log('\n💡 Key Features:');
console.log(' • Add same CVE-ID with different vendors');
console.log(' • Each vendor has separate document storage');
console.log(' • Quick Check shows all vendors for a CVE');
console.log(' • Document compliance tracking per vendor');
console.log(' • Required docs: Advisory (mandatory for most vendors)\n');
} }
// Main execution // ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
async function main() { async function main() {
console.log('🚀 CVE Database Setup (Multi-Vendor Support)\n'); console.log('🚀 CVE Dashboard v1.0.0 — Database Setup\n');
console.log('════════════════════════════════════════\n'); console.log('════════════════════════════════════════\n');
try { try {
// Create uploads directory createDirectories();
createUploadsDirectory();
// Initialize database const db = new sqlite3.Database(DB_FILE);
const db = await initializeDatabase(); await initializeDatabase(db);
// Create default admin user
await createDefaultAdmin(db); await createDefaultAdmin(db);
// Add sample data
await addSampleData(db);
// Verify setup
await verifySetup(db);
// Close database connection
db.close((err) => { db.close((err) => {
if (err) console.error('Error closing database:', err); if (err) console.error('Error closing database:', err);
else console.log('\n✓ Database connection closed'); else console.log('✓ Database connection closed');
// Display summary
displaySummary(); displaySummary();
}); });
} catch (error) { } catch (error) {
console.error('❌ Setup Error:', error); console.error('❌ Setup Error:', error);
process.exit(1); process.exit(1);
} }
} }
// Run the setup
main(); main();