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.
120 lines
3.1 KiB
TypeScript
120 lines
3.1 KiB
TypeScript
import * as React from 'react'
|
|
import { SessionProvider, useSession, signIn, signOut } from 'next-auth/react'
|
|
import { Session } from 'next-auth'
|
|
import { AuthUser, UserRole } from './types'
|
|
import { hasPermission, hasRole } from './permissions'
|
|
|
|
interface AuthContextType {
|
|
user: AuthUser | null
|
|
isAuthenticated: boolean
|
|
isLoading: boolean
|
|
signIn: typeof signIn
|
|
signOut: typeof signOut
|
|
hasPermission: (permission: string) => boolean
|
|
hasRole: (role: UserRole) => boolean
|
|
updateSession: () => Promise<void>
|
|
}
|
|
|
|
const AuthContext = React.createContext<AuthContextType | undefined>(undefined)
|
|
|
|
interface AuthProviderProps {
|
|
children: React.ReactNode
|
|
session?: Session | null
|
|
}
|
|
|
|
function AuthProviderContent({ children }: { children: React.ReactNode }) {
|
|
const { data: session, status, update } = useSession()
|
|
const isLoading = status === 'loading'
|
|
const isAuthenticated = status === 'authenticated'
|
|
|
|
const user: AuthUser | null = session?.user
|
|
? {
|
|
id: session.user.id,
|
|
email: session.user.email!,
|
|
name: session.user.name,
|
|
image: session.user.image,
|
|
role: session.user.role || UserRole.USER,
|
|
emailVerified: session.user.emailVerified,
|
|
}
|
|
: null
|
|
|
|
const checkPermission = React.useCallback(
|
|
(permission: string): boolean => {
|
|
if (!user) return false
|
|
return hasPermission(user.role, permission)
|
|
},
|
|
[user]
|
|
)
|
|
|
|
const checkRole = React.useCallback(
|
|
(requiredRole: UserRole): boolean => {
|
|
if (!user) return false
|
|
return hasRole(user.role, requiredRole)
|
|
},
|
|
[user]
|
|
)
|
|
|
|
const updateSession = React.useCallback(async () => {
|
|
await update()
|
|
}, [update])
|
|
|
|
const value: AuthContextType = {
|
|
user,
|
|
isAuthenticated,
|
|
isLoading,
|
|
signIn,
|
|
signOut,
|
|
hasPermission: checkPermission,
|
|
hasRole: checkRole,
|
|
updateSession,
|
|
}
|
|
|
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
|
|
}
|
|
|
|
export function AuthProvider({ children, session }: AuthProviderProps) {
|
|
return (
|
|
<SessionProvider session={session}>
|
|
<AuthProviderContent>{children}</AuthProviderContent>
|
|
</SessionProvider>
|
|
)
|
|
}
|
|
|
|
export function useAuth(): AuthContextType {
|
|
const context = React.useContext(AuthContext)
|
|
if (context === undefined) {
|
|
throw new Error('useAuth must be used within an AuthProvider')
|
|
}
|
|
return context
|
|
}
|
|
|
|
// Higher-order component for components that require auth
|
|
export function withAuth<P extends object>(
|
|
Component: React.ComponentType<P>,
|
|
options?: { requiredRole?: UserRole; fallback?: React.ReactNode }
|
|
) {
|
|
return function AuthenticatedComponent(props: P) {
|
|
const { isAuthenticated, isLoading, user } = useAuth()
|
|
|
|
if (isLoading) {
|
|
return options?.fallback || <div>Loading...</div>
|
|
}
|
|
|
|
if (!isAuthenticated) {
|
|
if (typeof window !== 'undefined') {
|
|
signIn()
|
|
}
|
|
return options?.fallback || <div>Redirecting to sign in...</div>
|
|
}
|
|
|
|
if (options?.requiredRole && user) {
|
|
if (!hasRole(user.role, options.requiredRole)) {
|
|
return <div>Access denied. Insufficient permissions.</div>
|
|
}
|
|
}
|
|
|
|
return <Component {...props} />
|
|
}
|
|
}
|
|
|
|
export { AuthContext }
|