feat: add captcha verification to contact form

Added math-based captcha system to prevent spam on the contact form:
- Created captcha API endpoint with simple arithmetic questions
- Added captcha UI component with refresh functionality
- Integrated captcha verification into contact form submission
- Relaxed spam filters since captcha provides better protection

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-10 17:53:56 +00:00
parent 9158ffa637
commit 989f231d5a
6 changed files with 268 additions and 18 deletions

View File

@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from 'next/server'
import { smtpService } from '@/lib/smtp'
import { verifyCaptcha } from '@/lib/captcha'
import { z } from 'zod'
export const runtime = 'nodejs'
@@ -8,7 +9,9 @@ const contactSchema = z.object({
name: z.string().min(1, 'Name is required').max(100),
email: z.string().email('Invalid email address'),
subject: z.string().min(1, 'Subject is required').max(200),
message: z.string().min(10, 'Message must be at least 10 characters').max(5000)
message: z.string().min(10, 'Message must be at least 10 characters').max(5000),
captchaId: z.string().min(1, 'Captcha ID is required'),
captchaAnswer: z.string().min(1, 'Captcha answer is required')
})
export async function POST(request: NextRequest) {
@@ -25,21 +28,26 @@ export async function POST(request: NextRequest) {
}, { status: 400 })
}
const { name, email, subject, message } = validationResult.data
const { name, email, subject, message, captchaId, captchaAnswer } = validationResult.data
// Basic spam prevention - check for common spam indicators
const spamIndicators = [
message.includes('http://'),
message.includes('https://'),
message.includes('www.'),
message.includes('bitcoin'),
message.includes('cryptocurrency'),
message.length < 10,
name.length < 2
]
// Verify captcha
const isValidCaptcha = verifyCaptcha(captchaId, captchaAnswer)
const spamScore = spamIndicators.filter(Boolean).length
if (spamScore >= 2) {
if (!isValidCaptcha) {
return NextResponse.json({
success: false,
error: 'Invalid captcha answer. Please try again.'
}, { status: 400 })
}
// Basic spam prevention - only check for obvious spam
// Allow URLs in messages since users may want to share links
const isSpam = (
(message.includes('bitcoin') || message.includes('cryptocurrency')) &&
(message.includes('http://') || message.includes('https://'))
)
if (isSpam) {
return NextResponse.json({
success: false,
error: 'Message flagged as potential spam'