import { NextRequest } from "next/server"; import { z } from "zod"; import { getPrisma } from "../../../../lib/db"; import { hashPassword } from "../../../../lib/auth/password"; import { isStrongPassword, sanitizeText } from "../../../../lib/auth/validation"; import { ok, fail } from "../../../../lib/http"; import { loadSystemConfig } from "../../../../lib/system-config"; import { sendEmail } from "../../../../lib/email"; import { verifyCaptcha } from "../../../../lib/captcha"; import crypto from "crypto"; export const runtime = "nodejs"; const Body = z.object({ firstName: z.string().min(1), lastName: z.string().min(1), gender: z.string().optional(), dob: z.string().optional(), address: z.string().optional(), email: z.string().email(), password: z.string().min(8), confirmPassword: z.string().min(8), captchaId: z.string().optional(), captchaCode: z.string().optional(), }); export async function POST(req: NextRequest) { const cfg = await loadSystemConfig(); const prisma = await getPrisma(); const body = Body.safeParse(await req.json().catch(() => ({}))); if (!body.success) return fail(new Error("Invalid input")); if (!prisma) return fail(new Error("Database not configured"), { status: 503 }); // Validate CAPTCHA if provided if (body.data.captchaId && body.data.captchaCode) { const captchaResult = verifyCaptcha(body.data.captchaId, body.data.captchaCode); if (!captchaResult.success) { return fail(new Error(captchaResult.error || "CAPTCHA verification failed. Please try again.")); } } const email = body.data.email.toLowerCase(); const password = body.data.password; const confirmPassword = body.data.confirmPassword; if (password !== confirmPassword) return fail(new Error("Passwords do not match")); if (!isStrongPassword(password)) return fail(new Error("Password is not strong enough")); // Validate birth date if provided if (body.data.dob) { const birthDate = new Date(body.data.dob); const today = new Date(); const minDate = new Date(today.getFullYear() - 100, today.getMonth(), today.getDate()); const maxDate = new Date(today.getFullYear() - 18, today.getMonth(), today.getDate()); if (birthDate < minDate || birthDate > maxDate) { return fail(new Error("Birth date must be between 18 and 100 years old")); } } const passwordHash = await hashPassword(password); try { const user = await prisma.user.create({ data: { email, role: "USER", name: `${body.data.firstName} ${body.data.lastName}`, firstName: sanitizeText(body.data.firstName), lastName: sanitizeText(body.data.lastName), gender: body.data.gender ? sanitizeText(body.data.gender) : null, dob: body.data.dob ? new Date(body.data.dob) : null, address: body.data.address ? sanitizeText(body.data.address) : null, // If email is enabled, require verification; otherwise mark as verified emailVerified: !cfg.email?.enabled, forcePasswordReset: false, isActive: true, }, }); // Create credential for email/password authentication await prisma.credential.create({ data: { userId: user.id, password: passwordHash, }, }); // Only create verification token and send email if email is enabled if (cfg.email?.enabled) { const token = crypto.randomBytes(32).toString("hex"); const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); await prisma.verification.create({ data: { userId: user.id, identifier: email, value: token, expiresAt, }, }); const baseUrl = process.env.APP_BASE_URL || "http://localhost:3000"; const link = `${baseUrl}/auth/verify?token=${token}`; await sendEmail({ to: email, subject: "Verify your email", html: `
Welcome! Please verify your email:
`, }); return ok({ message: "Account created. Please check your email to verify your account." }); } else { return ok({ message: "Account created successfully. You can now log in." }); } } catch (e: any) { // Check if the error is due to unique constraint on email if (e.code === 'P2002' && e.meta?.target?.includes('email')) { return fail(new Error("This email is already taken")); } console.error("Registration error:", e); return fail(new Error("Account creation failed. Please try again or contact support.")); } return ok({ message: "Account created. Please check your email to verify your account." }); }