From 67527c2f52e76725ad78719d4b0307e702bd0da1 Mon Sep 17 00:00:00 2001 From: schererleander Date: Fri, 26 Dec 2025 16:24:36 +0100 Subject: feat(2fa): implement google authenticator 2fa - add otplib and qrcode dependencies - update user model with 2fa fields - add twoFactorCode to validation schema - implement api routes for setup, enable, disable - add 2fa verification in auth flow - add 2fa management ui in settings - implement 2fa challenge in login page --- src/app/api/user/2fa/route.ts | 97 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/app/api/user/2fa/route.ts (limited to 'src/app/api/user/2fa') diff --git a/src/app/api/user/2fa/route.ts b/src/app/api/user/2fa/route.ts new file mode 100644 index 0000000..c5fcf83 --- /dev/null +++ b/src/app/api/user/2fa/route.ts @@ -0,0 +1,97 @@ +import { NextRequest, NextResponse } from "next/server" +import { getServerSession } from "next-auth" +import { authenticator } from "otplib" +import QRCode from "qrcode" +import dbConnect from "@/lib/mongodb" +import User from "@/model/User" +import { authOptions } from "@/lib/auth" + +export async function POST(req: NextRequest) { + try { + const session = await getServerSession(authOptions) + if (!session?.user?.id) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) + } + + const { code, secret } = await req.json() + + if (!code || !secret) { + return NextResponse.json( + { error: "Code and secret are required" }, + { status: 400 } + ) + } + + const isValid = authenticator.check(code, secret) + + if (!isValid) { + return NextResponse.json( + { error: "Invalid two-factor code" }, + { status: 400 } + ) + } + + await dbConnect() + await User.findByIdAndUpdate(session.user.id, { + twoFactorEnabled: true, + twoFactorSecret: secret, + }) + + return NextResponse.json({ success: true }) + } catch (error) { + console.error("2FA enable error:", error) + return NextResponse.json( + { error: "Failed to enable two-factor authentication" }, + { status: 500 } + ) + } +} + +export async function DELETE() { + try { + const session = await getServerSession(authOptions) + if (!session?.user?.id) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) + } + + await dbConnect() + await User.findByIdAndUpdate(session.user.id, { + twoFactorEnabled: false, + $unset: { twoFactorSecret: 1 }, + }) + + return NextResponse.json({ success: true }) + } catch (error) { + console.error("2FA disable error:", error) + return NextResponse.json( + { error: "Failed to disable two-factor authentication" }, + { status: 500 } + ) + } +} + +// Generate new secret and QR code for setup +export async function PUT() { + try { + const session = await getServerSession(authOptions) + if (!session?.user?.email) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) + } + + const secret = authenticator.generateSecret() + const otpauth = authenticator.keyuri( + session.user.email, + "Next-Boilerplate", + secret + ) + const qrCode = await QRCode.toDataURL(otpauth) + + return NextResponse.json({ secret, qrCode }) + } catch (error) { + console.error("2FA setup error:", error) + return NextResponse.json( + { error: "Failed to generate two-factor setup" }, + { status: 500 } + ) + } +} -- cgit v1.3.1