From 11d9fec3ec057abc395f06a7b059caed74cb5482 Mon Sep 17 00:00:00 2001 From: Jordan Ramos Date: Wed, 24 Jun 2026 11:41:50 -0600 Subject: [PATCH] Add page visibility by group with centralized matrix Introduce a Page Visibility Matrix that controls which pages each user group can access, enforced in both frontend and backend: Frontend: - Create frontend/src/config/pageVisibility.js with PAGE_VISIBILITY matrix and canAccessPage() / getAccessiblePages() helpers - NavDrawer: replace inline requiredGroups with canAccessPage() filter - App.js: replace per-page isInGroup()/isAdmin() checks with generic route guard in setCurrentPage; remove VALID_PAGES constant - localStorage validation: verify persisted page is accessible on load Backend (page-level access enforcement): - jiraTickets.js: add router-level requireGroup('Admin','Standard_User') - archerTemplates.js: add router-level requireGroup('Admin','Standard_User') - VCL multi-vertical already had requireGroup('Admin','Leadership') Visibility matrix: - Home, Knowledge Base: all groups - Triage, Compliance, Exports: Admin, Standard_User, Leadership - CCP Metrics: Admin, Leadership - Jira, Archer Templates: Admin, Standard_User - Admin Panel: Admin only - Read_Only sees only Home and Knowledge Base --- backend/routes/archerTemplates.js | 4 +++ backend/routes/jiraTickets.js | 4 +++ frontend/src/App.js | 16 +++++------- frontend/src/components/NavDrawer.js | 11 ++++---- frontend/src/config/pageVisibility.js | 37 +++++++++++++++++++++++++++ 5 files changed, 58 insertions(+), 14 deletions(-) create mode 100644 frontend/src/config/pageVisibility.js diff --git a/backend/routes/archerTemplates.js b/backend/routes/archerTemplates.js index db47f49..0e98d5a 100644 --- a/backend/routes/archerTemplates.js +++ b/backend/routes/archerTemplates.js @@ -20,6 +20,10 @@ const SECTION_MAX_LENGTH = 10000; function createArcherTemplatesRouter() { const router = express.Router(); + // All Archer template routes require authentication and Admin or Standard_User group (page-level access) + router.use(requireAuth()); + router.use(requireGroup('Admin', 'Standard_User')); + // --- Hierarchy endpoints (MUST be defined before /:id to avoid route conflicts) --- /** diff --git a/backend/routes/jiraTickets.js b/backend/routes/jiraTickets.js index e5b3c63..d73a5c0 100644 --- a/backend/routes/jiraTickets.js +++ b/backend/routes/jiraTickets.js @@ -30,6 +30,10 @@ function isValidVendor(vendor) { function createJiraTicketsRouter() { const router = express.Router(); + // All Jira routes require authentication and Admin or Standard_User group (page-level access) + router.use(requireAuth()); + router.use(requireGroup('Admin', 'Standard_User')); + // ----------------------------------------------------------------------- // Jira API integration endpoints // ----------------------------------------------------------------------- diff --git a/frontend/src/App.js b/frontend/src/App.js index 79b8af6..95a6e99 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -19,20 +19,20 @@ import ArcherTemplatePage from './components/pages/ArcherTemplatePage'; import HomePage from './components/pages/HomePage'; import FeedbackModal from './components/FeedbackModal'; import NotificationBell from './components/NotificationBell'; +import { canAccessPage } from './config/pageVisibility'; import './App.css'; -const VALID_PAGES = new Set(['home', 'triage', 'compliance', 'knowledge-base', 'exports', 'jira', 'admin', 'archer-templates']); - export default function App() { - const { isAuthenticated, loading: authLoading, canWrite, isAdmin, isInGroup } = useAuth(); + const { isAuthenticated, loading: authLoading, canWrite, user } = useAuth(); const [currentPage, setCurrentPageRaw] = useState(() => { try { const saved = localStorage.getItem('cve-dashboard-page'); - return saved && VALID_PAGES.has(saved) ? saved : 'home'; + return saved && canAccessPage(saved, user?.group) ? saved : 'home'; } catch { return 'home'; } }); const setCurrentPage = (page) => { + if (!canAccessPage(page, user?.group)) { setCurrentPageRaw('home'); return; } setCurrentPageRaw(page); try { localStorage.setItem('cve-dashboard-page', page); } catch {} }; @@ -160,18 +160,16 @@ export default function App() { - {/* Page content */} + {/* Page content — generic route guard via canAccessPage */} {currentPage === 'home' && } {currentPage === 'triage' && } {currentPage === 'compliance' && } - {currentPage === 'ccp-metrics' && isInGroup('Admin', 'Leadership') && } - {currentPage === 'ccp-metrics' && !isInGroup('Admin', 'Leadership') && (() => { setCurrentPage('home'); return null; })()} + {currentPage === 'ccp-metrics' && } {currentPage === 'knowledge-base' && } {currentPage === 'exports' && } {currentPage === 'jira' && } {currentPage === 'archer-templates' && } - {currentPage === 'admin' && isAdmin() && } - {currentPage === 'admin' && !isAdmin() && (() => { setCurrentPage('home'); return null; })()} + {currentPage === 'admin' && } {/* Global Modals */} {showUserManagement && setShowUserManagement(false)} />} diff --git a/frontend/src/components/NavDrawer.js b/frontend/src/components/NavDrawer.js index 2807bb1..82f9839 100644 --- a/frontend/src/components/NavDrawer.js +++ b/frontend/src/components/NavDrawer.js @@ -1,12 +1,13 @@ import React from 'react'; import { X, Home, BarChart2, BookOpen, Download, ShieldCheck, Settings, Ticket, Building2, Layers } from 'lucide-react'; import { useAuth } from '../contexts/AuthContext'; +import { canAccessPage } from '../config/pageVisibility'; 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', requiredGroups: ['Admin', 'Leadership'] }, + { id: 'ccp-metrics', label: 'CCP Metrics', icon: Building2, color: '#A78BFA', description: 'Cross-vertical VCL reporting' }, { 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 +17,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, isInGroup } = useAuth(); + const { user } = useAuth(); if (!isOpen) return null; @@ -70,7 +71,7 @@ export default function NavDrawer({ isOpen, onClose, currentPage, onNavigate }) {/* Nav items */}