@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
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
createAuditManager,
|
|
3
3
|
emitAuditEvent,
|
|
4
4
|
setGlobalAuditManager
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-Q7APDV6H.js";
|
|
6
6
|
|
|
7
7
|
// src/rbac/types.ts
|
|
8
8
|
var RBACError = class extends Error {
|
|
@@ -13,6 +13,16 @@ var RBACError = class extends Error {
|
|
|
13
13
|
this.name = "RBACError";
|
|
14
14
|
}
|
|
15
15
|
};
|
|
16
|
+
var PermissionDeniedError = class extends RBACError {
|
|
17
|
+
constructor(permission, context) {
|
|
18
|
+
super(
|
|
19
|
+
`Permission denied: ${permission}`,
|
|
20
|
+
"PERMISSION_DENIED",
|
|
21
|
+
{ permission, ...context }
|
|
22
|
+
);
|
|
23
|
+
this.name = "PermissionDeniedError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
16
26
|
var OrganisationContextRequiredError = class extends RBACError {
|
|
17
27
|
constructor() {
|
|
18
28
|
super(
|
|
@@ -31,6 +41,25 @@ var RBACNotInitializedError = class extends RBACError {
|
|
|
31
41
|
this.name = "RBACNotInitializedError";
|
|
32
42
|
}
|
|
33
43
|
};
|
|
44
|
+
var InvalidScopeError = class extends RBACError {
|
|
45
|
+
constructor(scope, reason) {
|
|
46
|
+
super(
|
|
47
|
+
`Invalid scope provided: ${JSON.stringify(scope)}. ${reason}`,
|
|
48
|
+
"INVALID_SCOPE",
|
|
49
|
+
{ scope, reason }
|
|
50
|
+
);
|
|
51
|
+
this.name = "InvalidScopeError";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var MissingUserContextError = class extends RBACError {
|
|
55
|
+
constructor() {
|
|
56
|
+
super(
|
|
57
|
+
"User context is required but not available. Make sure to wrap your app with an auth provider.",
|
|
58
|
+
"MISSING_USER_CONTEXT"
|
|
59
|
+
);
|
|
60
|
+
this.name = "MissingUserContextError";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
34
63
|
|
|
35
64
|
// src/rbac/cache.ts
|
|
36
65
|
var RBACCache = class {
|
|
@@ -122,7 +151,30 @@ var RBACCache = class {
|
|
|
122
151
|
};
|
|
123
152
|
}
|
|
124
153
|
/**
|
|
125
|
-
* Generate cache key for permission check
|
|
154
|
+
* Generate cache key for permission check (simplified signature)
|
|
155
|
+
*
|
|
156
|
+
* @param userId - User ID
|
|
157
|
+
* @param permission - Permission string
|
|
158
|
+
* @param organisationId - Organisation ID (optional)
|
|
159
|
+
* @param eventId - Event ID (optional)
|
|
160
|
+
* @param appId - App ID (optional)
|
|
161
|
+
* @param pageId - Page ID (optional)
|
|
162
|
+
* @returns String cache key
|
|
163
|
+
*/
|
|
164
|
+
static generateKey(userId, permission, organisationId, eventId, appId, pageId) {
|
|
165
|
+
const parts = [
|
|
166
|
+
"perm",
|
|
167
|
+
userId,
|
|
168
|
+
organisationId || "null",
|
|
169
|
+
eventId || "null",
|
|
170
|
+
appId || "null",
|
|
171
|
+
permission || "null",
|
|
172
|
+
pageId || "null"
|
|
173
|
+
];
|
|
174
|
+
return parts.join(":");
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Generate cache key for permission check (object signature)
|
|
126
178
|
*
|
|
127
179
|
* @param key - Permission cache key object
|
|
128
180
|
* @returns String cache key
|
|
@@ -413,6 +465,124 @@ function initializeCacheInvalidation(supabase) {
|
|
|
413
465
|
return globalCacheInvalidationManager;
|
|
414
466
|
}
|
|
415
467
|
|
|
468
|
+
// src/rbac/errors.ts
|
|
469
|
+
var RATE_LIMIT_STATUS_CODES = /* @__PURE__ */ new Set([429]);
|
|
470
|
+
var AUTH_STATUS_CODES = /* @__PURE__ */ new Set([401]);
|
|
471
|
+
var AUTHZ_STATUS_CODES = /* @__PURE__ */ new Set([403]);
|
|
472
|
+
function normalize(value) {
|
|
473
|
+
if (!value) {
|
|
474
|
+
return "";
|
|
475
|
+
}
|
|
476
|
+
if (typeof value === "string") {
|
|
477
|
+
return value.toLowerCase();
|
|
478
|
+
}
|
|
479
|
+
if (value instanceof Error && typeof value.message === "string") {
|
|
480
|
+
return value.message.toLowerCase();
|
|
481
|
+
}
|
|
482
|
+
return String(value).toLowerCase();
|
|
483
|
+
}
|
|
484
|
+
function categorizeError(error) {
|
|
485
|
+
if (error instanceof PermissionDeniedError) {
|
|
486
|
+
return "authorization_error" /* AUTHORIZATION */;
|
|
487
|
+
}
|
|
488
|
+
if (error instanceof OrganisationContextRequiredError || error instanceof InvalidScopeError) {
|
|
489
|
+
return "validation_error" /* VALIDATION */;
|
|
490
|
+
}
|
|
491
|
+
if (error instanceof MissingUserContextError) {
|
|
492
|
+
return "authentication_error" /* AUTHENTICATION */;
|
|
493
|
+
}
|
|
494
|
+
if (error instanceof RBACError) {
|
|
495
|
+
switch (error.code) {
|
|
496
|
+
case "PERMISSION_DENIED":
|
|
497
|
+
return "authorization_error" /* AUTHORIZATION */;
|
|
498
|
+
case "ORGANISATION_CONTEXT_REQUIRED":
|
|
499
|
+
case "INVALID_SCOPE":
|
|
500
|
+
return "validation_error" /* VALIDATION */;
|
|
501
|
+
case "MISSING_USER_CONTEXT":
|
|
502
|
+
return "authentication_error" /* AUTHENTICATION */;
|
|
503
|
+
default:
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (error && typeof error === "object") {
|
|
508
|
+
const status = error.status;
|
|
509
|
+
if (typeof status === "number") {
|
|
510
|
+
if (RATE_LIMIT_STATUS_CODES.has(status)) {
|
|
511
|
+
return "rate_limit_error" /* RATE_LIMIT */;
|
|
512
|
+
}
|
|
513
|
+
if (AUTH_STATUS_CODES.has(status)) {
|
|
514
|
+
return "authentication_error" /* AUTHENTICATION */;
|
|
515
|
+
}
|
|
516
|
+
if (AUTHZ_STATUS_CODES.has(status)) {
|
|
517
|
+
return "authorization_error" /* AUTHORIZATION */;
|
|
518
|
+
}
|
|
519
|
+
if (status >= 500) {
|
|
520
|
+
return "database_error" /* DATABASE */;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const codeValue = normalize(error.code);
|
|
524
|
+
if (codeValue) {
|
|
525
|
+
if (codeValue.includes("network")) {
|
|
526
|
+
return "network_error" /* NETWORK */;
|
|
527
|
+
}
|
|
528
|
+
if (codeValue.includes("postgres") || codeValue.includes("database") || codeValue.includes("db")) {
|
|
529
|
+
return "database_error" /* DATABASE */;
|
|
530
|
+
}
|
|
531
|
+
if (codeValue.includes("rate")) {
|
|
532
|
+
return "rate_limit_error" /* RATE_LIMIT */;
|
|
533
|
+
}
|
|
534
|
+
if (codeValue.includes("auth")) {
|
|
535
|
+
return "authentication_error" /* AUTHENTICATION */;
|
|
536
|
+
}
|
|
537
|
+
if (codeValue.includes("permission")) {
|
|
538
|
+
return "authorization_error" /* AUTHORIZATION */;
|
|
539
|
+
}
|
|
540
|
+
if (codeValue.includes("scope") || codeValue.includes("invalid")) {
|
|
541
|
+
return "validation_error" /* VALIDATION */;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
const message = normalize(error);
|
|
546
|
+
if (message.includes("timeout") || message.includes("network") || message.includes("fetch")) {
|
|
547
|
+
return "network_error" /* NETWORK */;
|
|
548
|
+
}
|
|
549
|
+
if (message.includes("postgres") || message.includes("database") || message.includes("connection")) {
|
|
550
|
+
return "database_error" /* DATABASE */;
|
|
551
|
+
}
|
|
552
|
+
if (message.includes("rate limit") || message.includes("too many requests")) {
|
|
553
|
+
return "rate_limit_error" /* RATE_LIMIT */;
|
|
554
|
+
}
|
|
555
|
+
if (message.includes("permission") || message.includes("forbidden")) {
|
|
556
|
+
return "authorization_error" /* AUTHORIZATION */;
|
|
557
|
+
}
|
|
558
|
+
if (message.includes("auth") || message.includes("token") || message.includes("session")) {
|
|
559
|
+
return "authentication_error" /* AUTHENTICATION */;
|
|
560
|
+
}
|
|
561
|
+
if (message.includes("invalid") || message.includes("scope") || message.includes("validation")) {
|
|
562
|
+
return "validation_error" /* VALIDATION */;
|
|
563
|
+
}
|
|
564
|
+
return "unknown_error" /* UNKNOWN */;
|
|
565
|
+
}
|
|
566
|
+
function mapErrorCategoryToSecurityEventType(category) {
|
|
567
|
+
switch (category) {
|
|
568
|
+
case "authorization_error" /* AUTHORIZATION */:
|
|
569
|
+
return "permission_denied";
|
|
570
|
+
case "network_error" /* NETWORK */:
|
|
571
|
+
return "network_error";
|
|
572
|
+
case "database_error" /* DATABASE */:
|
|
573
|
+
return "database_error";
|
|
574
|
+
case "validation_error" /* VALIDATION */:
|
|
575
|
+
return "validation_error";
|
|
576
|
+
case "rate_limit_error" /* RATE_LIMIT */:
|
|
577
|
+
return "rate_limit_error";
|
|
578
|
+
case "authentication_error" /* AUTHENTICATION */:
|
|
579
|
+
return "authentication_error";
|
|
580
|
+
case "unknown_error" /* UNKNOWN */:
|
|
581
|
+
default:
|
|
582
|
+
return "unknown_error";
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
416
586
|
// src/rbac/security.ts
|
|
417
587
|
var RBACSecurityValidator = class {
|
|
418
588
|
/**
|
|
@@ -555,10 +725,17 @@ var RBACSecurityValidator = class {
|
|
|
555
725
|
case "permission_denied":
|
|
556
726
|
return "low";
|
|
557
727
|
case "invalid_input":
|
|
558
|
-
return "medium";
|
|
559
728
|
case "rate_limit_exceeded":
|
|
729
|
+
case "rate_limit_error":
|
|
730
|
+
case "network_error":
|
|
560
731
|
return "medium";
|
|
732
|
+
case "validation_error":
|
|
733
|
+
return "high";
|
|
734
|
+
case "authentication_error":
|
|
735
|
+
case "database_error":
|
|
736
|
+
return "critical";
|
|
561
737
|
case "suspicious_activity":
|
|
738
|
+
case "unknown_error":
|
|
562
739
|
return "high";
|
|
563
740
|
default:
|
|
564
741
|
return "low";
|
|
@@ -574,7 +751,21 @@ var DEFAULT_SECURITY_CONFIG = {
|
|
|
574
751
|
};
|
|
575
752
|
var RBACSecurityMiddleware = class {
|
|
576
753
|
constructor(config = DEFAULT_SECURITY_CONFIG) {
|
|
754
|
+
/**
|
|
755
|
+
* In-memory rate limiting cache (sliding window)
|
|
756
|
+
* Note: For production, this should use Redis or Supabase Edge Functions
|
|
757
|
+
*/
|
|
758
|
+
this.rateLimitCache = /* @__PURE__ */ new Map();
|
|
577
759
|
this.config = config;
|
|
760
|
+
this._startCleanupInterval();
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Start periodic cleanup of expired entries
|
|
764
|
+
*/
|
|
765
|
+
_startCleanupInterval() {
|
|
766
|
+
setInterval(() => {
|
|
767
|
+
this.clearExpiredEntries();
|
|
768
|
+
}, 5 * 60 * 1e3);
|
|
578
769
|
}
|
|
579
770
|
/**
|
|
580
771
|
* Validate input before processing
|
|
@@ -584,13 +775,10 @@ var RBACSecurityMiddleware = class {
|
|
|
584
775
|
*/
|
|
585
776
|
async validateInput(input, context) {
|
|
586
777
|
const errors = [];
|
|
587
|
-
if (!this.config.enableInputValidation) {
|
|
588
|
-
return { isValid: true, errors: [] };
|
|
589
|
-
}
|
|
590
778
|
if (!RBACSecurityValidator.validateUserId(context.userId)) {
|
|
591
779
|
errors.push("Invalid user ID format");
|
|
592
780
|
}
|
|
593
|
-
if (!RBACSecurityValidator.validateUUID(context.organisationId)) {
|
|
781
|
+
if (context.organisationId && !RBACSecurityValidator.validateUUID(context.organisationId)) {
|
|
594
782
|
errors.push("Invalid organisation ID format");
|
|
595
783
|
}
|
|
596
784
|
if (input.permission && !RBACSecurityValidator.validatePermission(input.permission)) {
|
|
@@ -599,6 +787,14 @@ var RBACSecurityMiddleware = class {
|
|
|
599
787
|
if (input.scope && !RBACSecurityValidator.validateScope(input.scope)) {
|
|
600
788
|
errors.push("Invalid scope format");
|
|
601
789
|
}
|
|
790
|
+
if (this.config.enableInputValidation) {
|
|
791
|
+
if (context.ipAddress && typeof context.ipAddress !== "string") {
|
|
792
|
+
errors.push("Invalid IP address format");
|
|
793
|
+
}
|
|
794
|
+
if (context.userAgent && typeof context.userAgent !== "string") {
|
|
795
|
+
errors.push("Invalid user agent format");
|
|
796
|
+
}
|
|
797
|
+
}
|
|
602
798
|
if (errors.length > 0) {
|
|
603
799
|
RBACSecurityValidator.logSecurityEvent({
|
|
604
800
|
type: "invalid_input",
|
|
@@ -620,15 +816,49 @@ var RBACSecurityMiddleware = class {
|
|
|
620
816
|
if (!this.config.enableRateLimiting) {
|
|
621
817
|
return { isAllowed: true, remaining: this.config.maxPermissionChecksPerMinute };
|
|
622
818
|
}
|
|
623
|
-
const isAllowed = await
|
|
624
|
-
|
|
625
|
-
"permission_check"
|
|
626
|
-
);
|
|
819
|
+
const isAllowed = await this._checkRateLimitInternal(context.userId);
|
|
820
|
+
const remaining = isAllowed ? this.config.maxPermissionChecksPerMinute - this._getRequestCount(context.userId) : 0;
|
|
627
821
|
return {
|
|
628
822
|
isAllowed,
|
|
629
|
-
remaining:
|
|
823
|
+
remaining: Math.max(0, remaining)
|
|
630
824
|
};
|
|
631
825
|
}
|
|
826
|
+
async _checkRateLimitInternal(userId) {
|
|
827
|
+
const now = Date.now();
|
|
828
|
+
const windowMs = 60 * 1e3;
|
|
829
|
+
const entries = this.rateLimitCache.get(userId) || [];
|
|
830
|
+
const validEntries = entries.filter((entry) => now - entry.timestamp < windowMs);
|
|
831
|
+
const requestCount = validEntries.length;
|
|
832
|
+
const isAllowed = requestCount < this.config.maxPermissionChecksPerMinute;
|
|
833
|
+
if (isAllowed) {
|
|
834
|
+
validEntries.push({ timestamp: now });
|
|
835
|
+
}
|
|
836
|
+
this.rateLimitCache.set(userId, validEntries);
|
|
837
|
+
return isAllowed;
|
|
838
|
+
}
|
|
839
|
+
_getRequestCount(userId) {
|
|
840
|
+
const now = Date.now();
|
|
841
|
+
const windowMs = 60 * 1e3;
|
|
842
|
+
const entries = this.rateLimitCache.get(userId) || [];
|
|
843
|
+
const validEntries = entries.filter((entry) => now - entry.timestamp < windowMs);
|
|
844
|
+
return validEntries.length;
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Clear old rate limit entries to prevent memory leaks
|
|
848
|
+
* Should be called periodically (e.g., every 5 minutes)
|
|
849
|
+
*/
|
|
850
|
+
clearExpiredEntries() {
|
|
851
|
+
const now = Date.now();
|
|
852
|
+
const windowMs = 60 * 1e3;
|
|
853
|
+
for (const [userId, entries] of this.rateLimitCache.entries()) {
|
|
854
|
+
const validEntries = entries.filter((entry) => now - entry.timestamp < windowMs);
|
|
855
|
+
if (validEntries.length === 0) {
|
|
856
|
+
this.rateLimitCache.delete(userId);
|
|
857
|
+
} else {
|
|
858
|
+
this.rateLimitCache.set(userId, validEntries);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
632
862
|
/**
|
|
633
863
|
* Sanitize input data
|
|
634
864
|
* @param input - Input to sanitize
|
|
@@ -649,65 +879,73 @@ var RBACEngine = class {
|
|
|
649
879
|
/**
|
|
650
880
|
* Check if a user has a specific permission
|
|
651
881
|
*
|
|
882
|
+
* This method now delegates to the database RPC function for all the heavy lifting.
|
|
883
|
+
*
|
|
652
884
|
* @param input - Permission check input
|
|
653
|
-
* @param securityContext -
|
|
885
|
+
* @param securityContext - Security context for validation (required)
|
|
654
886
|
* @returns Promise resolving to permission result
|
|
655
887
|
*/
|
|
656
888
|
async isPermitted(input, securityContext) {
|
|
657
889
|
const startTime = Date.now();
|
|
658
890
|
const { userId, permission, scope, pageId } = input;
|
|
659
891
|
let cacheHit = false;
|
|
660
|
-
let cacheSource = "
|
|
892
|
+
let cacheSource = "rpc";
|
|
661
893
|
try {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
return false;
|
|
671
|
-
}
|
|
672
|
-
const rateLimit = await this.securityMiddleware.checkRateLimit(securityContext);
|
|
673
|
-
if (!rateLimit.isAllowed) {
|
|
674
|
-
RBACSecurityValidator.logSecurityEvent({
|
|
675
|
-
type: "rate_limit_exceeded",
|
|
676
|
-
userId,
|
|
677
|
-
details: { remaining: rateLimit.remaining }
|
|
678
|
-
});
|
|
679
|
-
return false;
|
|
680
|
-
}
|
|
894
|
+
const validation = await this.securityMiddleware.validateInput(input, securityContext);
|
|
895
|
+
if (!validation.isValid) {
|
|
896
|
+
RBACSecurityValidator.logSecurityEvent({
|
|
897
|
+
type: "invalid_input",
|
|
898
|
+
userId,
|
|
899
|
+
details: { errors: validation.errors, input: JSON.stringify(input) }
|
|
900
|
+
});
|
|
901
|
+
return false;
|
|
681
902
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
903
|
+
const rateLimit = await this.securityMiddleware.checkRateLimit(securityContext);
|
|
904
|
+
if (!rateLimit.isAllowed) {
|
|
905
|
+
RBACSecurityValidator.logSecurityEvent({
|
|
906
|
+
type: "rate_limit_exceeded",
|
|
907
|
+
userId,
|
|
908
|
+
details: { remaining: rateLimit.remaining }
|
|
909
|
+
});
|
|
910
|
+
return false;
|
|
911
|
+
}
|
|
912
|
+
if (!RBACSecurityValidator.validateUserId(userId)) {
|
|
913
|
+
RBACSecurityValidator.logSecurityEvent({
|
|
914
|
+
type: "invalid_input",
|
|
915
|
+
userId,
|
|
916
|
+
details: { error: "Invalid user ID format" }
|
|
917
|
+
});
|
|
918
|
+
return false;
|
|
919
|
+
}
|
|
920
|
+
if (!RBACSecurityValidator.validatePermission(permission)) {
|
|
921
|
+
RBACSecurityValidator.logSecurityEvent({
|
|
922
|
+
type: "invalid_input",
|
|
923
|
+
userId,
|
|
924
|
+
details: { error: "Invalid permission format", permission }
|
|
925
|
+
});
|
|
926
|
+
return false;
|
|
927
|
+
}
|
|
928
|
+
if (!RBACSecurityValidator.validateScope(scope)) {
|
|
929
|
+
RBACSecurityValidator.logSecurityEvent({
|
|
930
|
+
type: "invalid_input",
|
|
931
|
+
userId,
|
|
932
|
+
details: { error: "Invalid scope format", scope }
|
|
933
|
+
});
|
|
934
|
+
return false;
|
|
707
935
|
}
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
936
|
+
const cacheKey = RBACCache.generateKey(
|
|
937
|
+
userId,
|
|
938
|
+
permission,
|
|
939
|
+
scope.organisationId,
|
|
940
|
+
scope.eventId,
|
|
941
|
+
scope.appId,
|
|
942
|
+
pageId
|
|
943
|
+
);
|
|
944
|
+
const cached = rbacCache.get(cacheKey);
|
|
945
|
+
if (cached !== null) {
|
|
946
|
+
cacheHit = true;
|
|
947
|
+
cacheSource = "memory";
|
|
948
|
+
const duration2 = Date.now() - startTime;
|
|
711
949
|
if (scope.organisationId) {
|
|
712
950
|
const resolvedPageId = await this.resolvePageId(pageId, scope.appId);
|
|
713
951
|
await emitAuditEvent({
|
|
@@ -718,600 +956,281 @@ var RBACEngine = class {
|
|
|
718
956
|
appId: scope.appId,
|
|
719
957
|
pageId: resolvedPageId,
|
|
720
958
|
permission,
|
|
721
|
-
decision:
|
|
959
|
+
decision: cached,
|
|
722
960
|
source: "api",
|
|
723
|
-
|
|
724
|
-
|
|
961
|
+
duration_ms: duration2,
|
|
962
|
+
cache_hit: true,
|
|
963
|
+
cache_source: "memory"
|
|
725
964
|
});
|
|
726
965
|
}
|
|
727
|
-
return
|
|
966
|
+
return cached;
|
|
728
967
|
}
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
968
|
+
const { data, error } = await this.supabase.rpc("rbac_check_permission_simplified", {
|
|
969
|
+
p_user_id: userId,
|
|
970
|
+
p_permission: permission,
|
|
971
|
+
p_organisation_id: scope.organisationId || void 0,
|
|
972
|
+
p_event_id: scope.eventId || void 0,
|
|
973
|
+
p_app_id: scope.appId || void 0,
|
|
974
|
+
p_page_id: pageId || void 0
|
|
975
|
+
});
|
|
976
|
+
if (error) {
|
|
977
|
+
console.error("[RBACEngine] RPC error:", error);
|
|
978
|
+
const category = categorizeError(error);
|
|
979
|
+
const eventType = mapErrorCategoryToSecurityEventType(category);
|
|
980
|
+
const errorDetails = error;
|
|
981
|
+
RBACSecurityValidator.logSecurityEvent({
|
|
982
|
+
type: eventType,
|
|
983
|
+
userId,
|
|
984
|
+
details: {
|
|
985
|
+
error: errorDetails?.message || "RPC call failed",
|
|
986
|
+
code: errorDetails?.code,
|
|
987
|
+
hint: errorDetails?.hint,
|
|
988
|
+
details: errorDetails?.details,
|
|
741
989
|
permission,
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
reason: "invalid_context_requirements",
|
|
745
|
-
app_requires_event: scope.appId ? await this.getAppConfig(scope.appId).then((config) => config?.requires_event) : null
|
|
746
|
-
}
|
|
747
|
-
});
|
|
748
|
-
}
|
|
749
|
-
return false;
|
|
750
|
-
}
|
|
751
|
-
const grants = await this.collectActiveGrants(userId, validatedScope, pageId);
|
|
752
|
-
console.log("[RBACEngine] Collected grants:", grants);
|
|
753
|
-
const denies = grants.filter((g) => g.type === "deny");
|
|
754
|
-
console.log("[RBACEngine] Deny grants:", denies);
|
|
755
|
-
for (const deny of denies) {
|
|
756
|
-
const matches = this.permissionMatches(deny.permission, permission);
|
|
757
|
-
console.log("[RBACEngine] Checking deny:", {
|
|
758
|
-
denyPermission: deny.permission,
|
|
759
|
-
requestedPermission: permission,
|
|
760
|
-
matches
|
|
761
|
-
});
|
|
762
|
-
if (matches) {
|
|
763
|
-
const duration = Date.now() - startTime;
|
|
764
|
-
console.log("[RBACEngine] Permission DENIED by explicit deny rule");
|
|
765
|
-
if (scope.organisationId) {
|
|
766
|
-
const resolvedPageId = await this.resolvePageId(pageId, scope.appId);
|
|
767
|
-
await emitAuditEvent({
|
|
768
|
-
type: "permission_denied",
|
|
769
|
-
userId,
|
|
770
|
-
organisationId: scope.organisationId,
|
|
771
|
-
eventId: scope.eventId,
|
|
772
|
-
appId: scope.appId,
|
|
773
|
-
pageId: resolvedPageId,
|
|
774
|
-
permission,
|
|
775
|
-
source: "api"
|
|
776
|
-
});
|
|
777
|
-
}
|
|
778
|
-
return false;
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
const allows = grants.filter((g) => g.type === "allow");
|
|
782
|
-
console.log("[RBACEngine] Allow grants:", allows);
|
|
783
|
-
let hasPermission2 = false;
|
|
784
|
-
const scopeOrder = ["page", "eventApp", "organisation", "global"];
|
|
785
|
-
for (const scopeType of scopeOrder) {
|
|
786
|
-
const scopeAllows = allows.filter((g) => g.scope === scopeType);
|
|
787
|
-
console.log(`[RBACEngine] Checking ${scopeType} allows:`, scopeAllows);
|
|
788
|
-
for (const allow of scopeAllows) {
|
|
789
|
-
console.log(`[RBACEngine] About to check permission match for ${scopeType}:`, {
|
|
790
|
-
allowPermission: allow.permission,
|
|
791
|
-
requestedPermission: permission,
|
|
792
|
-
scopeType
|
|
793
|
-
});
|
|
794
|
-
const matches = this.permissionMatches(allow.permission, permission);
|
|
795
|
-
console.log(`[RBACEngine] Permission match result:`, {
|
|
796
|
-
scopeType,
|
|
797
|
-
allowPermission: allow.permission,
|
|
798
|
-
requestedPermission: permission,
|
|
799
|
-
matches
|
|
800
|
-
});
|
|
801
|
-
if (matches) {
|
|
802
|
-
console.log(`[RBACEngine] Permission GRANTED by ${scopeType} allow rule`);
|
|
803
|
-
hasPermission2 = true;
|
|
804
|
-
break;
|
|
990
|
+
scope: JSON.stringify(scope),
|
|
991
|
+
category
|
|
805
992
|
}
|
|
806
|
-
}
|
|
807
|
-
|
|
993
|
+
});
|
|
994
|
+
return false;
|
|
808
995
|
}
|
|
809
|
-
const
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
userId,
|
|
813
|
-
permission,
|
|
814
|
-
pageId,
|
|
815
|
-
hasPermission: hasPermission2,
|
|
816
|
-
grantsCount: grants.length,
|
|
817
|
-
allowsCount: allows.length,
|
|
818
|
-
deniesCount: denies.length,
|
|
819
|
-
duration: _duration
|
|
820
|
-
});
|
|
996
|
+
const hasPermission2 = data === true;
|
|
997
|
+
rbacCache.set(cacheKey, hasPermission2, 6e4);
|
|
998
|
+
const duration = Date.now() - startTime;
|
|
821
999
|
if (scope.organisationId) {
|
|
822
1000
|
const resolvedPageId = await this.resolvePageId(pageId, scope.appId);
|
|
823
1001
|
await emitAuditEvent({
|
|
824
|
-
type: "permission_check",
|
|
1002
|
+
type: hasPermission2 ? "permission_check" : "permission_denied",
|
|
825
1003
|
userId,
|
|
826
1004
|
organisationId: scope.organisationId,
|
|
827
1005
|
eventId: scope.eventId,
|
|
828
1006
|
appId: scope.appId,
|
|
829
1007
|
pageId: resolvedPageId,
|
|
830
1008
|
permission,
|
|
831
|
-
decision:
|
|
1009
|
+
decision: hasPermission2,
|
|
832
1010
|
source: "api",
|
|
833
|
-
duration_ms:
|
|
1011
|
+
duration_ms: duration,
|
|
834
1012
|
cache_hit: cacheHit,
|
|
835
1013
|
cache_source: cacheSource
|
|
836
1014
|
});
|
|
837
1015
|
}
|
|
838
|
-
return
|
|
1016
|
+
return hasPermission2;
|
|
839
1017
|
} catch (error) {
|
|
1018
|
+
const category = categorizeError(error);
|
|
1019
|
+
const eventType = mapErrorCategoryToSecurityEventType(category);
|
|
1020
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
840
1021
|
RBACSecurityValidator.logSecurityEvent({
|
|
841
|
-
type:
|
|
1022
|
+
type: eventType,
|
|
842
1023
|
userId,
|
|
843
1024
|
details: {
|
|
844
|
-
error:
|
|
1025
|
+
error: errorMessage,
|
|
845
1026
|
permission,
|
|
846
|
-
scope: JSON.stringify(scope)
|
|
1027
|
+
scope: JSON.stringify(scope),
|
|
1028
|
+
category
|
|
847
1029
|
}
|
|
848
1030
|
});
|
|
1031
|
+
console.error("[RBACEngine] Permission check failed:", error);
|
|
849
1032
|
return false;
|
|
850
1033
|
}
|
|
851
1034
|
}
|
|
852
1035
|
/**
|
|
853
1036
|
* Get user's access level in a scope
|
|
854
1037
|
*
|
|
1038
|
+
* This is derived from roles, not permissions.
|
|
1039
|
+
*
|
|
855
1040
|
* @param input - Access level input
|
|
856
1041
|
* @returns Promise resolving to access level
|
|
857
1042
|
*/
|
|
858
1043
|
async getAccessLevel(input) {
|
|
859
1044
|
const { userId, scope } = input;
|
|
860
|
-
const isSuperAdmin2 = await this.checkSuperAdmin(userId);
|
|
861
|
-
if (isSuperAdmin2) {
|
|
862
|
-
return "super";
|
|
863
|
-
}
|
|
864
|
-
const validatedScope = await this.validateContextRequirements(scope, scope.appId);
|
|
865
|
-
if (!validatedScope) {
|
|
866
|
-
return "viewer";
|
|
867
|
-
}
|
|
868
1045
|
const cacheKey = RBACCache.generateAccessLevelKey(
|
|
869
1046
|
userId,
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1047
|
+
scope.organisationId || "",
|
|
1048
|
+
scope.eventId,
|
|
1049
|
+
scope.appId
|
|
873
1050
|
);
|
|
874
1051
|
const cached = rbacCache.get(cacheKey);
|
|
875
1052
|
if (cached) {
|
|
876
1053
|
return cached;
|
|
877
1054
|
}
|
|
878
|
-
const
|
|
879
|
-
if (
|
|
880
|
-
rbacCache.set(cacheKey, "
|
|
881
|
-
return "
|
|
1055
|
+
const isSuperAdmin2 = await this.checkSuperAdmin(userId);
|
|
1056
|
+
if (isSuperAdmin2) {
|
|
1057
|
+
rbacCache.set(cacheKey, "super", 6e4);
|
|
1058
|
+
return "super";
|
|
882
1059
|
}
|
|
883
|
-
if (
|
|
884
|
-
const
|
|
885
|
-
if (
|
|
886
|
-
rbacCache.set(cacheKey, "admin");
|
|
1060
|
+
if (scope.organisationId) {
|
|
1061
|
+
const { data: orgRole } = await this.supabase.from("rbac_organisation_roles").select("role").eq("user_id", userId).eq("organisation_id", scope.organisationId).eq("status", "active").is("revoked_at", null).single();
|
|
1062
|
+
if (orgRole?.role === "org_admin") {
|
|
1063
|
+
rbacCache.set(cacheKey, "admin", 6e4);
|
|
887
1064
|
return "admin";
|
|
888
1065
|
}
|
|
889
|
-
|
|
890
|
-
|
|
1066
|
+
}
|
|
1067
|
+
if (scope.eventId && scope.appId) {
|
|
1068
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1069
|
+
const { data: eventRole } = await this.supabase.from("rbac_event_app_roles").select("role").eq("user_id", userId).eq("event_id", scope.eventId).eq("app_id", scope.appId).eq("status", "active").lte("valid_from", now).or(`valid_to.is.null,valid_to.gte.${now}`).single();
|
|
1070
|
+
if (eventRole?.role === "event_admin") {
|
|
1071
|
+
rbacCache.set(cacheKey, "admin", 6e4);
|
|
1072
|
+
return "admin";
|
|
1073
|
+
}
|
|
1074
|
+
if (eventRole?.role === "planner") {
|
|
1075
|
+
rbacCache.set(cacheKey, "planner", 6e4);
|
|
891
1076
|
return "planner";
|
|
892
1077
|
}
|
|
893
|
-
if (eventRole === "participant") {
|
|
894
|
-
rbacCache.set(cacheKey, "participant");
|
|
1078
|
+
if (eventRole?.role === "participant") {
|
|
1079
|
+
rbacCache.set(cacheKey, "participant", 6e4);
|
|
895
1080
|
return "participant";
|
|
896
1081
|
}
|
|
897
1082
|
}
|
|
898
|
-
rbacCache.set(cacheKey, "viewer");
|
|
1083
|
+
rbacCache.set(cacheKey, "viewer", 6e4);
|
|
899
1084
|
return "viewer";
|
|
900
1085
|
}
|
|
901
1086
|
/**
|
|
902
1087
|
* Get user's permission map for a scope
|
|
903
1088
|
*
|
|
1089
|
+
* This builds a map of page IDs to allowed operations.
|
|
1090
|
+
* Uses the simplified RPC for each permission check.
|
|
1091
|
+
*
|
|
904
1092
|
* @param input - Permission map input
|
|
905
1093
|
* @returns Promise resolving to permission map
|
|
906
1094
|
*/
|
|
907
1095
|
async getPermissionMap(input) {
|
|
908
1096
|
const { userId, scope } = input;
|
|
1097
|
+
const cacheKey = RBACCache.generatePermissionMapKey(
|
|
1098
|
+
userId,
|
|
1099
|
+
scope.organisationId || "",
|
|
1100
|
+
scope.eventId,
|
|
1101
|
+
scope.appId
|
|
1102
|
+
);
|
|
909
1103
|
const isSuperAdmin2 = await this.checkSuperAdmin(userId);
|
|
910
1104
|
if (isSuperAdmin2) {
|
|
911
|
-
|
|
1105
|
+
const wildcardMap = { "*": true };
|
|
1106
|
+
rbacCache.set(cacheKey, wildcardMap, 6e4);
|
|
1107
|
+
return wildcardMap;
|
|
912
1108
|
}
|
|
913
|
-
|
|
914
|
-
if (!validatedScope) {
|
|
1109
|
+
if (!scope.organisationId) {
|
|
915
1110
|
return {};
|
|
916
1111
|
}
|
|
917
|
-
const cacheKey = RBACCache.generatePermissionMapKey(
|
|
918
|
-
userId,
|
|
919
|
-
validatedScope.organisationId,
|
|
920
|
-
validatedScope.eventId,
|
|
921
|
-
validatedScope.appId
|
|
922
|
-
);
|
|
923
1112
|
const cached = rbacCache.get(cacheKey);
|
|
924
1113
|
if (cached) {
|
|
925
1114
|
return cached;
|
|
926
1115
|
}
|
|
927
1116
|
const permissionMap = {};
|
|
928
|
-
if (
|
|
929
|
-
const { data: pages } = await this.supabase.from("rbac_app_pages").select("id, page_name").eq("app_id",
|
|
1117
|
+
if (scope.appId) {
|
|
1118
|
+
const { data: pages } = await this.supabase.from("rbac_app_pages").select("id, page_name").eq("app_id", scope.appId);
|
|
930
1119
|
if (pages) {
|
|
1120
|
+
const securityContext = {
|
|
1121
|
+
userId,
|
|
1122
|
+
organisationId: scope.organisationId,
|
|
1123
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
1124
|
+
};
|
|
931
1125
|
for (const page of pages) {
|
|
932
|
-
const operations = [];
|
|
933
1126
|
for (const operation of ["read", "create", "update", "delete"]) {
|
|
934
|
-
const hasPermission2 = await this.isPermitted(
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1127
|
+
const hasPermission2 = await this.isPermitted(
|
|
1128
|
+
{
|
|
1129
|
+
userId,
|
|
1130
|
+
scope,
|
|
1131
|
+
permission: `${operation}:${page.page_name}`,
|
|
1132
|
+
pageId: page.id
|
|
1133
|
+
},
|
|
1134
|
+
securityContext
|
|
1135
|
+
);
|
|
1136
|
+
const permissionKey = `${operation}:${page.page_name}`;
|
|
1137
|
+
permissionMap[permissionKey] = hasPermission2;
|
|
943
1138
|
}
|
|
944
|
-
permissionMap[page.id] = operations;
|
|
945
1139
|
}
|
|
946
1140
|
}
|
|
947
1141
|
}
|
|
948
|
-
rbacCache.set(cacheKey, permissionMap);
|
|
1142
|
+
rbacCache.set(cacheKey, permissionMap, 6e4);
|
|
949
1143
|
return permissionMap;
|
|
950
1144
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
*/
|
|
961
|
-
async checkSuperAdmin(userId) {
|
|
962
|
-
const cacheKey = `super_admin:${userId}`;
|
|
963
|
-
const cached = rbacCache.get(cacheKey);
|
|
964
|
-
if (cached !== null) {
|
|
965
|
-
return cached;
|
|
966
|
-
}
|
|
967
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
968
|
-
const { data, error } = await this.supabase.from("rbac_global_roles").select("role").eq("user_id", userId).eq("role", "super_admin").lte("valid_from", now).or(`valid_to.is.null,valid_to.gte.${now}`).limit(1);
|
|
969
|
-
const isSuperAdmin2 = !error && data && data.length > 0;
|
|
970
|
-
rbacCache.set(cacheKey, isSuperAdmin2, 6e4);
|
|
971
|
-
return Boolean(isSuperAdmin2);
|
|
972
|
-
}
|
|
973
|
-
/**
|
|
974
|
-
* Get app configuration including requires_event setting
|
|
975
|
-
*
|
|
976
|
-
* @param appId - App ID
|
|
977
|
-
* @returns Promise resolving to app configuration
|
|
978
|
-
*/
|
|
979
|
-
async getAppConfig(appId) {
|
|
980
|
-
const { data, error } = await this.supabase.from("rbac_apps").select("requires_event").eq("id", appId).eq("is_active", true).single();
|
|
981
|
-
if (error || !data) {
|
|
982
|
-
return null;
|
|
983
|
-
}
|
|
984
|
-
return { requires_event: data.requires_event };
|
|
985
|
-
}
|
|
986
|
-
/**
|
|
987
|
-
* Resolve organisation ID from event ID
|
|
988
|
-
*
|
|
989
|
-
* @param eventId - Event ID
|
|
990
|
-
* @returns Promise resolving to organisation ID
|
|
991
|
-
*/
|
|
992
|
-
async resolveOrganisationFromEvent(eventId) {
|
|
993
|
-
const { data, error } = await this.supabase.from("event").select("organisation_id").eq("id", eventId).single();
|
|
994
|
-
if (error || !data) {
|
|
995
|
-
return null;
|
|
996
|
-
}
|
|
997
|
-
return data.organisation_id;
|
|
998
|
-
}
|
|
999
|
-
/**
|
|
1000
|
-
* Validate context requirements based on app configuration
|
|
1001
|
-
*
|
|
1002
|
-
* @param scope - Permission scope
|
|
1003
|
-
* @param appId - Optional app ID
|
|
1004
|
-
* @returns Promise resolving to validated scope with resolved organisation ID
|
|
1005
|
-
*/
|
|
1006
|
-
async validateContextRequirements(scope, appId) {
|
|
1007
|
-
if (appId) {
|
|
1008
|
-
const appConfig = await this.getAppConfig(appId);
|
|
1009
|
-
if (!appConfig) {
|
|
1145
|
+
async resolveAppContext(input) {
|
|
1146
|
+
try {
|
|
1147
|
+
const { userId, appName } = input;
|
|
1148
|
+
const { data, error } = await this.supabase.rpc("util_app_resolve", {
|
|
1149
|
+
p_user_id: userId,
|
|
1150
|
+
p_app_name: appName
|
|
1151
|
+
});
|
|
1152
|
+
if (error) {
|
|
1153
|
+
console.error("[RBACEngine] Failed to resolve app context:", error);
|
|
1010
1154
|
return null;
|
|
1011
1155
|
}
|
|
1012
|
-
if (
|
|
1013
|
-
|
|
1014
|
-
return null;
|
|
1015
|
-
}
|
|
1016
|
-
if (!scope.organisationId) {
|
|
1017
|
-
const resolvedOrgId = await this.resolveOrganisationFromEvent(scope.eventId);
|
|
1018
|
-
if (!resolvedOrgId) {
|
|
1019
|
-
return null;
|
|
1020
|
-
}
|
|
1021
|
-
return {
|
|
1022
|
-
...scope,
|
|
1023
|
-
organisationId: resolvedOrgId
|
|
1024
|
-
};
|
|
1025
|
-
}
|
|
1026
|
-
return scope;
|
|
1027
|
-
} else {
|
|
1028
|
-
if (!scope.organisationId) {
|
|
1029
|
-
return null;
|
|
1030
|
-
}
|
|
1031
|
-
return scope;
|
|
1156
|
+
if (!data || data.length === 0) {
|
|
1157
|
+
return null;
|
|
1032
1158
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1159
|
+
const appData = data[0];
|
|
1160
|
+
if (!appData?.app_id) {
|
|
1161
|
+
return null;
|
|
1162
|
+
}
|
|
1163
|
+
return {
|
|
1164
|
+
appId: appData.app_id,
|
|
1165
|
+
hasAccess: appData.has_access !== false
|
|
1166
|
+
};
|
|
1167
|
+
} catch (error) {
|
|
1168
|
+
console.error("[RBACEngine] Unexpected error resolving app context:", error);
|
|
1035
1169
|
return null;
|
|
1036
1170
|
}
|
|
1037
|
-
return scope;
|
|
1038
1171
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
userRoles.push(...eventRoles.map((r) => r.role));
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
if (scope.organisationId) {
|
|
1060
|
-
const { data: orgRoles } = await this.supabase.from("rbac_organisation_roles").select("role, status, valid_from, valid_to").eq("user_id", userId).eq("organisation_id", scope.organisationId).eq("status", "active").lte("valid_from", now).or(`valid_to.is.null,valid_to.gte.${now}`);
|
|
1061
|
-
if (orgRoles) {
|
|
1062
|
-
userRoles.push(...orgRoles.map((r) => r.role));
|
|
1172
|
+
async getRoleContext(input) {
|
|
1173
|
+
const result = {
|
|
1174
|
+
globalRole: null,
|
|
1175
|
+
organisationRole: null,
|
|
1176
|
+
eventAppRole: null
|
|
1177
|
+
};
|
|
1178
|
+
try {
|
|
1179
|
+
const { userId, scope } = input;
|
|
1180
|
+
const { data, error } = await this.supabase.rpc("rbac_permissions_get", {
|
|
1181
|
+
p_user_id: userId,
|
|
1182
|
+
p_organisation_id: scope.organisationId || null,
|
|
1183
|
+
p_event_id: scope.eventId || null,
|
|
1184
|
+
p_app_id: scope.appId || null
|
|
1185
|
+
});
|
|
1186
|
+
if (error) {
|
|
1187
|
+
console.error("[RBACEngine] Failed to load role context:", error);
|
|
1188
|
+
return result;
|
|
1063
1189
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
if (pageId) {
|
|
1067
|
-
let resolvedPageId = null;
|
|
1068
|
-
if (typeof pageId === "string") {
|
|
1069
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1070
|
-
if (uuidRegex.test(pageId)) {
|
|
1071
|
-
resolvedPageId = pageId;
|
|
1072
|
-
} else {
|
|
1073
|
-
const appId = scope.appId;
|
|
1074
|
-
if (appId) {
|
|
1075
|
-
const { data: page } = await this.supabase.from("rbac_app_pages").select("id").eq("app_id", appId).eq("page_name", pageId).single();
|
|
1076
|
-
resolvedPageId = page?.id || null;
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
} else {
|
|
1080
|
-
resolvedPageId = pageId;
|
|
1190
|
+
if (!Array.isArray(data)) {
|
|
1191
|
+
return result;
|
|
1081
1192
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
if (typeof pageId === "string" && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(pageId)) {
|
|
1086
|
-
pageName = pageId;
|
|
1087
|
-
} else {
|
|
1088
|
-
const { data: page } = await this.supabase.from("rbac_app_pages").select("page_name").eq("id", resolvedPageId).single();
|
|
1089
|
-
pageName = page?.page_name || null;
|
|
1193
|
+
for (const permission of data) {
|
|
1194
|
+
if (permission.permission_type === "all_permissions") {
|
|
1195
|
+
result.globalRole = "super_admin";
|
|
1090
1196
|
}
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
p_app_id: scope.appId,
|
|
1094
|
-
p_event_id: scope.eventId || null,
|
|
1095
|
-
p_organisation_id: scope.organisationId || null,
|
|
1096
|
-
p_page_id: resolvedPageId
|
|
1097
|
-
});
|
|
1098
|
-
const { data: rpcPermissions } = rpcResult;
|
|
1099
|
-
console.log("[collectActiveGrants] RPC page permissions:", rpcPermissions);
|
|
1100
|
-
if (rpcPermissions && pageName) {
|
|
1101
|
-
const pagePerms = rpcPermissions.filter(
|
|
1102
|
-
(p) => p.permission_type !== "all_permissions" && p.permission_type !== "organisation_access" && p.permission_type !== "event_app_access"
|
|
1103
|
-
);
|
|
1104
|
-
for (const perm of pagePerms) {
|
|
1105
|
-
if (userRoles.includes(perm.role_name)) {
|
|
1106
|
-
console.log("[collectActiveGrants] Adding page grant:", { operation: perm.permission_type, role: perm.role_name, allowed: perm.has_permission, pageName });
|
|
1107
|
-
grants.push({
|
|
1108
|
-
type: perm.has_permission ? "allow" : "deny",
|
|
1109
|
-
// Use permission_type directly as it already includes the page name
|
|
1110
|
-
permission: perm.permission_type,
|
|
1111
|
-
scope: "page",
|
|
1112
|
-
source: "rbac_page_permissions"
|
|
1113
|
-
});
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1197
|
+
if (permission.permission_type === "organisation_access") {
|
|
1198
|
+
result.organisationRole = permission.role_name;
|
|
1116
1199
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
const { data: globalRoles } = await this.supabase.from("rbac_global_roles").select("role, valid_from, valid_to").eq("user_id", userId).lte("valid_from", now).or(`valid_to.is.null,valid_to.gte.${now}`);
|
|
1120
|
-
if (globalRoles) {
|
|
1121
|
-
for (const role of globalRoles) {
|
|
1122
|
-
if (role.role === "super_admin") {
|
|
1123
|
-
grants.push(
|
|
1124
|
-
{ type: "allow", permission: "read:*", scope: "global", source: "rbac_global_roles" },
|
|
1125
|
-
{ type: "allow", permission: "create:*", scope: "global", source: "rbac_global_roles" },
|
|
1126
|
-
{ type: "allow", permission: "update:*", scope: "global", source: "rbac_global_roles" },
|
|
1127
|
-
{ type: "allow", permission: "delete:*", scope: "global", source: "rbac_global_roles" }
|
|
1128
|
-
);
|
|
1200
|
+
if (permission.permission_type === "event_app_access") {
|
|
1201
|
+
result.eventAppRole = permission.role_name;
|
|
1129
1202
|
}
|
|
1130
1203
|
}
|
|
1204
|
+
return result;
|
|
1205
|
+
} catch (error) {
|
|
1206
|
+
console.error("[RBACEngine] Unexpected error loading role context:", error);
|
|
1207
|
+
return result;
|
|
1131
1208
|
}
|
|
1132
|
-
console.log("[collectActiveGrants] Final grants:", grants);
|
|
1133
|
-
return grants;
|
|
1134
|
-
}
|
|
1135
|
-
/**
|
|
1136
|
-
* Check page-specific permissions
|
|
1137
|
-
*
|
|
1138
|
-
* @param userId - User ID
|
|
1139
|
-
* @param pageId - Page ID
|
|
1140
|
-
* @param permission - Permission to check
|
|
1141
|
-
* @param scope - Permission scope
|
|
1142
|
-
* @returns Promise resolving to page permission result
|
|
1143
|
-
*/
|
|
1144
|
-
async checkPagePermissions(userId, pageId, permission, scope) {
|
|
1145
|
-
if (!pageId) {
|
|
1146
|
-
return true;
|
|
1147
|
-
}
|
|
1148
|
-
const [operation] = permission.split(":");
|
|
1149
|
-
const userRoles = [];
|
|
1150
|
-
if (scope.organisationId) {
|
|
1151
|
-
const orgRole = await this.getOrganisationRole(userId, scope.organisationId);
|
|
1152
|
-
if (orgRole) {
|
|
1153
|
-
userRoles.push(orgRole);
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
if (scope.eventId && scope.appId) {
|
|
1157
|
-
const eventRole = await this.getEventAppRole(userId, scope.eventId, scope.appId);
|
|
1158
|
-
if (eventRole) {
|
|
1159
|
-
userRoles.push(eventRole);
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
let resolvedPageId = null;
|
|
1163
|
-
if (typeof pageId === "string") {
|
|
1164
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1165
|
-
if (uuidRegex.test(pageId)) {
|
|
1166
|
-
resolvedPageId = pageId;
|
|
1167
|
-
} else {
|
|
1168
|
-
let appId = scope.appId;
|
|
1169
|
-
if (!appId) {
|
|
1170
|
-
const appName = import.meta.env.VITE_APP_NAME || import.meta.env.REACT_APP_NAME;
|
|
1171
|
-
if (appName) {
|
|
1172
|
-
const { data: app } = await this.supabase.from("rbac_apps").select("id").eq("name", appName).eq("is_active", true).single();
|
|
1173
|
-
if (app) {
|
|
1174
|
-
appId = app.id;
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
if (appId) {
|
|
1179
|
-
const { data: page } = await this.supabase.from("rbac_app_pages").select("id").eq("app_id", appId).eq("page_name", pageId).single();
|
|
1180
|
-
resolvedPageId = page?.id || null;
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
} else {
|
|
1184
|
-
resolvedPageId = pageId;
|
|
1185
|
-
}
|
|
1186
|
-
if (!resolvedPageId) {
|
|
1187
|
-
return false;
|
|
1188
|
-
}
|
|
1189
|
-
const { data: pagePermissions } = await this.supabase.from("rbac_page_permissions").select("allowed").eq("app_page_id", resolvedPageId).eq("operation", operation).in("role_name", userRoles).single();
|
|
1190
|
-
return pagePermissions?.allowed ?? false;
|
|
1191
|
-
}
|
|
1192
|
-
/**
|
|
1193
|
-
* Get organisation role for a user
|
|
1194
|
-
*
|
|
1195
|
-
* @param userId - User ID
|
|
1196
|
-
* @param organisationId - Organisation ID
|
|
1197
|
-
* @returns Promise resolving to organisation role
|
|
1198
|
-
*/
|
|
1199
|
-
async getOrganisationRole(userId, organisationId) {
|
|
1200
|
-
const { data, error } = await this.supabase.from("rbac_organisation_roles").select("role").eq("user_id", userId).eq("organisation_id", organisationId).eq("status", "active").single();
|
|
1201
|
-
return error ? null : data?.role || null;
|
|
1202
1209
|
}
|
|
1203
1210
|
/**
|
|
1204
|
-
*
|
|
1211
|
+
* Check if user is super admin
|
|
1205
1212
|
*
|
|
1206
1213
|
* @param userId - User ID
|
|
1207
|
-
* @
|
|
1208
|
-
* @param appId - App ID
|
|
1209
|
-
* @returns Promise resolving to event-app role
|
|
1210
|
-
*/
|
|
1211
|
-
async getEventAppRole(userId, eventId, appId) {
|
|
1212
|
-
const { data, error } = await this.supabase.from("rbac_event_app_roles").select("role, status, valid_from, valid_to").eq("user_id", userId).eq("event_id", eventId).eq("app_id", appId).eq("status", "active").lte("valid_from", (/* @__PURE__ */ new Date()).toISOString()).or(`valid_to.is.null,valid_to.gte.${(/* @__PURE__ */ new Date()).toISOString()}`).single();
|
|
1213
|
-
return error ? null : data?.role || null;
|
|
1214
|
-
}
|
|
1215
|
-
/**
|
|
1216
|
-
* Get permission for organisation role
|
|
1217
|
-
*
|
|
1218
|
-
* @param role - Organisation role
|
|
1219
|
-
* @returns Permission string
|
|
1220
|
-
*/
|
|
1221
|
-
getPermissionForOrgRole(role) {
|
|
1222
|
-
switch (role) {
|
|
1223
|
-
case "org_admin":
|
|
1224
|
-
return "read:*";
|
|
1225
|
-
// Will be expanded to all CRUD in collectActiveGrants
|
|
1226
|
-
case "leader":
|
|
1227
|
-
return "read:organisation.*";
|
|
1228
|
-
// Will be expanded to all CRUD in collectActiveGrants
|
|
1229
|
-
case "member":
|
|
1230
|
-
return "read:organisation.*";
|
|
1231
|
-
case "supporter":
|
|
1232
|
-
return "read:organisation.public";
|
|
1233
|
-
default:
|
|
1234
|
-
return "read:organisation.public";
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
/**
|
|
1238
|
-
* Get permission for event-app role
|
|
1239
|
-
*
|
|
1240
|
-
* @param role - Event-app role
|
|
1241
|
-
* @returns Permission string
|
|
1242
|
-
*/
|
|
1243
|
-
getPermissionForEventRole(role) {
|
|
1244
|
-
switch (role) {
|
|
1245
|
-
case "event_admin":
|
|
1246
|
-
return "read:event.*";
|
|
1247
|
-
// Will be expanded to all CRUD in collectActiveGrants
|
|
1248
|
-
case "planner":
|
|
1249
|
-
return "read:event.planning";
|
|
1250
|
-
// Will be expanded to all CRUD in collectActiveGrants
|
|
1251
|
-
case "participant":
|
|
1252
|
-
return "read:event.*";
|
|
1253
|
-
case "viewer":
|
|
1254
|
-
return "read:event.public";
|
|
1255
|
-
default:
|
|
1256
|
-
return "read:event.public";
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
/**
|
|
1260
|
-
* Check if a permission matches another permission
|
|
1261
|
-
*
|
|
1262
|
-
* @param grantPermission - Permission from grant
|
|
1263
|
-
* @param requestedPermission - Requested permission
|
|
1264
|
-
* @returns True if permissions match
|
|
1214
|
+
* @returns Promise resolving to super admin status
|
|
1265
1215
|
*/
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
return
|
|
1271
|
-
}
|
|
1272
|
-
if (grantPermission.endsWith(":*") || grantPermission.endsWith(".*")) {
|
|
1273
|
-
const [grantOp, grantResource] = grantPermission.split(":");
|
|
1274
|
-
const [requestedOp, requestedResource] = requestedPermission.split(":");
|
|
1275
|
-
console.log("[permissionMatches] Wildcard check:", {
|
|
1276
|
-
grantOp,
|
|
1277
|
-
grantResource,
|
|
1278
|
-
requestedOp,
|
|
1279
|
-
requestedResource,
|
|
1280
|
-
operationsMatch: grantOp === requestedOp
|
|
1281
|
-
});
|
|
1282
|
-
if (grantOp === requestedOp) {
|
|
1283
|
-
const prefix = grantResource.slice(0, -1);
|
|
1284
|
-
const matches = prefix === "" || requestedResource.startsWith(prefix);
|
|
1285
|
-
console.log("[permissionMatches] Wildcard match result:", { prefix, matches });
|
|
1286
|
-
return matches;
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
if (grantPermission.includes("*")) {
|
|
1290
|
-
const [grantOp, grantResource] = grantPermission.split(":");
|
|
1291
|
-
const [requestedOp, requestedResource] = requestedPermission.split(":");
|
|
1292
|
-
console.log("[permissionMatches] Other wildcard check:", {
|
|
1293
|
-
grantOp,
|
|
1294
|
-
grantResource,
|
|
1295
|
-
requestedOp,
|
|
1296
|
-
requestedResource,
|
|
1297
|
-
operationsMatch: grantOp === requestedOp
|
|
1298
|
-
});
|
|
1299
|
-
if (grantOp === requestedOp) {
|
|
1300
|
-
const prefix = grantResource.replace("*", "");
|
|
1301
|
-
const matches = requestedResource.startsWith(prefix);
|
|
1302
|
-
console.log("[permissionMatches] Other wildcard match result:", { prefix, matches });
|
|
1303
|
-
return matches;
|
|
1304
|
-
}
|
|
1216
|
+
async checkSuperAdmin(userId) {
|
|
1217
|
+
const cacheKey = `super_admin:${userId}`;
|
|
1218
|
+
const cached = rbacCache.get(cacheKey);
|
|
1219
|
+
if (cached !== null) {
|
|
1220
|
+
return cached;
|
|
1305
1221
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1222
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1223
|
+
const { data, error } = await this.supabase.from("rbac_global_roles").select("role").eq("user_id", userId).eq("role", "super_admin").lte("valid_from", now).or(`valid_to.is.null,valid_to.gte.${now}`).limit(1);
|
|
1224
|
+
const isSuperAdmin2 = !error && data && data.length > 0;
|
|
1225
|
+
rbacCache.set(cacheKey, isSuperAdmin2, 6e4);
|
|
1226
|
+
return Boolean(isSuperAdmin2);
|
|
1308
1227
|
}
|
|
1309
1228
|
/**
|
|
1310
1229
|
* Resolve a page ID to UUID if it's a page name
|
|
1311
1230
|
*
|
|
1312
1231
|
* @param pageId - Page ID (UUID) or page name (string)
|
|
1313
1232
|
* @param appId - App ID to look up the page
|
|
1314
|
-
* @returns Resolved page ID (UUID) or original pageId
|
|
1233
|
+
* @returns Resolved page ID (UUID) or original pageId
|
|
1315
1234
|
*/
|
|
1316
1235
|
async resolvePageId(pageId, appId) {
|
|
1317
1236
|
if (!pageId) {
|
|
@@ -1447,9 +1366,24 @@ async function getPermissionMap(input) {
|
|
|
1447
1366
|
const engine = getEngine();
|
|
1448
1367
|
return engine.getPermissionMap(input);
|
|
1449
1368
|
}
|
|
1369
|
+
async function resolveAppContext(input) {
|
|
1370
|
+
const engine = getEngine();
|
|
1371
|
+
return engine.resolveAppContext(input);
|
|
1372
|
+
}
|
|
1373
|
+
async function getRoleContext(input) {
|
|
1374
|
+
const engine = getEngine();
|
|
1375
|
+
return engine.getRoleContext(input);
|
|
1376
|
+
}
|
|
1450
1377
|
async function isPermitted(input) {
|
|
1451
1378
|
const engine = getEngine();
|
|
1452
|
-
|
|
1379
|
+
const securityContext = {
|
|
1380
|
+
userId: input.userId,
|
|
1381
|
+
organisationId: input.scope.organisationId || input.userId,
|
|
1382
|
+
// Fallback to userId as UUID
|
|
1383
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
1384
|
+
// Optional fields can be omitted
|
|
1385
|
+
};
|
|
1386
|
+
return engine.isPermitted(input, securityContext);
|
|
1453
1387
|
}
|
|
1454
1388
|
async function isPermittedCached(input) {
|
|
1455
1389
|
const { userId, scope, permission, pageId } = input;
|
|
@@ -1503,8 +1437,20 @@ async function isSuperAdmin(userId) {
|
|
|
1503
1437
|
return engine["checkSuperAdmin"](userId);
|
|
1504
1438
|
}
|
|
1505
1439
|
async function getAppConfig(appId) {
|
|
1506
|
-
|
|
1507
|
-
return
|
|
1440
|
+
console.warn("[RBAC] getAppConfig called without Supabase client - returning null");
|
|
1441
|
+
return null;
|
|
1442
|
+
}
|
|
1443
|
+
async function getAppConfigWithClient(client, appId) {
|
|
1444
|
+
try {
|
|
1445
|
+
const { data, error } = await client.from("rbac_apps").select("requires_event").eq("id", appId).eq("is_active", true).single();
|
|
1446
|
+
if (error || !data) {
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1449
|
+
return { requires_event: data.requires_event };
|
|
1450
|
+
} catch (err) {
|
|
1451
|
+
console.error("[RBAC] Error fetching app config:", err);
|
|
1452
|
+
return null;
|
|
1453
|
+
}
|
|
1508
1454
|
}
|
|
1509
1455
|
async function isOrganisationAdmin(userId, organisationId) {
|
|
1510
1456
|
const accessLevel = await getAccessLevel({
|
|
@@ -1555,6 +1501,8 @@ export {
|
|
|
1555
1501
|
setupRBAC,
|
|
1556
1502
|
getAccessLevel,
|
|
1557
1503
|
getPermissionMap,
|
|
1504
|
+
resolveAppContext,
|
|
1505
|
+
getRoleContext,
|
|
1558
1506
|
isPermitted,
|
|
1559
1507
|
isPermittedCached,
|
|
1560
1508
|
hasPermission,
|
|
@@ -1562,6 +1510,7 @@ export {
|
|
|
1562
1510
|
hasAllPermissions,
|
|
1563
1511
|
isSuperAdmin,
|
|
1564
1512
|
getAppConfig,
|
|
1513
|
+
getAppConfigWithClient,
|
|
1565
1514
|
isOrganisationAdmin,
|
|
1566
1515
|
isEventAdmin,
|
|
1567
1516
|
invalidateUserCache,
|
|
@@ -1570,4 +1519,4 @@ export {
|
|
|
1570
1519
|
invalidateAppCache,
|
|
1571
1520
|
clearCache
|
|
1572
1521
|
};
|
|
1573
|
-
//# sourceMappingURL=chunk-
|
|
1522
|
+
//# sourceMappingURL=chunk-S63MFSY6.js.map
|