Initial commit

This commit is contained in:
Developer
2026-02-06 21:44:04 -06:00
commit f85e93c7a6
151 changed files with 22916 additions and 0 deletions

76
lib/captcha.ts Normal file
View File

@@ -0,0 +1,76 @@
import crypto from "crypto";
// Simple in-memory CAPTCHA store (in production, use Redis or database)
const captchaStore: Map<string, { code: string; createdAt: number; attempts: number }> = new Map();
const CAPTCHA_TTL = 5 * 60 * 1000; // 5 minutes
const MAX_ATTEMPTS = 5;
const CLEANUP_INTERVAL = 10 * 60 * 1000; // 10 minutes
// Cleanup old captchas periodically
setInterval(() => {
const now = Date.now();
for (const [key, value] of captchaStore.entries()) {
if (now - value.createdAt > CAPTCHA_TTL) {
captchaStore.delete(key);
}
}
}, CLEANUP_INTERVAL);
export function generateCaptcha(identifier: string): { id: string; code: string } {
const code = Math.random().toString().slice(2, 8); // 6-digit code
const id = crypto.randomBytes(16).toString("hex");
captchaStore.set(id, {
code,
createdAt: Date.now(),
attempts: 0,
});
return { id, code };
}
export interface CaptchaVerificationResult {
success: boolean;
error?: string;
attemptsRemaining?: number;
}
export function verifyCaptcha(id: string, code: string): CaptchaVerificationResult {
const captcha = captchaStore.get(id);
if (!captcha) {
return { success: false, error: "CAPTCHA not found or expired. Please reload and try again." };
}
// Check if expired
if (Date.now() - captcha.createdAt > CAPTCHA_TTL) {
captchaStore.delete(id);
return { success: false, error: "CAPTCHA code has expired. Please request a new one." };
}
// Check attempts
if (captcha.attempts >= MAX_ATTEMPTS) {
captchaStore.delete(id);
return { success: false, error: "Too many incorrect attempts. Please request a new CAPTCHA code." };
}
// Verify code
if (captcha.code === code) {
captchaStore.delete(id);
return { success: true };
}
// Increment attempts
captcha.attempts++;
const remaining = MAX_ATTEMPTS - captcha.attempts;
return {
success: false,
error: `Incorrect CAPTCHA code. ${remaining} attempt${remaining === 1 ? "" : "s"} remaining.`,
attemptsRemaining: remaining,
};
}
export function getMaxAttempts(): number {
return MAX_ATTEMPTS;
}