localgreenchain/lib/auth/useAuth.ts
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

157 lines
4 KiB
TypeScript

import { useSession, signIn, signOut } from 'next-auth/react'
import { useCallback, useMemo } from 'react'
import { AuthUser, UserRole } from './types'
import { hasPermission, hasRole, hasAnyPermission, hasAllPermissions } from './permissions'
interface UseAuthReturn {
// User state
user: AuthUser | null
isAuthenticated: boolean
isLoading: boolean
// Auth actions
login: (provider?: string, options?: { callbackUrl?: string }) => Promise<void>
logout: (options?: { callbackUrl?: string }) => Promise<void>
loginWithCredentials: (email: string, password: string, callbackUrl?: string) => Promise<void>
// Permission checks
can: (permission: string) => boolean
canAny: (permissions: string[]) => boolean
canAll: (permissions: string[]) => boolean
is: (role: UserRole) => boolean
isAtLeast: (role: UserRole) => boolean
// Session management
refreshSession: () => Promise<void>
}
export function useAuth(): UseAuthReturn {
const { data: session, status, update } = useSession()
const isLoading = status === 'loading'
const isAuthenticated = status === 'authenticated'
const user: AuthUser | null = useMemo(() => {
if (!session?.user) return null
return {
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,
}
}, [session])
const login = useCallback(
async (provider?: string, options?: { callbackUrl?: string }) => {
await signIn(provider, { callbackUrl: options?.callbackUrl || '/' })
},
[]
)
const logout = useCallback(async (options?: { callbackUrl?: string }) => {
await signOut({ callbackUrl: options?.callbackUrl || '/' })
}, [])
const loginWithCredentials = useCallback(
async (email: string, password: string, callbackUrl?: string) => {
const result = await signIn('credentials', {
email,
password,
redirect: false,
})
if (result?.error) {
throw new Error(result.error)
}
if (callbackUrl) {
window.location.href = callbackUrl
}
},
[]
)
const can = useCallback(
(permission: string): boolean => {
if (!user) return false
return hasPermission(user.role, permission)
},
[user]
)
const canAny = useCallback(
(permissions: string[]): boolean => {
if (!user) return false
return hasAnyPermission(user.role, permissions)
},
[user]
)
const canAll = useCallback(
(permissions: string[]): boolean => {
if (!user) return false
return hasAllPermissions(user.role, permissions)
},
[user]
)
const is = useCallback(
(role: UserRole): boolean => {
if (!user) return false
return user.role === role
},
[user]
)
const isAtLeast = useCallback(
(role: UserRole): boolean => {
if (!user) return false
return hasRole(user.role, role)
},
[user]
)
const refreshSession = useCallback(async () => {
await update()
}, [update])
return {
user,
isAuthenticated,
isLoading,
login,
logout,
loginWithCredentials,
can,
canAny,
canAll,
is,
isAtLeast,
refreshSession,
}
}
// Hook for checking a specific permission
export function usePermission(permission: string): boolean {
const { can } = useAuth()
return can(permission)
}
// Hook for checking a specific role
export function useRole(role: UserRole): boolean {
const { isAtLeast } = useAuth()
return isAtLeast(role)
}
// Hook for requiring authentication (with redirect)
export function useRequireAuth(options?: { redirectTo?: string }) {
const { isAuthenticated, isLoading } = useAuth()
if (!isLoading && !isAuthenticated && typeof window !== 'undefined') {
const redirectTo = options?.redirectTo || '/auth/signin'
window.location.href = `${redirectTo}?callbackUrl=${encodeURIComponent(window.location.pathname)}`
}
return { isAuthenticated, isLoading }
}