ReportIt

Community incident reporting for neighborhood associations — no accounts required for reporters, and a layered spam-defense stack to make that survivable.

Rust Axum AWS Lambda DynamoDB SvelteKit SES

What It Is

ReportIt started as a one-off system for my own neighborhood association: a way for neighbors to report broken streetlights, graffiti, and potholes, and for volunteer board members to track and resolve them. It grew into a multi-tenant SaaS — each association gets its own subdomain, categories, admin dashboard, and community map.

The stack is deliberately serverless: a Rust/Axum API compiled for ARM64 Lambda behind API Gateway, DynamoDB for storage, S3 for photos, SES for email, and Cognito for admin authentication. A SvelteKit frontend is served from S3 + CloudFront. There is nothing to patch and nothing idling — which matters when your customers are volunteer-run nonprofits.

The No-Accounts Problem

The core product decision is that reporters never create accounts. A neighbor photographing a fallen tree branch will not complete a signup flow; every screen of friction loses reports. Identity is just an email address, verified after the fact by a confirmation link. Unconfirmed reports carry a DynamoDB TTL and silently expire.

That decision deletes every abuse control that authentication normally provides, so the public endpoints stack defenses instead: a honeypot field (bots that fill it get a fake success response), a minimum form-fill time, per-IP and per-email rate limits, a captcha, and a suppression list for addresses that have bounced or complained.

Upload Authorization

The hardest endpoint to protect is photo upload. Photos go directly to S3 via presigned URLs (Lambda's payload limits make proxying impractical), and uploads start while the reporter is still filling out the form — before the report exists, before the captcha, before anything has been verified. An anonymous endpoint that hands out S3 write access is an open invitation to use your bucket as free file hosting.

The defense is to make asking expensive. Before requesting an upload URL, the browser derives a token with PBKDF2-SHA256 at 100,000 iterations over email|hostname|timestamp using WebCrypto — roughly a second of CPU on a typical phone. The server re-derives the same value to verify it, then caches the verified authorization in DynamoDB alongside a pre-assigned report ID, so a five-photo report only pays the verification cost once. It's not classic proof-of-work — there's no nonce lottery, just a fixed, unavoidable computation — but the economics are the same: one legitimate reporter never notices, and bulk-minting upload sessions costs an attacker linear CPU against a 15-minute expiry window.

Interactive Demo

This is the actual client-side derivation ReportIt runs before photo uploads (with a demo salt). Pick an iteration count, compute an authorization, and see what the work costs your machine — then what it would cost someone trying to mint spam sessions in bulk.