fix(frontend): hide viewer-only risk-compute UI, proactively renew session
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled

- Executive Dashboard no longer auto-triggers POST /risk/compute or shows
  the Refresh scores button for roles the backend rejects (viewer), which
  was causing a 403 on load
- AuthContext now proactively renews the session cookie every 20 minutes
  while the app is open, so an actively-used session doesn't rely solely
  on the reactive on-401 refresh racing against its own token's expiry
This commit is contained in:
kitos
2026-07-07 13:42:54 +02:00
parent bd26b09827
commit 1ddbd6989f
3 changed files with 46 additions and 15 deletions
+25 -15
View File
@@ -32,6 +32,11 @@ import { getCoverageByTactic } from "../api/metrics";
import { getThreatActors } from "../api/threat-actors";
import { getTechniques, type TechniqueSummary } from "../api/techniques";
import { getRiskProfiles, computeRiskScores, type RiskProfile } from "../api/risk";
import { useAuth } from "../context/AuthContext";
// Roles allowed to trigger a risk-score recompute (matches the backend's
// POST /risk/compute role gate) — viewer must never call this endpoint.
const CAN_COMPUTE_RISK_ROLES = ["admin", "red_lead", "blue_lead"];
// ── Score Gauge Component ────────────────────────────────────────────
@@ -159,6 +164,8 @@ function sinceDate(days: number | null): string | undefined {
export default function ExecutiveDashboardPage() {
const navigate = useNavigate();
const { user } = useAuth();
const canComputeRisk = CAN_COMPUTE_RISK_ROLES.includes(user?.role ?? "");
const [timeRange, setTimeRange] = useState<TimeRange>("all");
const rangeOption = TIME_RANGE_OPTIONS.find((o) => o.value === timeRange)!;
@@ -214,9 +221,10 @@ export default function ExecutiveDashboardPage() {
},
});
// Auto-compute on first load if no profiles exist
// Auto-compute on first load if no profiles exist — only for roles the
// backend actually allows to call POST /risk/compute (viewer would 403).
useEffect(() => {
if (!loadingRisk && riskProfiles !== undefined && riskProfiles.length === 0) {
if (canComputeRisk && !loadingRisk && riskProfiles !== undefined && riskProfiles.length === 0) {
computeMutation.mutate();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -708,19 +716,21 @@ export default function ExecutiveDashboardPage() {
Critical Gaps Top 10 by Risk Priority
<MetricTooltip title="Critical Gaps" description="The 10 most dangerous attack techniques with no detection coverage, ranked by a composite risk score based on: how many threat actors use this technique, recent threat intelligence, and test history." context="These are your highest-priority gaps — real adversaries actively use these techniques and you cannot currently detect them." />
</h2>
<button
onClick={() => computeMutation.mutate()}
disabled={computeMutation.isPending}
className="flex items-center gap-1.5 rounded-lg border border-gray-700 bg-gray-800 px-2.5 py-1 text-xs text-gray-400 hover:text-cyan-400 hover:border-cyan-500/30 disabled:opacity-50 transition-colors"
title="Recompute risk scores"
>
{computeMutation.isPending ? (
<Loader2 className="h-3 w-3 animate-spin" />
) : (
<RefreshCw className="h-3 w-3" />
)}
{computeMutation.isPending ? "Computing…" : "Refresh scores"}
</button>
{canComputeRisk && (
<button
onClick={() => computeMutation.mutate()}
disabled={computeMutation.isPending}
className="flex items-center gap-1.5 rounded-lg border border-gray-700 bg-gray-800 px-2.5 py-1 text-xs text-gray-400 hover:text-cyan-400 hover:border-cyan-500/30 disabled:opacity-50 transition-colors"
title="Recompute risk scores"
>
{computeMutation.isPending ? (
<Loader2 className="h-3 w-3 animate-spin" />
) : (
<RefreshCw className="h-3 w-3" />
)}
{computeMutation.isPending ? "Computing…" : "Refresh scores"}
</button>
)}
</div>
<div className="overflow-x-auto">
<table className="w-full text-sm">