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

93
lib/captcha.ts Normal file
View File

@@ -0,0 +1,93 @@
import { randomInt } from 'crypto'
interface CaptchaChallenge {
answer: number
expires: number
}
// Simple in-memory store for captcha challenges
const captchaStore = new Map<string, CaptchaChallenge>()
// Clean up expired captchas every 5 minutes
setInterval(() => {
const now = Date.now()
for (const [key, value] of captchaStore.entries()) {
if (value.expires < now) {
captchaStore.delete(key)
}
}
}, 5 * 60 * 1000)
function generateCaptchaId(): string {
return `captcha_${Date.now()}_${randomInt(10000, 99999)}`
}
export interface CaptchaData {
captchaId: string
question: string
}
export function generateCaptcha(): CaptchaData {
// Generate simple math problem
const num1 = randomInt(1, 20)
const num2 = randomInt(1, 20)
const operations = ['+', '-', '×'] as const
const operation = operations[randomInt(0, operations.length)]
let answer: number
let question: string
switch (operation) {
case '+':
answer = num1 + num2
question = `${num1} + ${num2}`
break
case '-':
// Ensure positive result
const larger = Math.max(num1, num2)
const smaller = Math.min(num1, num2)
answer = larger - smaller
question = `${larger} - ${smaller}`
break
case '×':
// Use smaller numbers for multiplication
const small1 = randomInt(2, 10)
const small2 = randomInt(2, 10)
answer = small1 * small2
question = `${small1} × ${small2}`
break
}
const captchaId = generateCaptchaId()
// Store captcha with 10 minute expiration
captchaStore.set(captchaId, {
answer,
expires: Date.now() + 10 * 60 * 1000
})
return {
captchaId,
question
}
}
export function verifyCaptcha(captchaId: string, answer: string | number): boolean {
const stored = captchaStore.get(captchaId)
if (!stored) {
return false
}
if (stored.expires < Date.now()) {
captchaStore.delete(captchaId)
return false
}
const isValid = parseInt(answer.toString()) === stored.answer
// Delete captcha after verification (one-time use)
captchaStore.delete(captchaId)
return isValid
}