All files / src/app/api/auth/signup route.ts

100% Statements 93/93
100% Branches 16/16
100% Functions 1/1
100% Lines 93/93

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 951x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 30x 30x 30x 30x 30x 30x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 26x 26x 26x 26x 26x 30x 6x 6x 6x 6x 6x 20x 20x 20x 20x 20x 19x 30x 2x 2x 2x 2x 2x 17x 17x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 15x 15x 15x 15x 30x 14x 30x 1x 1x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 30x 3x 3x 3x 3x 3x 3x    
import { prisma } from "@/lib/prisma";
import bcryptjs from "bcryptjs";
import { NextRequest, NextResponse } from "next/server";
import { signupSchema, validateInput } from "@/lib/validation-schemas";
import { checkRateLimit, getRateLimitInfo, rateLimitPresets } from "@/lib/security";
import { sendVerificationEmail } from "@/lib/integrations";
import { logger } from "@/lib/logging";
import { PASSWORD_CONFIG } from "@/lib/security";
import crypto from "crypto";
 
export async function POST(request: NextRequest) {
  try {
    // Get client IP for rate limiting
    const ip = request.headers.get("x-forwarded-for") || request.headers.get("x-real-ip") || "unknown";
 
    // Check rate limit: 5 signups per hour per IP
    if (!checkRateLimit(`signup-${ip}`, rateLimitPresets.signup.limit, rateLimitPresets.signup.windowMs)) {
//
      const rateLimitInfo = getRateLimitInfo(`signup-${ip}`, rateLimitPresets.signup.limit);
      return NextResponse.json(
        { error: "Too many signup attempts. Please try again later." },
        {
          status: 429,
          headers: {
            "X-RateLimit-Limit": rateLimitInfo.limit.toString(),
            "X-RateLimit-Remaining": rateLimitInfo.remaining.toString(),
            "X-RateLimit-Reset": rateLimitInfo.resetTime,
            ...(rateLimitInfo.retryAfter ? { "Retry-After": rateLimitInfo.retryAfter.toString() } : {})}}
      );
    }
 
    const body = await request.json();
 
    // Validate input
    const validation = validateInput(signupSchema, body);
    if (!validation.success) {
      return NextResponse.json(
        { error: "Validation failed", details: validation.errors },
        { status: 400 }
      );
    }
 
    const { email, password, name } = validation.data!;
 
    const existingUser = await prisma.user.findUnique({
      where: { email }});
 
    if (existingUser) {
      return NextResponse.json(
        { error: "Email already registered" },
        { status: 400 }
      );
    }
 
    const hashedPassword = await bcryptjs.hash(password, PASSWORD_CONFIG.BCRYPT_ROUNDS);
 
    // Generate email verification token
    const verificationToken = crypto.randomBytes(32).toString("hex");
 
    const user = await prisma.user.create({
      data: {
        email: email.toLowerCase(),
        name,
        password: hashedPassword,
        verificationToken,
        emailVerified: null}});
 
    // Send verification email
    const emailSent = await sendVerificationEmail(email, verificationToken);
 
    if (emailSent) {
      logger.info(`Verification email sent to ${email}`, { category: "API" });
    } else {
      logger.warn(`Failed to send verification email to ${email}`, { category: "API" });
    }
 
    return NextResponse.json(
      {
        message: "Account created successfully. Please check your email to verify your account.",
        user: {
          id: user.id,
          email: user.email,
          name: user.name},
        emailVerificationRequired: true},
      { status: 201 }
    );
  } catch (error) {
    logger.error("Signup error", error instanceof Error ? error : new Error(String(error)), { category: "API" });
    return NextResponse.json(
      { error: "Failed to create account" },
      { status: 500 }
    );
  }
}