Add authentication protection to admin and member routes
CRITICAL SECURITY FIX: All admin and member routes now require authentication. ## Changes: - Created ProtectedRoute component to enforce authentication - Protected all member routes (/dashboard, /access, /orders, /profile) - Protected all admin routes (/admin/*) with admin role check - Added redirect-after-login functionality using sessionStorage - Non-authenticated users accessing protected pages are redirected to /auth - Non-admin users accessing admin pages are redirected to /dashboard ## Security Impact: - Prevents unauthorized access to admin panel and member areas - Users must login to access any protected functionality - Admin routes additionally verify user role is 'admin' - After login, users are redirected back to their intended page 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
141
src/App.tsx
141
src/App.tsx
@@ -6,6 +6,7 @@ import { BrowserRouter, Routes, Route } from "react-router-dom";
|
||||
import { AuthProvider } from "@/hooks/useAuth";
|
||||
import { CartProvider } from "@/contexts/CartContext";
|
||||
import { BrandingProvider } from "@/hooks/useBranding";
|
||||
import { ProtectedRoute } from "@/components/ProtectedRoute";
|
||||
import Index from "./pages/Index";
|
||||
import Auth from "./pages/Auth";
|
||||
import ConfirmOTP from "./pages/ConfirmOTP";
|
||||
@@ -65,25 +66,131 @@ const App = () => (
|
||||
<Route path="/calendar" element={<CalendarPage />} />
|
||||
<Route path="/privacy" element={<Privacy />} />
|
||||
<Route path="/terms" element={<Terms />} />
|
||||
|
||||
|
||||
{/* Member routes */}
|
||||
<Route path="/dashboard" element={<MemberDashboard />} />
|
||||
<Route path="/access" element={<MemberAccess />} />
|
||||
<Route path="/orders" element={<MemberOrders />} />
|
||||
<Route path="/orders/:id" element={<OrderDetail />} />
|
||||
<Route path="/profile" element={<MemberProfile />} />
|
||||
|
||||
<Route
|
||||
path="/dashboard"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<MemberDashboard />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/access"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<MemberAccess />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/orders"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<MemberOrders />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/orders/:id"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<OrderDetail />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/profile"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<MemberProfile />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Admin routes */}
|
||||
<Route path="/admin" element={<AdminDashboard />} />
|
||||
<Route path="/admin/products" element={<AdminProducts />} />
|
||||
<Route path="/admin/products/:id/curriculum" element={<ProductCurriculum />} />
|
||||
<Route path="/admin/bootcamp" element={<AdminBootcamp />} />
|
||||
<Route path="/admin/orders" element={<AdminOrders />} />
|
||||
<Route path="/admin/members" element={<AdminMembers />} />
|
||||
<Route path="/admin/events" element={<AdminEvents />} />
|
||||
<Route path="/admin/settings" element={<AdminSettings />} />
|
||||
<Route path="/admin/consulting" element={<AdminConsulting />} />
|
||||
<Route path="/admin/reviews" element={<AdminReviews />} />
|
||||
<Route
|
||||
path="/admin"
|
||||
element={
|
||||
<ProtectedRoute requireAdmin>
|
||||
<AdminDashboard />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/products"
|
||||
element={
|
||||
<ProtectedRoute requireAdmin>
|
||||
<AdminProducts />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/products/:id/curriculum"
|
||||
element={
|
||||
<ProtectedRoute requireAdmin>
|
||||
<ProductCurriculum />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/bootcamp"
|
||||
element={
|
||||
<ProtectedRoute requireAdmin>
|
||||
<AdminBootcamp />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/orders"
|
||||
element={
|
||||
<ProtectedRoute requireAdmin>
|
||||
<AdminOrders />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/members"
|
||||
element={
|
||||
<ProtectedRoute requireAdmin>
|
||||
<AdminMembers />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/events"
|
||||
element={
|
||||
<ProtectedRoute requireAdmin>
|
||||
<AdminEvents />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/settings"
|
||||
element={
|
||||
<ProtectedRoute requireAdmin>
|
||||
<AdminSettings />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/consulting"
|
||||
element={
|
||||
<ProtectedRoute requireAdmin>
|
||||
<AdminConsulting />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/reviews"
|
||||
element={
|
||||
<ProtectedRoute requireAdmin>
|
||||
<AdminReviews />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
|
||||
64
src/components/ProtectedRoute.tsx
Normal file
64
src/components/ProtectedRoute.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
|
||||
interface ProtectedRouteProps {
|
||||
children: React.ReactNode;
|
||||
requireAdmin?: boolean;
|
||||
}
|
||||
|
||||
export function ProtectedRoute({ children, requireAdmin = false }: ProtectedRouteProps) {
|
||||
const { user, loading: authLoading } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (!authLoading) {
|
||||
if (!user) {
|
||||
// Save current URL to redirect back after login
|
||||
const currentPath = window.location.pathname + window.location.search;
|
||||
sessionStorage.setItem('redirectAfterLogin', currentPath);
|
||||
navigate('/auth');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for admin role if required
|
||||
if (requireAdmin) {
|
||||
const userRole = user.user_metadata?.role;
|
||||
if (userRole !== 'admin') {
|
||||
// Redirect non-admin users to member dashboard
|
||||
navigate('/dashboard');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [user, authLoading, navigate, requireAdmin]);
|
||||
|
||||
// Show loading skeleton while checking auth
|
||||
if (authLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="max-w-4xl mx-auto space-y-4">
|
||||
<Skeleton className="h-10 w-1/3" />
|
||||
<Skeleton className="h-4 w-full" />
|
||||
<Skeleton className="h-4 w-3/4" />
|
||||
<Skeleton className="h-64 w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Don't render children if user is not authenticated
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Don't render if admin access required but user is not admin
|
||||
if (requireAdmin && user.user_metadata?.role !== 'admin') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -29,7 +29,14 @@ export default function Auth() {
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
navigate('/dashboard');
|
||||
// Check if there's a saved redirect path
|
||||
const savedRedirect = sessionStorage.getItem('redirectAfterLogin');
|
||||
if (savedRedirect) {
|
||||
sessionStorage.removeItem('redirectAfterLogin');
|
||||
navigate(savedRedirect);
|
||||
} else {
|
||||
navigate('/dashboard');
|
||||
}
|
||||
}
|
||||
}, [user, navigate]);
|
||||
|
||||
@@ -101,8 +108,12 @@ export default function Auth() {
|
||||
toast({ title: 'Error', description: error.message, variant: 'destructive' });
|
||||
setLoading(false);
|
||||
} else {
|
||||
// Get redirect from URL state or use default
|
||||
const redirectTo = (location.state as any)?.redirectTo || '/dashboard';
|
||||
// Get redirect from sessionStorage or use default
|
||||
const savedRedirect = sessionStorage.getItem('redirectAfterLogin');
|
||||
const redirectTo = savedRedirect || '/dashboard';
|
||||
if (savedRedirect) {
|
||||
sessionStorage.removeItem('redirectAfterLogin');
|
||||
}
|
||||
navigate(redirectTo);
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user