feat: consolidate setup.js with complete v1.0.0 schema — all tables, indexes, triggers for fresh deployments
This commit is contained in:
887
backend/setup.js
887
backend/setup.js
@@ -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,334 +14,628 @@ 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);
|
||||||
|
|
||||||
const schema = `
|
|
||||||
CREATE TABLE IF NOT EXISTS 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)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS documents (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
cve_id VARCHAR(20) NOT NULL,
|
|
||||||
vendor VARCHAR(100) NOT NULL,
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
type VARCHAR(50) NOT NULL,
|
|
||||||
file_path VARCHAR(500) NOT NULL,
|
|
||||||
file_size VARCHAR(20),
|
|
||||||
mime_type VARCHAR(100),
|
|
||||||
uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
notes TEXT,
|
|
||||||
FOREIGN KEY (cve_id) REFERENCES cves(cve_id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS required_documents (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
vendor VARCHAR(100) NOT NULL,
|
|
||||||
document_type VARCHAR(50) NOT NULL,
|
|
||||||
is_mandatory BOOLEAN DEFAULT 1,
|
|
||||||
description TEXT
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_cve_id ON cves(cve_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_vendor ON cves(vendor);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_severity ON cves(severity);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_status ON cves(status);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_doc_cve_id ON documents(cve_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_doc_vendor ON documents(vendor);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_doc_type ON documents(type);
|
|
||||||
|
|
||||||
-- Users table for authentication
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
username VARCHAR(50) UNIQUE NOT NULL,
|
|
||||||
email VARCHAR(255) UNIQUE NOT NULL,
|
|
||||||
password_hash VARCHAR(255) NOT NULL,
|
|
||||||
role VARCHAR(20) NOT NULL DEFAULT 'viewer',
|
|
||||||
is_active BOOLEAN DEFAULT 1,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
last_login TIMESTAMP,
|
|
||||||
CHECK (role IN ('admin', 'editor', 'viewer'))
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Sessions table for session management
|
|
||||||
CREATE TABLE IF NOT EXISTS sessions (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
session_id VARCHAR(255) UNIQUE NOT NULL,
|
|
||||||
user_id INTEGER NOT NULL,
|
|
||||||
expires_at TIMESTAMP NOT NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_sessions_session_id ON sessions(session_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_users_username ON users(username);
|
|
||||||
|
|
||||||
-- Audit log table for tracking user actions
|
|
||||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
user_id INTEGER,
|
|
||||||
username VARCHAR(50) NOT NULL,
|
|
||||||
action VARCHAR(50) NOT NULL,
|
|
||||||
entity_type VARCHAR(50) NOT NULL,
|
|
||||||
entity_id VARCHAR(100),
|
|
||||||
details TEXT,
|
|
||||||
ip_address VARCHAR(45),
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_audit_user_id ON audit_logs(user_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_logs(action);
|
|
||||||
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);
|
|
||||||
|
|
||||||
INSERT OR IGNORE INTO required_documents (vendor, document_type, is_mandatory, description) VALUES
|
|
||||||
('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');
|
|
||||||
|
|
||||||
CREATE VIEW IF NOT EXISTS 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;
|
|
||||||
`;
|
|
||||||
|
|
||||||
db.exec(schema, (err) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
console.log('✓ Database initialized successfully');
|
|
||||||
resolve(db);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create uploads directory structure
|
function dbGet(db, sql, params = []) {
|
||||||
function createUploadsDirectory() {
|
|
||||||
if (!fs.existsSync(UPLOADS_DIR)) {
|
|
||||||
fs.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
||||||
console.log('✓ Created uploads directory');
|
|
||||||
} else {
|
|
||||||
console.log('✓ Uploads directory already exists');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create default admin user
|
|
||||||
async function createDefaultAdmin(db) {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// Check if admin already exists
|
db.get(sql, params, (err, row) => {
|
||||||
db.get('SELECT id FROM users WHERE username = ?', ['admin'], async (err, row) => {
|
if (err) reject(err);
|
||||||
if (err) {
|
else resolve(row);
|
||||||
reject(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (row) {
|
|
||||||
console.log('✓ Default admin user already exists');
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a random admin password on first run
|
|
||||||
const generatedPassword = crypto.randomBytes(12).toString('base64url');
|
|
||||||
const passwordHash = await bcrypt.hash(generatedPassword, 10);
|
|
||||||
|
|
||||||
db.run(
|
|
||||||
`INSERT INTO users (username, email, password_hash, role, is_active)
|
|
||||||
VALUES (?, ?, ?, ?, ?)`,
|
|
||||||
['admin', 'admin@localhost', passwordHash, 'admin', 1],
|
|
||||||
(err) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
console.log('✓ Created default admin user');
|
|
||||||
console.log(`\n ╔══════════════════════════════════════════╗`);
|
|
||||||
console.log(` ║ Admin credentials (save these now!) ║`);
|
|
||||||
console.log(` ║ Username: admin ║`);
|
|
||||||
console.log(` ║ Password: ${generatedPassword.padEnd(29)}║`);
|
|
||||||
console.log(` ╚══════════════════════════════════════════╝\n`);
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add sample CVE data (optional - for testing)
|
function dbExec(db, sql) {
|
||||||
async function addSampleData(db) {
|
return new Promise((resolve, reject) => {
|
||||||
console.log('\n📝 Adding sample CVE data for testing...');
|
db.exec(sql, (err) => {
|
||||||
|
if (err) reject(err);
|
||||||
const sampleCVEs = [
|
else resolve();
|
||||||
{
|
});
|
||||||
cve_id: 'CVE-2024-SAMPLE-1',
|
});
|
||||||
vendor: 'Microsoft',
|
}
|
||||||
severity: 'Critical',
|
|
||||||
description: 'Sample remote code execution vulnerability',
|
// ---------------------------------------------------------------------------
|
||||||
published_date: '2024-01-15'
|
// Schema — complete v1.0.0 database structure
|
||||||
},
|
// ---------------------------------------------------------------------------
|
||||||
{
|
async function initializeDatabase(db) {
|
||||||
cve_id: 'CVE-2024-SAMPLE-1',
|
await dbExec(db, `
|
||||||
vendor: 'Cisco',
|
|
||||||
severity: 'High',
|
-- =================================================================
|
||||||
description: 'Sample remote code execution vulnerability',
|
-- Core CVE tracking tables
|
||||||
published_date: '2024-01-15'
|
-- =================================================================
|
||||||
}
|
|
||||||
|
CREATE TABLE IF NOT EXISTS 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)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS documents (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
cve_id VARCHAR(20) NOT NULL,
|
||||||
|
vendor VARCHAR(100) NOT NULL,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
type VARCHAR(50) NOT NULL,
|
||||||
|
file_path VARCHAR(500) NOT NULL,
|
||||||
|
file_size VARCHAR(20),
|
||||||
|
mime_type VARCHAR(100),
|
||||||
|
uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
notes TEXT,
|
||||||
|
FOREIGN KEY (cve_id) REFERENCES cves(cve_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS required_documents (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
vendor VARCHAR(100) NOT NULL,
|
||||||
|
document_type VARCHAR(50) NOT NULL,
|
||||||
|
is_mandatory BOOLEAN DEFAULT 1,
|
||||||
|
description TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_cve_id ON cves(cve_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_vendor ON cves(vendor);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_severity ON cves(severity);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_status ON cves(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_doc_cve_id ON documents(cve_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_doc_vendor ON documents(vendor);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_doc_type ON documents(type);
|
||||||
|
|
||||||
|
-- =================================================================
|
||||||
|
-- Authentication and session management
|
||||||
|
-- =================================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
username VARCHAR(50) UNIQUE NOT NULL,
|
||||||
|
email VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
|
role VARCHAR(20) NOT NULL DEFAULT 'viewer',
|
||||||
|
is_active BOOLEAN DEFAULT 1,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
last_login TIMESTAMP,
|
||||||
|
user_group VARCHAR(20) NOT NULL DEFAULT 'Read_Only',
|
||||||
|
CHECK (role IN ('admin', 'editor', 'viewer'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
session_id VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
expires_at TIMESTAMP NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_sessions_session_id ON sessions(session_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_users_username ON users(username);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_users_user_group ON users(user_group);
|
||||||
|
|
||||||
|
-- =================================================================
|
||||||
|
-- Audit logging
|
||||||
|
-- =================================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER,
|
||||||
|
username VARCHAR(50) NOT NULL,
|
||||||
|
action VARCHAR(50) NOT NULL,
|
||||||
|
entity_type VARCHAR(50) NOT NULL,
|
||||||
|
entity_id VARCHAR(100),
|
||||||
|
details TEXT,
|
||||||
|
ip_address VARCHAR(45),
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_audit_user_id ON audit_logs(user_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_logs(action);
|
||||||
|
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);
|
||||||
|
|
||||||
|
-- =================================================================
|
||||||
|
-- Jira integration
|
||||||
|
-- =================================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS jira_tickets (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
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
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- =================================================================
|
||||||
|
-- Seed data
|
||||||
|
-- =================================================================
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO required_documents (vendor, document_type, is_mandatory, description) VALUES
|
||||||
|
('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');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Directory setup
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
function createDirectories() {
|
||||||
|
const dirs = [
|
||||||
|
UPLOADS_DIR,
|
||||||
|
path.join(UPLOADS_DIR, 'temp'),
|
||||||
|
path.join(UPLOADS_DIR, 'knowledge_base'),
|
||||||
];
|
];
|
||||||
|
for (const dir of dirs) {
|
||||||
for (const cve of sampleCVEs) {
|
if (!fs.existsSync(dir)) {
|
||||||
await new Promise((resolve, reject) => {
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
db.run(
|
console.log(`✓ Created directory: ${path.relative(__dirname, dir)}`);
|
||||||
`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) {
|
// Default admin user
|
||||||
return new Promise((resolve) => {
|
// ---------------------------------------------------------------------------
|
||||||
db.get('SELECT sql FROM sqlite_master WHERE type="table" AND name="cves"', (err, row) => {
|
async function createDefaultAdmin(db) {
|
||||||
if (err) {
|
const existing = await dbGet(db, 'SELECT id FROM users WHERE username = ?', ['admin']);
|
||||||
console.error('Warning: Could not verify setup:', err);
|
if (existing) {
|
||||||
} else {
|
console.log('✓ Default admin user already exists');
|
||||||
console.log('\n📋 CVEs table structure:');
|
return;
|
||||||
console.log(row.sql);
|
}
|
||||||
|
|
||||||
// Check if UNIQUE constraint is correct
|
const generatedPassword = crypto.randomBytes(12).toString('base64url');
|
||||||
if (row.sql.includes('UNIQUE(cve_id, vendor)')) {
|
const passwordHash = await bcrypt.hash(generatedPassword, 10);
|
||||||
console.log('\n✅ Multi-vendor support: ENABLED');
|
|
||||||
} else {
|
await dbRun(db,
|
||||||
console.log('\n⚠️ Warning: Multi-vendor constraint may not be set correctly');
|
`INSERT INTO users (username, email, password_hash, role, user_group, is_active)
|
||||||
}
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||||
}
|
['admin', 'admin@localhost', passwordHash, 'admin', 'Admin', 1]
|
||||||
resolve();
|
);
|
||||||
});
|
|
||||||
});
|
console.log('✓ Created default admin user');
|
||||||
|
console.log(`\n ╔══════════════════════════════════════════╗`);
|
||||||
|
console.log(` ║ Admin credentials (save these now!) ║`);
|
||||||
|
console.log(` ║ Username: admin ║`);
|
||||||
|
console.log(` ║ Password: ${generatedPassword.padEnd(29)}║`);
|
||||||
|
console.log(` ╚══════════════════════════════════════════╝\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display setup summary
|
// ---------------------------------------------------------------------------
|
||||||
|
// 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 {
|
|
||||||
// Create uploads directory
|
|
||||||
createUploadsDirectory();
|
|
||||||
|
|
||||||
// Initialize database
|
|
||||||
const db = await initializeDatabase();
|
|
||||||
|
|
||||||
// Create default admin user
|
try {
|
||||||
|
createDirectories();
|
||||||
|
|
||||||
|
const db = new sqlite3.Database(DB_FILE);
|
||||||
|
await initializeDatabase(db);
|
||||||
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();
|
||||||
|
|||||||
Reference in New Issue
Block a user