localgreenchain/pages/api/auth/[...nextauth].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

191 lines
5.1 KiB
TypeScript

import NextAuth, { NextAuthOptions } from 'next-auth'
import CredentialsProvider from 'next-auth/providers/credentials'
import GitHubProvider from 'next-auth/providers/github'
import GoogleProvider from 'next-auth/providers/google'
import bcrypt from 'bcryptjs'
import { UserRole } from '@/lib/auth/types'
// In-memory user store for MVP (will be replaced with Prisma in Agent 2)
// This simulates database operations
interface StoredUser {
id: string
email: string
name: string | null
passwordHash: string | null
role: UserRole
emailVerified: Date | null
image: string | null
createdAt: Date
}
// Temporary in-memory store (will be replaced with database)
const users: Map<string, StoredUser> = new Map()
// Helper to find user by email
function findUserByEmail(email: string): StoredUser | undefined {
for (const user of users.values()) {
if (user.email.toLowerCase() === email.toLowerCase()) {
return user
}
}
return undefined
}
// Helper to create user
export function createUser(data: {
email: string
name?: string
passwordHash?: string
role?: UserRole
}): StoredUser {
const id = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
const user: StoredUser = {
id,
email: data.email.toLowerCase(),
name: data.name || null,
passwordHash: data.passwordHash || null,
role: data.role || UserRole.USER,
emailVerified: null,
image: null,
createdAt: new Date(),
}
users.set(id, user)
return user
}
// Helper to get user by ID
export function getUserById(id: string): StoredUser | undefined {
return users.get(id)
}
// Helper to verify password
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash)
}
export const authOptions: NextAuthOptions = {
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
email: { label: 'Email', type: 'email', placeholder: 'your@email.com' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
throw new Error('Email and password are required')
}
const user = findUserByEmail(credentials.email)
if (!user) {
throw new Error('No user found with this email')
}
if (!user.passwordHash) {
throw new Error('Please sign in with your OAuth provider')
}
const isValid = await verifyPassword(credentials.password, user.passwordHash)
if (!isValid) {
throw new Error('Invalid password')
}
return {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
emailVerified: user.emailVerified,
image: user.image,
}
},
}),
...(process.env.GITHUB_ID && process.env.GITHUB_SECRET
? [
GitHubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
]
: []),
...(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET
? [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
]
: []),
],
session: {
strategy: 'jwt',
maxAge: 30 * 24 * 60 * 60, // 30 days
},
jwt: {
maxAge: 30 * 24 * 60 * 60, // 30 days
},
pages: {
signIn: '/auth/signin',
signOut: '/auth/signout',
error: '/auth/error',
verifyRequest: '/auth/verify-request',
newUser: '/auth/new-user',
},
callbacks: {
async signIn({ user, account }) {
// Handle OAuth sign-in
if (account?.provider !== 'credentials') {
const existingUser = findUserByEmail(user.email!)
if (!existingUser) {
// Create new user for OAuth sign-in
createUser({
email: user.email!,
name: user.name || undefined,
role: UserRole.USER,
})
}
}
return true
},
async jwt({ token, user, trigger, session }) {
// Initial sign-in
if (user) {
token.id = user.id
token.role = user.role || UserRole.USER
token.emailVerified = user.emailVerified
}
// Handle session update
if (trigger === 'update' && session) {
token.name = session.name
token.role = session.role
}
return token
},
async session({ session, token }) {
if (session.user) {
session.user.id = token.id
session.user.role = token.role
session.user.emailVerified = token.emailVerified
}
return session
},
},
events: {
async signIn({ user, isNewUser }) {
console.log(`User signed in: ${user.email}, isNewUser: ${isNewUser}`)
},
async signOut({ token }) {
console.log(`User signed out: ${token.email}`)
},
},
debug: process.env.NODE_ENV === 'development',
}
export default NextAuth(authOptions)
// Export helper for use in registration API
export { findUserByEmail, verifyPassword }