Skip to content

fix(compose): mount gates wait for an unresolved session - #819

Closed
adenarc wants to merge 1 commit into
NEARBuilders:mainfrom
adenarc:fix/mount-gates-unresolved-session
Closed

adenarc wants to merge 1 commit into
NEARBuilders:mainfrom
adenarc:fix/mount-gates-unresolved-session

Conversation

@adenarc

@adenarc adenarc commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #818. Found via NEARBuilders/citynode.app#482.

On a client-rendered full page load the root context's session is still undefined when the mount gates run, so they treated a signed-in visitor as signed out. The admin gate also dropped the return path, so an admin reloading /admin landed on /dashboard.

  • HostContext gets an optional resolveSession. When session/user are unresolved (undefined), the authenticated and admin gates await it before deciding. A null session is still an authoritative sign-out and isn't read again.
  • The client router supplies resolveSession from the existing session query (sessionQueryOptions), so the gate and requireSession share one cached fetch.
  • The admin gate now redirects to /login?redirect=<path> like the session gate.
  • The server router is unchanged: SSR already gets the session from the host.

Tests

  • tests/ui/manifest-construct.test.ts: gates await an unresolved session, trust a resolved or null one without reading again, and the admin gate keeps the return path. The existing gate tests now await the async gate. The new gate cases fail on main.
  • tests/ui/router-client-session.test.ts: the client router's resolveSession reads through and caches the session query.

Checks run locally

  • pnpm run typecheck:packages and biome check pass.
  • every-plugin tests: 206/206.
  • everything-dev tests/ui + tests/unit: all pass apart from 3 (codegen-artifact-gen, process-kill) that fail on main here too.
  • oxlint stops locally on a config parse error in node_modules/@effect/tsgo; CI's Lint and Type Check passes.

Browser: checked on a local citynode.app stack (rc.39, client-rendered bos dev) with this branch's built every-plugin and everything-dev dist swapped in, signed in with a real NEAR wallet sign-in as a platform admin.

main (rc.39) this PR
signed in, full load of /admin ends on /dashboard stays on /admin
signed in, full load of /admin/nodes ends on /dashboard stays on /admin/nodes
signed in, full load + reload of /dashboard/node bounces through /login, back to the page stays on the page
signed out, /admin/nodes /login?redirect=%2Fdashboard /login?redirect=%2Fadmin%2Fnodes

before: admin reload ends on /dashboard
after: admin reload stays on /admin
after: signed-out admin keeps the return path

Not checked: a server-rendered deploy (that path is unchanged here).

On a client-rendered full page load the host hands the browser no
session, so the root context's session is undefined when the mount
gates run. The gates read that as signed out: the authenticated mount
bounced through /login, and the admin mount redirected to /login with
no return path, so signed-in admins landed on /dashboard.

Gates now await an optional resolveSession from the router context when
the session is unresolved (undefined), and the client router supplies
it from the session query. A null session is still an authoritative
sign-out. The admin gate keeps the ?redirect= return path.
@adenarc
adenarc marked this pull request as ready for review November 7, 2026 05:09
@elliotBraem

Copy link
Copy Markdown
Contributor

Thanks for the sharp reproduction, the failing-on-main tests, and the fix — this is absorbed into #825, which takes a different seam after design review: the mount registry drops gate kinds entirely and constructTree now takes guards — ordinary beforeLoads keyed by mount id — supplied by the composing framework (default: authenticated → requireSession, admin → requireAdmin). The mount guards therefore resolve through the session query cache and inherit the auth policy (tri-state reads, banned handling, pluginHref login redirects) from auth-guards.ts instead of reimplementing resolution behind an optional resolveSession on the router context — an app-authored router factory without the closure would have silently reintroduced the bug. Your tri-state HostContext typing, the admin ?redirect= fix, and the test shapes all carried over (credited via Co-authored-by). Closing as superseded — #825 fixes #818.

@elliotBraem elliotBraem closed this Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mount gates redirect signed-in visitors on a client-rendered full page load

2 participants