From a65bf4d46aab9e668ca11ed078b09efac4a9e3df Mon Sep 17 00:00:00 2001 From: Anso Date: Wed, 29 Jul 2026 15:51:27 -0400 Subject: [PATCH] fix(rbac): handle permission metadata failures (#1735) --- docs/features/rbac.mdx | 3 + frontend/src/App.tsx | 16 +++++- frontend/src/components/EditorLayout.tsx | 12 ++-- .../components/EditorLayout/ViewRouter.tsx | 3 +- .../src/components/settings/SectionGate.tsx | 6 +- frontend/src/context/AuthContext.test.tsx | 53 ++++++++++++++++++ frontend/src/context/AuthContext.tsx | 55 ++++++++++++------- frontend/src/lib/routing/reachability.test.ts | 7 +++ 8 files changed, 127 insertions(+), 28 deletions(-) create mode 100644 frontend/src/context/AuthContext.test.tsx diff --git a/docs/features/rbac.mdx b/docs/features/rbac.mdx index bd978f68..8efb4f74 100644 --- a/docs/features/rbac.mdx +++ b/docs/features/rbac.mdx @@ -225,6 +225,9 @@ Entries include the acting user, IP address, HTTP method and path, response stat ## Troubleshooting + + Sencho could not verify your current permissions. Existing pages stay open, but changes remain disabled until verification succeeds. Select **Retry** in the notification bar. If the notice returns, check that the Sencho instance is reachable and sign in again if your session has expired. + The Users entry is hidden in two cases. **One,** you are signed in as a non-admin (Viewer, Deployer, Auditor): the entry is admin-only. **Two,** you have a remote node selected: the panel is hub-only and is hidden in the sidebar when any remote node is active. Switch back to the local node via the node switcher in the masthead. diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index aea66650..461b5779 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -11,6 +11,8 @@ import { MfaChallenge } from './components/MfaChallenge'; import { DeployFeedbackProvider } from './context/DeployFeedbackContext'; import { DeployFeedbackPortal } from './components/DeployFeedbackPortal'; import { ToastContainer } from './components/ui/toast'; +import { Button } from './components/ui/button'; +import { AlertCircle, RefreshCw } from 'lucide-react'; /** Gates framer-motion animations on the "Reduced motion" appearance setting. * 'always' suppresses transform/layout motion app-wide; 'user' defers to the OS @@ -27,7 +29,7 @@ function MotionProvider({ children }: { children: ReactNode }) { } function AppContent() { - const { appStatus, isAuthenticated, needsSetup, completeSetup } = useAuth(); + const { appStatus, isAuthenticated, needsSetup, completeSetup, permissionsStatus, retryPermissions } = useAuth(); if (appStatus === 'loading') { return ( @@ -53,6 +55,18 @@ function AppContent() { + {permissionsStatus === 'error' && ( +
+
+ + Permission controls are unavailable. Changes remain disabled until access is verified. +
+ +
+ )} {/* Portal lives inside LicenseProvider so the editor surface and its portalled overlays can read license state via useLicense(). diff --git a/frontend/src/components/EditorLayout.tsx b/frontend/src/components/EditorLayout.tsx index 88a94c67..8c73458b 100644 --- a/frontend/src/components/EditorLayout.tsx +++ b/frontend/src/components/EditorLayout.tsx @@ -84,7 +84,7 @@ const NetworkingView = lazy(() => import('./networking/NetworkingView').then(m = const GlobalObservabilityView = lazy(() => import('./GlobalObservabilityView').then(m => ({ default: m.GlobalObservabilityView }))); export default function EditorLayout() { - const { isAdmin, can, permissions } = useAuth(); + const { isAdmin, can, permissions, permissionsStatus } = useAuth(); const { status: trivy } = useTrivyStatus(); const { runWithLog, panelState, logRows, healthGate } = useDeployFeedback(); @@ -837,12 +837,14 @@ export default function EditorLayout() { } }, [permissions, can]); - const createStackSlot = can('stack:create') ? ( + const canCreateStack = can('stack:create'); + const createStackSlot = (canCreateStack || permissionsStatus === 'loading') ? ( <>