admin / Synapse-Sonar
publicAttack Surface Simulation
Synapse-Sonar / synapse-sonar / app / (app) / layout.tsx
2753 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 | import Link from "next/link"; import { redirect } from "next/navigation"; import { getServerSession } from "next-auth"; import { authOptions } from "@/lib/authOptions"; import SignOutButton from "@/components/SignOutButton"; import Logo from "@/components/Logo"; import { prisma } from "@/lib/prisma"; // Touches the DB + session per request — never prerender at build time. export const dynamic = "force-dynamic"; /** * Authenticated command-center shell. The login/setup routes live outside this * group so they render without the navigation rail. */ export default async function AppShell({ children }: { children: React.ReactNode }) { // First-run redirect: if nobody has been provisioned yet, send to setup. // (Auth itself is enforced by middleware.ts.) const userCount = await prisma.user.count(); if (userCount === 0) redirect("/setup"); // Org-wide MFA policy gate: if the tenant requires MFA and this user hasn't // enrolled yet, force them through mandatory enrollment before anything else. const session = await getServerSession(authOptions); if (session?.user?.id) { const me = await prisma.user.findUnique({ where: { id: session.user.id }, select: { mfaEnabled: true, tenant: { select: { mfaRequired: true } } }, }); if (me?.tenant?.mfaRequired && !me.mfaEnabled) { redirect("/mfa-required"); } } const isAdmin = session?.user?.role === "TENANT_ADMIN"; return ( <div className="flex h-screen"> <aside className="flex w-56 flex-col border-r border-slate-800 bg-[#0f172a]"> <div className="flex items-center gap-2.5 border-b border-slate-800 px-5 py-4"> <Logo variant="icon" className="h-8 w-8" /> <span className="font-semibold tracking-tight text-slate-100"> Synapse<span className="text-synapse-cyan">Sonar</span> </span> </div> <nav className="flex flex-1 flex-col gap-1 p-3 text-sm"> <NavLink href="/" label="Topology Map" /> <NavLink href="/integrations" label="Integrations" /> {isAdmin && <NavLink href="/agents" label="Agents & Collectors" />} {isAdmin && <NavLink href="/admin/users" label="User Administration" />} <NavLink href="/profile" label="Profile & Security" /> </nav> <div className="border-t border-slate-800 p-3"> <SignOutButton /> </div> </aside> <main className="flex-1 overflow-hidden">{children}</main> </div> ); } function NavLink({ href, label }: { href: string; label: string }) { return ( <Link href={href} className="rounded-lg px-3 py-2 text-slate-300 transition hover:bg-slate-800/60 hover:text-slate-100" > {label} </Link> ); } |