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 = 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 { 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 }