4.1 KiB
4.1 KiB
1. TASK METADATA
- Target Files:
server/routes/events.ts,ui/components/LoginPage.tsx,ui/components/RegisterPage.tsx,ui/components/EventJoinPage.tsx - Core Objective: Implement Phase 2 Event Overhaul: Add PIN discovery links, normalize input, apply tiered Valkey rate limiting, and implement NAT-safe idempotent re-entry on join.
- Dependencies:
server/middleware.ts(for getClientIp),server/ratelimit.ts(for rate limiting logic). - Additional Important Notes: Frontend changes should mirror backend
normalization. Re-entry must seamlessly return an existing active guest
session linked to the correct event without incrementing
seats_claimed.
2. Architectural Considerations & Risks
-
Risks:
- If idempotent re-entry is flawed, returning a mismatched event session could allow access to an event a user didn't register for, or incorrectly redirect them. We must strictly verify the existing session is a guest session belonging to the target event.
- Rate limiting logic might accidentally block legitimate NAT'ed traffic if thresholds are set too low; 5 fail / 3 success per minute per IP should be monitored.
- Normalization on the backend must be implemented securely using robust
string replacements to prevent SQL injection or unexpected matching
behaviors, especially with PostgreSQL parameterized queries
(
sqlWrapper.sql).
-
Alternatives:
- For normalization, instead of just runtime stripping, we could normalize
upon creation and store both normalized and display values. However,
stripping at runtime with
REPLACE(pin_code, '-', '')andLOWER(slug)is an acceptable and low-friction approach for our scale. - Rate limiting could use sliding window algorithms, but Valkey-backed fixed
windows via the existing
rateLimitWrapperare more efficient and standard for our stack.
- For normalization, instead of just runtime stripping, we could normalize
upon creation and store both normalized and display values. However,
stripping at runtime with
3. Proposed Implementation
Phase 1: Login & Registration Discovery Links
- File:
ui/components/LoginPage.tsx- Add a "Join with PIN" link next to the existing "Register with Invite" in the footer links section to ensure unauthenticated users can easily discover the join portal.
- File:
ui/components/RegisterPage.tsx- Add a similar "Join with PIN" link to the footer for consistency across entry points.
Phase 2: Input Normalization (Frontend & Backend)
- File:
ui/components/EventJoinPage.tsx- Enhance the UI script to tolerate input formatting, naturally handling
hyphens, spaces, and case differences during submission, providing immediate
visual feedback without rejecting valid raw input (e.g.
241881vs241-881).
- Enhance the UI script to tolerate input formatting, naturally handling
hyphens, spaces, and case differences during submission, providing immediate
visual feedback without rejecting valid raw input (e.g.
- File:
server/routes/events.ts(POST /api/join&GET /join/:slug)- Strip whitespace, hyphens, and force lowercase on the incoming code on the
backend (
code.trim().replace(/[-\s]/g, '').toLowerCase()). - Update the
sqlWrapperquery to match normalized inputs:WHERE (LOWER(slug) = ${code} OR REPLACE(pin_code, '-', '') = ${code}).
- Strip whitespace, hyphens, and force lowercase on the incoming code on the
backend (
Phase 3: Anti-DoS Rate Limiting
- File:
server/routes/events.ts(POST /api/join)- Use
getClientIp(c)from our middleware to determine the client IP address. - Mount tiered Valkey rate limits using the keys
ratelimit:join:fail:<ip>(limit: 5 per 60s) andratelimit:join:success:<ip>(limit: 3 per 60s). - Apply the rate limiting logic prior to executing any database queries to prevent database DoS on brute-force PIN attempts.
- Use
Phase 4: NAT-Safe Idempotent Re-entry
- File:
server/routes/events.ts(POST /api/join)- Before claiming a seat and generating a new session, check for an existing
valid user session using
getAuthenticatedUser(c). - If a valid session exists, query the database to determine if the
user_idis linked to a guest account for the target event (e.g., username matchesguest_<event.slug>_%). - If the user is already a guest of this event, bypass the
seats_claimed + 1update and early-return{ success: true, sessionId, redirectUrl }, keeping the existing session intact.
- Before claiming a seat and generating a new session, check for an existing
valid user session using