src/app/api/admin/auth/verify-email/route.ts
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.
Concepts détectés — comprends la théorie
ORM Prisma
4 occurrencesCe fichier accède à la base de données via Prisma. Prisma est l'ORM utilisé côté backend pour les requêtes typées sur PostgreSQL.
Voir l'article général
Route API Next.js
3 occurrencesCe fichier est une route API Next.js (App Router). Voir le contrat API complet pour les conventions de réponse et d'auth.
Voir l'article général
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";
export const dynamic = "force-dynamic";
// POST /api/admin/auth/verify-email
// Body: { code: "WARI-XXXX-XXXX", verifCode: "123456" }
// Vérifie le code 6 chiffres reçu par email, marque emailVerifiedAt,
// passe à onboardingStep=USERNAME.
export async function POST(req: NextRequest) {
let body: { code?: string; verifCode?: string };
try {
body = await req.json();
} catch {
return NextResponse.json({ error: "Body invalide" }, { status: 400 });
}
const code = normalizeCode(body.code ?? "");
const verifCode = (body.verifCode ?? "").trim();
if (!isValidCodeFormat(code)) {
return NextResponse.json({ error: "Code d'accès invalide" }, { status: 400 });
}
if (!/^\d{6}$/.test(verifCode)) {
return NextResponse.json({ error: "Code à 6 chiffres requis" }, { status: 400 });
}
const tenant = await prisma.tenant.findUnique({
where: { codeAcces: code },
select: {
id: true,
onboardingStep: true,
actif: true,
users: {
where: { isPrimary: true },
select: { id: true, resetToken: true, resetTokenExpiry: true, emailVerifiedAt: 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 });
}
const primary = tenant.users[0];
if (!primary) {
return NextResponse.json(
{ error: "Aucun email saisi pour ce tenant. Recommence l'étape précédente." },
{ status: 422 }
);
}
if (!primary.resetToken || !primary.resetTokenExpiry) {
return NextResponse.json(
{ error: "Aucun code en attente. Demande un nouveau code." },
{ status: 422 }
);
}
if (new Date(primary.resetTokenExpiry) < new Date()) {
return NextResponse.json(
{ error: "Code expiré. Demande un nouveau code." },
{ status: 410 }
);
}
if (primary.resetToken !== verifCode) {
return NextResponse.json({ error: "Code incorrect." }, { status: 401 });
}
// OK : marque verified + nettoie le code
await prisma.user.update({
where: { id: primary.id },
data: {
emailVerifiedAt: new Date(),
resetToken: null,
resetTokenExpiry: null,
},
});
await prisma.tenant.update({
where: { id: tenant.id },
data: { onboardingStep: "USERNAME" },
});
return NextResponse.json({ ok: true });
}
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma/client";
import { isValidCodeFormat, normalizeCode } from "@/lib/admin/code-acces";
export const dynamic = "force-dynamic";
// POST /api/admin/auth/verify-email
// Body: { code: "WARI-XXXX-XXXX", verifCode: "123456" }
// Vérifie le code 6 chiffres reçu par email, marque emailVerifiedAt,
// passe à onboardingStep=USERNAME.
export async function POST(req: NextRequest) {
let body: { code?: string; verifCode?: string };
try {
body = await req.json();
} catch {
return NextResponse.json({ error: "Body invalide" }, { status: 400 });
}
const code = normalizeCode(body.code ?? "");
const verifCode = (body.verifCode ?? "").trim();
if (!isValidCodeFormat(code)) {
return NextResponse.json({ error: "Code d'accès invalide" }, { status: 400 });
}
if (!/^\d{6}$/.test(verifCode)) {
return NextResponse.json({ error: "Code à 6 chiffres requis" }, { status: 400 });
}
const tenant = await prisma.tenant.findUnique({
where: { codeAcces: code },
select: {
id: true,
onboardingStep: true,
actif: true,
users: {
where: { isPrimary: true },
select: { id: true, resetToken: true, resetTokenExpiry: true, emailVerifiedAt: 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 });
}
const primary = tenant.users[0];
if (!primary) {
return NextResponse.json(
{ error: "Aucun email saisi pour ce tenant. Recommence l'étape précédente." },
{ status: 422 }
);
}
if (!primary.resetToken || !primary.resetTokenExpiry) {
return NextResponse.json(
{ error: "Aucun code en attente. Demande un nouveau code." },
{ status: 422 }
);
}
if (new Date(primary.resetTokenExpiry) < new Date()) {
return NextResponse.json(
{ error: "Code expiré. Demande un nouveau code." },
{ status: 410 }
);
}
if (primary.resetToken !== verifCode) {
return NextResponse.json({ error: "Code incorrect." }, { status: 401 });
}
// OK : marque verified + nettoie le code
await prisma.user.update({
where: { id: primary.id },
data: {
emailVerifiedAt: new Date(),
resetToken: null,
resetTokenExpiry: null,
},
});
await prisma.tenant.update({
where: { id: tenant.id },
data: { onboardingStep: "USERNAME" },
});
return NextResponse.json({ ok: true });
}