import NextAuth from "next-auth"; import Credentials from "next-auth/providers/credentials"; import { compare } from "bcryptjs"; import { z } from "zod"; import { prisma } from "@/lib/prisma"; import { checkRateLimit, AUTH_RATE_LIMITS } from "@/lib/rate-limit"; import authConfig from "./auth.config"; declare module "next-auth" { interface Session { user: { id: string; email: string; name?: string | null; image?: string | null; isEmailVerified: boolean; }; } } declare module "@auth/core/jwt" { interface JWT { id: string; isEmailVerified: boolean; } } const loginSchema = z.object({ email: z.string().email("Invalid email address"), password: z.string().min(8, "Password must be at least 8 characters"), }); export const { handlers, auth, signIn, signOut } = NextAuth({ ...authConfig, providers: [ Credentials({ credentials: { email: { label: "Email", type: "email" }, password: { label: "Password", type: "password" }, }, async authorize(credentials, request) { const parsed = loginSchema.safeParse(credentials); if (!parsed.success) return null; const { email, password } = parsed.data; const ip = (request instanceof Request ? request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() : undefined) ?? "unknown"; const rl = await checkRateLimit(`login:${ip}`, AUTH_RATE_LIMITS.login); if (!rl.allowed) return null; const user = await prisma.user.findUnique({ where: { email: email.toLowerCase() }, }); if (!user) return null; const isValid = await compare(password, user.passwordHash); if (!isValid) return null; return { id: user.id, email: user.email, name: user.name, }; }, }), ], callbacks: { async jwt({ token, user, trigger }) { if (user) { token.id = user.id as string; } if (trigger === "update" || user) { const dbUser = await prisma.user.findUnique({ where: { id: token.id }, select: { emailVerified: true }, }); if (dbUser) { token.isEmailVerified = dbUser.emailVerified; } } return token; }, session({ session, token }) { session.user.id = token.id; session.user.isEmailVerified = token.isEmailVerified; return session; }, }, });