fix(permissions): hide non-actionable UI + fix viewer route access
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
1. /executive-dashboard: add 'viewer' to ProtectedRoute roles — sidebar showed the link to viewers but the route redirected them to /dashboard. 2. /comparison: same fix — viewer was in sidebar roles but not in route. 3. /techniques/review-queue: add ProtectedRoute (leads+admin) — the page had no route-level protection, any authenticated user could access it. 4. TechniqueDetailPage review banner: hide from users who can't act on it. Previously shown to everyone with a 'Leads only' badge; now only shown to canReview users (admin/red_lead/blue_lead). Non-leads don't need to see alerts about changes they cannot acknowledge. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -53,13 +53,20 @@ export default function App() {
|
|||||||
<Route path="/techniques/:mitreId" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><TechniqueDetailPage /></Suspense>} />
|
<Route path="/techniques/:mitreId" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><TechniqueDetailPage /></Suspense>} />
|
||||||
|
|
||||||
<Route path="/matrix" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><MatrixPage /></Suspense>} />
|
<Route path="/matrix" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><MatrixPage /></Suspense>} />
|
||||||
<Route path="/techniques/review-queue" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><ReviewQueuePage /></Suspense>} />
|
<Route
|
||||||
|
path="/techniques/review-queue"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute roles={["admin", "red_lead", "blue_lead"]}>
|
||||||
|
<Suspense fallback={<LoadingSpinner text="Loading…" />}><ReviewQueuePage /></Suspense>
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* ── Executive Dashboard (leads + admin) ──────────────── */}
|
{/* ── Executive Dashboard (leads + admin + viewer) ──────── */}
|
||||||
<Route
|
<Route
|
||||||
path="/executive-dashboard"
|
path="/executive-dashboard"
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute roles={["admin", "red_lead", "blue_lead"]}>
|
<ProtectedRoute roles={["admin", "red_lead", "blue_lead", "viewer"]}>
|
||||||
<Suspense fallback={<LoadingSpinner text="Loading…" />}><ExecutiveDashboardPage /></Suspense>
|
<Suspense fallback={<LoadingSpinner text="Loading…" />}><ExecutiveDashboardPage /></Suspense>
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
@@ -84,11 +91,11 @@ export default function App() {
|
|||||||
{/* ── Compliance ───────────────────────────────────────── */}
|
{/* ── Compliance ───────────────────────────────────────── */}
|
||||||
<Route path="/compliance" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><CompliancePage /></Suspense>} />
|
<Route path="/compliance" element={<Suspense fallback={<LoadingSpinner text="Loading…" />}><CompliancePage /></Suspense>} />
|
||||||
|
|
||||||
{/* ── Comparison (leads + admin) ───────────────────────── */}
|
{/* ── Comparison (leads + admin + viewer) ──────────────── */}
|
||||||
<Route
|
<Route
|
||||||
path="/comparison"
|
path="/comparison"
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute roles={["admin", "red_lead", "blue_lead"]}>
|
<ProtectedRoute roles={["admin", "red_lead", "blue_lead", "viewer"]}>
|
||||||
<Suspense fallback={<LoadingSpinner text="Loading…" />}><ComparisonPage /></Suspense>
|
<Suspense fallback={<LoadingSpinner text="Loading…" />}><ComparisonPage /></Suspense>
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -273,8 +273,8 @@ export default function TechniqueDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Review required banner */}
|
{/* Review required banner — only shown to users who can act on it */}
|
||||||
{technique.review_required && (
|
{technique.review_required && canReview && (
|
||||||
<div className="flex items-start gap-3 rounded-xl border border-amber-500/30 bg-amber-500/5 p-4">
|
<div className="flex items-start gap-3 rounded-xl border border-amber-500/30 bg-amber-500/5 p-4">
|
||||||
<AlertTriangle className="mt-0.5 h-5 w-5 shrink-0 text-amber-400" />
|
<AlertTriangle className="mt-0.5 h-5 w-5 shrink-0 text-amber-400" />
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
@@ -286,15 +286,10 @@ export default function TechniqueDetailPage() {
|
|||||||
{technique.mitre_last_modified && (
|
{technique.mitre_last_modified && (
|
||||||
<> Last modified in ATT&CK: <span className="font-mono">{technique.mitre_last_modified.slice(0, 10)}</span>.</>
|
<> Last modified in ATT&CK: <span className="font-mono">{technique.mitre_last_modified.slice(0, 10)}</span>.</>
|
||||||
)}
|
)}
|
||||||
{" "}A lead or admin should review the changes and click{" "}
|
{" "}Click{" "}
|
||||||
<span className="font-semibold">Mark as Reviewed</span> to acknowledge them.
|
<span className="font-semibold">Mark as Reviewed</span> to acknowledge the changes.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{!canReview && (
|
|
||||||
<span className="shrink-0 rounded-full border border-amber-500/20 bg-amber-500/10 px-2 py-0.5 text-[10px] text-amber-400">
|
|
||||||
Leads only
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user