fix: standaryzacja i bezpieczeństwo formularzy
- Ujednolicono kontakt/actions.ts (rate limit, escapeHtml, getMailClient, usunięto bezpośrednie Resend) - Usunięto nieużywany plik lib/recaptcha.ts - Naprawiono brak obsługi błędów API (Resend error) w zgloszenie-awarii, zamowienie i zapytanie-multisplit - Dodano logowanie błędów w catch dla tych formularzy - Posprzątano stare skrypty robocze z roota
This commit is contained in:
+23
-12
@@ -1,10 +1,9 @@
|
||||
"use server";
|
||||
|
||||
import { z } from "zod";
|
||||
import { Resend } from "resend";
|
||||
import { verifyRecaptcha } from "@/lib/recaptcha";
|
||||
|
||||
const resend = new Resend(process.env.RESEND_API_KEY);
|
||||
import { escapeHtml, rateLimit, verifyRecaptcha } from "@/lib/security";
|
||||
import { getMailClient } from "@/lib/mail";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
const contactFormSchema = z.object({
|
||||
name: z.string().min(2, "Imię musi mieć min. 2 znaki"),
|
||||
@@ -18,12 +17,16 @@ const contactFormSchema = z.object({
|
||||
|
||||
export async function submitContactForm(prevState: unknown, formData: FormData) {
|
||||
try {
|
||||
const ip = (await headers()).get('x-forwarded-for') || 'unknown';
|
||||
if (!rateLimit(ip)) {
|
||||
return { success: false, message: 'Wysłano zbyt wiele zapytań. Spróbuj ponownie później.' };
|
||||
}
|
||||
if (process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY && process.env.RECAPTCHA_SECRET_KEY) {
|
||||
const token = formData.get('recaptchaToken') as string;
|
||||
if (!token) {
|
||||
return { success: false, message: "Weryfikacja bezpieczeństwa nie powiodła się. Odśwież stronę i spróbuj ponownie." };
|
||||
}
|
||||
const { success } = await verifyRecaptcha(token);
|
||||
const success = await verifyRecaptcha(token);
|
||||
if (!success) {
|
||||
return { success: false, message: "Weryfikacja bezpieczeństwa nie powiodła się. Odśwież stronę i spróbuj ponownie." };
|
||||
}
|
||||
@@ -42,8 +45,16 @@ export async function submitContactForm(prevState: unknown, formData: FormData)
|
||||
|
||||
const validatedData = contactFormSchema.parse(rawData);
|
||||
|
||||
const mailClient = getMailClient();
|
||||
if (!mailClient) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Funkcja wysyłania wiadomości jest tymczasowo niedostępna.",
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Wysłanie e-maila przez Resend
|
||||
const { data, error } = await resend.emails.send({
|
||||
const { data, error } = await mailClient.resend.emails.send({
|
||||
from: process.env.RESEND_FROM_EMAIL || "[email protected]",
|
||||
to: process.env.RESEND_TO_EMAIL || "[email protected]", // Docelowy e-mail (odbiorca)
|
||||
replyTo: validatedData.email,
|
||||
@@ -59,29 +70,29 @@ export async function submitContactForm(prevState: unknown, formData: FormData)
|
||||
<table style="width: 100%; border-collapse: collapse; margin-bottom: 20px;">
|
||||
<tr>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9; width: 120px;"><strong>Imię:</strong></td>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;">${validatedData.name}</td>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;">${escapeHtml(validatedData.name)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;"><strong>Telefon:</strong></td>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;"><a href="tel:${validatedData.phone}" style="color: #0F2A47; text-decoration: none;">${validatedData.phone}</a></td>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;"><a href="tel:${escapeHtml(validatedData.phone)}" style="color: #0F2A47; text-decoration: none;">${escapeHtml(validatedData.phone)}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;"><strong>E-mail:</strong></td>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;"><a href="mailto:${validatedData.email}" style="color: #0F2A47; text-decoration: none;">${validatedData.email}</a></td>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;"><a href="mailto:${escapeHtml(validatedData.email || '')}" style="color: #0F2A47; text-decoration: none;">${escapeHtml(validatedData.email || '')}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;"><strong>Miasto:</strong></td>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;">${validatedData.city}</td>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;">${escapeHtml(validatedData.city)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;"><strong>Temat:</strong></td>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;">${validatedData.subject}</td>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;">${escapeHtml(validatedData.subject || '')}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2 style="font-size: 18px; border-bottom: 2px solid #f1f5f9; padding-bottom: 10px;">Treść wiadomości:</h2>
|
||||
<div style="background-color: #f8fafc; padding: 15px; border-radius: 6px; font-size: 15px; line-height: 1.6; white-space: pre-wrap;">
|
||||
${validatedData.message}
|
||||
${escapeHtml(validatedData.message || '')}
|
||||
</div>
|
||||
|
||||
<p style="margin-top: 24px; font-size: 12px; color: #64748b; text-align: center;">
|
||||
|
||||
Reference in New Issue
Block a user