@jmruthers/pace-core 0.5.142 → 0.5.144
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-TUJSIWX6.js → chunk-FC46D3KC.js} +49 -20
- package/dist/chunk-FC46D3KC.js.map +1 -0
- package/dist/components.js +1 -1
- package/dist/index.js +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +2 -2
- package/docs/rbac/README.md +9 -4
- package/package.json +1 -1
- package/src/components/NavigationMenu/NavigationMenu.tsx +82 -21
- package/dist/chunk-TUJSIWX6.js.map +0 -1
package/docs/api/modules.md
CHANGED
package/docs/rbac/README.md
CHANGED
|
@@ -188,15 +188,18 @@ if (appConfig?.requires_event) {
|
|
|
188
188
|
|
|
189
189
|
```tsx
|
|
190
190
|
import { UnifiedAuthProvider, OrganisationProvider } from '@jmruthers/pace-core';
|
|
191
|
-
import { setRBACAppName } from '@jmruthers/pace-core/utils';
|
|
192
191
|
|
|
193
|
-
// CRITICAL:
|
|
192
|
+
// CRITICAL: App name is automatically resolved from environment variable
|
|
193
|
+
// Make sure VITE_APP_NAME (or NEXT_PUBLIC_APP_NAME) is set in your .env file
|
|
194
|
+
// Optionally, you can also call setRBACAppName() to override:
|
|
195
|
+
// import { setRBACAppName } from '@jmruthers/pace-core/utils';
|
|
196
|
+
// setRBACAppName('your-app-name');
|
|
197
|
+
|
|
194
198
|
const APP_NAME = import.meta.env.VITE_APP_NAME;
|
|
195
|
-
setRBACAppName(APP_NAME);
|
|
196
199
|
|
|
197
200
|
function App() {
|
|
198
201
|
return (
|
|
199
|
-
<UnifiedAuthProvider supabaseClient={supabase}>
|
|
202
|
+
<UnifiedAuthProvider supabaseClient={supabase} appName={APP_NAME}>
|
|
200
203
|
<OrganisationProvider>
|
|
201
204
|
<YourApp />
|
|
202
205
|
</OrganisationProvider>
|
|
@@ -205,6 +208,8 @@ function App() {
|
|
|
205
208
|
}
|
|
206
209
|
```
|
|
207
210
|
|
|
211
|
+
**Note**: The `appName` prop on `UnifiedAuthProvider` is used for other features. RBAC resolution automatically uses the environment variable (`VITE_APP_NAME` or `NEXT_PUBLIC_APP_NAME`). You can optionally call `setRBACAppName()` if you need to override the environment variable.
|
|
212
|
+
|
|
208
213
|
### 2. Protect Pages with PagePermissionGuard
|
|
209
214
|
|
|
210
215
|
**⚠️ CRITICAL: Always use `PagePermissionGuard` for page-level access. This is the ONLY way to ensure permissions are checked correctly.**
|
package/package.json
CHANGED
|
@@ -437,14 +437,24 @@ export const NavigationMenu = React.forwardRef<
|
|
|
437
437
|
logger.warn('NavigationMenu', 'useRBAC not available, permission filtering disabled');
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
+
// Get event context state for checking readiness
|
|
441
|
+
// Store the raw value to check if it's undefined (tests without event provider)
|
|
442
|
+
const eventLoadingRaw = authContext?.eventLoading;
|
|
443
|
+
const eventLoading = eventLoadingRaw ?? false;
|
|
444
|
+
const selectedEvent = authContext?.selectedEvent || null;
|
|
445
|
+
// Check org context readiness: use isContextReady if available, otherwise fall back to checking selectedOrganisation
|
|
446
|
+
// This handles both production (with isContextReady) and test scenarios (without it)
|
|
447
|
+
const orgContextReady = authContext?.isContextReady ?? (authContext?.selectedOrganisation?.id ? true : false);
|
|
448
|
+
|
|
440
449
|
// Get resolved scope for permission checks
|
|
450
|
+
// Note: Always call useResolvedScope (hooks must be called unconditionally)
|
|
451
|
+
// When filterByPermissions is false, we'll simply ignore the resolved scope
|
|
441
452
|
const { supabase } = authContext || {};
|
|
442
453
|
const { selectedOrganisation } = authContext || {};
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
selectedEventId: selectedEvent?.event_id || null
|
|
454
|
+
const { resolvedScope, isLoading: scopeLoading, error: scopeError } = useResolvedScope({
|
|
455
|
+
supabase: filterByPermissions ? (supabase || null) : null,
|
|
456
|
+
selectedOrganisationId: filterByPermissions ? (selectedOrganisation?.id || null) : null,
|
|
457
|
+
selectedEventId: filterByPermissions ? (selectedEvent?.event_id || null) : null
|
|
448
458
|
});
|
|
449
459
|
|
|
450
460
|
// Stabilize scope object to prevent unnecessary permission refetches
|
|
@@ -457,12 +467,30 @@ export const NavigationMenu = React.forwardRef<
|
|
|
457
467
|
appId: undefined
|
|
458
468
|
});
|
|
459
469
|
|
|
460
|
-
//
|
|
470
|
+
// Build scope from resolvedScope if available, otherwise fall back to context values
|
|
471
|
+
// This handles the case where useResolvedScope errored initially but context is now ready
|
|
472
|
+
const effectiveScope = React.useMemo(() => {
|
|
473
|
+
if (!scopeLoading && resolvedScope?.organisationId) {
|
|
474
|
+
// Use resolved scope if available
|
|
475
|
+
return resolvedScope;
|
|
476
|
+
} else if (!scopeLoading && selectedOrganisation?.id) {
|
|
477
|
+
// Fall back to building scope from context if resolvedScope is null but context is ready
|
|
478
|
+
// This handles the case where useResolvedScope errored but context is now available
|
|
479
|
+
return {
|
|
480
|
+
organisationId: selectedOrganisation.id,
|
|
481
|
+
eventId: selectedEvent?.event_id || undefined,
|
|
482
|
+
appId: undefined // App ID will be resolved by usePermissions if needed
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
return null;
|
|
486
|
+
}, [scopeLoading, resolvedScope, selectedOrganisation?.id, selectedEvent?.event_id]);
|
|
487
|
+
|
|
488
|
+
// Only update stable scope when effective scope is available AND values actually changed
|
|
461
489
|
// This prevents triggering permission refetches during scope transitions
|
|
462
|
-
if (
|
|
463
|
-
const newOrgId =
|
|
464
|
-
const newEventId =
|
|
465
|
-
const newAppId =
|
|
490
|
+
if (effectiveScope?.organisationId) {
|
|
491
|
+
const newOrgId = effectiveScope.organisationId;
|
|
492
|
+
const newEventId = effectiveScope.eventId;
|
|
493
|
+
const newAppId = effectiveScope.appId;
|
|
466
494
|
|
|
467
495
|
// Only update if values actually changed
|
|
468
496
|
if (stableScopeRef.current.organisationId !== newOrgId ||
|
|
@@ -474,7 +502,7 @@ export const NavigationMenu = React.forwardRef<
|
|
|
474
502
|
appId: newAppId
|
|
475
503
|
};
|
|
476
504
|
}
|
|
477
|
-
} else if (!scopeLoading && !
|
|
505
|
+
} else if (!scopeLoading && !effectiveScope) {
|
|
478
506
|
// Only reset if we had a previous value and scope is resolved - don't clear during loading
|
|
479
507
|
if (stableScopeRef.current.organisationId !== '') {
|
|
480
508
|
stableScopeRef.current = {
|
|
@@ -504,12 +532,36 @@ export const NavigationMenu = React.forwardRef<
|
|
|
504
532
|
// Security: If filtering is enabled but we're missing required context or still loading, show NO items
|
|
505
533
|
// This prevents security risk of showing items before permissions are verified
|
|
506
534
|
if (filterByPermissions) {
|
|
507
|
-
//
|
|
508
|
-
|
|
535
|
+
// CRITICAL: Wait for BOTH organisation AND event context to be ready before checking permissions
|
|
536
|
+
// This prevents the error "No organisation or event context available" when event context is still loading
|
|
537
|
+
const isOrgContextReady = orgContextReady && selectedOrganisation?.id;
|
|
538
|
+
// Event context is ready when not loading
|
|
539
|
+
// If eventLoadingRaw is undefined (tests without event provider), consider it ready
|
|
540
|
+
// Only wait for event context if we're actually loading events
|
|
541
|
+
const isEventContextReady = eventLoadingRaw === undefined ? true : !eventLoading;
|
|
542
|
+
|
|
543
|
+
// Check if we have valid context for permission checking
|
|
544
|
+
// Use actual context values, not just resolvedScope, because resolvedScope might be null
|
|
545
|
+
// if useResolvedScope errored initially but context is now ready
|
|
546
|
+
const hasValidContext = isOrgContextReady && isEventContextReady;
|
|
547
|
+
|
|
548
|
+
// If scope is still loading or we don't have valid context yet, show nothing
|
|
549
|
+
// BUT: If scope errored but context is now ready, retry (don't block forever)
|
|
550
|
+
const shouldWaitForScope = scopeLoading || (!hasValidContext);
|
|
551
|
+
const shouldRetryAfterError = scopeError && hasValidContext && !scopeLoading;
|
|
552
|
+
|
|
553
|
+
// During initial load or when scope/context is loading, show nothing
|
|
554
|
+
if (!authContext || !rbacContext || (shouldWaitForScope && !shouldRetryAfterError)) {
|
|
509
555
|
// Still loading - show nothing to prevent security risk
|
|
556
|
+
// Note: We check both org and event context readiness to prevent premature permission checks
|
|
557
|
+
// Exception: If scope errored but context is now ready, we'll retry below
|
|
510
558
|
return [];
|
|
511
559
|
}
|
|
512
560
|
|
|
561
|
+
// If scope errored but context is now ready, we can proceed with permission checks
|
|
562
|
+
// The resolvedScope might be null, but we'll use the actual context values
|
|
563
|
+
// usePermissions will handle the scope resolution internally
|
|
564
|
+
|
|
513
565
|
// During permission refetch (after initial load), preserve previous items if we have them
|
|
514
566
|
// This prevents navigation from disappearing when switching events
|
|
515
567
|
if (permissionsLoading) {
|
|
@@ -709,26 +761,35 @@ export const NavigationMenu = React.forwardRef<
|
|
|
709
761
|
permissionMap,
|
|
710
762
|
hasAnyPermission,
|
|
711
763
|
scopeLoading,
|
|
764
|
+
scopeError,
|
|
712
765
|
permissionsLoading,
|
|
713
766
|
resolvedScope,
|
|
714
|
-
|
|
767
|
+
effectiveScope,
|
|
768
|
+
auditLog,
|
|
769
|
+
// Add event context state to dependencies so we re-check permissions when event context becomes available
|
|
770
|
+
eventLoadingRaw,
|
|
771
|
+
eventLoading,
|
|
772
|
+
selectedEvent,
|
|
773
|
+
orgContextReady,
|
|
774
|
+
selectedOrganisation?.id
|
|
715
775
|
]);
|
|
716
776
|
|
|
717
777
|
// Log navigation access attempts for debugging
|
|
718
778
|
React.useEffect(() => {
|
|
719
779
|
if (auditLog && authContext) {
|
|
780
|
+
// Find the current navigation item to log its actual permissions
|
|
781
|
+
const currentItem = items?.find(item => item.href === currentPath || item.id === 'navigation-menu');
|
|
720
782
|
logger.debug('NavigationMenu', 'Navigation access attempt:', {
|
|
721
|
-
itemId: 'navigation-menu',
|
|
722
|
-
label: 'Navigation Menu',
|
|
783
|
+
itemId: currentItem?.id || 'navigation-menu',
|
|
784
|
+
label: currentItem?.label || 'Navigation Menu',
|
|
723
785
|
href: currentPath,
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
accessLevel: null,
|
|
786
|
+
permissions: currentItem?.permissions || null,
|
|
787
|
+
roles: currentItem?.roles || null,
|
|
788
|
+
accessLevel: currentItem?.accessLevel || null,
|
|
728
789
|
timestamp: new Date().toISOString()
|
|
729
790
|
});
|
|
730
791
|
}
|
|
731
|
-
}, [auditLog, authContext, currentPath]);
|
|
792
|
+
}, [auditLog, authContext, currentPath, items]);
|
|
732
793
|
|
|
733
794
|
|
|
734
795
|
// Handle keyboard navigation for hierarchical mode
|