4.5 KiB
4.5 KiB
TASK METADATA
- Target Files:
server/routes/events.ts,ui/components/LoginPage.tsx,ui/components/RegisterPage.tsx,ui/components/EventJoinPage.tsx,server/tests/events.test.ts - Core Objective: Implement Phase 2 Event Overhaul: Add PIN discovery links, bifurcate slug/PIN input normalization, apply tiered Valkey rate limiting on failed brute-force attempts, and implement NAT-safe idempotent cookie re-entry.
- Dependencies:
server/middleware.ts(forgetClientIp),server/ratelimit.ts(for rate limiting logic). - Additional Important Notes: Attendee identity must remain 100% IP-agnostic by relying on browser session cookies (NAT-safe for shared WiFi). IP tracking is strictly reserved for throttling failed brute-force attacks and forensic audit logs.
2. Architectural Considerations & Risks
-
Risks:
- Slug Hyphen Destruction: Event slugs legitimately contain hyphens (e.g.,
testing-workshop-event-...). Stripping hyphens globally breaks slug lookups. Normalization MUST bifurcate: preserve hyphens for slugs (LOWER(slug)), while stripping hyphens only for PIN matching (REPLACE(pin_code, '-', '')). - NAT False-Positives: Never bind attendee identity or successful seats
strictly to an IP. Shared conference/classroom WiFi shares one egress IP.
Idempotent re-entry must inspect the incoming
session_idcookie. - Rate Limiter State Machine: Pre-check gates at the route entry must
reject exceeded IPs (
429), while post-execution increments must distinctly separate failed code attempts (ratelimit:join:fail:<ip>) from successful joins.
- Slug Hyphen Destruction: Event slugs legitimately contain hyphens (e.g.,
-
Alternatives:
- Storing pre-normalized columns was evaluated and rejected; runtime
normalization with
LOWER(slug)andREPLACE(pin_code, '-', '')is fast and zero-migration.
- Storing pre-normalized columns was evaluated and rejected; runtime
normalization with
3. Proposed Implementation
Phase 1: Login & Registration Discovery Links
ui/components/LoginPage.tsx: Add a "Join with PIN" link next to "Register with Invite" in the footer links section.ui/components/RegisterPage.tsx: Add a similar "Join with PIN" link to the footer for consistency across all unauthenticated entry points.
Phase 2: Bifurcated Input Normalization
ui/components/EventJoinPage.tsx: Enhance the client-side script to accept raw 6 digits (241881) or hyphenated PINs (241-881) and slugs without throwing client-side validation errors.server/routes/events.ts(POST /api/join&GET /join/:slug):- Create two normalized representations:
rawNormalized = code.trim().toLowerCase()(preserves hyphens for slugs).pinNormalized = code.trim().replace(/[-\s]/g, '')(strips hyphens/spaces for PINs).
- Update SQL query:
WHERE (LOWER(slug) = ${rawNormalized} OR REPLACE(pin_code, '-', '') = ${pinNormalized})AND is_active = TRUE AND (expires_at IS NULL OR expires_at > NOW())
- Create two normalized representations:
Phase 3: Anti-DoS Rate Limiting on POST /api/join
- Pre-Check Gate (Top of Route):
- Resolve IP via
const clientIp = getClientIp(c). - Check if
ratelimit:join:fail:${clientIp}exceeds 5 attempts per 60s. If so, return429 Too Many Requests.
- Resolve IP via
- Post-Execution Increments:
- If lookup returns 0 rows (invalid code / expired): increment
ratelimit:join:fail:${clientIp}(window: 60s) before returning404. - If seat is successfully claimed: optionally increment
ratelimit:join:success:${clientIp}(limit: 3 per 60s).
- If lookup returns 0 rows (invalid code / expired): increment
Phase 4: NAT-Safe Idempotent Re-entry
- Session Cookie Inspection (
POST /api/join):- Before executing the
seats_claimed + 1update, checkgetAuthenticatedUser(c). - If a valid session exists, verify if the session is a guest session
belonging to the matched event slug
(
username.startsWith('guest_' + event.slug + '_')). - If matched:
- Re-issue the
session_idcookie with fresh TTL. - Return
{ success: true, sessionId: auth.sessionId, redirectUrl, reused: true }without incrementingseats_claimed.
- Re-issue the
- Before executing the
Phase 5: Unit Testing
server/tests/events.test.ts:- Add test:
POST /api/join - normalizes PIN without hyphens (241881 -> 241-881). - Add test:
POST /api/join - normalizes slug case-insensitively while preserving hyphens. - Add test:
POST /api/join - idempotent re-entry returns existing session without burning seat. - Add test:
POST /api/join - enforces 5 failed attempts rate limit per IP.
- Add test: