@jmruthers/pace-core 0.5.76 → 0.5.78
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/CHANGELOG.md +8 -0
- package/dist/{RBACService-C4udt_Zp.d.ts → AuthService-Df3IozMG.d.ts} +10 -118
- package/dist/{DataTable-ntgmhO2W.d.ts → DataTable-BE0OXZKQ.d.ts} +9 -2
- package/dist/{DataTable-4GAVPIEG.js → DataTable-ETGVF4Y5.js} +50 -13
- package/dist/{PublicLoadingSpinner-BiNER8F5.d.ts → PublicLoadingSpinner-CnUaz0vG.d.ts} +5 -2
- package/dist/{UnifiedAuthProvider-Bj6YCf7c.d.ts → UnifiedAuthProvider-B391Aqum.d.ts} +42 -45
- package/dist/{UnifiedAuthProvider-3NKDOSOK.js → UnifiedAuthProvider-P5SOJAQ6.js} +4 -5
- package/dist/{api-DDMUKIUD.js → api-KG4A2X7P.js} +9 -3
- package/dist/{audit-6TOCAMKO.js → audit-65VNHEV2.js} +2 -2
- package/dist/{chunk-K34IM5CT.js → chunk-2OGV6IRV.js} +196 -626
- package/dist/chunk-2OGV6IRV.js.map +1 -0
- package/dist/{chunk-NTNILOBC.js → chunk-5BO3MI5Y.js} +4 -4
- package/dist/{chunk-XLZ7U46Z.js → chunk-CVMVPYAL.js} +9 -60
- package/dist/chunk-CVMVPYAL.js.map +1 -0
- package/dist/{chunk-URUTVZ7N.js → chunk-FL4ZCQLD.js} +2 -2
- package/dist/{chunk-LW7MMEAQ.js → chunk-FT2M4R4F.js} +2 -2
- package/dist/{chunk-5BSLGBYI.js → chunk-JCQZ6LA7.js} +2 -8
- package/dist/{chunk-5BSLGBYI.js.map → chunk-JCQZ6LA7.js.map} +1 -1
- package/dist/{chunk-KHJS6VIA.js → chunk-LRQ6RBJC.js} +157 -112
- package/dist/chunk-LRQ6RBJC.js.map +1 -0
- package/dist/{chunk-WN6XJWOS.js → chunk-MNJXXD6C.js} +274 -743
- package/dist/chunk-MNJXXD6C.js.map +1 -0
- package/dist/{chunk-KK73ZB4E.js → chunk-PTR5PMPE.js} +153 -132
- package/dist/chunk-PTR5PMPE.js.map +1 -0
- package/dist/{chunk-B2WTCLCV.js → chunk-Q7APDV6H.js} +18 -8
- package/dist/chunk-Q7APDV6H.js.map +1 -0
- package/dist/{chunk-A4FUBC7B.js → chunk-QGVSOUJ2.js} +2 -4
- package/dist/{chunk-A4FUBC7B.js.map → chunk-QGVSOUJ2.js.map} +1 -1
- package/dist/{chunk-FGMFQSHX.js → chunk-S63MFSY6.js} +500 -551
- package/dist/chunk-S63MFSY6.js.map +1 -0
- package/dist/{chunk-AFGTSUAD.js → chunk-VSOKOFRF.js} +4 -4
- package/dist/chunk-WUXCWRL6.js +20 -0
- package/dist/chunk-WUXCWRL6.js.map +1 -0
- package/dist/{chunk-Y6TXWPJO.js → chunk-YVVGHRGI.js} +105 -31
- package/dist/chunk-YVVGHRGI.js.map +1 -0
- package/dist/{chunk-M5IWZRBT.js → chunk-ZMNXIJP4.js} +2187 -981
- package/dist/chunk-ZMNXIJP4.js.map +1 -0
- package/dist/components.d.ts +6 -6
- package/dist/components.js +14 -18
- package/dist/components.js.map +1 -1
- package/dist/{database-C3Szpi5J.d.ts → database-BXAfr2Y_.d.ts} +18 -0
- package/dist/hooks.d.ts +5 -5
- package/dist/hooks.js +8 -9
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +19 -27
- package/dist/index.js +21 -29
- package/dist/index.js.map +1 -1
- package/dist/{organisation-BtshODVF.d.ts → organisation-D6qRDtbF.d.ts} +1 -1
- package/dist/providers.d.ts +7 -21
- package/dist/providers.js +3 -10
- package/dist/rbac/index.d.ts +71 -221
- package/dist/rbac/index.js +15 -16
- package/dist/{types-CGX9Vyf5.d.ts → types-BDg1mAGG.d.ts} +36 -6
- package/dist/types.d.ts +3 -3
- package/dist/types.js +61 -18
- package/dist/types.js.map +1 -1
- package/dist/{unified-CM7T0aTK.d.ts → unified-DQ4VcT7H.d.ts} +1 -1
- package/dist/{usePublicRouteParams-B-CumWRc.d.ts → usePublicRouteParams-BlgwXweB.d.ts} +3 -3
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +52 -9
- package/dist/utils.js.map +1 -1
- package/docs/CONTENT_AUDIT_REPORT.md +253 -0
- package/docs/DOCUMENTATION_AUDIT.md +172 -0
- package/docs/README.md +142 -147
- package/docs/STYLE_GUIDE.md +37 -0
- package/docs/api/classes/ColumnFactory.md +17 -17
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +4 -4
- package/docs/api/classes/MissingUserContextError.md +4 -4
- package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
- package/docs/api/classes/PermissionDeniedError.md +5 -5
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +8 -8
- package/docs/api/classes/RBACCache.md +35 -5
- package/docs/api/classes/RBACEngine.md +49 -20
- package/docs/api/classes/RBACError.md +4 -4
- package/docs/api/classes/RBACNotInitializedError.md +4 -4
- 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 +4 -4
- package/docs/api/interfaces/ButtonProps.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 +11 -0
- package/docs/api/interfaces/DataTableAction.md +65 -29
- package/docs/api/interfaces/DataTableColumn.md +36 -23
- package/docs/api/interfaces/DataTableProps.md +80 -38
- package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
- package/docs/api/interfaces/EmptyStateConfig.md +5 -5
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventLogoProps.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/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 +11 -11
- package/docs/api/interfaces/NavigationContextType.md +9 -9
- 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 +7 -7
- 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 +16 -3
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
- 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/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +2 -2
- package/docs/api/interfaces/RouteConfig.md +2 -2
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.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/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +94 -521
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +16 -16
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.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/UserEventAccess.md +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +251 -269
- package/docs/api-reference/components.md +193 -0
- package/docs/api-reference/hooks.md +265 -0
- package/docs/api-reference/providers.md +6 -0
- package/docs/api-reference/types.md +6 -0
- package/docs/api-reference/utilities.md +207 -0
- package/docs/architecture/README.md +6 -0
- package/docs/{database-schema-requirements.md → architecture/database-schema-requirements.md} +6 -0
- package/docs/architecture/rbac-security-architecture.md +258 -0
- package/docs/architecture/services.md +9 -1
- package/docs/best-practices/README.md +6 -0
- package/docs/best-practices/accessibility.md +6 -0
- package/docs/{common-patterns.md → best-practices/common-patterns.md} +6 -0
- package/docs/best-practices/deployment.md +6 -0
- package/docs/best-practices/performance.md +475 -2
- package/docs/best-practices/security.md +6 -0
- package/docs/best-practices/testing.md +6 -0
- package/docs/core-concepts/authentication.md +6 -0
- package/docs/core-concepts/events.md +6 -0
- package/docs/core-concepts/organisations.md +6 -0
- package/docs/core-concepts/permissions.md +6 -0
- package/docs/core-concepts/rbac-system.md +8 -0
- package/docs/documentation-index.md +121 -182
- package/docs/{consuming-app-vite-config.md → getting-started/consuming-app-vite-config.md} +6 -0
- package/docs/getting-started/documentation-index.md +40 -0
- package/docs/getting-started/examples/README.md +878 -35
- package/docs/{faq.md → getting-started/faq.md} +7 -1
- package/docs/getting-started/installation-guide.md +6 -0
- package/docs/{quick-reference.md → getting-started/quick-reference.md} +6 -0
- package/docs/implementation-guides/app-layout.md +6 -0
- package/docs/implementation-guides/authentication.md +1021 -0
- package/docs/implementation-guides/component-styling.md +6 -0
- package/docs/implementation-guides/data-tables.md +1264 -2076
- package/docs/implementation-guides/dynamic-colors.md +6 -0
- package/docs/implementation-guides/event-theming-summary.md +6 -0
- package/docs/{file-reference-system.md → implementation-guides/file-reference-system.md} +6 -0
- package/docs/implementation-guides/file-upload-storage.md +6 -0
- package/docs/implementation-guides/forms.md +6 -0
- package/docs/implementation-guides/inactivity-tracking.md +6 -0
- package/docs/implementation-guides/navigation.md +6 -0
- package/docs/implementation-guides/organisation-security.md +6 -0
- package/docs/implementation-guides/permission-enforcement.md +6 -0
- package/docs/implementation-guides/public-pages-advanced.md +6 -0
- package/docs/implementation-guides/public-pages.md +6 -0
- package/docs/migration/MIGRATION_GUIDE.md +827 -351
- package/docs/migration/README.md +7 -1
- package/docs/migration/organisation-context-timing-fix.md +6 -0
- package/docs/migration/rbac-migration.md +44 -1
- package/docs/migration/service-architecture.md +6 -0
- package/docs/migration/v0.4.15-tailwind-scanning.md +6 -0
- package/docs/migration/v0.4.16-css-first-approach.md +6 -0
- package/docs/migration/v0.4.17-source-path-fix.md +6 -0
- package/docs/rbac/README-rbac-rls-integration.md +6 -0
- package/docs/rbac/README.md +6 -0
- package/docs/rbac/advanced-patterns.md +6 -0
- package/docs/rbac/api-reference.md +7 -1
- package/docs/rbac/breaking-changes-v3.md +222 -0
- package/docs/rbac/examples/rbac-rls-integration-example.md +6 -0
- package/docs/rbac/examples.md +6 -0
- package/docs/rbac/getting-started.md +6 -0
- package/docs/rbac/migration-guide.md +260 -0
- package/docs/rbac/quick-start.md +70 -13
- package/docs/rbac/rbac-rls-integration.md +6 -0
- package/docs/rbac/super-admin-guide.md +6 -0
- package/docs/rbac/troubleshooting.md +6 -0
- package/docs/security/README.md +6 -0
- package/docs/security/checklist.md +6 -0
- package/docs/styles/README.md +7 -1
- package/docs/{usage.md → styles/usage.md} +6 -0
- package/docs/testing/README.md +6 -0
- package/docs/{visual-testing.md → testing/visual-testing.md} +6 -0
- package/docs/troubleshooting/README.md +387 -5
- package/docs/troubleshooting/cake-page-permission-guard-issue-summary.md +6 -0
- package/docs/troubleshooting/common-issues.md +6 -0
- package/docs/troubleshooting/database-view-compatibility.md +6 -0
- package/docs/troubleshooting/organisation-context-setup.md +6 -0
- package/docs/troubleshooting/react-hooks-issue-analysis.md +6 -0
- package/docs/troubleshooting/styling-issues.md +6 -0
- package/docs/troubleshooting/tailwind-content-scanning.md +6 -0
- package/package.json +1 -1
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -1
- package/src/__tests__/helpers/test-providers.tsx +3 -53
- package/src/components/DataTable/DataTable.test.tsx +319 -0
- package/src/components/DataTable/DataTable.tsx +32 -11
- package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx.skip} +6 -4
- package/src/components/DataTable/__tests__/{DataTable.test.tsx → DataTable.test.tsx.skip} +6 -4
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +31 -9
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +601 -0
- package/src/components/DataTable/__tests__/keyboard.test.tsx +615 -0
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +639 -0
- package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx.skip +330 -0
- package/src/components/DataTable/components/AccessDeniedPage.tsx +2 -2
- package/src/components/DataTable/components/ActionButtons.tsx +88 -104
- package/src/components/DataTable/components/DataTableCore.tsx +309 -337
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +4 -2
- package/src/components/DataTable/components/DataTableModals.tsx +22 -1
- package/src/components/DataTable/components/EditableRow.tsx +69 -84
- package/src/components/DataTable/components/EmptyState.tsx +5 -1
- package/src/components/DataTable/components/ImportModal.tsx +65 -36
- package/src/components/DataTable/components/PaginationControls.tsx +40 -100
- package/src/components/DataTable/components/UnifiedTableBody.tsx +125 -148
- package/src/components/DataTable/context/DataTableContext.tsx +1 -1
- package/src/components/DataTable/core/ColumnFactory.ts +5 -0
- package/src/components/DataTable/examples/HierarchicalActionsExample.tsx +12 -10
- package/src/components/DataTable/examples/HierarchicalExample.tsx +1 -1
- package/src/components/DataTable/examples/InitialPageSizeExample.tsx +1 -0
- package/src/components/DataTable/examples/PerformanceExample.tsx +1 -0
- package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +1 -5
- package/src/components/DataTable/hooks/__tests__/useColumnVisibilityPersistence.test.ts +167 -0
- package/src/components/DataTable/hooks/index.ts +7 -0
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +32 -15
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +102 -0
- package/src/components/DataTable/hooks/useDataTableConfiguration.ts +89 -0
- package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +117 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +71 -27
- package/src/components/DataTable/hooks/useDataTableState.ts +39 -11
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +33 -0
- package/src/components/DataTable/hooks/useHierarchicalState.ts +15 -1
- package/src/components/DataTable/hooks/useKeyboardNavigation.ts +447 -0
- package/src/components/DataTable/hooks/useServerSideDataEffect.ts +94 -0
- package/src/components/DataTable/hooks/useTableColumns.ts +10 -7
- package/src/components/DataTable/hooks/useTableHandlers.ts +174 -0
- package/src/components/DataTable/index.ts +12 -3
- package/src/components/DataTable/types.ts +129 -9
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +159 -22
- package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +111 -0
- package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +15 -29
- package/src/components/DataTable/utils/a11yUtils.ts +244 -0
- package/src/components/DataTable/utils/debugTools.ts +609 -0
- package/src/components/DataTable/utils/exportUtils.ts +114 -16
- package/src/components/DataTable/utils/flexibleImport.ts +202 -32
- package/src/components/DataTable/utils/hierarchicalUtils.ts +1 -1
- package/src/components/DataTable/utils/index.ts +2 -0
- package/src/components/DataTable/utils/paginationUtils.ts +350 -0
- package/src/components/DataTable/utils/rowUtils.ts +6 -5
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +19 -24
- package/src/components/NavigationMenu/NavigationMenu.tsx +19 -8
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +1 -23
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +56 -6
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +137 -13
- package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +1 -1
- package/src/components/Select/Select.tsx +1 -0
- package/src/components/examples/PermissionExample.tsx +173 -0
- package/src/examples/CorrectPublicPageImplementation.tsx +301 -0
- package/src/examples/PublicEventPage.tsx +274 -0
- package/src/examples/PublicPageApp.tsx +308 -0
- package/src/examples/PublicPageUsageExample.tsx +216 -0
- package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +12 -1
- package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +129 -17
- package/src/hooks/__tests__/useRBAC.unit.test.ts +151 -846
- package/src/hooks/useOrganisationPermissions.test.ts +42 -18
- package/src/hooks/useOrganisationPermissions.ts +12 -6
- package/src/hooks/useOrganisationSecurity.test.ts +138 -85
- package/src/hooks/useOrganisationSecurity.ts +41 -10
- package/src/index.ts +0 -1
- package/src/providers/AuthProvider.simplified.tsx +880 -0
- package/src/providers/UnifiedAuthProvider.test.simple.tsx +8 -8
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +29 -19
- package/src/providers/index.ts +0 -1
- package/src/providers/services/EventServiceProvider.tsx +19 -15
- package/src/providers/services/InactivityServiceProvider.tsx +19 -15
- package/src/providers/services/OrganisationServiceProvider.tsx +19 -15
- package/src/providers/services/UnifiedAuthProvider.tsx +156 -127
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +1 -1
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -3
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +25 -27
- package/src/rbac/__tests__/auth-rbac-security.integration.test.tsx +313 -0
- package/src/rbac/__tests__/engine.comprehensive.test.ts +114 -348
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +28 -110
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +33 -85
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +2 -2
- package/src/rbac/adapters.tsx +26 -69
- package/src/rbac/api.test.ts +90 -27
- package/src/rbac/api.ts +61 -10
- package/src/rbac/audit.test.ts +33 -38
- package/src/rbac/audit.ts +21 -6
- package/src/rbac/cache.ts +33 -1
- package/src/rbac/components/NavigationGuard.tsx +11 -11
- package/src/rbac/components/NavigationProvider.test.tsx +11 -5
- package/src/rbac/components/NavigationProvider.tsx +37 -13
- package/src/rbac/components/PagePermissionGuard.tsx +111 -50
- package/src/rbac/components/PagePermissionProvider.tsx +5 -5
- package/src/rbac/components/PermissionEnforcer.tsx +11 -11
- package/src/rbac/components/RoleBasedRouter.tsx +5 -5
- package/src/rbac/components/SecureDataProvider.tsx +5 -5
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +8 -8
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +14 -14
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +12 -12
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +6 -6
- package/src/rbac/engine.test.simple.ts +19 -13
- package/src/rbac/engine.test.ts +1 -0
- package/src/rbac/engine.ts +330 -766
- package/src/rbac/errors.ts +156 -0
- package/src/rbac/hooks/usePermissions.ts +32 -10
- package/src/rbac/hooks/useRBAC.test.ts +126 -512
- package/src/rbac/hooks/useRBAC.ts +147 -193
- package/src/rbac/hooks/useResolvedScope.ts +12 -0
- package/src/rbac/index.ts +7 -4
- package/src/rbac/security.ts +109 -18
- package/src/rbac/types.ts +12 -1
- package/src/services/AuthService.ts +2 -15
- package/src/services/EventService.ts +43 -46
- package/src/services/OrganisationService.ts +51 -31
- package/src/services/__tests__/AuthService.test.ts +1 -1
- package/src/services/__tests__/EventService.test.ts +1 -1
- package/src/services/__tests__/OrganisationService.test.ts +1 -1
- package/src/services/base/BaseService.ts +8 -0
- package/src/styles/base.css +208 -0
- package/src/styles/semantic.css +24 -0
- package/src/types/database.generated.ts +7347 -0
- package/src/types/database.ts +20 -0
- package/src/utils/logger.ts +179 -0
- package/src/utils/organisationContext.ts +11 -4
- package/src/utils/storage/__tests__/helpers.unit.test.ts +6 -2
- package/dist/appNameResolver-UURKN7NF.js +0 -22
- package/dist/audit-6TOCAMKO.js.map +0 -1
- package/dist/chunk-B2WTCLCV.js.map +0 -1
- package/dist/chunk-FGMFQSHX.js.map +0 -1
- package/dist/chunk-K34IM5CT.js.map +0 -1
- package/dist/chunk-KHJS6VIA.js.map +0 -1
- package/dist/chunk-KK73ZB4E.js.map +0 -1
- package/dist/chunk-M5IWZRBT.js.map +0 -1
- package/dist/chunk-ULBI5JGB.js +0 -109
- package/dist/chunk-ULBI5JGB.js.map +0 -1
- package/dist/chunk-WN6XJWOS.js.map +0 -1
- package/dist/chunk-XLZ7U46Z.js.map +0 -1
- package/dist/chunk-Y6TXWPJO.js.map +0 -1
- package/docs/DOCUMENTATION_CHECKLIST.md +0 -281
- package/docs/TERMINOLOGY.md +0 -231
- package/docs/api/interfaces/RBACContextType.md +0 -468
- package/docs/api/interfaces/RBACProviderProps.md +0 -107
- package/docs/best-practices/performance-expansion.md +0 -473
- package/docs/breaking-changes.md +0 -179
- package/docs/consuming-app-example.md +0 -290
- package/docs/documentation-templates.md +0 -539
- package/docs/examples/navigation-menu-auth-fix.md +0 -344
- package/docs/getting-started/examples/basic-auth-app.md +0 -520
- package/docs/getting-started/examples/full-featured-app.md +0 -616
- package/docs/getting-started/quick-start.md +0 -376
- package/docs/implementation-guides/datatable-filtering.md +0 -313
- package/docs/implementation-guides/datatable-rbac-usage.md +0 -317
- package/docs/implementation-guides/hierarchical-datatable.md +0 -850
- package/docs/implementation-guides/large-datasets.md +0 -281
- package/docs/implementation-guides/performance.md +0 -403
- package/docs/migration/quick-migration-guide.md +0 -320
- package/docs/migration-guide.md +0 -193
- package/docs/migration-guides/unified-auth-provider-mandatory-timeouts.md +0 -226
- package/docs/performance/README.md +0 -551
- package/docs/style-guide.md +0 -964
- package/docs/troubleshooting/authentication-issues.md +0 -334
- package/docs/troubleshooting/debugging.md +0 -1117
- package/docs/troubleshooting/migration.md +0 -918
- package/src/__tests__/hooks/usePermissions.test.ts +0 -261
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +0 -574
- package/src/hooks/__tests__/ServiceHooks.test.tsx +0 -613
- package/src/hooks/services/__tests__/useServiceHooks.test.tsx +0 -137
- package/src/hooks/services/usePermissions.ts +0 -70
- package/src/hooks/services/useRBACService.ts +0 -30
- package/src/hooks/usePermissionCheck.ts +0 -150
- package/src/providers/__tests__/ServiceProviders.test.tsx +0 -477
- package/src/providers/services/RBACServiceProvider.tsx +0 -79
- package/src/rbac/__tests__/integration.authflow.test.tsx +0 -119
- package/src/rbac/__tests__/integration.navigation.test.tsx +0 -69
- package/src/rbac/__tests__/integration.securedata.test.tsx +0 -92
- package/src/rbac/__tests__/integration.smoke.test.tsx +0 -73
- package/src/rbac/providers/RBACProvider.tsx +0 -645
- package/src/rbac/providers/__tests__/RBACProvider.integration.test.tsx +0 -688
- package/src/rbac/providers/__tests__/RBACProvider.test.tsx +0 -1186
- package/src/rbac/providers/index.ts +0 -11
- package/src/services/RBACService.ts +0 -522
- package/src/services/__tests__/RBACService.test.ts +0 -492
- package/src/services/interfaces/IRBACService.ts +0 -62
- package/src/utils/appNameResolver.test 2.ts +0 -494
- /package/dist/{DataTable-4GAVPIEG.js.map → DataTable-ETGVF4Y5.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-3NKDOSOK.js.map → UnifiedAuthProvider-P5SOJAQ6.js.map} +0 -0
- /package/dist/{api-DDMUKIUD.js.map → api-KG4A2X7P.js.map} +0 -0
- /package/dist/{appNameResolver-UURKN7NF.js.map → audit-65VNHEV2.js.map} +0 -0
- /package/dist/{chunk-NTNILOBC.js.map → chunk-5BO3MI5Y.js.map} +0 -0
- /package/dist/{chunk-URUTVZ7N.js.map → chunk-FL4ZCQLD.js.map} +0 -0
- /package/dist/{chunk-LW7MMEAQ.js.map → chunk-FT2M4R4F.js.map} +0 -0
- /package/dist/{chunk-AFGTSUAD.js.map → chunk-VSOKOFRF.js.map} +0 -0
- /package/docs/{app.css.example → styles/app.css.example} +0 -0
|
@@ -4,111 +4,84 @@
|
|
|
4
4
|
* @module RBAC/Hooks
|
|
5
5
|
* @since 0.3.0
|
|
6
6
|
*
|
|
7
|
-
* A React hook that provides access to the
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* - Real-time role detection (global, organisation, event-app)
|
|
12
|
-
* - Permission checking with database validation
|
|
13
|
-
* - Hierarchical permission resolution
|
|
14
|
-
* - Loading states and error handling
|
|
15
|
-
* - Type-safe permission operations
|
|
16
|
-
* - Automatic context detection
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```tsx
|
|
20
|
-
* import { useRBAC } from '@jmruthers/pace-core/rbac';
|
|
21
|
-
*
|
|
22
|
-
* function MyComponent() {
|
|
23
|
-
* const {
|
|
24
|
-
* globalRole,
|
|
25
|
-
* organisationRole,
|
|
26
|
-
* eventAppRole,
|
|
27
|
-
* hasPermission,
|
|
28
|
-
* isSuperAdmin,
|
|
29
|
-
* isLoading,
|
|
30
|
-
* error
|
|
31
|
-
* } = useRBAC();
|
|
32
|
-
*
|
|
33
|
-
* if (isLoading) return <div>Loading permissions...</div>;
|
|
34
|
-
* if (error) return <div>Error: {error.message}</div>;
|
|
35
|
-
*
|
|
36
|
-
* return (
|
|
37
|
-
* <div>
|
|
38
|
-
* {isSuperAdmin && <AdminPanel />}
|
|
39
|
-
* {hasPermission('read', 'dashboard') && <Dashboard />}
|
|
40
|
-
* {hasPermission('create', 'events') && <CreateEventButton />}
|
|
41
|
-
* </div>
|
|
42
|
-
* );
|
|
43
|
-
* }
|
|
44
|
-
* ```
|
|
45
|
-
*
|
|
46
|
-
* @accessibility
|
|
47
|
-
* - No direct accessibility concerns (hook)
|
|
48
|
-
* - Enables accessible permission-based UI rendering
|
|
49
|
-
* - Supports screen reader friendly conditional content
|
|
50
|
-
*
|
|
51
|
-
* @security
|
|
52
|
-
* - Database-backed permission validation
|
|
53
|
-
* - Hierarchical permission resolution
|
|
54
|
-
* - Organisation context enforcement
|
|
55
|
-
* - Real-time permission updates
|
|
56
|
-
*
|
|
57
|
-
* @performance
|
|
58
|
-
* - Optimized with useMemo and useCallback
|
|
59
|
-
* - Permission caching
|
|
60
|
-
* - Minimal re-renders
|
|
61
|
-
* - Lazy loading of permissions
|
|
62
|
-
*
|
|
63
|
-
* @dependencies
|
|
64
|
-
* - React 18+ - Hooks and effects
|
|
65
|
-
* - @supabase/supabase-js - Database integration
|
|
66
|
-
* - RBAC types - Type definitions
|
|
7
|
+
* A React hook that provides access to the RBAC (Role-Based Access Control) system
|
|
8
|
+
* through the hardened RBAC engine API. The hook defers all permission and role
|
|
9
|
+
* resolution to the shared engine to ensure consistent security behaviour across
|
|
10
|
+
* applications.
|
|
67
11
|
*/
|
|
68
12
|
|
|
69
13
|
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
70
14
|
import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
|
|
71
15
|
import { useOrganisations } from '../../hooks/useOrganisations';
|
|
72
16
|
import { useEvents } from '../../hooks/useEvents';
|
|
73
|
-
import
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
17
|
+
import {
|
|
18
|
+
getPermissionMap,
|
|
19
|
+
getAccessLevel,
|
|
20
|
+
isPermittedCached,
|
|
21
|
+
resolveAppContext,
|
|
22
|
+
getRoleContext,
|
|
23
|
+
} from '../api';
|
|
24
|
+
import { getRBACLogger } from '../config';
|
|
25
|
+
import type {
|
|
26
|
+
UserRBACContext,
|
|
27
|
+
GlobalRole,
|
|
28
|
+
OrganisationRole,
|
|
29
|
+
EventAppRole,
|
|
78
30
|
Operation,
|
|
79
|
-
|
|
31
|
+
Permission,
|
|
32
|
+
Scope,
|
|
33
|
+
PermissionMap,
|
|
34
|
+
UUID,
|
|
80
35
|
} from '../types';
|
|
81
36
|
|
|
37
|
+
function mapAccessLevelToEventRole(level: string | null): EventAppRole | null {
|
|
38
|
+
switch (level) {
|
|
39
|
+
case 'viewer':
|
|
40
|
+
return 'viewer';
|
|
41
|
+
case 'participant':
|
|
42
|
+
return 'participant';
|
|
43
|
+
case 'planner':
|
|
44
|
+
return 'planner';
|
|
45
|
+
case 'admin':
|
|
46
|
+
case 'super':
|
|
47
|
+
return 'event_admin';
|
|
48
|
+
default:
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
82
53
|
export function useRBAC(pageId?: string): UserRBACContext {
|
|
83
|
-
const
|
|
54
|
+
const logger = getRBACLogger();
|
|
55
|
+
const { user, session, appName } = useUnifiedAuth();
|
|
84
56
|
const { selectedOrganisation } = useOrganisations();
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
let selectedEvent = null;
|
|
57
|
+
|
|
58
|
+
let selectedEvent: { event_id: string } | null = null;
|
|
88
59
|
try {
|
|
89
60
|
const eventsContext = useEvents();
|
|
90
61
|
selectedEvent = eventsContext.selectedEvent;
|
|
91
62
|
} catch (error) {
|
|
92
|
-
|
|
93
|
-
console.debug('useRBAC: EventProvider not available, continuing without event context');
|
|
63
|
+
logger.debug('[useRBAC] Event provider not available, continuing without event context', error);
|
|
94
64
|
}
|
|
95
|
-
|
|
96
|
-
// State
|
|
65
|
+
|
|
97
66
|
const [globalRole, setGlobalRole] = useState<GlobalRole | null>(null);
|
|
98
67
|
const [organisationRole, setOrganisationRole] = useState<OrganisationRole | null>(null);
|
|
99
68
|
const [eventAppRole, setEventAppRole] = useState<EventAppRole | null>(null);
|
|
100
|
-
const [
|
|
69
|
+
const [permissionMap, setPermissionMap] = useState<PermissionMap>({} as PermissionMap);
|
|
70
|
+
const [currentScope, setCurrentScope] = useState<Scope | null>(null);
|
|
101
71
|
const [isLoading, setIsLoading] = useState(false);
|
|
102
72
|
const [error, setError] = useState<Error | null>(null);
|
|
103
73
|
|
|
104
|
-
|
|
74
|
+
const resetState = useCallback(() => {
|
|
75
|
+
setGlobalRole(null);
|
|
76
|
+
setOrganisationRole(null);
|
|
77
|
+
setEventAppRole(null);
|
|
78
|
+
setPermissionMap({} as PermissionMap);
|
|
79
|
+
setCurrentScope(null);
|
|
80
|
+
}, []);
|
|
81
|
+
|
|
105
82
|
const loadRBACContext = useCallback(async () => {
|
|
106
|
-
if (!user || !session
|
|
107
|
-
|
|
108
|
-
setGlobalRole(null);
|
|
109
|
-
setOrganisationRole(null);
|
|
110
|
-
setEventAppRole(null);
|
|
111
|
-
setPermissions([]);
|
|
83
|
+
if (!user || !session) {
|
|
84
|
+
resetState();
|
|
112
85
|
return;
|
|
113
86
|
}
|
|
114
87
|
|
|
@@ -116,144 +89,125 @@ export function useRBAC(pageId?: string): UserRBACContext {
|
|
|
116
89
|
setError(null);
|
|
117
90
|
|
|
118
91
|
try {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
console.warn('App not found or inactive:', appName);
|
|
127
|
-
setIsLoading(false);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const app = appData[0];
|
|
132
|
-
if (!app.has_access) {
|
|
133
|
-
console.warn('User does not have access to app:', appName);
|
|
134
|
-
setIsLoading(false);
|
|
135
|
-
return;
|
|
92
|
+
let appId: UUID | undefined;
|
|
93
|
+
if (appName) {
|
|
94
|
+
const resolved = await resolveAppContext({ userId: user.id as UUID, appName });
|
|
95
|
+
if (!resolved || !resolved.hasAccess) {
|
|
96
|
+
throw new Error(`User does not have access to app "${appName}"`);
|
|
97
|
+
}
|
|
98
|
+
appId = resolved.appId;
|
|
136
99
|
}
|
|
137
100
|
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
// Set roles (take the highest permission for each type)
|
|
156
|
-
setGlobalRole(globalPerms.length > 0 ? 'super_admin' : null);
|
|
157
|
-
setOrganisationRole(orgPerms.length > 0 ? orgPerms[0].role_name as OrganisationRole : null);
|
|
158
|
-
setEventAppRole(eventPerms.length > 0 ? eventPerms[0].role_name as EventAppRole : null);
|
|
159
|
-
setPermissions(data);
|
|
160
|
-
} else {
|
|
161
|
-
setPermissions([]);
|
|
162
|
-
}
|
|
101
|
+
const scope: Scope = {
|
|
102
|
+
organisationId: selectedOrganisation?.id,
|
|
103
|
+
eventId: selectedEvent?.event_id || undefined,
|
|
104
|
+
appId,
|
|
105
|
+
};
|
|
106
|
+
setCurrentScope(scope);
|
|
107
|
+
|
|
108
|
+
const [map, roleContext, accessLevel] = await Promise.all([
|
|
109
|
+
getPermissionMap({ userId: user.id as UUID, scope }),
|
|
110
|
+
getRoleContext({ userId: user.id as UUID, scope }),
|
|
111
|
+
getAccessLevel({ userId: user.id as UUID, scope }),
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
setPermissionMap(map);
|
|
115
|
+
setGlobalRole(roleContext.globalRole);
|
|
116
|
+
setOrganisationRole(roleContext.organisationRole);
|
|
117
|
+
setEventAppRole(roleContext.eventAppRole || mapAccessLevelToEventRole(accessLevel));
|
|
163
118
|
} catch (err) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
119
|
+
const handledError = err instanceof Error ? err : new Error('Failed to load RBAC context');
|
|
120
|
+
logger.error('[useRBAC] Error loading RBAC context:', handledError);
|
|
121
|
+
setError(handledError);
|
|
122
|
+
resetState();
|
|
167
123
|
} finally {
|
|
168
124
|
setIsLoading(false);
|
|
169
125
|
}
|
|
170
|
-
}, [
|
|
126
|
+
}, [appName, logger, resetState, selectedEvent?.event_id, selectedOrganisation?.id, session, user]);
|
|
171
127
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
128
|
+
const hasPermission = useCallback(
|
|
129
|
+
async (operationOrPermission: Operation | string, targetPageId?: string): Promise<boolean> => {
|
|
130
|
+
if (!user) {
|
|
131
|
+
logger.warn('[useRBAC] Permission check attempted without authenticated user context');
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
175
134
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
135
|
+
if (!currentScope || !currentScope.organisationId) {
|
|
136
|
+
logger.error('[useRBAC] Permission check denied due to missing organisation context', {
|
|
137
|
+
hasScope: !!currentScope,
|
|
138
|
+
scope: currentScope,
|
|
139
|
+
userId: user.id,
|
|
140
|
+
permission: operationOrPermission,
|
|
141
|
+
});
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
180
144
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
145
|
+
if (globalRole === 'super_admin' || permissionMap['*']) {
|
|
146
|
+
logger.info('[useRBAC] Super admin bypass granted', {
|
|
147
|
+
userId: user.id,
|
|
148
|
+
permission: operationOrPermission,
|
|
149
|
+
});
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
187
152
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
153
|
+
let permission: Permission;
|
|
154
|
+
if (operationOrPermission.includes(':')) {
|
|
155
|
+
permission = operationOrPermission as Permission;
|
|
156
|
+
} else if (targetPageId || pageId) {
|
|
157
|
+
permission = `${operationOrPermission}:${targetPageId || pageId}` as Permission;
|
|
158
|
+
} else {
|
|
159
|
+
permission = operationOrPermission as Permission;
|
|
191
160
|
}
|
|
192
161
|
|
|
193
|
-
const
|
|
194
|
-
if (
|
|
195
|
-
|
|
162
|
+
const cachedValue = permissionMap[permission];
|
|
163
|
+
if (cachedValue === true) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
if (cachedValue === false) {
|
|
196
167
|
return false;
|
|
197
168
|
}
|
|
198
169
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
p_event_id: selectedEvent?.event_id,
|
|
205
|
-
p_organisation_id: selectedOrganisation?.id
|
|
170
|
+
return isPermittedCached({
|
|
171
|
+
userId: user.id as UUID,
|
|
172
|
+
scope: currentScope,
|
|
173
|
+
permission,
|
|
174
|
+
pageId: targetPageId ?? pageId,
|
|
206
175
|
});
|
|
176
|
+
},
|
|
177
|
+
[currentScope, globalRole, logger, pageId, permissionMap, user],
|
|
178
|
+
);
|
|
207
179
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
180
|
+
const hasGlobalPermission = useCallback(
|
|
181
|
+
(permission: string): boolean => {
|
|
182
|
+
if (globalRole === 'super_admin' || permissionMap['*']) {
|
|
183
|
+
return true;
|
|
211
184
|
}
|
|
212
185
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return false;
|
|
217
|
-
}
|
|
218
|
-
}, [user, supabase, appName, selectedEvent, selectedOrganisation, pageId, globalRole]);
|
|
186
|
+
if (permission === 'super_admin') {
|
|
187
|
+
return globalRole === 'super_admin';
|
|
188
|
+
}
|
|
219
189
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (globalRole === 'super_admin') {
|
|
224
|
-
return true;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Check specific global roles
|
|
228
|
-
if (permission === 'super_admin') {
|
|
229
|
-
return globalRole === 'super_admin';
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (permission === 'org_admin') {
|
|
233
|
-
return organisationRole === 'org_admin' || globalRole === 'super_admin';
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return false;
|
|
237
|
-
}, [globalRole, organisationRole]);
|
|
190
|
+
if (permission === 'org_admin') {
|
|
191
|
+
return organisationRole === 'org_admin';
|
|
192
|
+
}
|
|
238
193
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const isEventAdmin = useMemo(() => eventAppRole === 'event_admin', [eventAppRole]);
|
|
243
|
-
const canManageOrganisation = useMemo(() =>
|
|
244
|
-
isSuperAdmin || isOrgAdmin, [isSuperAdmin, isOrgAdmin]
|
|
245
|
-
);
|
|
246
|
-
const canManageEvent = useMemo(() =>
|
|
247
|
-
isSuperAdmin || isEventAdmin, [isSuperAdmin, isEventAdmin]
|
|
194
|
+
return permissionMap[permission as Permission] === true;
|
|
195
|
+
},
|
|
196
|
+
[globalRole, organisationRole, permissionMap],
|
|
248
197
|
);
|
|
249
198
|
|
|
250
|
-
|
|
199
|
+
const isSuperAdmin = useMemo(() => globalRole === 'super_admin' || permissionMap['*'] === true, [globalRole, permissionMap]);
|
|
200
|
+
const isOrgAdmin = useMemo(() => organisationRole === 'org_admin' || isSuperAdmin, [organisationRole, isSuperAdmin]);
|
|
201
|
+
const isEventAdmin = useMemo(() => eventAppRole === 'event_admin' || isSuperAdmin, [eventAppRole, isSuperAdmin]);
|
|
202
|
+
const canManageOrganisation = useMemo(() => isSuperAdmin || organisationRole === 'org_admin', [isSuperAdmin, organisationRole]);
|
|
203
|
+
const canManageEvent = useMemo(() => isSuperAdmin || eventAppRole === 'event_admin', [isSuperAdmin, eventAppRole]);
|
|
204
|
+
|
|
251
205
|
useEffect(() => {
|
|
252
206
|
loadRBACContext();
|
|
253
207
|
}, [loadRBACContext]);
|
|
254
208
|
|
|
255
209
|
return {
|
|
256
|
-
user,
|
|
210
|
+
user,
|
|
257
211
|
globalRole,
|
|
258
212
|
organisationRole,
|
|
259
213
|
eventAppRole,
|
|
@@ -265,6 +219,6 @@ export function useRBAC(pageId?: string): UserRBACContext {
|
|
|
265
219
|
canManageOrganisation,
|
|
266
220
|
canManageEvent,
|
|
267
221
|
isLoading,
|
|
268
|
-
error
|
|
222
|
+
error,
|
|
269
223
|
};
|
|
270
224
|
}
|
|
@@ -144,8 +144,17 @@ export function useResolvedScope({
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
// Debug logging
|
|
148
|
+
console.log('[useResolvedScope] Attempting to resolve scope:', {
|
|
149
|
+
selectedOrganisationId,
|
|
150
|
+
selectedEventId,
|
|
151
|
+
appId: appId || 'NOT RESOLVED YET',
|
|
152
|
+
resolveStep: 'initial'
|
|
153
|
+
});
|
|
154
|
+
|
|
147
155
|
// If we have both organisation and event, use them directly
|
|
148
156
|
if (selectedOrganisationId && selectedEventId) {
|
|
157
|
+
console.log('[useResolvedScope] Resolving with both org and event');
|
|
149
158
|
if (!cancelled) {
|
|
150
159
|
setResolvedScope({
|
|
151
160
|
organisationId: selectedOrganisationId,
|
|
@@ -159,6 +168,7 @@ export function useResolvedScope({
|
|
|
159
168
|
|
|
160
169
|
// If we only have organisation, use it
|
|
161
170
|
if (selectedOrganisationId) {
|
|
171
|
+
console.log('[useResolvedScope] Resolving with organisation only');
|
|
162
172
|
if (!cancelled) {
|
|
163
173
|
setResolvedScope({
|
|
164
174
|
organisationId: selectedOrganisationId,
|
|
@@ -173,6 +183,7 @@ export function useResolvedScope({
|
|
|
173
183
|
// If we only have event, resolve organisation from event
|
|
174
184
|
if (selectedEventId && supabase) {
|
|
175
185
|
try {
|
|
186
|
+
console.log('[useResolvedScope] Resolving from event:', { selectedEventId, appId });
|
|
176
187
|
const eventScope = await createScopeFromEvent(supabase, selectedEventId, appId);
|
|
177
188
|
if (!eventScope) {
|
|
178
189
|
console.error('[useResolvedScope] Could not resolve organization from event context');
|
|
@@ -183,6 +194,7 @@ export function useResolvedScope({
|
|
|
183
194
|
}
|
|
184
195
|
return;
|
|
185
196
|
}
|
|
197
|
+
console.log('[useResolvedScope] Resolved from event:', eventScope);
|
|
186
198
|
// Preserve the resolved app ID
|
|
187
199
|
if (!cancelled) {
|
|
188
200
|
setResolvedScope({
|
package/src/rbac/index.ts
CHANGED
|
@@ -71,14 +71,15 @@ export {
|
|
|
71
71
|
createRBACEngine,
|
|
72
72
|
} from './engine';
|
|
73
73
|
|
|
74
|
-
// Components
|
|
74
|
+
// Components
|
|
75
75
|
export * from './components';
|
|
76
76
|
|
|
77
|
-
// Hooks
|
|
77
|
+
// Hooks
|
|
78
78
|
export * from './hooks';
|
|
79
79
|
|
|
80
|
-
// Providers -
|
|
81
|
-
|
|
80
|
+
// Providers - Note: RBACProvider was removed as part of the refactoring
|
|
81
|
+
// Users should now use useRBAC() hook directly without a provider wrapper
|
|
82
|
+
// export * from './providers';
|
|
82
83
|
|
|
83
84
|
// Adapters
|
|
84
85
|
export {
|
|
@@ -97,6 +98,8 @@ export {
|
|
|
97
98
|
export {
|
|
98
99
|
getAccessLevel,
|
|
99
100
|
getPermissionMap,
|
|
101
|
+
resolveAppContext,
|
|
102
|
+
getRoleContext,
|
|
100
103
|
isPermitted,
|
|
101
104
|
isPermittedCached,
|
|
102
105
|
hasPermission,
|
package/src/rbac/security.ts
CHANGED
|
@@ -176,7 +176,17 @@ export class RBACSecurityValidator {
|
|
|
176
176
|
* @param event - Security event details
|
|
177
177
|
*/
|
|
178
178
|
static logSecurityEvent(event: {
|
|
179
|
-
type:
|
|
179
|
+
type:
|
|
180
|
+
| 'permission_denied'
|
|
181
|
+
| 'invalid_input'
|
|
182
|
+
| 'rate_limit_exceeded'
|
|
183
|
+
| 'suspicious_activity'
|
|
184
|
+
| 'network_error'
|
|
185
|
+
| 'database_error'
|
|
186
|
+
| 'validation_error'
|
|
187
|
+
| 'rate_limit_error'
|
|
188
|
+
| 'authentication_error'
|
|
189
|
+
| 'unknown_error';
|
|
180
190
|
userId: UUID;
|
|
181
191
|
details: Record<string, any>;
|
|
182
192
|
timestamp?: Date;
|
|
@@ -205,10 +215,17 @@ export class RBACSecurityValidator {
|
|
|
205
215
|
case 'permission_denied':
|
|
206
216
|
return 'low';
|
|
207
217
|
case 'invalid_input':
|
|
208
|
-
return 'medium';
|
|
209
218
|
case 'rate_limit_exceeded':
|
|
219
|
+
case 'rate_limit_error':
|
|
220
|
+
case 'network_error':
|
|
210
221
|
return 'medium';
|
|
222
|
+
case 'validation_error':
|
|
223
|
+
return 'high';
|
|
224
|
+
case 'authentication_error':
|
|
225
|
+
case 'database_error':
|
|
226
|
+
return 'critical';
|
|
211
227
|
case 'suspicious_activity':
|
|
228
|
+
case 'unknown_error':
|
|
212
229
|
return 'high';
|
|
213
230
|
default:
|
|
214
231
|
return 'low';
|
|
@@ -243,7 +260,7 @@ export const DEFAULT_SECURITY_CONFIG: RBACSecurityConfig = {
|
|
|
243
260
|
*/
|
|
244
261
|
export interface SecurityContext {
|
|
245
262
|
userId: UUID;
|
|
246
|
-
organisationId
|
|
263
|
+
organisationId?: UUID; // Optional for operations without organisation context
|
|
247
264
|
ipAddress?: string;
|
|
248
265
|
userAgent?: string;
|
|
249
266
|
timestamp: Date;
|
|
@@ -257,6 +274,17 @@ export class RBACSecurityMiddleware {
|
|
|
257
274
|
|
|
258
275
|
constructor(config: RBACSecurityConfig = DEFAULT_SECURITY_CONFIG) {
|
|
259
276
|
this.config = config;
|
|
277
|
+
this._startCleanupInterval();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Start periodic cleanup of expired entries
|
|
282
|
+
*/
|
|
283
|
+
private _startCleanupInterval(): void {
|
|
284
|
+
// Clear expired entries every 5 minutes
|
|
285
|
+
setInterval(() => {
|
|
286
|
+
this.clearExpiredEntries();
|
|
287
|
+
}, 5 * 60 * 1000);
|
|
260
288
|
}
|
|
261
289
|
|
|
262
290
|
/**
|
|
@@ -271,30 +299,33 @@ export class RBACSecurityMiddleware {
|
|
|
271
299
|
}> {
|
|
272
300
|
const errors: string[] = [];
|
|
273
301
|
|
|
274
|
-
|
|
275
|
-
return { isValid: true, errors: [] };
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Validate user ID
|
|
302
|
+
// Core validations are always enforced regardless of configuration
|
|
279
303
|
if (!RBACSecurityValidator.validateUserId(context.userId)) {
|
|
280
304
|
errors.push('Invalid user ID format');
|
|
281
305
|
}
|
|
282
306
|
|
|
283
|
-
|
|
284
|
-
if (!RBACSecurityValidator.validateUUID(context.organisationId)) {
|
|
307
|
+
if (context.organisationId && !RBACSecurityValidator.validateUUID(context.organisationId)) {
|
|
285
308
|
errors.push('Invalid organisation ID format');
|
|
286
309
|
}
|
|
287
310
|
|
|
288
|
-
// Validate permission if present
|
|
289
311
|
if (input.permission && !RBACSecurityValidator.validatePermission(input.permission)) {
|
|
290
312
|
errors.push('Invalid permission format');
|
|
291
313
|
}
|
|
292
314
|
|
|
293
|
-
// Validate scope if present
|
|
294
315
|
if (input.scope && !RBACSecurityValidator.validateScope(input.scope)) {
|
|
295
316
|
errors.push('Invalid scope format');
|
|
296
317
|
}
|
|
297
318
|
|
|
319
|
+
if (this.config.enableInputValidation) {
|
|
320
|
+
if (context.ipAddress && typeof context.ipAddress !== 'string') {
|
|
321
|
+
errors.push('Invalid IP address format');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (context.userAgent && typeof context.userAgent !== 'string') {
|
|
325
|
+
errors.push('Invalid user agent format');
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
298
329
|
// Log suspicious activity
|
|
299
330
|
if (errors.length > 0) {
|
|
300
331
|
RBACSecurityValidator.logSecurityEvent({
|
|
@@ -323,18 +354,78 @@ export class RBACSecurityMiddleware {
|
|
|
323
354
|
return { isAllowed: true, remaining: this.config.maxPermissionChecksPerMinute };
|
|
324
355
|
}
|
|
325
356
|
|
|
326
|
-
//
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
);
|
|
357
|
+
// Implementation: In-memory rate limiting with sliding window
|
|
358
|
+
// For production, consider using Redis or Supabase Edge Functions
|
|
359
|
+
const isAllowed = await this._checkRateLimitInternal(context.userId);
|
|
360
|
+
|
|
361
|
+
const remaining = isAllowed ? this.config.maxPermissionChecksPerMinute - this._getRequestCount(context.userId) : 0;
|
|
331
362
|
|
|
332
363
|
return {
|
|
333
364
|
isAllowed,
|
|
334
|
-
remaining:
|
|
365
|
+
remaining: Math.max(0, remaining),
|
|
335
366
|
};
|
|
336
367
|
}
|
|
337
368
|
|
|
369
|
+
/**
|
|
370
|
+
* In-memory rate limiting cache (sliding window)
|
|
371
|
+
* Note: For production, this should use Redis or Supabase Edge Functions
|
|
372
|
+
*/
|
|
373
|
+
private rateLimitCache = new Map<UUID, Array<{ timestamp: number }>>();
|
|
374
|
+
|
|
375
|
+
private async _checkRateLimitInternal(userId: UUID): Promise<boolean> {
|
|
376
|
+
const now = Date.now();
|
|
377
|
+
const windowMs = 60 * 1000; // 1 minute window
|
|
378
|
+
|
|
379
|
+
// Get or create rate limit entries for this user
|
|
380
|
+
const entries = this.rateLimitCache.get(userId) || [];
|
|
381
|
+
|
|
382
|
+
// Remove entries outside the time window
|
|
383
|
+
const validEntries = entries.filter(entry => now - entry.timestamp < windowMs);
|
|
384
|
+
|
|
385
|
+
// Check if user exceeded rate limit
|
|
386
|
+
const requestCount = validEntries.length;
|
|
387
|
+
const isAllowed = requestCount < this.config.maxPermissionChecksPerMinute;
|
|
388
|
+
|
|
389
|
+
// If allowed, add current request
|
|
390
|
+
if (isAllowed) {
|
|
391
|
+
validEntries.push({ timestamp: now });
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Update cache
|
|
395
|
+
this.rateLimitCache.set(userId, validEntries);
|
|
396
|
+
|
|
397
|
+
return isAllowed;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private _getRequestCount(userId: UUID): number {
|
|
401
|
+
const now = Date.now();
|
|
402
|
+
const windowMs = 60 * 1000; // 1 minute window
|
|
403
|
+
|
|
404
|
+
const entries = this.rateLimitCache.get(userId) || [];
|
|
405
|
+
const validEntries = entries.filter(entry => now - entry.timestamp < windowMs);
|
|
406
|
+
|
|
407
|
+
return validEntries.length;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Clear old rate limit entries to prevent memory leaks
|
|
412
|
+
* Should be called periodically (e.g., every 5 minutes)
|
|
413
|
+
*/
|
|
414
|
+
private clearExpiredEntries(): void {
|
|
415
|
+
const now = Date.now();
|
|
416
|
+
const windowMs = 60 * 1000; // 1 minute window
|
|
417
|
+
|
|
418
|
+
for (const [userId, entries] of this.rateLimitCache.entries()) {
|
|
419
|
+
const validEntries = entries.filter(entry => now - entry.timestamp < windowMs);
|
|
420
|
+
|
|
421
|
+
if (validEntries.length === 0) {
|
|
422
|
+
this.rateLimitCache.delete(userId);
|
|
423
|
+
} else {
|
|
424
|
+
this.rateLimitCache.set(userId, validEntries);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
338
429
|
/**
|
|
339
430
|
* Sanitize input data
|
|
340
431
|
* @param input - Input to sanitize
|