import NextAuth from "next-auth"; import Credentials from "next-auth/providers/credentials"; import bcrypt from "bcryptjs"; import { z } from "zod"; import { isEmailShape, normalizeEmail, normalizeUsername, USERNAME_RE, } from "@/lib/account-identifiers"; import { prisma } from "@/lib/prisma"; import { authConfig } from "./auth.config"; const credentialsSchema = z.object({ email: z.string().trim().min(1), password: z.string().min(1), }); export const { handlers, auth, signIn, signOut } = NextAuth({ ...authConfig, providers: [ Credentials({ name: "Credentials", credentials: { email: { label: "Email or username", type: "text" }, password: { label: "Password", type: "password" }, }, async authorize(raw) { const parsed = credentialsSchema.safeParse(raw); if (!parsed.success) return null; const identifier = parsed.data.email; const password = parsed.data.password; let user = null; if (isEmailShape(identifier)) { const emailLookup = normalizeEmail(identifier); user = await prisma.user.findFirst({ where: { email: { equals: emailLookup, mode: "insensitive" } }, }); } else { const loginName = normalizeUsername(identifier); if (!USERNAME_RE.test(loginName)) return null; user = await prisma.user.findUnique({ where: { username: loginName } }); } if (!user?.passwordHash) return null; const ok = await bcrypt.compare(password, user.passwordHash); if (!ok) return null; return { id: user.id, email: user.email, name: user.name ?? undefined, username: user.username, role: user.role, }; }, }), ], callbacks: { jwt({ token, user }) { if (user) { token.id = user.id; if (user.username) token.username = user.username; const role = (user as { role?: string }).role; token.role = role === "ADMIN" || role === "USER" ? role : "USER"; } return token; }, session({ session, token }) { if (session.user) { session.user.id = (token.id as string) ?? (token.sub as string); if (typeof token.username === "string") session.user.username = token.username; const role = token.role; session.user.role = role === "ADMIN" || role === "USER" ? role : "USER"; } return session; }, }, });