Catalyst / admin/Synapse-Sonar 14.8 GB / 57.8 GB 40.0 GB free
Help Sign in

admin / Synapse-Sonar

public

Attack Surface Simulation

Code Issues Pull requests Pipelines Packages Security Insights Wiki Settings
Synapse-Sonar / synapse-sonar / app / api / graph / route.ts 2515 B · main
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import { NextResponse } from "next/server";
import { requireTenantContext, HttpError } from "@/lib/auth";
import { assertCan, can } from "@/lib/rbac";
import { prisma } from "@/lib/prisma";
import { getEnabledIntegration, NetscanXiConfig } from "@/lib/integrations";
import { IntegrationProvider } from "@prisma/client";

export const runtime = "nodejs";

/**
 * GET /api/graph
 * Tenant-scoped topology payload for NetworkGraph.tsx.
 * Vulnerability indicators are only included when the NetscanXi toggle is On.
 */
export async function GET() {
  try {
    const ctx = await requireTenantContext();
    assertCan(ctx.role, "asset:read");

    const netscanEnabled = Boolean(
      await getEnabledIntegration<NetscanXiConfig>(
        ctx.tenantId,
        IntegrationProvider.NETSCAN_XI
      )
    );

    const [assets, connections] = await Promise.all([
      prisma.asset.findMany({
        where: { tenantId: ctx.tenantId },
        include: {
          vulnerabilities: netscanEnabled
            ? { select: { cveId: true, cvssScore: true, severity: true, title: true } }
            : false,
        },
      }),
      prisma.connection.findMany({ where: { tenantId: ctx.tenantId } }),
    ]);

    const nodes = assets.map((a) => {
      const vulns = netscanEnabled ? a.vulnerabilities ?? [] : [];
      const criticalCount = vulns.filter((v) => v.severity === "CRITICAL").length;
      return {
        id: a.id,
        hostname: a.hostname ?? a.ipAddress,
        ipAddress: a.ipAddress,
        relatedIps: a.relatedIps,
        assetType: a.assetType,
        criticality: a.criticality,
        isRogue: a.isRogue,
        offline: a.manualOffline,
        openPorts: a.openPorts,
        vulnerabilities: vulns,
        criticalCount,
      };
    });

    const links = connections.map((c) => ({
      id: c.id,
      source: c.sourceAssetId,
      target: c.targetAssetId,
      protocol: c.protocol,
      port: c.port,
      // Normalize cumulative bytes into a 1-10 weight for edge thickness/speed.
      volume: Number(c.bytes),
      lastSeenAt: c.lastSeenAt,
    }));

    return NextResponse.json({
      nodes,
      links,
      netscanEnabled,
      canEdit: can(ctx.role, "asset:write"),
    });
  } catch (err) {
    if (err instanceof HttpError) {
      return NextResponse.json({ error: err.message }, { status: err.status });
    }
    const status = (err as { status?: number }).status ?? 500;
    return NextResponse.json({ error: (err as Error).message }, { status });
  }
}