Description:
The sidebar in
sidebar.tsx
renders an "Admin" link for every user:
// bottomItems includes:
{ href: "/admin", label: "Admin", icon: Shield, adminOnly: true }
// But the rendering loop ignores adminOnly:
{bottomItems.map((item) => (
The adminOnly: true flag exists but is never checked. Any authenticated user can navigate to /admin and see (or eventually modify) platform-wide statistics and user data.
Fix:
Check adminOnly in the render:
if (item.adminOnly && !user?.isAdmin) return null
Add an isAdmin field to UserProfile type
Add a server-side check (middleware or layout guard) on the /admin route that returns 403 for non-admins
Determine admin status from the JWT claims, not client-side state alone
Acceptance criteria:
Admin link hidden in sidebar for non-admin users
/admin page redirects non-admin users to /dashboard
UserProfile type includes isAdmin: boolean
Server-side guard cannot be bypassed by modifying client state
Description:
The sidebar in
sidebar.tsx
renders an "Admin" link for every user:
// bottomItems includes:
{ href: "/admin", label: "Admin", icon: Shield, adminOnly: true }
// But the rendering loop ignores adminOnly:
The adminOnly: true flag exists but is never checked. Any authenticated user can navigate to /admin and see (or eventually modify) platform-wide statistics and user data.{bottomItems.map((item) => (
Fix:
Check adminOnly in the render:
if (item.adminOnly && !user?.isAdmin) return null
Add an isAdmin field to UserProfile type
Add a server-side check (middleware or layout guard) on the /admin route that returns 403 for non-admins
Determine admin status from the JWT claims, not client-side state alone
Acceptance criteria:
Admin link hidden in sidebar for non-admin users
/admin page redirects non-admin users to /dashboard
UserProfile type includes isAdmin: boolean
Server-side guard cannot be bypassed by modifying client state