Bug fix (agreed pre-existing bug #2 of 3): /api/compare called fetchRPKIPerPrefix(asn, prefix), never defined anywhere in the codebase -- threw ReferenceError whenever either ASN had >=1 sample prefix, caught by the outer try/catch, returning 500. Replaced both call sites with validateRPKIWithCache(asn, prefix), the existing single-prefix RPKI check used everywhere else in the codebase -- its return shape ({status, ...}) is exactly what the surrounding code already expects. Verified via node --check, eslint no-undef (only 1 known bug remains: webhooks POST's `secret`), and smoke-test harness (28/28 match). The compare fix itself isn't exercised by the smoke test's fixed test ASNs (both return empty prefix samples locally), but the fix is a direct, same-shape substitution of an already-proven function. server.js: 1736 -> 989 lines.
60 lines
2.1 KiB
JavaScript
60 lines
2.1 KiB
JavaScript
const localDb = require("../../local-db-client");
|
|
const { fetchTopology } = require("../topology");
|
|
|
|
// Feature 25: Topology endpoint
|
|
async function handleTopology(req, res, url) {
|
|
const rawAsn = (url.searchParams.get("asn") || "").replace(/[^0-9]/g, "");
|
|
const depth = parseInt(url.searchParams.get("depth") || "2") || 2;
|
|
if (!rawAsn) {
|
|
res.writeHead(400);
|
|
return res.end(JSON.stringify({ error: "Missing or invalid ASN parameter" }));
|
|
}
|
|
const start = Date.now();
|
|
try {
|
|
const topology = await fetchTopology(parseInt(rawAsn), depth);
|
|
|
|
// ---- Threat Intelligence Enrichment for Topology Nodes ----
|
|
const threatMap = {};
|
|
const threatPromises = topology.nodes.slice(0, 100).map(async (node) => {
|
|
try {
|
|
const asNum = String(node.asn);
|
|
const threat = await localDb.getThreatIntel(asNum);
|
|
if (threat) {
|
|
threatMap[node.asn] = {
|
|
threat_level: threat.threat_level,
|
|
confidence_score: threat.confidence_score,
|
|
source: threat.source,
|
|
};
|
|
}
|
|
} catch (e) {
|
|
console.error(`[Topology Threat Lookup] Error checking ASN ${node.asn}:`, e.message);
|
|
}
|
|
});
|
|
await Promise.race([
|
|
Promise.all(threatPromises),
|
|
new Promise(r => setTimeout(r, 3000)),
|
|
]);
|
|
|
|
// Attach threat status to node objects
|
|
topology.nodes.forEach((node) => {
|
|
if (threatMap[node.asn]) {
|
|
node.threat_level = threatMap[node.asn].threat_level;
|
|
node.threat_confidence = threatMap[node.asn].confidence_score;
|
|
node.threat_source = threatMap[node.asn].source;
|
|
}
|
|
});
|
|
|
|
topology.meta = {
|
|
query: "AS" + rawAsn, depth: depth, duration_ms: Date.now() - start,
|
|
timestamp: new Date().toISOString(), node_count: topology.nodes.length, edge_count: topology.edges.length,
|
|
};
|
|
return res.end(JSON.stringify(topology, null, 2));
|
|
} catch (err) {
|
|
res.writeHead(500);
|
|
return res.end(JSON.stringify({ error: "Topology query failed", message: err.message }));
|
|
}
|
|
|
|
}
|
|
|
|
module.exports = { handleTopology };
|