Pre-launch P0 fixes surfaced by docs/specs/events-functional-test-matrix.md
(Findings 1, 2, 3).
1. Series-pass bypass (Finding 1 / matrix S1 P3): register.post.js now
loads the linked Series when tickets.requiresSeriesTicket is set and
rejects drop-in registration unless series.allowIndividualEventTickets
is true or the user has a valid pass. Data-integrity 500 if the
referenced series is missing.
2. Hidden-event leak (Finding 2 / matrix E11): extract loadPublicEvent
into server/utils/loadEvent.js. All five public event endpoints
([id].get, register, tickets/available, tickets/reserve,
tickets/purchase) now go through the helper, which 404s when
isVisible === false and the requester is not an admin. Admin detection
uses a new non-throwing getOptionalMember() in server/utils/auth.js
(extracted from the pattern already inlined in api/auth/status.get.js).
3. Deadline enforcement + legacy pricing retirement (Finding 3 / matrix
E8): register.post.js and tickets/reserve.post.js delegate gating to
validateTicketPurchase (which already covers deadline, cancelled,
started, members-only, sold-out, and already-registered);
tickets/available.get.js gets an explicit registrationDeadline check.
Legacy pricing.paymentRequired 402 branch removed from register.post.js.
Extracts hasMemberAccess(member) in tickets.js and uses it across event
registration, ticket purchase, and series purchase flows so guest, suspended,
and cancelled records no longer count as members while pending_payment still
does.
The /api/events/[id]/guest-register endpoint has no production
callers: it's superseded by tickets/purchase.post.js, which
handles guest Member upsert via status:"guest" when
body.createAccount is true. Drops the route file, its
source-assertion tests, guestRegisterSchema, and its validation
coverage.
Non-members who register for an event now get a persistent identity:
with consent, a status:"guest" Member is upserted and an auth cookie is
set so the "You're Registered" state survives a page refresh.
Tiered auto-login matches passwordless-auth norms — auto-login is only
safe when the account holds no privileges:
- New email → create guest + cookie
- Returning guest → cookie
- Existing non-guest (active/pending/etc.) → attach ticket only, no
cookie, confirmation email includes a sign-in link
Guests are gated on status === "guest", so admin/middleware code that
keys on status === "active" naturally excludes them. Guests are also
treated as non-members for ticket pricing/validation to prevent picking
up member-only pricing on their second registration.