feat: add admin dashboard and core API routes with Docker configuration
This commit is contained in:
@@ -5,6 +5,15 @@ import crypto from 'crypto';
|
||||
|
||||
import { verifyCsrfToken, verifyOrigin } from '@/lib/csrf';
|
||||
|
||||
function escapeHtml(str: string): string {
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
// CSRF Protection
|
||||
const isOriginValid = verifyOrigin(request);
|
||||
@@ -19,7 +28,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
try {
|
||||
const rawBody = await request.json();
|
||||
const name = rawBody.name;
|
||||
const name = typeof rawBody.name === 'string' ? rawBody.name.trim() : '';
|
||||
const email = rawBody.email?.toLowerCase().trim();
|
||||
const password = rawBody.password;
|
||||
|
||||
@@ -31,9 +40,9 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
if (password.length < 8) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Le mot de passe doit contenir au moins 6 caractères' },
|
||||
{ error: 'Le mot de passe doit contenir au moins 8 caractères' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
@@ -74,14 +83,16 @@ export async function POST(request: NextRequest) {
|
||||
where: { id: 'singleton' }
|
||||
});
|
||||
|
||||
const siteName = settings?.siteName || 'Afrohub';
|
||||
const safeName = escapeHtml(name);
|
||||
const safeEmail = escapeHtml(email);
|
||||
const siteName = escapeHtml(settings?.siteName || 'Afrohub');
|
||||
const subject = settings?.verifyEmailSubject || 'Vérification de votre compte - Afrohub';
|
||||
const verifyUrl = `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/api/auth/verify?token=${verificationToken}`;
|
||||
|
||||
let html = settings?.verifyEmailTemplate || `
|
||||
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e5e7eb; border-radius: 8px;">
|
||||
<h2 style="color: #0d9488; text-align: center;">${siteName}</h2>
|
||||
<p>Bonjour ${name},</p>
|
||||
<p>Bonjour ${safeName},</p>
|
||||
<p>Merci de vous être inscrit sur ${siteName}. Veuillez vérifier votre adresse email en cliquant sur le bouton ci-dessous :</p>
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="${verifyUrl}" style="background-color: #0d9488; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; font-weight: bold;">Vérifier mon compte</a>
|
||||
@@ -89,9 +100,9 @@ export async function POST(request: NextRequest) {
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Replace placeholders
|
||||
// Replace placeholders with HTML-escaped values
|
||||
html = html
|
||||
.replace(/{name}/g, name)
|
||||
.replace(/{name}/g, safeName)
|
||||
.replace(/{verifyUrl}/g, verifyUrl)
|
||||
.replace(/{siteName}/g, siteName);
|
||||
|
||||
@@ -107,7 +118,7 @@ export async function POST(request: NextRequest) {
|
||||
// Send admin notification email
|
||||
try {
|
||||
const adminEmail = settings?.contactEmail || 'support@afrohub.com';
|
||||
const adminSubject = `[Admin] Nouvelle inscription sur ${siteName} : ${name}`;
|
||||
const adminSubject = `[Admin] Nouvelle inscription sur ${settings?.siteName || 'Afrohub'} : ${name}`;
|
||||
const adminHtml = `
|
||||
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e5e7eb; border-radius: 8px;">
|
||||
<h2 style="color: #4f46e5; text-align: center;">Notification Admin - Inscription</h2>
|
||||
@@ -116,11 +127,11 @@ export async function POST(request: NextRequest) {
|
||||
<table style="width: 100%; border-collapse: collapse; margin-top: 15px;">
|
||||
<tr>
|
||||
<td style="padding: 8px; border-bottom: 1px solid #e5e7eb; font-weight: bold; width: 35%;">Nom complet :</td>
|
||||
<td style="padding: 8px; border-bottom: 1px solid #e5e7eb;">${name}</td>
|
||||
<td style="padding: 8px; border-bottom: 1px solid #e5e7eb;">${safeName}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; border-bottom: 1px solid #e5e7eb; font-weight: bold;">Adresse email :</td>
|
||||
<td style="padding: 8px; border-bottom: 1px solid #e5e7eb;"><a href="mailto:${email}">${email}</a></td>
|
||||
<td style="padding: 8px; border-bottom: 1px solid #e5e7eb;"><a href="mailto:${safeEmail}">${safeEmail}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; border-bottom: 1px solid #e5e7eb; font-weight: bold;">Date d'inscription :</td>
|
||||
@@ -149,11 +160,25 @@ export async function POST(request: NextRequest) {
|
||||
console.error('Failed to send registration email(s):', mailError);
|
||||
}
|
||||
|
||||
// Omit password from response
|
||||
const { password: _, ...userWithoutPassword } = user;
|
||||
// Return only safe user fields (never leak password, verificationToken, resetToken)
|
||||
const safeUser = {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
avatar: user.avatar,
|
||||
bio: user.bio,
|
||||
location: user.location,
|
||||
phone: user.phone,
|
||||
countryId: user.countryId,
|
||||
isSuspended: user.isSuspended,
|
||||
emailVerified: user.emailVerified,
|
||||
createdAt: user.createdAt,
|
||||
updatedAt: user.updatedAt,
|
||||
};
|
||||
|
||||
return NextResponse.json(
|
||||
{ message: 'Compte créé avec succès', user: userWithoutPassword },
|
||||
{ message: 'Compte créé avec succès', user: safeUser },
|
||||
{ status: 201 }
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user