src/app/api/admin/auth/email/route.ts

route·app·3.2 KB · 113 lignes· Voir l'itinéraire
Annotation non disponible

Lance npm run annotate (nécessite ANTHROPIC_API_KEY dans .env.local) pour générer une annotation française par Claude Haiku 4.5.

2 exports

POSTdynamic

Code source· typescript

import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma/client";
import { isValidCodeFormat, normalizeCode } from "@/lib/admin/code-acces";
import { envoyerCodeVerifAdmin } from "@/lib/email";

export const dynamic = "force-dynamic";

const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const CODE_TTL_MIN = 15;

function genCode6(): string {
  return String(Math.floor(100000 + Math.random() * 900000));
}

// POST /api/admin/auth/email
// Body: { code: "WARI-XXXX-XXXX", email: "..." }
// Crée le User primaire si nécessaire (verified=false, motDePasseHash=null),
// génère un code 6 chiffres, envoie l'email Resend, passe à onboardingStep=VERIF.
export async function POST(req: NextRequest) {
  let body: { code?: string; email?: string };
  try {
    body = await req.json();
  } catch {
    return NextResponse.json({ error: "Body invalide" }, { status: 400 });
  }

  const code = normalizeCode(body.code ?? "");
  const email = (body.email ?? "").trim().toLowerCase();

  if (!isValidCodeFormat(code)) {
    return NextResponse.json({ error: "Code d'accès invalide" }, { status: 400 });
  }
  if (!EMAIL_REGEX.test(email)) {
    return NextResponse.json({ error: "Adresse email invalide" }, { status: 400 });
  }

  const tenant = await prisma.tenant.findUnique({
    where: { codeAcces: code },
    select: {
      id: true,
      nom: true,
      onboardingStep: true,
      actif: true,
      users: { where: { isPrimary: true }, select: { id: true }, take: 1 },
    },
  });
  if (!tenant) {
    return NextResponse.json({ error: "Code invalide" }, { status: 404 });
  }
  if (!tenant.actif || tenant.onboardingStep === "DONE") {
    return NextResponse.json({ error: "Setup non disponible" }, { status: 422 });
  }

  // Vérifier que cet email n'est pas déjà utilisé par un autre tenant (unique globally)
  const emailExistant = await prisma.user.findFirst({
    where: { email, NOT: { tenantId: tenant.id } },
    select: { id: true },
  });
  if (emailExistant) {
    return NextResponse.json(
      { error: "Cet email est déjà utilisé par un autre compte wari.pro." },
      { status: 409 }
    );
  }

  const code6 = genCode6();
  const expiry = new Date(Date.now() + CODE_TTL_MIN * 60 * 1000);

  // Crée ou met à jour le User primaire
  const primary = tenant.users[0];
  if (primary) {
    await prisma.user.update({
      where: { id: primary.id },
      data: {
        email,
        emailVerifiedAt: null, // réinitialise si changement d'email
        resetToken: code6,
        resetTokenExpiry: expiry,
      },
    });
  } else {
    await prisma.user.create({
      data: {
        tenantId: tenant.id,
        email,
        role: "TENANT_ADMIN",
        isPrimary: true,
        resetToken: code6,
        resetTokenExpiry: expiry,
      },
    });
  }

  await prisma.tenant.update({
    where: { id: tenant.id },
    data: { onboardingStep: "VERIF" },
  });

  // Envoi email best-effort (ne pas bloquer si Resend down)
  try {
    await envoyerCodeVerifAdmin({
      email,
      code: code6,
      tenantNom: tenant.nom,
      expiresInMinutes: CODE_TTL_MIN,
    });
  } catch (e) {
    console.error("Erreur envoi code email verif:", e);
  }

  return NextResponse.json({ ok: true, expiresInMinutes: CODE_TTL_MIN });
}