Skip to main content

Party Planner Event Access

/party/:eventId (dashboard layout) and /party/:eventId/palette (editor, deliberately outside that layout) gate the same event. Both call the same two helpers in app/lib/party/access.server.ts, in the same order.

What each function does

FunctionJob
requireValidEvent(eventId, ctx, cacheStrategy?)Loads the event or 404s. Pass CacheStrategy.none where the caller must read its own writes.
requireEventAccess(event, ctx, { returnTo })Resolves who is viewing and what they may do, or redirects. Returns { shopifyGid, permissionLevel, isDebugModeEnabled }.
resolveDebugGid(session, event)Debug mode only — views the event as the impersonated member, else the owner. Clears an impersonation whose GID left the roster.
requireCustomerGid(customerAccount, session, returnTo)Checks isLoggedIn, then reads getCustomerGid. Redirects to login if either fails.
getPermissionLevel(gid, event)Returns OWNER / ADMIN / MEMBER / NONE. The only membership check.
isEventAdmin(level)True for OWNER and ADMIN.

api.party.debug.ts owns setImpersonation. Loaders read it or clear it but never select, so a prefetch="intent" hover cannot change who you are viewing as.

The gate

  • getPermissionLevel answers membership, so no route runs a separate roster check. It runs ahead of the Admin API enrichment fan-out, so a non-member 404s without triggering it.
  • requireEventAccess takes a ValidEvent. That signature puts the 404 before authentication and a caller cannot reorder the two.
  • requireEventAccess redirects on NONE and returns Exclude<EventPermissionLevel, 'NONE'>, so callers cannot forget the check. requireEventAdmin asks only the admin question.
  • Debug mode derives the level from the impersonated GID, so an impersonated member hits exactly the denials a real member hits. It grants access without authenticating at all — a deliberate bypass for testing.

Outcomes — palette editor

VisitorDebugLevelOutcome
logged outoff302 /login?return_to=…; never calls getCustomerGid
logged in, memberoffMEMBER302 /party/:eventId
logged in, owner/adminoffOWNER / ADMIN200 editor
logged in or outon, nothing impersonatedOWNER200 editor
logged in or outon, impersonating owner/adminOWNER / ADMIN200 editor
logged in or outon, impersonating memberMEMBER302 /party/:eventId
logged in or outon, impersonating a GID off the rosterOWNERclears the impersonation, 200 editor

Debug mode ignores login state — it takes the resolveDebugGid branch, so requireCustomerGid never runs and the visitor's own identity has no effect on the outcome.