aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/auth.ts21
-rw-r--r--src/lib/validation.ts6
2 files changed, 24 insertions, 3 deletions
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
index 0ed9d12..ad47d5f 100644
--- a/src/lib/auth.ts
+++ b/src/lib/auth.ts
@@ -1,6 +1,7 @@
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"
@@ -11,7 +12,8 @@ export const authOptions: NextAuthOptions = {
name: "credentials",
credentials: {
email: { label: "Email", type: "email" },
- password: { label: "Password", type: "password" }
+ password: { label: "Password", type: "password" },
+ twoFactorCode: { label: "2FA Code", type: "text" }
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) return null
@@ -19,7 +21,7 @@ export const authOptions: NextAuthOptions = {
const result = loginSchema.safeParse(credentials)
if (!result.success) return null
- const { email, password } = result.data
+ const { email, password, twoFactorCode } = result.data
try {
await dbConnect()
@@ -30,6 +32,17 @@ export const authOptions: NextAuthOptions = {
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,
@@ -38,6 +51,10 @@ export const authOptions: NextAuthOptions = {
}
} 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
}
}
diff --git a/src/lib/validation.ts b/src/lib/validation.ts
index ab9416e..bc5a440 100644
--- a/src/lib/validation.ts
+++ b/src/lib/validation.ts
@@ -30,7 +30,11 @@ export const loginSchema = z.object({
.max(254, 'Email must be at most 254 characters'),
password: z
.string()
- .max(128, 'Password must be at most 128 characters')
+ .max(128, 'Password must be at most 128 characters'),
+ twoFactorCode: z
+ .string()
+ .length(6, 'Code must be 6 digits')
+ .optional()
})
// Profile update schema (reusing name and email from registerSchema)