diff --git a/.auto-claude-status b/.auto-claude-status index c2d956d..9ee88fb 100644 --- a/.auto-claude-status +++ b/.auto-claude-status @@ -3,13 +3,13 @@ "spec": "017-replace-in-memory-rate-limiter-with-persistent-sol", "state": "building", "subtasks": { - "completed": 7, + "completed": 8, "total": 12, "in_progress": 1, "failed": 0 }, "phase": { - "current": "Migrate Contact Form", + "current": "Remove Old Implementation", "id": null, "total": 2 }, @@ -18,8 +18,8 @@ "max": 1 }, "session": { - "number": 5, + "number": 6, "started_at": "2026-01-25T11:52:31.718294" }, - "last_update": "2026-01-25T12:07:33.420389" + "last_update": "2026-01-25T12:11:11.631187" } \ No newline at end of file diff --git a/src/components-vite/ContactForm.tsx b/src/components-vite/ContactForm.tsx deleted file mode 100644 index ad7d1b1..0000000 --- a/src/components-vite/ContactForm.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import React, { useState } from 'react'; -import { Send, AlertCircle } from 'lucide-react'; -import { getCsrfToken } from '../utils/csrf'; -import { rateLimiter } from '../utils/rateLimiting'; - -interface ContactFormProps { - onSubmit: (data: FormData) => Promise; -} - -const ContactForm: React.FC = ({ onSubmit }) => { - const [name, setName] = useState(''); - const [email, setEmail] = useState(''); - const [message, setMessage] = useState(''); - const [error, setError] = useState(null); - const [success, setSuccess] = useState(false); - const [loading, setLoading] = useState(false); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setError(null); - setLoading(true); - - try { - // Check rate limiting - const clientIp = '127.0.0.1'; // In production, get this from the request - if (rateLimiter.isRateLimited(clientIp)) { - const timeToReset = Math.ceil(rateLimiter.getTimeToReset(clientIp) / 1000 / 60); - throw new Error(`Too many attempts. Please try again in ${timeToReset} minutes.`); - } - - // Validate CSRF token - const formData = new FormData(); - formData.append('name', name); - formData.append('email', email); - formData.append('message', message); - formData.append('csrf_token', getCsrfToken()); - - await onSubmit(formData); - setSuccess(true); - setName(''); - setEmail(''); - setMessage(''); - } catch (err) { - setError(err instanceof Error ? err.message : 'An error occurred'); - } finally { - setLoading(false); - } - }; - - return ( -
- {error && ( -
- - {error} -
- )} - - {success && ( -
- Message sent successfully! -
- )} - -
- - setName(e.target.value)} - className="w-full bg-gray-900/50 border border-white/10 rounded-lg px-4 py-3 text-white placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-orange-500" - required - /> -
- -
- - setEmail(e.target.value)} - className="w-full bg-gray-900/50 border border-white/10 rounded-lg px-4 py-3 text-white placeholder-white/40 focus:outline-none focus:ring-2 focus:ring-orange-500" - required - /> -
- -
- - -
- - -
- ); -}; - -export default ContactForm; \ No newline at end of file diff --git a/src/utils/rateLimiting.ts b/src/utils/rateLimiting.ts deleted file mode 100644 index f1a77b7..0000000 --- a/src/utils/rateLimiting.ts +++ /dev/null @@ -1,55 +0,0 @@ -interface RateLimitEntry { - count: number; - timestamp: number; -} - -const WINDOW_SIZE_MS = 3600000; // 1 hour -const MAX_REQUESTS = 5; // Maximum requests per hour - -class RateLimiter { - private cache: Map; - - constructor() { - this.cache = new Map(); - } - - isRateLimited(ip: string): boolean { - const now = Date.now(); - const entry = this.cache.get(ip); - - if (!entry) { - this.cache.set(ip, { count: 1, timestamp: now }); - return false; - } - - if (now - entry.timestamp > WINDOW_SIZE_MS) { - this.cache.set(ip, { count: 1, timestamp: now }); - return false; - } - - if (entry.count >= MAX_REQUESTS) { - return true; - } - - entry.count++; - return false; - } - - getRemainingAttempts(ip: string): number { - const entry = this.cache.get(ip); - if (!entry) { - return MAX_REQUESTS; - } - return Math.max(0, MAX_REQUESTS - entry.count); - } - - getTimeToReset(ip: string): number { - const entry = this.cache.get(ip); - if (!entry) { - return 0; - } - return Math.max(0, WINDOW_SIZE_MS - (Date.now() - entry.timestamp)); - } -} - -export const rateLimiter = new RateLimiter(); \ No newline at end of file