aboutsummaryrefslogtreecommitdiff
path: root/src/lib/auth.ts
blob: ad47d5f3b3467c967d7bba42448b562d2c29945d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import { type NextAuthOptions } from "next-auth"
import CredentialsProvider from "next-auth/providers/credentials"
import bcrypt from "bcryptjs"
import { authenticator } from "otplib"
import dbConnect from "./mongodb"
import User from "@/model/User"
import { loginSchema } from "./validation"

export const authOptions: NextAuthOptions = {
  providers: [
    CredentialsProvider({
      name: "credentials",
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Password", type: "password" },
        twoFactorCode: { label: "2FA Code", type: "text" }
      },
      async authorize(credentials) {
        if (!credentials?.email || !credentials?.password) return null

        const result = loginSchema.safeParse(credentials)
        if (!result.success) return null

        const { email, password, twoFactorCode } = result.data

        try {
            await dbConnect()
            
            const user = await User.findOne({ email })
            if (!user) return null

            const isPasswordValid = await bcrypt.compare(password, user.password)
            if (!isPasswordValid) return null

            if (user.twoFactorEnabled) {
              if (!twoFactorCode) {
                throw new Error("2FA_REQUIRED")
              }

              const isValid = authenticator.check(twoFactorCode, user.twoFactorSecret)
              if (!isValid) {
                throw new Error("Invalid 2FA Code")
              }
            }

            return {
                id: user._id.toString(),
                email: user.email,
                name: user.name,
                image: user.profileImage?.url || null,
            }
        } catch (error) {
            console.error("Auth error:", error)
            // Rethrow specific 2FA errors so they reach the client
            if (error instanceof Error && (error.message === "2FA_REQUIRED" || error.message === "Invalid 2FA Code")) {
              throw error
            }
            return null
        }
      }
    })
  ],
  session: { strategy: "jwt" },
  callbacks: {
    async jwt({ token, user }) {
      if (user) token.id = user.id
      return token
    },
    async session({ session, token }) {
      if (token) {
        session.user.id = token.id as string
        await dbConnect()
        const currentUser = await User.findById(token.id)
        if (currentUser) {
          session.user.name = currentUser.name
          session.user.email = currentUser.email
          session.user.image = currentUser.profileImage?.url || null
        }
      }
      return session
    },
  },
  pages: { signIn: "/login" },
}