auth-yes/tasks/new/2026-0826.01.jul.story.events.join-reentry-and-limits-2156.md
google-labs-jules[bot] 25451ff172 story(events): add phase 2 event overhaul planning document
Adds a new task specification in `tasks/new/` documenting the plan for Phase 2 Event Overhaul,
including PIN discovery, input normalization, rate limiting, and NAT-safe idempotent re-entry
for event joins.

Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
2026-08-26 22:01:03 +00:00

3.9 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, '-', '') and LOWER(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 rateLimitWrapper are more efficient and standard for our stack.

3. Proposed Implementation

  • 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. 241881 vs 241-881).
  • 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 sqlWrapper query to match normalized inputs: WHERE (LOWER(slug) = ${code} OR REPLACE(pin_code, '-', '') = ${code}).

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) and ratelimit: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.

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_id is linked to a guest account for the target event (e.g., username matches guest_<event.slug>_%).
    • If the user is already a guest of this event, bypass the seats_claimed + 1 update and early-return { success: true, sessionId, redirectUrl }, keeping the existing session intact.