125 lines
4.6 KiB
TypeScript
125 lines
4.6 KiB
TypeScript
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: `<p>Welcome! Please verify your email:</p><p><a href="${link}">Verify Email</a></p>`,
|
|
});
|
|
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." });
|
|
}
|