123 lines
5.4 KiB
Markdown
123 lines
5.4 KiB
Markdown
# 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`.
|