fix(permissions): hide action buttons for unauthorized roles
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
TestCatalogPage: 'Use Template' button had no role check — any user (including viewer/blue_tech/red_tech) could see and click it, which would fail at the backend (POST /tests/from-template requires red_lead|blue_lead). Added canUseTemplate check; button hidden for viewer, blue_tech, red_tech. TechniqueDetailPage: 'Run This Test' / 'Re-run' buttons in the Available Templates section also had no role check. Added canRunTemplate (same criteria: admin|red_lead|blue_lead). The 'View test' button for active tests remains visible to everyone (read-only navigation). Principle: if a user cannot perform the action, the button does not appear — no permission error messages, just absence of the control. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -158,6 +158,10 @@ export default function TechniqueDetailPage() {
|
||||
const canReview =
|
||||
user?.role === "admin" || user?.role === "red_lead" || user?.role === "blue_lead";
|
||||
|
||||
// Same roles that can create tests (mirrors backend POST /tests/from-template)
|
||||
const canRunTemplate =
|
||||
user?.role === "admin" || user?.role === "red_lead" || user?.role === "blue_lead";
|
||||
|
||||
const {
|
||||
data: technique,
|
||||
isLoading,
|
||||
@@ -584,7 +588,7 @@ export default function TechniqueDetailPage() {
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
View test
|
||||
</button>
|
||||
) : (
|
||||
) : canRunTemplate ? (
|
||||
<button
|
||||
onClick={() => setTemplateFormId(tpl.id)}
|
||||
className={`flex items-center gap-1 rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||
@@ -596,7 +600,7 @@ export default function TechniqueDetailPage() {
|
||||
<FlaskConical className="h-3.5 w-3.5" />
|
||||
{needsReRun ? "Run This Test" : latestValidated ? "Re-run" : "Run This Test"}
|
||||
</button>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import { getTemplates } from "../api/test-templates";
|
||||
import TestFromTemplateForm from "../components/TestFromTemplateForm";
|
||||
import type { TestTemplateSummary } from "../types/models";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
|
||||
// ── Constants ──────────────────────────────────────────────────────
|
||||
|
||||
@@ -75,6 +76,13 @@ export default function TestCatalogPage() {
|
||||
const navigate = useNavigate();
|
||||
const { templateId } = useParams<{ templateId: string }>();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const { user } = useAuth();
|
||||
|
||||
// Only leads and admins can create tests from templates
|
||||
const canUseTemplate =
|
||||
user?.role === "admin" ||
|
||||
user?.role === "red_lead" ||
|
||||
user?.role === "blue_lead";
|
||||
|
||||
const [search, setSearch] = useState(searchParams.get("search") || "");
|
||||
const [source, setSource] = useState(searchParams.get("source") || "");
|
||||
@@ -236,6 +244,7 @@ export default function TestCatalogPage() {
|
||||
<TemplateCard
|
||||
key={tpl.id}
|
||||
template={tpl}
|
||||
canUse={canUseTemplate}
|
||||
onUse={() => navigate(`/test-catalog/${tpl.id}/use`)}
|
||||
/>
|
||||
))}
|
||||
@@ -286,9 +295,11 @@ export default function TestCatalogPage() {
|
||||
|
||||
function TemplateCard({
|
||||
template,
|
||||
canUse,
|
||||
onUse,
|
||||
}: {
|
||||
template: TestTemplateSummary;
|
||||
canUse: boolean;
|
||||
onUse: () => void;
|
||||
}) {
|
||||
return (
|
||||
@@ -341,7 +352,8 @@ function TemplateCard({
|
||||
{/* Spacer */}
|
||||
<div className="flex-1" />
|
||||
|
||||
{/* Action */}
|
||||
{/* Action — only visible to users who can create tests */}
|
||||
{canUse && (
|
||||
<button
|
||||
onClick={onUse}
|
||||
className="mt-4 flex w-full items-center justify-center gap-1.5 rounded-lg border border-cyan-500/30 bg-cyan-500/10 py-2 text-sm font-medium text-cyan-400 hover:bg-cyan-500/20 transition-colors"
|
||||
@@ -349,6 +361,7 @@ function TemplateCard({
|
||||
<FlaskConical className="h-4 w-4" />
|
||||
Use Template
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user