localgreenchain/components/auth/AuthGuard.tsx
Claude 39b6081baa
Implement comprehensive authentication system (Agent 1)
Add complete user authentication with NextAuth.js supporting:
- Email/password credentials authentication
- OAuth providers (GitHub, Google) with optional configuration
- JWT-based session management with 30-day expiry
- Role-based access control (USER, GROWER, FARM_MANAGER, ADMIN)
- Permission system with granular access control
- Secure password hashing with bcrypt (12 rounds)
- Rate limiting on auth endpoints
- Password reset flow with secure tokens
- Email verification system

Files added:
- lib/auth/: Core auth library (types, permissions, context, hooks, middleware)
- pages/api/auth/: Auth API routes (NextAuth, register, forgot-password, verify-email)
- pages/auth/: Auth pages (signin, signup, forgot-password, reset-password, verify-email)
- components/auth/: Reusable auth components (LoginForm, RegisterForm, AuthGuard, etc.)

Updated _app.tsx to include SessionProvider for auth state management.
2025-11-23 03:52:09 +00:00

128 lines
3.7 KiB
TypeScript

import { useEffect } from 'react'
import { useRouter } from 'next/router'
import { useAuth } from '@/lib/auth/useAuth'
import { UserRole } from '@/lib/auth/types'
import { hasRole, hasPermission } from '@/lib/auth/permissions'
interface AuthGuardProps {
children: React.ReactNode
requiredRole?: UserRole
requiredPermission?: string
fallback?: React.ReactNode
redirectTo?: string
}
export function AuthGuard({
children,
requiredRole,
requiredPermission,
fallback,
redirectTo = '/auth/signin',
}: AuthGuardProps) {
const router = useRouter()
const { user, isAuthenticated, isLoading } = useAuth()
useEffect(() => {
if (!isLoading && !isAuthenticated) {
const returnUrl = encodeURIComponent(router.asPath)
router.push(`${redirectTo}?callbackUrl=${returnUrl}`)
}
}, [isLoading, isAuthenticated, router, redirectTo])
// Show loading state
if (isLoading) {
return (
fallback || (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-green-600 mx-auto"></div>
<p className="mt-4 text-gray-600">Loading...</p>
</div>
</div>
)
)
}
// Not authenticated
if (!isAuthenticated) {
return (
fallback || (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<p className="text-gray-600">Redirecting to sign in...</p>
</div>
</div>
)
)
}
// Check role requirement
if (requiredRole && user) {
if (!hasRole(user.role, requiredRole)) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center max-w-md mx-auto p-8">
<div className="text-red-500 text-6xl mb-4">403</div>
<h1 className="text-2xl font-bold text-gray-900 mb-2">Access Denied</h1>
<p className="text-gray-600 mb-4">
You don't have permission to access this page. This page requires{' '}
<span className="font-medium">{requiredRole}</span> role or higher.
</p>
<button
onClick={() => router.back()}
className="text-green-600 hover:text-green-500 font-medium"
>
Go back
</button>
</div>
</div>
)
}
}
// Check permission requirement
if (requiredPermission && user) {
if (!hasPermission(user.role, requiredPermission)) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center max-w-md mx-auto p-8">
<div className="text-red-500 text-6xl mb-4">403</div>
<h1 className="text-2xl font-bold text-gray-900 mb-2">Access Denied</h1>
<p className="text-gray-600 mb-4">
You don't have the required permission to access this page.
</p>
<button
onClick={() => router.back()}
className="text-green-600 hover:text-green-500 font-medium"
>
Go back
</button>
</div>
</div>
)
}
}
return <>{children}</>
}
// Higher-order component version
export function withAuthGuard<P extends object>(
Component: React.ComponentType<P>,
options?: {
requiredRole?: UserRole
requiredPermission?: string
fallback?: React.ReactNode
redirectTo?: string
}
) {
return function AuthGuardedComponent(props: P) {
return (
<AuthGuard {...options}>
<Component {...props} />
</AuthGuard>
)
}
}
export default AuthGuard