refactor: enhance security and performance in configuration and API routes

- Update Content Security Policy (CSP) in next.config.ts to avoid `unsafe-eval` in production, improving security against XSS attacks.
- Refactor API routes to enforce admin authentication and session validation, ensuring secure access to sensitive endpoints.
- Optimize analytics data retrieval by using database aggregation instead of loading all records into memory, improving performance and reducing memory usage.
- Implement session token creation and verification for better session management and security across the application.
- Enhance error handling and input validation in various API routes to ensure robustness and prevent potential issues.
This commit is contained in:
2026-01-11 22:44:26 +01:00
parent 9cc03bc475
commit 9072faae43
28 changed files with 433 additions and 288 deletions

View File

@@ -2,6 +2,7 @@ import { type NextRequest, NextResponse } from "next/server";
import nodemailer from "nodemailer";
import SMTPTransport from "nodemailer/lib/smtp-transport";
import Mail from "nodemailer/lib/mailer";
import { checkRateLimit, getRateLimitHeaders, getClientIp, requireSessionAuth } from "@/lib/auth";
const BRAND = {
siteUrl: "https://dk0.dev",
@@ -172,9 +173,10 @@ const emailTemplates = {
},
reply: {
subject: "Antwort auf deine Nachricht 📧",
template: (name: string, originalMessage: string) => {
template: (name: string, originalMessage: string, responseMessage: string) => {
const safeName = escapeHtml(name);
const safeMsg = nl2br(escapeHtml(originalMessage));
const safeOriginal = nl2br(escapeHtml(originalMessage));
const safeResponse = nl2br(escapeHtml(responseMessage));
return baseEmail({
title: `Antwort für ${safeName}`,
subtitle: "Neue Nachricht",
@@ -189,7 +191,16 @@ const emailTemplates = {
<div style="font-size:12px;letter-spacing:0.08em;text-transform:uppercase;font-weight:800;color:#57534e;">Antwort</div>
</div>
<div style="padding:16px;line-height:1.65;color:${BRAND.text};font-size:14px;border-left:4px solid ${BRAND.mint};">
${safeMsg}
${safeResponse}
</div>
</div>
<div style="margin-top:16px;background:${BRAND.bg};border:1px solid ${BRAND.border};border-radius:16px;overflow:hidden;">
<div style="padding:14px 16px;background:${BRAND.sand};border-bottom:1px solid ${BRAND.border};">
<div style="font-size:12px;letter-spacing:0.08em;text-transform:uppercase;font-weight:800;color:#57534e;">Deine ursprüngliche Nachricht</div>
</div>
<div style="padding:16px;line-height:1.65;color:${BRAND.text};font-size:14px;border-left:4px solid ${BRAND.border};">
${safeOriginal}
</div>
</div>
`.trim(),
@@ -200,25 +211,39 @@ const emailTemplates = {
export async function POST(request: NextRequest) {
try {
const isAdminRequest = request.headers.get("x-admin-request") === "true";
if (!isAdminRequest) return NextResponse.json({ error: "Admin access required" }, { status: 403 });
const authError = requireSessionAuth(request);
if (authError) return authError;
const ip = getClientIp(request);
if (!checkRateLimit(ip, 10, 60000)) {
return NextResponse.json(
{ error: "Rate limit exceeded" },
{ status: 429, headers: { ...getRateLimitHeaders(ip, 10, 60000) } },
);
}
const body = (await request.json()) as {
to: string;
name: string;
template: 'welcome' | 'project' | 'quick' | 'reply';
originalMessage: string;
response?: string;
};
const { to, name, template, originalMessage } = body;
console.log('📧 Email response request:', { to, name, template, messageLength: originalMessage.length });
const { to, name, template, originalMessage, response } = body;
// Validate input
if (!to || !name || !template || !originalMessage) {
console.error('❌ Validation failed: Missing required fields');
return NextResponse.json(
{ error: "Alle Felder sind erforderlich" },
{ status: 400 },
);
}
if (template === "reply" && (!response || !response.trim())) {
return NextResponse.json({ error: "Antworttext ist erforderlich" }, { status: 400 });
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -232,7 +257,6 @@ export async function POST(request: NextRequest) {
// Check if template exists
if (!emailTemplates[template]) {
console.error('❌ Validation failed: Invalid template');
return NextResponse.json(
{ error: "Ungültiges Template" },
{ status: 400 },
@@ -274,9 +298,7 @@ export async function POST(request: NextRequest) {
// Verify transport configuration
try {
await transport.verify();
console.log('✅ SMTP connection verified successfully');
} catch (verifyError) {
console.error('❌ SMTP verification failed:', verifyError);
} catch (_verifyError) {
return NextResponse.json(
{ error: "E-Mail-Server-Verbindung fehlgeschlagen" },
{ status: 500 },
@@ -284,19 +306,27 @@ export async function POST(request: NextRequest) {
}
const selectedTemplate = emailTemplates[template];
let html: string;
if (template === "reply") {
html = emailTemplates.reply.template(name, originalMessage, response || "");
} else {
// Narrow the template type so TS knows this is not the 3-arg reply template
const nonReplyTemplate = template as Exclude<typeof template, "reply">;
html = emailTemplates[nonReplyTemplate].template(name, originalMessage);
}
const mailOptions: Mail.Options = {
from: `"Dennis Konkol" <${user}>`,
to: to,
replyTo: "contact@dk0.dev",
subject: selectedTemplate.subject,
html: selectedTemplate.template(name, originalMessage),
html,
text: `
Hallo ${name}!
Vielen Dank für deine Nachricht:
${originalMessage}
Ich werde mich so schnell wie möglich bei dir melden.
${template === "reply" ? `\nAntwort:\n${response || ""}\n` : "\nIch werde mich so schnell wie möglich bei dir melden.\n"}
Beste Grüße,
Dennis Konkol
@@ -306,23 +336,18 @@ contact@dk0.dev
`,
};
console.log('📤 Sending templated email...');
const sendMailPromise = () =>
new Promise<string>((resolve, reject) => {
transport.sendMail(mailOptions, function (err, info) {
if (!err) {
console.log('✅ Templated email sent successfully:', info.response);
resolve(info.response);
} else {
console.error("❌ Error sending templated email:", err);
reject(err.message);
}
});
});
const result = await sendMailPromise();
console.log('🎉 Templated email process completed successfully');
return NextResponse.json({
message: "Template-E-Mail erfolgreich gesendet",
@@ -331,7 +356,6 @@ contact@dk0.dev
});
} catch (err) {
console.error("❌ Unexpected error in templated email API:", err);
return NextResponse.json({
error: "Fehler beim Senden der Template-E-Mail",
details: err instanceof Error ? err.message : 'Unbekannter Fehler'