fix(forms): rate-limit anonymous submitForm (E2, P2)
Anonymous, captcha-less submitForm was an unbounded DB-bloat vector. Added a generous per-IP+form limit (20/min) — deliberately loose because legitimate public signups at a hackerspace commonly share one NAT/wifi IP, so a tight limit would false-block a room; this still bounds automated flooding. Captcha (the real control) stays the documented deferred follow-up. 532 tests.
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
type Member,
|
||||
type ServerSupabase,
|
||||
} from '@/lib/auth-helpers'
|
||||
import { checkRateLimit } from '@/lib/security'
|
||||
import {
|
||||
createFormSchema,
|
||||
updateFormSchema,
|
||||
@@ -446,6 +447,17 @@ export async function submitForm(input: unknown) {
|
||||
return { error: 'This form is not accepting responses' }
|
||||
}
|
||||
|
||||
// Anonymous endpoint with no captcha: bound the DB-bloat abuse vector.
|
||||
// Generous per-IP+form window because legitimate public signups at a
|
||||
// hackerspace often share one NAT/wifi IP (a captcha is the real control,
|
||||
// intentionally deferred). Best-effort IP from the proxy header.
|
||||
const h = await headers()
|
||||
const ip = (h.get('x-forwarded-for')?.split(',')[0] ?? '').trim() || 'unknown'
|
||||
const rl = checkRateLimit(`form-submit:${ip}:${form.id}`, 20, 60_000)
|
||||
if (!rl.allowed) {
|
||||
return { error: 'Too many submissions from your network. Please wait a moment and try again.' }
|
||||
}
|
||||
|
||||
const supabase = await createClient()
|
||||
const {
|
||||
data: { user },
|
||||
|
||||
Reference in New Issue
Block a user