All files / src/app/api/auth/resend-verification route.ts

100% Statements 96/96
100% Branches 15/15
100% Functions 1/1
100% Lines 96/96

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 95 96 97 981x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 13x 13x 13x 13x 13x 13x 13x 13x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 11x 11x 11x 11x 11x 13x 3x 3x 3x 3x 3x 3x 8x 8x 8x 8x 8x 8x 7x 13x 5x 1x 1x 1x 1x 1x 1x 4x 4x 4x 4x 4x 4x 4x 4x 3x 3x 3x 3x 5x 2x 5x 1x 1x 5x 5x 5x 5x 5x 5x 13x 2x 2x 2x 2x 2x 2x 2x    
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { sendVerificationEmail } from "@/lib/integrations";
import { checkRateLimit, getRateLimitInfo } from "@/lib/security";
import { addSecurityHeaders } from "@/lib/security";
import { z } from "zod";
import crypto from "crypto";
import { logger } from "@/lib/logging";
 
const ResendVerificationSchema = z.object({
  email: z.string().email("Invalid email address")});
 
/**
 * POST /api/auth/resend-verification
 * Resends email verification link
 */
export async function POST(request: NextRequest) {
  try {
    // Rate limiting: 3 requests per hour per IP
    const ip =
      request.headers.get("x-forwarded-for") ||
      request.headers.get("x-real-ip") ||
      "unknown";
 
    if (!checkRateLimit(`resend-verification-${ip}`, 3, 60 * 60 * 1000)) {
//
      const rateLimitInfo = getRateLimitInfo(`resend-verification-${ip}`, 3);
      const response = NextResponse.json(
        { error: "Too many requests. Please try again later." },
        {
          status: 429,
          headers: {
            "X-RateLimit-Limit": rateLimitInfo.limit.toString(),
            "X-RateLimit-Remaining": rateLimitInfo.remaining.toString(),
            "X-RateLimit-Reset": rateLimitInfo.resetTime}}
      );
      return addSecurityHeaders(response);
    }
 
    const body = await request.json();
 
    // Validate input
    const result = ResendVerificationSchema.safeParse(body);
    if (!result.success) {
      const response = NextResponse.json(
        { error: "Invalid email address" },
        { status: 400 }
      );
      return addSecurityHeaders(response);
    }
 
    const { email } = result.data;
 
    // Find user
    const user = await prisma.user.findUnique({
      where: { email: email.toLowerCase() }});
 
    if (user) {
      if (user.emailVerified) {
        const response = NextResponse.json(
          { error: "Email is already verified" },
          { status: 400 }
        );
        return addSecurityHeaders(response);
      }
 
      // Generate new verification token
      const verificationToken = crypto.randomBytes(32).toString("hex");
 
      // Update user with new token
      await prisma.user.update({
        where: { id: user.id },
        data: { verificationToken }});
 
      // Send verification email
      const emailSent = await sendVerificationEmail(email, verificationToken);
 
      if (emailSent) {
        logger.info(`Verification email resent to ${email}`, { category: "API" });
      } else {
        logger.error(`Failed to resend verification email to ${email}`, new Error("Email send failed"), { category: "API" });
      }
    }
 
    // Always return success (prevent email enumeration)
    const response = NextResponse.json({
      message: "If an unverified account exists, a verification email has been sent."});
    return addSecurityHeaders(response);
  } catch (error) {
    logger.error("Resend verification error", error instanceof Error ? error : new Error(String(error)), { category: "API" });
    const response = NextResponse.json(
      { error: "Failed to resend verification email" },
      { status: 500 }
    );
    return addSecurityHeaders(response);
  }
}