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 */}