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:
93
lib/captcha.ts
Normal file
93
lib/captcha.ts
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user