import { NextResponse } from "next/server"; import { hash } from "bcryptjs"; import crypto from "crypto"; import { z } from "zod"; import { prisma } from "@/lib/prisma"; import { sendEmail } from "@/lib/email"; import { checkRateLimit, AUTH_RATE_LIMITS } from "@/lib/rate-limit"; const registerSchema = z.object({ email: z.email("Invalid email address"), password: z.string().min(8, "Password must be at least 8 characters"), name: z.string().min(1).optional(), }); export async function POST(request: Request) { try { const ip = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? "unknown"; const rl = await checkRateLimit(`register:${ip}`, AUTH_RATE_LIMITS.register); if (!rl.allowed) { return NextResponse.json( { error: "Too many registration attempts. Please try again later." }, { status: 429, headers: { "Retry-After": String(Math.ceil((rl.resetAt - Date.now()) / 1000)) } } ); } const body: unknown = await request.json(); const parsed = registerSchema.safeParse(body); if (!parsed.success) { return NextResponse.json( { error: parsed.error.issues[0]?.message ?? "Invalid input" }, { status: 400 } ); } const { email, password, name } = parsed.data; const normalizedEmail = email.toLowerCase(); const existing = await prisma.user.findUnique({ where: { email: normalizedEmail }, }); if (existing) { return NextResponse.json( { message: "If this email is available, a confirmation email will be sent." }, { status: 200 } ); } const passwordHash = await hash(password, 12); const user = await prisma.user.create({ data: { email: normalizedEmail, passwordHash, name: name ?? null, subscription: { create: { tier: "FREE", sessionsLimit: 20, }, }, }, select: { id: true, email: true, name: true, createdAt: true, }, }); try { const rawToken = crypto.randomBytes(32).toString("hex"); const tokenHash = crypto .createHash("sha256") .update(rawToken) .digest("hex"); await prisma.emailVerificationToken.create({ data: { userId: user.id, token: tokenHash, expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), }, }); const verifyUrl = `https://agentlens.vectry.tech/verify-email?token=${rawToken}`; await sendEmail({ to: user.email, subject: "Verify your AgentLens email", html: `

Verify your email

Thanks for signing up for AgentLens. Click the link below to verify your email address.

Verify Email

This link expires in 24 hours. If you didn't create an account, you can safely ignore this email.

`, }); } catch (emailError) { console.error("[register] Failed to send verification email:", emailError); } return NextResponse.json( { message: "If this email is available, a confirmation email will be sent." }, { status: 200 } ); } catch { return NextResponse.json( { error: "Internal server error" }, { status: 500 } ); } }