feat: Cloudflare Turnstile (register + contact) + captcha altyapısı
- Turnstile widget bileşeni (public site key gömülü, VITE_TURNSTILE_SITE_KEY ile override) - register: signUp.email'e x-captcha-response header'ı - contact: token body'de; ContactService Cloudflare siteverify ile doğrular (TURNSTILE_SECRET_KEY yoksa atlanır), contact.dto'ya turnstileToken - @sase/config: TURNSTILE_SECRET_KEY env (opsiyonel) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,8 @@ export const contactMessageSchema = z.object({
|
||||
email: z.string().trim().email("Geçerli bir e-posta adresi girin").max(200),
|
||||
subject: z.string().trim().max(200, "Konu çok uzun").optional().default(""),
|
||||
message: z.string().trim().min(10, "Mesaj en az 10 karakter olmalı").max(5000, "Mesaj çok uzun"),
|
||||
// Cloudflare Turnstile token'ı (captcha aktifse zorunlu, sunucuda doğrulanır)
|
||||
turnstileToken: z.string().optional(),
|
||||
});
|
||||
|
||||
export type ContactMessage = z.infer<typeof contactMessageSchema>;
|
||||
|
||||
@@ -25,6 +25,11 @@ export class ContactService {
|
||||
throw new BadRequestException(parsed.error.issues[0]?.message ?? "Geçersiz form verisi");
|
||||
}
|
||||
|
||||
const ok = await this.verifyTurnstile(parsed.data.turnstileToken);
|
||||
if (!ok) {
|
||||
throw new BadRequestException("Doğrulama başarısız, lütfen tekrar deneyin.");
|
||||
}
|
||||
|
||||
const { name, email, message } = parsed.data;
|
||||
const subject = parsed.data.subject?.trim() || "Yeni mesaj";
|
||||
|
||||
@@ -47,4 +52,23 @@ export class ContactService {
|
||||
this.logger.log(`Contact form submitted by ${email}`);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
// Cloudflare Turnstile doğrulaması. Secret tanımlı değilse atlanır (true döner).
|
||||
private async verifyTurnstile(token: string | undefined): Promise<boolean> {
|
||||
const secret = process.env.TURNSTILE_SECRET_KEY;
|
||||
if (!secret) return true;
|
||||
if (!token) return false;
|
||||
try {
|
||||
const res = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams({ secret, response: token }),
|
||||
});
|
||||
const data = (await res.json()) as { success?: boolean };
|
||||
return data.success === true;
|
||||
} catch (error) {
|
||||
this.logger.error(`Turnstile verification failed: ${error}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user