src/app/api/auth/admin/reset-mdp/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
5 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
4 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
POSTPATCH
Code source· typescript
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma/client";
import { Resend } from "resend";
import crypto from "crypto";
const resend = new Resend(process.env.RESEND_API_KEY);
// POST /api/auth/admin/reset-mdp — demande de reset
export async function POST(req: NextRequest) {
try {
const { email } = await req.json();
if (!email) return NextResponse.json({ error: "Email requis" }, { status: 400 });
const user = await prisma.user.findFirst({
where: { email: email.toLowerCase().trim() },
include: { tenant: { select: { nom: true, subdomain: true } } },
});
// Toujours répondre OK pour ne pas révéler si l'email existe
if (!user) return NextResponse.json({ success: true });
const token = crypto.randomBytes(32).toString("hex");
const expiry = new Date(Date.now() + 1000 * 60 * 60); // 1h
await prisma.user.update({
where: { id: user.id },
data: { resetToken: token, resetTokenExpiry: expiry },
});
const domain = process.env.DOMAIN || "wari.pro";
const resetUrl = `https://${domain}/admin/reset-mdp?token=${token}`;
const tenantNom = user.tenant?.nom ?? "wari.pro";
await resend.emails.send({
from: `${tenantNom} <noreply@wari.pro>`,
to: user.email,
subject: "Réinitialisation de votre mot de passe",
html: `
<div style="font-family:sans-serif;max-width:480px;margin:0 auto;padding:32px">
<h2 style="font-size:18px;color:#111;margin-bottom:8px">Réinitialisation du mot de passe</h2>
<p style="color:#555;font-size:14px;margin-bottom:24px">
Vous avez demandé à réinitialiser votre mot de passe pour votre espace prestataire ${tenantNom}.
</p>
<a href="${resetUrl}" style="display:inline-block;background:#111;color:#fff;padding:12px 24px;border-radius:8px;text-decoration:none;font-size:14px;font-weight:500">
Réinitialiser mon mot de passe
</a>
<p style="color:#999;font-size:12px;margin-top:24px">
Ce lien expire dans 1 heure. Si vous n'avez pas fait cette demande, ignorez cet email.
</p>
</div>
`,
});
return NextResponse.json({ success: true });
} catch (error) {
console.error("reset-mdp admin POST error:", error);
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
}
}
// PATCH /api/auth/admin/reset-mdp — confirmer le nouveau mot de passe
export async function PATCH(req: NextRequest) {
try {
const { token, motDePasse } = await req.json();
if (!token || !motDePasse) return NextResponse.json({ error: "Données manquantes" }, { status: 400 });
if (motDePasse.length < 8) return NextResponse.json({ error: "Mot de passe trop court (8 caractères min)" }, { status: 400 });
const user = await prisma.user.findFirst({
where: {
resetToken: token,
resetTokenExpiry: { gt: new Date() },
},
});
if (!user) return NextResponse.json({ error: "Lien invalide ou expiré" }, { status: 400 });
const bcrypt = await import("bcryptjs");
const hash = await bcrypt.hash(motDePasse, 10);
await prisma.user.update({
where: { id: user.id },
data: { motDePasseHash: hash, resetToken: null, resetTokenExpiry: null },
});
return NextResponse.json({ success: true });
} catch (error) {
console.error("reset-mdp admin PATCH error:", error);
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
}
}
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma/client";
import { Resend } from "resend";
import crypto from "crypto";
const resend = new Resend(process.env.RESEND_API_KEY);
// POST /api/auth/admin/reset-mdp — demande de reset
export async function POST(req: NextRequest) {
try {
const { email } = await req.json();
if (!email) return NextResponse.json({ error: "Email requis" }, { status: 400 });
const user = await prisma.user.findFirst({
where: { email: email.toLowerCase().trim() },
include: { tenant: { select: { nom: true, subdomain: true } } },
});
// Toujours répondre OK pour ne pas révéler si l'email existe
if (!user) return NextResponse.json({ success: true });
const token = crypto.randomBytes(32).toString("hex");
const expiry = new Date(Date.now() + 1000 * 60 * 60); // 1h
await prisma.user.update({
where: { id: user.id },
data: { resetToken: token, resetTokenExpiry: expiry },
});
const domain = process.env.DOMAIN || "wari.pro";
const resetUrl = `https://${domain}/admin/reset-mdp?token=${token}`;
const tenantNom = user.tenant?.nom ?? "wari.pro";
await resend.emails.send({
from: `${tenantNom} <noreply@wari.pro>`,
to: user.email,
subject: "Réinitialisation de votre mot de passe",
html: `
<div style="font-family:sans-serif;max-width:480px;margin:0 auto;padding:32px">
<h2 style="font-size:18px;color:#111;margin-bottom:8px">Réinitialisation du mot de passe</h2>
<p style="color:#555;font-size:14px;margin-bottom:24px">
Vous avez demandé à réinitialiser votre mot de passe pour votre espace prestataire ${tenantNom}.
</p>
<a href="${resetUrl}" style="display:inline-block;background:#111;color:#fff;padding:12px 24px;border-radius:8px;text-decoration:none;font-size:14px;font-weight:500">
Réinitialiser mon mot de passe
</a>
<p style="color:#999;font-size:12px;margin-top:24px">
Ce lien expire dans 1 heure. Si vous n'avez pas fait cette demande, ignorez cet email.
</p>
</div>
`,
});
return NextResponse.json({ success: true });
} catch (error) {
console.error("reset-mdp admin POST error:", error);
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
}
}
// PATCH /api/auth/admin/reset-mdp — confirmer le nouveau mot de passe
export async function PATCH(req: NextRequest) {
try {
const { token, motDePasse } = await req.json();
if (!token || !motDePasse) return NextResponse.json({ error: "Données manquantes" }, { status: 400 });
if (motDePasse.length < 8) return NextResponse.json({ error: "Mot de passe trop court (8 caractères min)" }, { status: 400 });
const user = await prisma.user.findFirst({
where: {
resetToken: token,
resetTokenExpiry: { gt: new Date() },
},
});
if (!user) return NextResponse.json({ error: "Lien invalide ou expiré" }, { status: 400 });
const bcrypt = await import("bcryptjs");
const hash = await bcrypt.hash(motDePasse, 10);
await prisma.user.update({
where: { id: user.id },
data: { motDePasseHash: hash, resetToken: null, resetTokenExpiry: null },
});
return NextResponse.json({ success: true });
} catch (error) {
console.error("reset-mdp admin PATCH error:", error);
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
}
}