Skip to content

🛂 fix: Gate App Layout Pessimistically While Capabilities Load - #101

Open
dustinhealy wants to merge 1 commit into
mainfrom
fix-pessimistic-access-gate
Open

🛂 fix: Gate App Layout Pessimistically While Capabilities Load#101
dustinhealy wants to merge 1 commit into
mainfrom
fix-pessimistic-access-gate

Conversation

@dustinhealy

Copy link
Copy Markdown
Contributor

Summary

The _app layout guard renders AccessDenied only after the effective-capabilities query has settled:

if (!isLoading && !isError && !hasCapability(SystemCapabilities.ACCESS_ADMIN)) {
  return <AccessDenied />;
}

This has two problems for a logged-in user who lacks ACCESS_ADMIN:

  • Authorized-UI flash: while the query is in flight, the full authenticated chrome (sidebar, header, active route) renders for the duration of the round trip, then flips to 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.
  • Fail-open on error: if the capabilities query errors (anything not mapped by the 401/403/404/503 patterns in useCapabilities), !isError prevents 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, and AccessDenied only on a settled missing capability. The gate now fails closed in every non-success state.

Testing

tsc --noEmit clean, eslint clean, full test suite (799 tests) passing, from-scratch production build succeeds.

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.
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.

1 participant