Repository navigation
🛂 fix: Gate App Layout Pessimistically While Capabilities Load - #101
dustinhealy wants to merge 1 commit into
Conversation
The layout guard only rendered AccessDenied once the capabilities query had settled, so during the fetch an unauthorized user briefly saw the full authenticated chrome before the denial appeared. A query error skipped the guard entirely and left the layout rendered indefinitely. Applies the convention the child routes already use: render nothing while loading, PermissionsUnavailable on error, and AccessDenied only on a settled missing capability.
|
@codex review |
|
Codex Review: Didn't find any major issues. Hooray! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
…n Errors (#185) * 🛂 fix: Decide Admin Access Before the Panel Renders and Fail Closed on Errors The /_app layout read the viewer's capabilities with a component query and showed AccessDenied only once that query had succeeded. Until then it rendered the whole panel: sidebar, header and the routed page. A signed-in user without access:admin, such as one whose grant was revoked within the 60-second session revalidation window, saw the panel before the denial. When the capabilities request failed with an error the hook does not map, such as a 500, the panel stayed up for them. The /_app loader now loads the capabilities through capabilitiesQueryOptions(userId), the query useCapabilities reads, so the layout first renders with the answer. An admin gets the panel, anyone else gets AccessDenied, and a failed load shows PermissionsUnavailable with its Reload button. The AppPending shell covers the wait. A failed background refetch keeps the last answer, so an admin stays in the panel; a refetch without access:admin still takes the panel away. Admins also get their full sidebar on first paint instead of Configuration and Access appearing after load. The unused effectiveCapabilitiesOptions, which cached a different shape under the same query key, is removed. Supersedes #109 and #101 by @dustinhealy: the loader approach comes from #109 and the fail-closed states from #101. * 🛂 fix: Fetch Stale Capabilities Again Before the Panel Renders * 🛂 fix: Fail Closed When the Loader Can't Re-Verify Stale Capabilities
|
Thanks @dustinhealy for this. Both problems still reproduced on main:
#185, now merged, fixes both by combining this PR with #109 on current main. It keeps this PR's fail-closed idea: when the capabilities can't be loaded, the layout shows "Could not verify permissions" with a Reload button instead of the panel. Only Closing this in favor of #185. If you see a case it misses, please comment or open an issue. |
Summary
The
_applayout guard rendersAccessDeniedonly after the effective-capabilities query has settled:This has two problems for a logged-in user who lacks
ACCESS_ADMIN:AccessDenied. Child data queries are still rejected server-side, so no data is exposed, but the unauthorized user briefly sees the admin panel's layout and navigation.useCapabilities),!isErrorprevents the guard from ever tripping and the layout stays rendered indefinitely.Fix
Applies the same convention the child routes (
access.tsx, etc.) already use, at the layout level: render nothing while loading,PermissionsUnavailable(with its reload affordance) on error, andAccessDeniedonly on a settled missing capability. The gate now fails closed in every non-success state.Testing
tsc --noEmitclean,eslintclean, full test suite (799 tests) passing, from-scratch production build succeeds.