Files

61 lines
1.7 KiB
TypeScript

import { z } from 'zod';
export function escapeHtml(unsafe: string): string {
if (typeof unsafe !== 'string') return '';
return unsafe
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
const rateLimitMap = new Map<string, { count: number; expiresAt: number }>();
const MAX_REQUESTS = 5;
const WINDOW_MS = 10 * 60 * 1000;
export function rateLimit(ip: string | undefined | null): boolean {
if (!ip) return true;
const now = Date.now();
const entry = rateLimitMap.get(ip);
if (!entry || entry.expiresAt < now) {
rateLimitMap.set(ip, { count: 1, expiresAt: now + WINDOW_MS });
return true;
}
if (entry.count >= MAX_REQUESTS) {
return false;
}
entry.count++;
return true;
}
export async function verifyRecaptcha(token: string): Promise<boolean> {
const secretKey = process.env.RECAPTCHA_SECRET_KEY;
if (!secretKey) return false;
try {
const res = await fetch(`https://www.google.com/recaptcha/api/siteverify`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `secret=${secretKey}&response=${token}`,
});
const data = await res.json();
return data.success;
} catch (err) {
return false;
}
}
export const baseContactSchema = z.object({
name: z.string().min(2, "Imię musi mieć co najmniej 2 znaki").max(100, "Imię jest zbyt długie"),
phone: z.string().regex(/^[+\d\s-]{9,20}$/, "Niepoprawny format telefonu"),
email: z.string().email("Niepoprawny adres e-mail").max(150, "E-mail jest zbyt długi"),
consent: z.string().refine(val => val === "on" || val === "true", "Wymagana zgoda"),
});