From 479c61b88f2c3fe2984fd2651a0a85d61ef14f88 Mon Sep 17 00:00:00 2001 From: Jordan Ramos Date: Wed, 17 Jun 2026 09:27:01 -0600 Subject: [PATCH] Restrict VCL/CCP Metrics page to Admin and Leadership groups Add requireGroup('Admin', 'Leadership') as router-level middleware on all VCL multi-vertical routes. Hide the CCP Metrics nav item from users not in those groups and guard the page render in App.js with a redirect fallback. --- backend/routes/vclMultiVertical.js | 3 ++- frontend/src/App.js | 5 +++-- frontend/src/components/NavDrawer.js | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/backend/routes/vclMultiVertical.js b/backend/routes/vclMultiVertical.js index 4797a9e..b77f32e 100644 --- a/backend/routes/vclMultiVertical.js +++ b/backend/routes/vclMultiVertical.js @@ -186,8 +186,9 @@ function isSafeTempPath(filePath) { function createVCLMultiVerticalRouter(upload) { const router = express.Router(); - // All routes require authentication + // All routes require authentication + Leadership or Admin group router.use(requireAuth()); + router.use(requireGroup('Admin', 'Leadership')); /** * POST /preview diff --git a/frontend/src/App.js b/frontend/src/App.js index 42446ab..4bc4544 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -186,7 +186,7 @@ const getSeverityDotColor = (severity) => { const severityLevels = ['All Severities', 'Critical', 'High', 'Medium', 'Low']; export default function App() { - const { isAuthenticated, loading: authLoading, canWrite, canDelete, canExport, isAdmin, getActiveTeamsParam, adminScope } = useAuth(); + const { isAuthenticated, loading: authLoading, canWrite, canDelete, canExport, isAdmin, isInGroup, getActiveTeamsParam, adminScope } = useAuth(); const [searchQuery, setSearchQuery] = useState(''); const [selectedVendor, setSelectedVendor] = useState('All Vendors'); const [selectedSeverity, setSelectedSeverity] = useState('All Severities'); @@ -1102,7 +1102,8 @@ export default function App() { {/* Page content */} {currentPage === 'triage' && } {currentPage === 'compliance' && } - {currentPage === 'ccp-metrics' && } + {currentPage === 'ccp-metrics' && isInGroup('Admin', 'Leadership') && } + {currentPage === 'ccp-metrics' && !isInGroup('Admin', 'Leadership') && (() => { setCurrentPage('home'); return null; })()} {currentPage === 'knowledge-base' && } {currentPage === 'exports' && } {currentPage === 'jira' && } diff --git a/frontend/src/components/NavDrawer.js b/frontend/src/components/NavDrawer.js index 695266f..4d8ba93 100644 --- a/frontend/src/components/NavDrawer.js +++ b/frontend/src/components/NavDrawer.js @@ -6,7 +6,7 @@ const NAV_ITEMS = [ { id: 'home', label: 'Home', icon: Home, color: '#0EA5E9', description: 'Main dashboard' }, { id: 'triage', label: 'Vuln Triage', icon: BarChart2, color: '#F59E0B', description: 'Active findings & CVE triage' }, { id: 'compliance', label: 'Compliance', icon: ShieldCheck, color: '#14B8A6', description: 'AEO posture & metrics' }, - { id: 'ccp-metrics', label: 'CCP Metrics', icon: Building2, color: '#A78BFA', description: 'Cross-vertical VCL reporting' }, + { id: 'ccp-metrics', label: 'CCP Metrics', icon: Building2, color: '#A78BFA', description: 'Cross-vertical VCL reporting', requiredGroups: ['Admin', 'Leadership'] }, { id: 'knowledge-base', label: 'Knowledge Base', icon: BookOpen, color: '#10B981', description: 'Articles & documentation' }, { id: 'exports', label: 'Exports', icon: Download, color: '#8B5CF6', description: 'Export data & reports' }, { id: 'jira', label: 'Jira Tickets', icon: Ticket, color: '#6366F1', description: 'Jira issue tracking & sync' }, @@ -16,7 +16,7 @@ const NAV_ITEMS = [ const ADMIN_ITEM = { id: 'admin', label: 'Admin Panel', icon: Settings, color: '#EF4444', description: 'User management & audit' }; export default function NavDrawer({ isOpen, onClose, currentPage, onNavigate }) { - const { isAdmin } = useAuth(); + const { isAdmin, isInGroup } = useAuth(); if (!isOpen) return null; @@ -65,7 +65,7 @@ export default function NavDrawer({ isOpen, onClose, currentPage, onNavigate }) {/* Nav items */}