Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,18 @@ import nextTs from "eslint-config-next/typescript"

// Files over 200 lines, capped at their current size until #30 splits them.
const OVER_LIMIT = {
"src/lib/sso/sso.itest.ts": 708,
"src/components/settings/SetupGuides.tsx": 487,
"src/lib/dashboard.ts": 408,
"src/lib/auth/invitation-routes.itest.ts": 400,
"src/lib/scan/runner.ts": 394,
"src/components/reports/ReportCanvas.tsx": 393,
"src/components/credentials/SourceDrawer.tsx": 349,
"src/app/(dashboard)/dashboard/page.tsx": 345,
"src/lib/scan/runner.test.ts": 334,
"src/lib/auth/server.ts": 313,
"src/lib/sso/user-create.itest.ts": 311,
"src/components/settings/DirectoryConnections.tsx": 275,
"src/components/alerts/AlertTable.tsx": 271,
"src/components/rbac/RolesManager.tsx": 269,
"src/lib/auth/bootstrap.itest.ts": 263,
"src/components/employees/EmployeeTable.tsx": 263,
"src/lib/auth/invitation.itest.ts": 242,
"src/lib/sso/round-trip.itest.ts": 227,
"src/components/dashboard/WidgetLibrary.tsx": 226,
"src/contexts/DetailDrawerContext.tsx": 219,
"src/components/rbac/UserRoleAssignment.tsx": 215,
"src/components/credentials/SourceCatalog.tsx": 215,
"src/lib/auth/bootstrap.ts": 211,
"src/app/api/dashboard/detail/[kind]/[id]/route.ts": 209,
"scripts/check-licenses.mjs": 201,
}

export default defineConfig([
Expand Down
2 changes: 1 addition & 1 deletion prisma/seed.demo.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PrismaClient, type AlertStatus } from "@prisma/client"
import { PrismaPg } from "@prisma/adapter-pg"
import { normalizeType } from "@/lib/scan/normalize"
import { severityFor } from "@/lib/scan/runner"
import { severityFor } from "@/lib/scan/severity"
import { DEMO_BREACHES } from "./demo/breaches"
import { DEMO_DOMAIN, DEMO_PEOPLE } from "./demo/people"

Expand Down
28 changes: 3 additions & 25 deletions scripts/check-licenses.mjs
Original file line number Diff line number Diff line change
@@ -1,24 +1,4 @@
/**
* License gate and third-party notice generator.
*
* Reads package-lock.json (not node_modules): the lockfile carries a `license`
* field on every entry, so the check is deterministic, needs no install, and
* yields the same answer on every platform. Walking node_modules instead would
* report a different set of packages on Linux than on macOS, because optional
* platform-specific binaries (sharp's libvips builds) only install on the host
* they target.
*
* Scope is the runtime tree: entries the lockfile marks `dev: true` are skipped
* because they never reach a deployed artifact. A copyleft build tool is not a
* distribution problem; a copyleft runtime dependency is.
*
* The gate is an allowlist, not a denylist. A license nobody has reviewed yet
* fails the build and has to be added here on purpose, which is the whole point:
* a new AGPL transitive should stop a merge rather than land unnoticed.
*
* node scripts/check-licenses.mjs # verify (CI)
* node scripts/check-licenses.mjs --write # regenerate the notices file
*/
// License gate and notice generator, reading the lockfile runtime tree against an allowlist (see the wiki, Development).
import { readFileSync, writeFileSync } from "node:fs"
import { fileURLToPath } from "node:url"
import { dirname, join } from "node:path"
Expand All @@ -27,8 +7,7 @@ const root = join(dirname(fileURLToPath(import.meta.url)), "..")
const LOCKFILE = join(root, "package-lock.json")
const NOTICES = join(root, "THIRD-PARTY-NOTICES.md")

// Reviewed and accepted. Permissive unless noted; the copyleft entries are
// weak (file-level or library-level) and are documented in OBLIGATIONS below.
// Reviewed licenses; the few weak-copyleft ones are documented in OBLIGATIONS below.
const ALLOWED = new Set([
"0BSD",
"Apache-2.0",
Expand All @@ -53,8 +32,7 @@ const SPELLING = new Map([
["MIT and ISC", "MIT AND ISC"],
])

// Packages that ship a LICENSE file but no `license` field for the lockfile to
// record. Each was read by hand; do not add an entry without doing the same.
// Packages whose license was read by hand from their LICENSE file, since the lockfile has no field for them.
const MISSING_FIELD = new Map([
["png-js", "MIT"], // LICENSE: "MIT License, Copyright (c) 2017 Devon Govett"
["seq-queue", "MIT"], // LICENSE: "(The MIT License), Copyright (c) 2012 Netease, Inc."
Expand Down
103 changes: 103 additions & 0 deletions src/app/(dashboard)/dashboard/default-widgets.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import type { WidgetEntry } from "@/components/dashboard/DashboardCanvas"
import { buildTrendData, buildBreachSources, buildDataTypes } from "@/lib/dashboard-series"
import { StatsRow } from "@/components/dashboard/StatsRow"
import { TrendChart } from "@/components/dashboard/TrendChart"
import { DataTypeBreakdown } from "@/components/dashboard/DataTypeBreakdown"
import { BreachSourcesList } from "@/components/dashboard/BreachSourcesList"
import { TopRiskyEmployees } from "@/components/dashboard/TopRiskyEmployees"
import { DepartmentRisk } from "@/components/dashboard/DepartmentRisk"
import { AlertsFeed } from "@/components/dashboard/AlertsFeed"
import { SeverityDonut } from "@/components/dashboard/SeverityDonut"
import type { WidgetContext } from "./widget-context"

// Widgets shown on a new dashboard.
export function defaultWidgets({
data,
gatedRecords,
gatedCatalog,
sourceFor,
}: WidgetContext): WidgetEntry[] {
return [
{
instanceId: "stats-row",
type: "stats-row",
defaultTitle: "Key Metrics",
defaultSize: { w: 12, h: 4 },
defaultPosition: { x: 0, y: 0 },
minSize: { w: 6, h: 3 },
centerContent: true,
content: (
<StatsRow
compromisedEmployees={data.compromisedEmployees}
totalEmployees={data.totalEmployees}
openAlerts={data.openAlerts}
recentBreaches={data.recentBreaches}
riskScore={data.riskScore}
/>
),
},
{
instanceId: "trend-chart",
type: "trend-chart",
defaultTitle: "Incident Timeline",
defaultSize: { w: 7, h: 8 },
defaultPosition: { x: 0, y: 4 },
minSize: { w: 4, h: 5 },
content: <TrendChart data={buildTrendData(gatedRecords, sourceFor("trend-chart"))} />,
},
{
instanceId: "breach-sources",
type: "breach-sources",
defaultTitle: "Breach Sources",
defaultSize: { w: 5, h: 8 },
defaultPosition: { x: 7, y: 4 },
minSize: { w: 3, h: 4 },
content: <BreachSourcesList data={buildBreachSources(gatedCatalog, sourceFor("breach-sources"))} />,
},
{
instanceId: "top-risky-employees",
type: "top-risky-employees",
defaultTitle: "Top Employees at Risk",
defaultSize: { w: 4, h: 7 },
defaultPosition: { x: 0, y: 12 },
minSize: { w: 3, h: 4 },
content: <TopRiskyEmployees data={data.topRiskyEmployees} />,
},
{
instanceId: "department-risk",
type: "department-risk",
defaultTitle: "Department Exposure",
defaultSize: { w: 5, h: 7 },
defaultPosition: { x: 4, y: 12 },
minSize: { w: 3, h: 4 },
content: <DepartmentRisk data={data.departmentRisk} />,
},
{
instanceId: "severity-donut",
type: "severity-donut",
defaultTitle: "Alert Severity",
defaultSize: { w: 3, h: 7 },
defaultPosition: { x: 9, y: 12 },
minSize: { w: 2, h: 4 },
content: <SeverityDonut data={data.alertSeverity} />,
},
{
instanceId: "data-type-breakdown",
type: "data-type-breakdown",
defaultTitle: "Exposed Data Types",
defaultSize: { w: 6, h: 6 },
defaultPosition: { x: 0, y: 19 },
minSize: { w: 3, h: 4 },
content: <DataTypeBreakdown data={buildDataTypes(gatedRecords, sourceFor("data-type-breakdown"))} />,
},
{
instanceId: "alerts-feed",
type: "alerts-feed",
defaultTitle: "Recent Alerts",
defaultSize: { w: 6, h: 6 },
defaultPosition: { x: 6, y: 19 },
minSize: { w: 3, h: 4 },
content: <AlertsFeed data={data.recentAlerts} />,
},
]
}
136 changes: 136 additions & 0 deletions src/app/(dashboard)/dashboard/library-widgets.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import type { WidgetEntry } from "@/components/dashboard/DashboardCanvas"
import { buildBreachSources, buildDataTypes } from "@/lib/dashboard-series"
import { RiskGauge } from "@/components/dashboard/RiskGauge"
import { AlertsByMonth } from "@/components/dashboard/AlertsByMonth"
import { BreachSourceDonut } from "@/components/dashboard/BreachSourceDonut"
import { CriticalAlertsList } from "@/components/dashboard/CriticalAlertsList"
import { AlertsByDepartment } from "@/components/dashboard/AlertsByDepartment"
import { EmployeeExposure } from "@/components/dashboard/EmployeeExposure"
import { AlertStatusBreakdown } from "@/components/dashboard/AlertStatusBreakdown"
import { BreachTimeline } from "@/components/dashboard/BreachTimeline"
import { TopBreaches } from "@/components/dashboard/TopBreaches"
import { DataTypeRadar } from "@/components/dashboard/DataTypeRadar"
import { AlertVelocity } from "@/components/dashboard/AlertVelocity"
import { MfaCoverage } from "@/components/dashboard/MfaCoverage"
import type { WidgetContext } from "./widget-context"

// Widgets hidden by default, added from the Widget Library.
export function libraryWidgets({
data,
gatedRecords,
gatedCatalog,
sourceFor,
}: WidgetContext): WidgetEntry[] {
return [
{
instanceId: "risk-gauge",
type: "risk-gauge",
defaultTitle: "Risk Score",
defaultSize: { w: 3, h: 5 },
minSize: { w: 2, h: 4 },
defaultVisible: false,
content: <RiskGauge riskScore={data.riskScore} />,
},
{
instanceId: "alert-status-breakdown",
type: "alert-status-breakdown",
defaultTitle: "Alert Status",
defaultSize: { w: 4, h: 5 },
minSize: { w: 3, h: 3 },
defaultVisible: false,
content: <AlertStatusBreakdown data={data.alertStatusCounts} />,
},
{
instanceId: "employee-exposure",
type: "employee-exposure",
defaultTitle: "Employee Exposure",
defaultSize: { w: 4, h: 5 },
minSize: { w: 3, h: 3 },
defaultVisible: false,
content: <EmployeeExposure data={data.employeeExposureLevels} totalEmployees={data.totalEmployees} />,
},
{
instanceId: "alerts-by-month",
type: "alerts-by-month",
defaultTitle: "Alerts by Month",
defaultSize: { w: 7, h: 7 },
minSize: { w: 4, h: 5 },
defaultVisible: false,
content: <AlertsByMonth data={data.alertsByMonth} />,
},
{
instanceId: "alert-velocity",
type: "alert-velocity",
defaultTitle: "Alert Velocity",
defaultSize: { w: 5, h: 6 },
minSize: { w: 3, h: 4 },
defaultVisible: false,
content: <AlertVelocity data={data.alertVelocityData} />,
},
{
instanceId: "critical-alerts",
type: "critical-alerts",
defaultTitle: "Urgent Alerts",
defaultSize: { w: 5, h: 7 },
minSize: { w: 3, h: 4 },
defaultVisible: false,
content: <CriticalAlertsList data={data.urgentAlerts} />,
},
{
instanceId: "alerts-by-department",
type: "alerts-by-department",
defaultTitle: "Alerts by Department",
defaultSize: { w: 5, h: 6 },
minSize: { w: 3, h: 4 },
defaultVisible: false,
content: <AlertsByDepartment data={data.alertsByDepartment} />,
},
{
instanceId: "breach-source-donut",
type: "breach-source-donut",
defaultTitle: "Breach Origin",
defaultSize: { w: 3, h: 6 },
minSize: { w: 2, h: 4 },
defaultVisible: false,
content: (
<BreachSourceDonut data={buildBreachSources(gatedCatalog, sourceFor("breach-source-donut"))} />
),
},
{
instanceId: "breach-timeline",
type: "breach-timeline",
defaultTitle: "Breach Timeline",
defaultSize: { w: 4, h: 8 },
minSize: { w: 3, h: 5 },
defaultVisible: false,
content: <BreachTimeline data={buildBreachSources(gatedCatalog, sourceFor("breach-timeline"))} />,
},
{
instanceId: "top-breaches",
type: "top-breaches",
defaultTitle: "Top Breaches by Impact",
defaultSize: { w: 5, h: 6 },
minSize: { w: 3, h: 4 },
defaultVisible: false,
content: <TopBreaches data={buildBreachSources(gatedCatalog, sourceFor("top-breaches"))} />,
},
{
instanceId: "data-type-radar",
type: "data-type-radar",
defaultTitle: "Data Type Radar",
defaultSize: { w: 4, h: 6 },
minSize: { w: 3, h: 4 },
defaultVisible: false,
content: <DataTypeRadar data={buildDataTypes(gatedRecords, sourceFor("data-type-radar"))} />,
},
{
instanceId: "mfa-coverage",
type: "mfa-coverage",
defaultTitle: "MFA Coverage",
defaultSize: { w: 4, h: 5 },
minSize: { w: 3, h: 3 },
defaultVisible: false,
content: <MfaCoverage data={data.mfaCoverage} />,
},
]
}
Loading
Loading