# TASK METADATA - **Target Files:** - `server/pow.ts` - `server/routes/events.ts` - `server/valkey.ts` - `ui/components/events/JoinPage.tsx` - `ui/components/events/JoinScript.tsx` - `ui/components/events/EventLandingPage.tsx` - `server/tests/pow.test.ts` - **Core Objective:** Implement a transparent, sub-50ms client-side WebCrypto (`crypto.subtle`) SHA-256 Proof-of-Work (PoW) ingress shield to protect 6-digit workshop PIN codes and event slugs against distributed botnet brute-forcing and denial-of-service. - **Dependencies:** - Valkey 8 for atomic replay cache (`SET ... NX EX 60`). - Standard WebCrypto APIs supported across modern browsers and Deno. - **Additional Important Notes:** - PoW computation must be entirely transparent to human users (solved in background Web Workers or async microtasks during page load/typing). - Headless CLI clients (`curl ... | source`) must be provided with a streamlined solver or signed temporary ingress token. --- ## 2. Architectural Considerations & Risks ### Risks 1. **Client-Side CPU Exhaustion & Mobile Battery Drain:** If the cryptographic difficulty target is set too high, mobile devices or older laptops might freeze the main thread or experience noticeable latency before submitting a PIN. - _Mitigation:_ Calibrate the default baseline difficulty to ~16–18 leading zero bits (~20–40ms CPU time on standard mobile silicon). Run computation inside a Web Worker or async `requestAnimationFrame` chunks to guarantee zero UI frame drops. 2. **Replay Attacks on Solved Nonces:** An attacker could precompute a single valid PoW solution and reuse it across thousands of simultaneous requests to brute-force the 6-digit PIN. - _Mitigation:_ Bind challenges cryptographically to a short-lived timestamped seed (`HMAC-SHA256(secret, client_ip + timestamp_minute + event_id)`). Record solved nonces in Valkey with a strict 60-second TTL via `SET key nonce NX EX 60` to enforce single-use execution. 3. **Dynamic Threat Scaling:** Static difficulty is either too easy for large botnets or too heavy for low-powered mobile devices. - _Mitigation:_ Implement adaptive difficulty scaling in `server/pow.ts`: Normal traffic requires 16 bits; if an IP or specific event encounters $>3$ failed attempts within 60s, automatically scale difficulty to 22 bits for that subnet/event. ### Alternatives - **Cloudflare Turnstile / Google reCAPTCHA:** Heavy external dependency requiring network egress, proprietary script tags, and tracking cookies. Rejected. A self-contained, zero-dependency SHA-256 PoW preserves Auth-Yes's 100% sovereign, zero-external-dependency IAM architecture. - **Strict IP Rate Limiting Only:** In distributed botnet attacks, attackers rotate thousands of residential proxies, making single-IP rate limiting ineffective. PoW shifts the economic and compute cost onto the attacker. --- ## 3. Proposed Implementation ### Phase 1: PoW Challenge Engine (`server/pow.ts`) 1. Implement challenge minting: - `generateChallenge(clientIp: string, eventId?: string, difficultyBits = 16)`: - Mint challenge payload: `{ salt: string, timestamp: number, difficulty: number, signature: string }`. - Sign payload using internal HMAC key. 2. Implement validation: - `verifyProofOfWork(challenge: PoWChallenge, nonce: number, clientIp: string)`: - Verify HMAC signature on salt/timestamp (ensure timestamp within 120s window). - Compute `SHA-256(salt + nonce)`. - Check that the leading $N$ bits of the digest are zero. - Atomically store `nonce` in Valkey (`SET pow:${salt}:${nonce} 1 NX EX 120`); if key already exists, reject as replayed. ### Phase 2: Ingress Route Protection (`server/routes/events.ts`) 1. Add challenge endpoint: - `GET /api/join/challenge`: Returns `{ salt, timestamp, difficulty, signature }`. 2. Update `POST /api/join`: - Require `pow_salt`, `pow_nonce`, `pow_timestamp`, `pow_signature` in JSON body. - Validate PoW _before_ checking the PIN code or touching PostgreSQL. - If invalid or missing, immediately return 400 (`Bad Request: Invalid Proof-of-Work Challenge`) without consuming DB connections. 3. Adaptive Difficulty Escalation: - Track failed attempts per IP and per event in Valkey. Increase `difficulty` dynamically if failure rate spikes. ### Phase 3: Client-Side WebCrypto Solver (`ui/components/events/JoinScript.tsx`) 1. **Background Web Worker Solver:** - When the user lands on `/join` or `/e/:slug`, automatically fetch `/api/join/challenge` in the background. - Spawn a lightweight Web Worker executing WebCrypto `crypto.subtle.digest('SHA-256', ...)` in a tight loop to find the nonce while the user types their 6-digit PIN. - Attach solved nonce payload seamlessly to the form submit handler. 2. **Headless CLI Compatibility:** - For CLI joins (`/join/:slug?format=env`), embed a micro-solver script in the piped shell payload or provide a signed 1-click token for terminal workflows. ### Phase 4: Quality Gates & Benchmarks 1. Comprehensive test suite in `server/tests/pow.test.ts`: - Unit tests for challenge generation, difficulty checks, expiration, and replay prevention. - Performance benchmark asserting solver verification on server takes $<0.5\text{ms}$. 2. Run `deno fmt`, `deno task lint`, `deno task check`, and `deno test -A --no-check`.