@jmruthers/pace-core 0.6.11 → 0.6.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DataTable-EFYP2QLE.js → DataTable-AQAHSFLM.js} +7 -6
- package/dist/{api-BZR2CYXL.js → api-6OQXYT67.js} +2 -1
- package/dist/{chunk-L5LFKKLJ.js → chunk-2GBDPPUC.js} +1 -1
- package/dist/{chunk-J2KQK6DG.js → chunk-AP5FG7W4.js} +2 -2
- package/dist/{chunk-UZNAFKGW.js → chunk-GHCUP64P.js} +1 -21
- package/dist/{chunk-V7FTM2LU.js → chunk-H6RTU4DZ.js} +37 -19
- package/dist/{chunk-KJXRL3XE.js → chunk-HQTYP6BX.js} +79 -45
- package/dist/{chunk-YFTFFJIV.js → chunk-M7QE7XOA.js} +3 -3
- package/dist/{chunk-YYTWKVHO.js → chunk-MVVWZ7JV.js} +25 -30
- package/dist/{chunk-PCSHBLPB.js → chunk-NJ7FGQWB.js} +5 -5
- package/dist/{chunk-WY6Y7KC3.js → chunk-QWIG36BZ.js} +3 -3
- package/dist/{chunk-4R3T5ENU.js → chunk-S57OLCLO.js} +9 -6
- package/dist/chunk-VFLR5K2H.js +23 -0
- package/dist/{chunk-2OEVOGGR.js → chunk-Y2LWSLLB.js} +41 -25
- package/dist/{chunk-7A6IMHH2.js → chunk-YFGNMB67.js} +75 -6
- package/dist/components.d.ts +3 -3
- package/dist/components.js +13 -12
- package/dist/{functions-DH45k8ec.d.ts → functions-hF5ImHCr.d.ts} +1 -1
- package/dist/hooks.js +8 -7
- package/dist/index.d.ts +5 -5
- package/dist/index.js +17 -16
- package/dist/providers.js +3 -2
- package/dist/rbac/index.d.ts +10 -10
- package/dist/rbac/index.js +9 -8
- package/dist/{types-BE2sEHKd.d.ts → types-Besvoyzb.d.ts} +1 -1
- package/dist/{types-CvOPXWWZ.d.ts → types-CGHrxfqc.d.ts} +3 -0
- package/dist/types.d.ts +2 -2
- package/dist/{usePublicPageContext-B91dGYW1.d.ts → usePublicPageContext-BQrHf95t.d.ts} +1 -1
- package/dist/utils.js +4 -3
- package/docs/api/@jmruthers/namespaces/DialogPortal/README.md +14 -0
- package/docs/api/@jmruthers/namespaces/DialogPortal/variables/displayName.md +11 -0
- package/docs/api/README.md +6 -2
- package/docs/api/_media/README.md +186 -0
- package/docs/api/classes/ColumnFactory.md +225 -0
- package/docs/api/classes/Logger.md +246 -0
- package/docs/api/classes/RBACAuditManager.md +299 -0
- package/docs/api/classes/RBACCache.md +389 -0
- package/docs/api/classes/RBACEngine.md +181 -0
- package/docs/api/classes/SecureSupabaseClient.md +168 -0
- package/docs/api/classes/StorageUtils.md +324 -0
- package/docs/api/enumerations/FileCategory.md +137 -0
- package/docs/api/enumerations/LogLevel.md +43 -0
- package/docs/api/enumerations/RBACErrorCode.md +169 -0
- package/docs/api/enumerations/RPCFunction.md +89 -0
- package/docs/api/functions/AccessDenied.md +30 -0
- package/docs/api/functions/AppSwitcher.md +21 -0
- package/docs/api/functions/Badge.md +42 -0
- package/docs/api/functions/ContextSelector.md +43 -0
- package/docs/api/functions/DataTable.md +36 -0
- package/docs/api/functions/DatePickerWithTimezone.md +28 -0
- package/docs/api/functions/DialogBody.md +24 -0
- package/docs/api/functions/DialogFooter.md +24 -0
- package/docs/api/functions/DialogHeader.md +24 -0
- package/docs/api/functions/ErrorBoundaryProvider.md +28 -0
- package/docs/api/functions/EventServiceProvider.md +28 -0
- package/docs/api/functions/FileDisplay.md +25 -0
- package/docs/api/functions/FileUpload.md +21 -0
- package/docs/api/functions/Form.md +50 -0
- package/docs/api/functions/FormField.md +102 -0
- package/docs/api/functions/Header.md +21 -0
- package/docs/api/functions/InactivityServiceProvider.md +28 -0
- package/docs/api/functions/InactivityWarningModal.md +21 -0
- package/docs/api/functions/Input.md +49 -0
- package/docs/api/functions/NavigationGuard.md +31 -0
- package/docs/api/functions/OrganisationServiceProvider.md +28 -0
- package/docs/api/functions/PaceAppLayout.md +169 -0
- package/docs/api/functions/PasswordChangeForm.md +21 -0
- package/docs/api/functions/ProtectedRoute.md +37 -0
- package/docs/api/functions/PublicPageFooter.md +21 -0
- package/docs/api/functions/PublicPageHeader.md +21 -0
- package/docs/api/functions/PublicPageLayout.md +33 -0
- package/docs/api/functions/PublicPageProvider.md +30 -0
- package/docs/api/functions/Textarea.md +49 -0
- package/docs/api/functions/Toaster.md +34 -0
- package/docs/api/functions/UnifiedAuthProvider.md +28 -0
- package/docs/api/functions/applyPalette.md +33 -0
- package/docs/api/functions/archiveFile.md +47 -0
- package/docs/api/functions/average.md +63 -0
- package/docs/api/functions/buildAppUrl.md +46 -0
- package/docs/api/functions/clearInFlightRequests.md +17 -0
- package/docs/api/functions/clearPalette.md +18 -0
- package/docs/api/functions/clearPublicEventCache.md +18 -0
- package/docs/api/functions/clearPublicFileDisplayCache.md +18 -0
- package/docs/api/functions/clearPublicLogoCache.md +18 -0
- package/docs/api/functions/cn.md +21 -0
- package/docs/api/functions/count.md +56 -0
- package/docs/api/functions/createAuditManager.md +45 -0
- package/docs/api/functions/createBaseClient.md +75 -0
- package/docs/api/functions/createLogger.md +95 -0
- package/docs/api/functions/createRBACConfig.md +21 -0
- package/docs/api/functions/createRBACEngine.md +33 -0
- package/docs/api/functions/createRBACExpressMiddleware.md +84 -0
- package/docs/api/functions/createRBACMiddleware.md +88 -0
- package/docs/api/functions/createSecureClient.md +80 -0
- package/docs/api/functions/createSecureDataAccess.md +39 -0
- package/docs/api/functions/deleteFile.md +33 -0
- package/docs/api/functions/disablePerformanceMonitoring.md +17 -0
- package/docs/api/functions/downloadFile.md +33 -0
- package/docs/api/functions/emitAuditEvent.md +27 -0
- package/docs/api/functions/enablePerformanceMonitoring.md +17 -0
- package/docs/api/functions/err.md +23 -0
- package/docs/api/functions/exportToCSV.md +56 -0
- package/docs/api/functions/exportToCSVWithTableRows.md +46 -0
- package/docs/api/functions/extractEventCodeFromPath.md +24 -0
- package/docs/api/functions/extractFileMetadata.md +33 -0
- package/docs/api/functions/formatCompactNumber.md +27 -0
- package/docs/api/functions/formatCurrency.md +31 -0
- package/docs/api/functions/formatDate.md +23 -0
- package/docs/api/functions/formatDateTime.md +24 -0
- package/docs/api/functions/formatFileSize.md +23 -0
- package/docs/api/functions/formatInTimeZone.md +46 -0
- package/docs/api/functions/formatNumber.md +31 -0
- package/docs/api/functions/formatPercent.md +64 -0
- package/docs/api/functions/formatTime.md +24 -0
- package/docs/api/functions/formatTimeInTimeZone.md +40 -0
- package/docs/api/functions/fromSupabaseClient.md +49 -0
- package/docs/api/functions/fromZonedTime.md +41 -0
- package/docs/api/functions/generateCSVContent.md +55 -0
- package/docs/api/functions/generateFilePath.md +29 -0
- package/docs/api/functions/generateFileUrlsBatch.md +33 -0
- package/docs/api/functions/generatePublicRoutePath.md +27 -0
- package/docs/api/functions/generateUniqueFileName.md +24 -0
- package/docs/api/functions/getAccessLevel.md +48 -0
- package/docs/api/functions/getAllAppPorts.md +19 -0
- package/docs/api/functions/getAllStylePaths.md +15 -0
- package/docs/api/functions/getAppConfig.md +17 -0
- package/docs/api/functions/getAppPort.md +34 -0
- package/docs/api/functions/getBucketName.md +27 -0
- package/docs/api/functions/getCurrentAppId.md +17 -0
- package/docs/api/functions/getCurrentAppName.md +17 -0
- package/docs/api/functions/getFileSizeLimit.md +23 -0
- package/docs/api/functions/getGlobalAuditManager.md +19 -0
- package/docs/api/functions/getInFlightRequestCount.md +19 -0
- package/docs/api/functions/getPerformanceMetrics.md +17 -0
- package/docs/api/functions/getPerformanceSummary.md +17 -0
- package/docs/api/functions/getPermissionMap.md +52 -0
- package/docs/api/functions/getPublicEventCacheStats.md +25 -0
- package/docs/api/functions/getPublicFileDisplayCacheStats.md +25 -0
- package/docs/api/functions/getPublicLogoCacheStats.md +25 -0
- package/docs/api/functions/getPublicUrl.md +39 -0
- package/docs/api/functions/getRBACConfig.md +15 -0
- package/docs/api/functions/getRBACLogger.md +15 -0
- package/docs/api/functions/getRoleContext.md +31 -0
- package/docs/api/functions/getSignedUrl.md +34 -0
- package/docs/api/functions/getStylePath.md +21 -0
- package/docs/api/functions/getTimeZoneDifference.md +40 -0
- package/docs/api/functions/getTimezoneAbbreviation.md +40 -0
- package/docs/api/functions/getUserTimeZone.md +26 -0
- package/docs/api/functions/hasAllPermissions.md +41 -0
- package/docs/api/functions/hasAnyPermission.md +41 -0
- package/docs/api/functions/isDebugMode.md +15 -0
- package/docs/api/functions/isDevelopmentMode.md +15 -0
- package/docs/api/functions/isErr.md +29 -0
- package/docs/api/functions/isOk.md +29 -0
- package/docs/api/functions/isPerformanceMonitoringEnabled.md +17 -0
- package/docs/api/functions/isPermitted.md +58 -0
- package/docs/api/functions/isPermittedCached.md +36 -0
- package/docs/api/functions/isRBACInitialized.md +19 -0
- package/docs/api/functions/isSecureClient.md +38 -0
- package/docs/api/functions/isValidPermission.md +27 -0
- package/docs/api/functions/listFiles.md +29 -0
- package/docs/api/functions/max.md +63 -0
- package/docs/api/functions/min.md +63 -0
- package/docs/api/functions/ok.md +29 -0
- package/docs/api/functions/parseAndNormalizeEventColours.md +105 -0
- package/docs/api/functions/recordAuditEvent.md +23 -0
- package/docs/api/functions/recordPermissionCheck.md +31 -0
- package/docs/api/functions/resetPerformanceMetrics.md +17 -0
- package/docs/api/functions/resolveAppContext.md +27 -0
- package/docs/api/functions/roundToNearestMinutes.md +41 -0
- package/docs/api/functions/sanitizeFormData.md +49 -0
- package/docs/api/functions/sanitizeHtml.md +39 -0
- package/docs/api/functions/sanitizeUserInput.md +27 -0
- package/docs/api/functions/setAppConfig.md +23 -0
- package/docs/api/functions/setGlobalAuditManager.md +25 -0
- package/docs/api/functions/setupRBAC.md +31 -0
- package/docs/api/functions/sum.md +63 -0
- package/docs/api/functions/toZonedTime.md +41 -0
- package/docs/api/functions/uploadFile.md +32 -0
- package/docs/api/functions/useAccessLevel.md +71 -0
- package/docs/api/functions/useAccessibleApps.md +55 -0
- package/docs/api/functions/useAppConfig.md +20 -0
- package/docs/api/functions/useAuthService.md +15 -0
- package/docs/api/functions/useCan.md +99 -0
- package/docs/api/functions/useEventService.md +15 -0
- package/docs/api/functions/useEventTheme.md +26 -0
- package/docs/api/functions/useEvents.md +45 -0
- package/docs/api/functions/useFileReference.md +264 -0
- package/docs/api/functions/useFileReferenceById.md +63 -0
- package/docs/api/functions/useFileReferenceForRecord.md +129 -0
- package/docs/api/functions/useFilesByCategory.md +80 -0
- package/docs/api/functions/useFormDialog.md +62 -0
- package/docs/api/functions/useInactivityService.md +15 -0
- package/docs/api/functions/useInactivityTracker.md +21 -0
- package/docs/api/functions/useIsPublicPage.md +19 -0
- package/docs/api/functions/useMultiplePermissions.md +88 -0
- package/docs/api/functions/useOptionalEvents.md +31 -0
- package/docs/api/functions/useOrganisationPermissions.md +27 -0
- package/docs/api/functions/useOrganisationSecurity.md +15 -0
- package/docs/api/functions/useOrganisationService.md +15 -0
- package/docs/api/functions/useOrganisations.md +48 -0
- package/docs/api/functions/usePermissions.md +130 -0
- package/docs/api/functions/usePublicEvent.md +36 -0
- package/docs/api/functions/usePublicEventCode.md +32 -0
- package/docs/api/functions/usePublicEventLogo.md +48 -0
- package/docs/api/functions/usePublicFileDisplay.md +54 -0
- package/docs/api/functions/usePublicPageContext.md +19 -0
- package/docs/api/functions/usePublicRouteParams.md +31 -0
- package/docs/api/functions/useRBAC.md +21 -0
- package/docs/api/functions/useResolvedScope.md +46 -0
- package/docs/api/functions/useResourcePermissions.md +25 -0
- package/docs/api/functions/useRoleManagement.md +121 -0
- package/docs/api/functions/useSecureSupabase.md +51 -0
- package/docs/api/functions/useSessionRestoration.md +15 -0
- package/docs/api/functions/useSessionTracking.md +62 -0
- package/docs/api/functions/useToast.md +83 -0
- package/docs/api/functions/useUnifiedAuth.md +24 -0
- package/docs/api/functions/useZodForm.md +27 -0
- package/docs/api/functions/validateFileSize.md +31 -0
- package/docs/api/functions/warnIfInsecureClient.md +40 -0
- package/docs/api/functions/withAccessLevelGuard.md +67 -0
- package/docs/api/functions/withPermissionGuard.md +73 -0
- package/docs/api/functions/withRoleGuard.md +86 -0
- package/docs/api/globals.md +502 -0
- package/docs/api/interfaces/AccessDeniedProps.md +87 -0
- package/docs/api/interfaces/AccessibleApp.md +41 -0
- package/docs/api/interfaces/AddressFieldProps.md +195 -0
- package/docs/api/interfaces/AddressFieldRef.md +67 -0
- package/docs/api/interfaces/AggregateConfig.md +35 -0
- package/docs/api/interfaces/AppSwitcherProps.md +51 -0
- package/docs/api/interfaces/AuthSessionData.md +27 -0
- package/docs/api/interfaces/AutocompleteOptions.md +61 -0
- package/docs/api/interfaces/AvatarProps.md +97 -0
- package/docs/api/interfaces/BadgeProps.md +30 -0
- package/docs/api/interfaces/BuildAppUrlOptions.md +41 -0
- package/docs/api/interfaces/ButtonProps.md +46 -0
- package/docs/api/interfaces/CalendarProps.md +60 -0
- package/docs/api/interfaces/CardProps.md +56 -0
- package/docs/api/interfaces/ColorPalette.md +13 -0
- package/docs/api/interfaces/ColorShade.md +58 -0
- package/docs/api/interfaces/ContextSelectorProps.md +131 -0
- package/docs/api/interfaces/DataRecord.md +16 -0
- package/docs/api/interfaces/DataTableAction.md +198 -0
- package/docs/api/interfaces/DataTableColumn.md +422 -0
- package/docs/api/interfaces/DataTableProps.md +511 -0
- package/docs/api/interfaces/DataTableToolbarButton.md +75 -0
- package/docs/api/interfaces/DatePickerWithTimezoneProps.md +75 -0
- package/docs/api/interfaces/DialogBodyProps.md +55 -0
- package/docs/api/interfaces/DialogCloseProps.md +25 -0
- package/docs/api/interfaces/DialogContentProps.md +160 -0
- package/docs/api/interfaces/DialogFooterProps.md +25 -0
- package/docs/api/interfaces/DialogHeaderProps.md +25 -0
- package/docs/api/interfaces/DialogPortalProps.md +19 -0
- package/docs/api/interfaces/DialogProps.md +53 -0
- package/docs/api/interfaces/DialogTriggerProps.md +53 -0
- package/docs/api/interfaces/EmptyStateConfig.md +55 -0
- package/docs/api/interfaces/ErrorBoundaryProps.md +131 -0
- package/docs/api/interfaces/ErrorBoundaryProviderProps.md +31 -0
- package/docs/api/interfaces/ErrorBoundaryState.md +61 -0
- package/docs/api/interfaces/EventAppRoleData.md +54 -0
- package/docs/api/interfaces/ExportColumn.md +69 -0
- package/docs/api/interfaces/ExportOptions.md +109 -0
- package/docs/api/interfaces/FileDisplayProps.md +192 -0
- package/docs/api/interfaces/FileMetadata.md +97 -0
- package/docs/api/interfaces/FileReference.md +89 -0
- package/docs/api/interfaces/FileSizeLimits.md +13 -0
- package/docs/api/interfaces/FileUploadOptions.md +107 -0
- package/docs/api/interfaces/FooterProps.md +37 -0
- package/docs/api/interfaces/FormFieldProps.md +171 -0
- package/docs/api/interfaces/FormProps.md +93 -0
- package/docs/api/interfaces/GrantEventAppRoleParams.md +97 -0
- package/docs/api/interfaces/ImportSummary.md +49 -0
- package/docs/api/interfaces/InactivityWarningModalProps.md +87 -0
- package/docs/api/interfaces/InputProps.md +46 -0
- package/docs/api/interfaces/InvalidScopeError.md +37 -0
- package/docs/api/interfaces/LabelProps.md +85 -0
- package/docs/api/interfaces/LoggerConfig.md +51 -0
- package/docs/api/interfaces/LoginFormProps.md +146 -0
- package/docs/api/interfaces/MissingUserContextError.md +37 -0
- package/docs/api/interfaces/NavigationGuardProps.md +109 -0
- package/docs/api/interfaces/NavigationItem.md +91 -0
- package/docs/api/interfaces/NavigationMenuProps.md +169 -0
- package/docs/api/interfaces/Organisation.md +105 -0
- package/docs/api/interfaces/OrganisationContextRequiredError.md +37 -0
- package/docs/api/interfaces/OrganisationMembership.md +105 -0
- package/docs/api/interfaces/OrganisationSecurityError.md +49 -0
- package/docs/api/interfaces/PaceAppLayoutPermissionConfig.md +127 -0
- package/docs/api/interfaces/PaceAppLayoutRouteConfigItem.md +91 -0
- package/docs/api/interfaces/PaceAppLayoutRoutingConfig.md +79 -0
- package/docs/api/interfaces/PaceLoginPageProps.md +41 -0
- package/docs/api/interfaces/PagePermissionGuardProps.md +143 -0
- package/docs/api/interfaces/PaletteData.md +33 -0
- package/docs/api/interfaces/ParsedAddress.md +91 -0
- package/docs/api/interfaces/PermissionDeniedError.md +37 -0
- package/docs/api/interfaces/ProgressProps.md +35 -0
- package/docs/api/interfaces/ProtectedRouteProps.md +67 -0
- package/docs/api/interfaces/PublicPageFooterProps.md +97 -0
- package/docs/api/interfaces/PublicPageHeaderProps.md +99 -0
- package/docs/api/interfaces/PublicPageLayoutProps.md +153 -0
- package/docs/api/interfaces/RBACAccessValidateParams.md +41 -0
- package/docs/api/interfaces/RBACAccessValidateResult.md +33 -0
- package/docs/api/interfaces/RBACAuditLogParams.md +65 -0
- package/docs/api/interfaces/RBACAuditLogResult.md +41 -0
- package/docs/api/interfaces/RBACContext.md +41 -0
- package/docs/api/interfaces/RBACError.md +37 -0
- package/docs/api/interfaces/RBACLogger.md +97 -0
- package/docs/api/interfaces/RBACNotInitializedError.md +37 -0
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +57 -0
- package/docs/api/interfaces/RBACPerformanceMetrics.md +109 -0
- package/docs/api/interfaces/RBACPermissionCheckParams.md +57 -0
- package/docs/api/interfaces/RBACPermissionCheckResult.md +41 -0
- package/docs/api/interfaces/RBACPermissionsGetParams.md +49 -0
- package/docs/api/interfaces/RBACPermissionsGetResult.md +49 -0
- package/docs/api/interfaces/RBACResult.md +47 -0
- package/docs/api/interfaces/RBACRoleGrantParams.md +49 -0
- package/docs/api/interfaces/RBACRoleGrantResult.md +41 -0
- package/docs/api/interfaces/RBACRoleRevokeParams.md +49 -0
- package/docs/api/interfaces/RBACRoleRevokeResult.md +41 -0
- package/docs/api/interfaces/RBACRoleValidateParams.md +41 -0
- package/docs/api/interfaces/RBACRoleValidateResult.md +49 -0
- package/docs/api/interfaces/RBACRolesListParams.md +41 -0
- package/docs/api/interfaces/RBACRolesListResult.md +57 -0
- package/docs/api/interfaces/RBACSessionTrackParams.md +57 -0
- package/docs/api/interfaces/RBACSessionTrackResult.md +41 -0
- package/docs/api/interfaces/ResourcePermissions.md +119 -0
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +81 -0
- package/docs/api/interfaces/RoleManagementResult.md +41 -0
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +29 -0
- package/docs/api/interfaces/StorageConfig.md +33 -0
- package/docs/api/interfaces/StorageFileInfo.md +57 -0
- package/docs/api/interfaces/StorageFileMetadata.md +113 -0
- package/docs/api/interfaces/StorageListOptions.md +79 -0
- package/docs/api/interfaces/StorageListResult.md +33 -0
- package/docs/api/interfaces/StorageUploadOptions.md +81 -0
- package/docs/api/interfaces/StorageUploadResult.md +49 -0
- package/docs/api/interfaces/StorageUploadSuccess.md +35 -0
- package/docs/api/interfaces/StorageUrlOptions.md +49 -0
- package/docs/api/interfaces/StyleImport.md +17 -0
- package/docs/api/interfaces/SwitchProps.md +30 -0
- package/docs/api/interfaces/TabsContentProps.md +13 -0
- package/docs/api/interfaces/TabsListProps.md +13 -0
- package/docs/api/interfaces/TabsProps.md +13 -0
- package/docs/api/interfaces/TabsTriggerProps.md +41 -0
- package/docs/api/interfaces/TextareaProps.md +43 -0
- package/docs/api/interfaces/ToastActionElement.md +16 -0
- package/docs/api/interfaces/ToastProps.md +13 -0
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +129 -0
- package/docs/api/interfaces/UseAccessibleAppsReturn.md +55 -0
- package/docs/api/interfaces/UseFormDialogOptions.md +49 -0
- package/docs/api/interfaces/UseFormDialogReturn.md +95 -0
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +103 -0
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +91 -0
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +69 -0
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +66 -0
- package/docs/api/interfaces/UsePublicEventOptions.md +29 -0
- package/docs/api/interfaces/UsePublicEventReturn.md +56 -0
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +39 -0
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +96 -0
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +76 -0
- package/docs/api/interfaces/UseResolvedScopeOptions.md +49 -0
- package/docs/api/interfaces/UseResolvedScopeReturn.md +39 -0
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +29 -0
- package/docs/api/interfaces/UserEventAccess.md +92 -0
- package/docs/api/interfaces/UserMenuProps.md +69 -0
- package/docs/api/interfaces/UserProfile.md +49 -0
- package/docs/api/type-aliases/AccessLevel.md +11 -0
- package/docs/api/type-aliases/AccessLevelContext.md +14 -0
- package/docs/api/type-aliases/AllPermissions.md +11 -0
- package/docs/api/type-aliases/ApiError.md +37 -0
- package/docs/api/type-aliases/ApiResult.md +19 -0
- package/docs/api/type-aliases/AuditEventType.md +11 -0
- package/docs/api/type-aliases/BadgeVariant.md +18 -0
- package/docs/api/type-aliases/DataTableFeatureConfig.md +14 -0
- package/docs/api/type-aliases/DialogSize.md +13 -0
- package/docs/api/type-aliases/EventAppRole.md +11 -0
- package/docs/api/type-aliases/FileUploadProps.md +15 -0
- package/docs/api/type-aliases/GetRowId.md +33 -0
- package/docs/api/type-aliases/GlobalErrorHandler.md +35 -0
- package/docs/api/type-aliases/GlobalRole.md +11 -0
- package/docs/api/type-aliases/ImportHandlerResult.md +13 -0
- package/docs/api/type-aliases/NavigationMode.md +13 -0
- package/docs/api/type-aliases/Operation.md +11 -0
- package/docs/api/type-aliases/OrganisationContextType.md +13 -0
- package/docs/api/type-aliases/OrganisationRole.md +11 -0
- package/docs/api/type-aliases/PaceAppLayoutProps.md +14 -0
- package/docs/api/type-aliases/Permission.md +11 -0
- package/docs/api/type-aliases/PermissionCheck.md +46 -0
- package/docs/api/type-aliases/PermissionMap.md +11 -0
- package/docs/api/type-aliases/PermissionSource.md +13 -0
- package/docs/api/type-aliases/RBACConfig.md +13 -0
- package/docs/api/type-aliases/RBACFunctionResponse.md +57 -0
- package/docs/api/type-aliases/Scope.md +41 -0
- package/docs/api/type-aliases/SessionType.md +11 -0
- package/docs/api/type-aliases/UUID.md +11 -0
- package/docs/api/type-aliases/UnifiedAuthContextType.md +13 -0
- package/docs/api/type-aliases/UseFileReferenceForRecordReturn.md +161 -0
- package/docs/api/type-aliases/UseFileReferenceOptions.md +35 -0
- package/docs/api/type-aliases/UseFileReferenceReturn.md +13 -0
- package/docs/api/variables/ALL_PERMISSIONS.md +281 -0
- package/docs/api/variables/APP_PATH_MAPPING.md +14 -0
- package/docs/api/variables/AddressField.md +41 -0
- package/docs/api/variables/Alert.md +11 -0
- package/docs/api/variables/AlertDescription.md +11 -0
- package/docs/api/variables/AlertTitle.md +11 -0
- package/docs/api/variables/Avatar.md +13 -0
- package/docs/api/variables/Button.md +31 -0
- package/docs/api/variables/CACHE_PATTERNS.md +89 -0
- package/docs/api/variables/Calendar.md +74 -0
- package/docs/api/variables/Card.md +11 -0
- package/docs/api/variables/CardActions.md +11 -0
- package/docs/api/variables/CardContent.md +11 -0
- package/docs/api/variables/CardDescription.md +11 -0
- package/docs/api/variables/CardFooter.md +11 -0
- package/docs/api/variables/CardHeader.md +11 -0
- package/docs/api/variables/CardTitle.md +11 -0
- package/docs/api/variables/Checkbox.md +11 -0
- package/docs/api/variables/DEFAULT_APP_PORT_MAP.md +14 -0
- package/docs/api/variables/DEFAULT_FILE_SIZE_LIMIT.md +13 -0
- package/docs/api/variables/Dialog.md +14 -0
- package/docs/api/variables/DialogClose.md +14 -0
- package/docs/api/variables/DialogContent.md +28 -0
- package/docs/api/variables/DialogDescription.md +14 -0
- package/docs/api/variables/DialogPortal.md +14 -0
- package/docs/api/variables/DialogTitle.md +14 -0
- package/docs/api/variables/DialogTrigger.md +14 -0
- package/docs/api/variables/EVENT_APP_PERMISSIONS.md +109 -0
- package/docs/api/variables/ErrorBoundary.md +15 -0
- package/docs/api/variables/FILE_SIZE_LIMITS.md +13 -0
- package/docs/api/variables/Footer.md +11 -0
- package/docs/api/variables/GLOBAL_PERMISSIONS.md +29 -0
- package/docs/api/variables/Label.md +34 -0
- package/docs/api/variables/LoadingSpinner.md +28 -0
- package/docs/api/variables/LoginForm.md +34 -0
- package/docs/api/variables/NavigationMenu.md +203 -0
- package/docs/api/variables/ORGANISATION_PERMISSIONS.md +89 -0
- package/docs/api/variables/PAGE_PERMISSIONS.md +93 -0
- package/docs/api/variables/PaceLoginPage.md +40 -0
- package/docs/api/variables/PagePermissionGuard.md +11 -0
- package/docs/api/variables/Progress.md +32 -0
- package/docs/api/variables/SECURE_CLIENT_SYMBOL.md +14 -0
- package/docs/api/variables/STORAGE_CONFIG.md +13 -0
- package/docs/api/variables/Select.md +26 -0
- package/docs/api/variables/SelectContent.md +26 -0
- package/docs/api/variables/SelectGroup.md +26 -0
- package/docs/api/variables/SelectItem.md +26 -0
- package/docs/api/variables/SelectLabel.md +26 -0
- package/docs/api/variables/SelectSeparator.md +26 -0
- package/docs/api/variables/SelectTrigger.md +26 -0
- package/docs/api/variables/SelectValue.md +26 -0
- package/docs/api/variables/SessionRestorationLoader.md +11 -0
- package/docs/api/variables/Switch.md +23 -0
- package/docs/api/variables/Table.md +35 -0
- package/docs/api/variables/TableBody.md +11 -0
- package/docs/api/variables/TableCaption.md +11 -0
- package/docs/api/variables/TableCell.md +11 -0
- package/docs/api/variables/TableFooter.md +11 -0
- package/docs/api/variables/TableHead.md +11 -0
- package/docs/api/variables/TableHeader.md +11 -0
- package/docs/api/variables/TableRow.md +11 -0
- package/docs/api/variables/Tabs.md +25 -0
- package/docs/api/variables/TabsContent.md +24 -0
- package/docs/api/variables/TabsList.md +25 -0
- package/docs/api/variables/TabsTrigger.md +34 -0
- package/docs/api/variables/Toast.md +36 -0
- package/docs/api/variables/ToastAction.md +32 -0
- package/docs/api/variables/ToastClose.md +32 -0
- package/docs/api/variables/ToastDescription.md +32 -0
- package/docs/api/variables/ToastProvider.md +11 -0
- package/docs/api/variables/ToastTitle.md +32 -0
- package/docs/api/variables/ToastViewport.md +26 -0
- package/docs/api/variables/Tooltip.md +34 -0
- package/docs/api/variables/TooltipContent.md +34 -0
- package/docs/api/variables/TooltipProvider.md +11 -0
- package/docs/api/variables/TooltipRoot.md +11 -0
- package/docs/api/variables/TooltipTrigger.md +11 -0
- package/docs/api/variables/UserMenu.md +11 -0
- package/docs/api/variables/emailSchema.md +13 -0
- package/docs/api/variables/logger.md +203 -0
- package/docs/api/variables/nameSchema.md +13 -0
- package/docs/api/variables/passwordSchema.md +13 -0
- package/docs/api/variables/phoneSchema.md +13 -0
- package/docs/api/variables/rbacCache.md +16 -0
- package/docs/api/variables/styleConfig.md +25 -0
- package/docs/api/variables/urlSchema.md +13 -0
- package/docs/api-reference/hooks.md +2 -0
- package/docs/implementation-guides/data-tables.md +8 -0
- package/docs/rbac/getting-started.md +7 -0
- package/docs/rbac/troubleshooting.md +5 -1
- package/package.json +3 -3
- package/src/components/DataTable/hooks/useDataTableEffectiveActions.ts +29 -19
- package/src/components/DataTable/hooks/useDataTableScope.test.ts +5 -13
- package/src/components/DataTable/hooks/useDataTableScope.ts +16 -14
- package/src/components/Dialog/useDialogLifecycle.test.ts +4 -1
- package/src/components/FileDisplay/useFileDisplay.unit.test.ts +12 -8
- package/src/components/PaceAppLayout/PaceAppLayout.edge-cases.test.tsx +33 -9
- package/src/components/PaceAppLayout/useFilteredNavItems.ts +22 -7
- package/src/components/PaceAppLayout/usePaceAppLayoutConfig.ts +44 -23
- package/src/components/PaceAppLayout/usePaceAppLayoutPermissions.ts +1 -1
- package/src/components/PaceAppLayout/usePaceAppLayoutScope.ts +6 -4
- package/src/components/PaceAppLayout/useRoleBasedRouteAccess.ts +2 -2
- package/src/hooks/useAppConfig.unit.test.ts +74 -66
- package/src/hooks/useComponentPerformance.unit.test.tsx +6 -4
- package/src/hooks/useFileUrl.unit.test.ts +1 -3
- package/src/hooks/useInactivityTracker.unit.test.ts +6 -2
- package/src/hooks/usePerformanceMonitor.unit.test.ts +6 -16
- package/src/hooks/usePublicEvent.simple.test.ts +32 -47
- package/src/hooks/usePublicEvent.test.ts +9 -15
- package/src/providers/services/AuthServiceProvider.test.tsx +10 -5
- package/src/providers/services/EventServiceProvider.test.tsx +8 -3
- package/src/providers/services/InactivityServiceProvider.test.tsx +8 -3
- package/src/providers/services/OrganisationServiceProvider.test.tsx +8 -3
- package/src/rbac/README.md +7 -5
- package/src/rbac/api.test.ts +113 -56
- package/src/rbac/api.ts +80 -10
- package/src/rbac/components/NavigationGuard.tsx +2 -1
- package/src/rbac/components/PagePermissionGuard.test.tsx +23 -10
- package/src/rbac/engine.ts +23 -1
- package/src/rbac/hooks/permissions/runPermissionCheck.ts +18 -4
- package/src/rbac/hooks/permissions/useCan.test.ts +59 -20
- package/src/rbac/hooks/permissions/useCan.ts +7 -3
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +18 -9
- package/src/rbac/hooks/useCan.test.ts +2 -3
- package/src/rbac/hooks/usePageGuardScope.ts +6 -4
- package/src/rbac/hooks/usePagePermissionCheck.ts +4 -4
- package/src/rbac/hooks/useResolvedScope.ts +16 -10
- package/src/rbac/hooks/useResourcePermissions.test.ts +48 -58
- package/src/rbac/hooks/useResourcePermissions.ts +11 -20
- package/src/rbac/types.ts +3 -0
- package/src/services/AuthService.edge-cases.test.ts +2 -2
- package/src/services/EventService.ts +9 -4
- package/src/utils/file-reference/file-reference.test.ts +25 -14
- package/src/utils/supabase/createBaseClient.test.ts +30 -13
- package/docs/api/modules.md +0 -10028
|
@@ -45,6 +45,11 @@ vi.mock('../../utils/app/appNameResolver', () => ({
|
|
|
45
45
|
getCurrentAppName: vi.fn()
|
|
46
46
|
}));
|
|
47
47
|
|
|
48
|
+
// Mock useSuperAdminCheck so guard does not wait on async super-admin check
|
|
49
|
+
vi.mock('../hooks/useSuperAdminCheck', () => ({
|
|
50
|
+
useSuperAdminCheck: () => ({ isSuperAdmin: false })
|
|
51
|
+
}));
|
|
52
|
+
|
|
48
53
|
// Mock the RBAC API for super admin checks
|
|
49
54
|
vi.mock('../api', async () => {
|
|
50
55
|
const actual = await vi.importActual('../api');
|
|
@@ -546,7 +551,7 @@ describe('PagePermissionGuard Component', () => {
|
|
|
546
551
|
'read:page.dashboard',
|
|
547
552
|
'dashboard',
|
|
548
553
|
true,
|
|
549
|
-
|
|
554
|
+
false, // precomputedSuperAdmin (from mocked useSuperAdminCheck)
|
|
550
555
|
'test-app'
|
|
551
556
|
);
|
|
552
557
|
});
|
|
@@ -582,7 +587,7 @@ describe('PagePermissionGuard Component', () => {
|
|
|
582
587
|
'read:page.dashboard',
|
|
583
588
|
'dashboard',
|
|
584
589
|
true,
|
|
585
|
-
|
|
590
|
+
false, // precomputedSuperAdmin (from mocked useSuperAdminCheck)
|
|
586
591
|
'test-app'
|
|
587
592
|
);
|
|
588
593
|
});
|
|
@@ -650,7 +655,7 @@ describe('PagePermissionGuard Component', () => {
|
|
|
650
655
|
'read:page.dashboard',
|
|
651
656
|
'dashboard',
|
|
652
657
|
true,
|
|
653
|
-
|
|
658
|
+
false, // precomputedSuperAdmin (from mocked useSuperAdminCheck)
|
|
654
659
|
'test-app'
|
|
655
660
|
);
|
|
656
661
|
});
|
|
@@ -719,7 +724,7 @@ describe('PagePermissionGuard Component', () => {
|
|
|
719
724
|
'read:page.dashboard',
|
|
720
725
|
'dashboard',
|
|
721
726
|
true,
|
|
722
|
-
|
|
727
|
+
false, // precomputedSuperAdmin (from mocked useSuperAdminCheck)
|
|
723
728
|
'test-app'
|
|
724
729
|
);
|
|
725
730
|
});
|
|
@@ -1268,7 +1273,7 @@ describe('PagePermissionGuard Component', () => {
|
|
|
1268
1273
|
'read:page.dashboard',
|
|
1269
1274
|
'dashboard',
|
|
1270
1275
|
true,
|
|
1271
|
-
|
|
1276
|
+
false, // precomputedSuperAdmin (from mocked useSuperAdminCheck)
|
|
1272
1277
|
'PORTAL'
|
|
1273
1278
|
);
|
|
1274
1279
|
}, TEST_TIMEOUT);
|
|
@@ -1320,7 +1325,7 @@ describe('PagePermissionGuard Component', () => {
|
|
|
1320
1325
|
'read:page.dashboard',
|
|
1321
1326
|
'dashboard',
|
|
1322
1327
|
true,
|
|
1323
|
-
|
|
1328
|
+
false,
|
|
1324
1329
|
'ADMIN'
|
|
1325
1330
|
);
|
|
1326
1331
|
}, TEST_TIMEOUT);
|
|
@@ -1364,7 +1369,7 @@ describe('PagePermissionGuard Component', () => {
|
|
|
1364
1369
|
expect(screen.getByTestId('test-component')).toBeInTheDocument();
|
|
1365
1370
|
}, { interval: 10 });
|
|
1366
1371
|
|
|
1367
|
-
// Should use contextAppId as fallback
|
|
1372
|
+
// Should use contextAppId as fallback (precomputedSuperAdmin is false from mocked useSuperAdminCheck)
|
|
1368
1373
|
expect(mockUseCanFn).toHaveBeenCalledWith(
|
|
1369
1374
|
'user-123',
|
|
1370
1375
|
expect.objectContaining({
|
|
@@ -1373,7 +1378,7 @@ describe('PagePermissionGuard Component', () => {
|
|
|
1373
1378
|
'read:page.dashboard',
|
|
1374
1379
|
'dashboard',
|
|
1375
1380
|
true,
|
|
1376
|
-
|
|
1381
|
+
false,
|
|
1377
1382
|
'PORTAL'
|
|
1378
1383
|
);
|
|
1379
1384
|
}, TEST_TIMEOUT);
|
|
@@ -1403,7 +1408,7 @@ describe('PagePermissionGuard Component', () => {
|
|
|
1403
1408
|
error: null
|
|
1404
1409
|
});
|
|
1405
1410
|
|
|
1406
|
-
render(
|
|
1411
|
+
const { rerender } = render(
|
|
1407
1412
|
<PagePermissionGuard
|
|
1408
1413
|
pageName={mockPageName}
|
|
1409
1414
|
operation={mockOperation}
|
|
@@ -1415,12 +1420,20 @@ describe('PagePermissionGuard Component', () => {
|
|
|
1415
1420
|
// Component should show loading state
|
|
1416
1421
|
expect(screen.getByText('Checking permissions...')).toBeInTheDocument();
|
|
1417
1422
|
|
|
1418
|
-
// Then resolve the permission check
|
|
1423
|
+
// Then resolve the permission check and trigger re-render so the guard sees the update
|
|
1419
1424
|
mockUseCanFn.mockReturnValue({
|
|
1420
1425
|
can: true,
|
|
1421
1426
|
isLoading: false,
|
|
1422
1427
|
error: null
|
|
1423
1428
|
});
|
|
1429
|
+
rerender(
|
|
1430
|
+
<PagePermissionGuard
|
|
1431
|
+
pageName={mockPageName}
|
|
1432
|
+
operation={mockOperation}
|
|
1433
|
+
>
|
|
1434
|
+
<TestComponent>Protected Page</TestComponent>
|
|
1435
|
+
</PagePermissionGuard>
|
|
1436
|
+
);
|
|
1424
1437
|
|
|
1425
1438
|
await waitFor(() => {
|
|
1426
1439
|
expect(screen.getByTestId('test-component')).toBeInTheDocument();
|
package/src/rbac/engine.ts
CHANGED
|
@@ -91,11 +91,28 @@ export class RBACEngine {
|
|
|
91
91
|
// Validate input
|
|
92
92
|
const validation = await this.securityMiddleware.validateInput(input, securityContext);
|
|
93
93
|
if (!validation.isValid) {
|
|
94
|
+
const scopeRejected = validation.errors.includes('Invalid scope format');
|
|
95
|
+
getRBACLogger().warn('[RBAC] Validation failed — check that an event is selected if the Menu/units page requires it.', {
|
|
96
|
+
errors: validation.errors,
|
|
97
|
+
scope: input.scope,
|
|
98
|
+
permission: input.permission,
|
|
99
|
+
});
|
|
94
100
|
RBACSecurityValidator.logSecurityEvent({
|
|
95
101
|
type: 'invalid_input',
|
|
96
102
|
userId,
|
|
97
|
-
details: {
|
|
103
|
+
details: {
|
|
104
|
+
errors: validation.errors,
|
|
105
|
+
input: JSON.stringify(input),
|
|
106
|
+
...(scopeRejected && { scopeRejected: input.scope }),
|
|
107
|
+
},
|
|
98
108
|
});
|
|
109
|
+
if (scopeRejected) {
|
|
110
|
+
getRBACLogger().warn('[RBAC] Invalid scope (from middleware). Scope must have at least one of organisationId, eventId, appId; no empty strings.', {
|
|
111
|
+
scope: input.scope,
|
|
112
|
+
permission: input.permission,
|
|
113
|
+
userId,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
99
116
|
return false;
|
|
100
117
|
}
|
|
101
118
|
|
|
@@ -137,6 +154,11 @@ export class RBACEngine {
|
|
|
137
154
|
userId,
|
|
138
155
|
details: { error: 'Invalid scope format', scope },
|
|
139
156
|
});
|
|
157
|
+
getRBACLogger().warn('[RBAC] Invalid scope (engine check). Scope must have at least one of organisationId, eventId, appId; no empty strings.', {
|
|
158
|
+
scope,
|
|
159
|
+
permission,
|
|
160
|
+
userId,
|
|
161
|
+
});
|
|
140
162
|
return false;
|
|
141
163
|
}
|
|
142
164
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ApiResult } from '../../../types/api-result';
|
|
2
2
|
import type { Permission, Scope, UUID } from '../../types';
|
|
3
3
|
import { isPermitted, isPermittedCached } from '../../api';
|
|
4
|
+
import { getRBACConfig, getRBACLogger } from '../../config';
|
|
4
5
|
|
|
5
6
|
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6
7
|
|
|
@@ -56,16 +57,29 @@ export async function runPermissionCheck(params: {
|
|
|
56
57
|
!UUID_REGEX.test(pageId);
|
|
57
58
|
const needsAppIdForPageName = isPagePermission && !!isPageName;
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
// Only deny early when appId is missing and we cannot resolve it (no eventId).
|
|
61
|
+
// When eventId is present, pass scope through so api layer can resolve appId from current app name.
|
|
62
|
+
if (needsAppIdForPageName && isEmptyString(appId) && isEmptyString(eventId)) {
|
|
60
63
|
return { ok: true, data: false };
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
// Build scope with only non-empty values so we never send scope that fails validateScope (no empty strings)
|
|
63
67
|
const validScope: Scope = {
|
|
64
|
-
...(organisationId ? { organisationId } : {}),
|
|
65
|
-
...(eventId ? { eventId } : {}),
|
|
66
|
-
...(appId ? { appId } : {}),
|
|
68
|
+
...(!isEmptyString(organisationId) && organisationId ? { organisationId } : {}),
|
|
69
|
+
...(!isEmptyString(eventId) && eventId ? { eventId } : {}),
|
|
70
|
+
...(!isEmptyString(appId) && appId ? { appId } : {}),
|
|
67
71
|
};
|
|
68
72
|
|
|
73
|
+
// Never call engine with empty scope — validateScope requires at least one of org/event/app; otherwise engine logs invalid_input
|
|
74
|
+
if (!validScope.organisationId && !validScope.eventId && !validScope.appId) {
|
|
75
|
+
return { ok: true, data: false };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const config = getRBACConfig();
|
|
79
|
+
if (config?.debug || config?.logLevel === 'debug') {
|
|
80
|
+
getRBACLogger().debug('[RBAC] runPermissionCheck', { scope: validScope, permission, pageId, useCache });
|
|
81
|
+
}
|
|
82
|
+
|
|
69
83
|
if (useCache) {
|
|
70
84
|
return isPermittedCached({ userId, scope: validScope, permission, pageId }, appName);
|
|
71
85
|
}
|
|
@@ -35,7 +35,7 @@ vi.mock('../../api', async () => {
|
|
|
35
35
|
};
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
// Mock RBAC
|
|
38
|
+
// Mock RBAC config (runPermissionCheck calls getRBACConfig)
|
|
39
39
|
vi.mock('../../config', () => ({
|
|
40
40
|
getRBACLogger: vi.fn(() => ({
|
|
41
41
|
warn: vi.fn(),
|
|
@@ -43,15 +43,21 @@ vi.mock('../../config', () => ({
|
|
|
43
43
|
info: vi.fn(),
|
|
44
44
|
debug: vi.fn(),
|
|
45
45
|
})),
|
|
46
|
+
getRBACConfig: vi.fn().mockReturnValue({ debug: false, logLevel: 'warn' as const }),
|
|
46
47
|
}));
|
|
47
48
|
|
|
48
49
|
import { isPermitted, isPermittedCached, isSuperAdmin } from '../../api';
|
|
49
50
|
|
|
50
51
|
const mockUserId = 'user-123';
|
|
52
|
+
// Valid UUIDs so RBACSecurityValidator.validateScope passes in API
|
|
53
|
+
const VALID_ORG_ID = '00000000-0000-0000-0000-000000000001';
|
|
54
|
+
const VALID_APP_ID = '00000000-0000-0000-0000-000000000002';
|
|
55
|
+
const VALID_EVENT_ID = '00000000-0000-0000-0000-000000000003';
|
|
56
|
+
const VALID_ORG_ID_2 = '00000000-0000-0000-0000-000000000099'; // second org for refetch tests
|
|
51
57
|
const mockScope = {
|
|
52
|
-
organisationId:
|
|
53
|
-
eventId:
|
|
54
|
-
appId:
|
|
58
|
+
organisationId: VALID_ORG_ID,
|
|
59
|
+
eventId: VALID_EVENT_ID,
|
|
60
|
+
appId: VALID_APP_ID,
|
|
55
61
|
};
|
|
56
62
|
const mockPermission = 'read:users' as const;
|
|
57
63
|
|
|
@@ -272,7 +278,7 @@ describe('useCan Hook', () => {
|
|
|
272
278
|
|
|
273
279
|
it('times out when organisation context is missing for resource-level permission', async () => {
|
|
274
280
|
vi.useFakeTimers();
|
|
275
|
-
const scopeWithoutOrg = { eventId:
|
|
281
|
+
const scopeWithoutOrg = { eventId: VALID_EVENT_ID };
|
|
276
282
|
|
|
277
283
|
const { result } = renderHook(() =>
|
|
278
284
|
useCan(mockUserId, scopeWithoutOrg, mockPermission)
|
|
@@ -292,7 +298,7 @@ describe('useCan Hook', () => {
|
|
|
292
298
|
|
|
293
299
|
it('allows undefined organisationId for page-level permissions', async () => {
|
|
294
300
|
const pagePermission = 'read:page.users' as const;
|
|
295
|
-
const scopeWithoutOrg = { eventId:
|
|
301
|
+
const scopeWithoutOrg = { eventId: VALID_EVENT_ID };
|
|
296
302
|
mockIsPermittedCached.mockResolvedValue({ ok: true, data: true });
|
|
297
303
|
|
|
298
304
|
const { result } = renderHook(() =>
|
|
@@ -310,7 +316,7 @@ describe('useCan Hook', () => {
|
|
|
310
316
|
const pageId = 'page-123';
|
|
311
317
|
// pageId is a UUID, so it doesn't need appId
|
|
312
318
|
// Provide valid scope with eventId and appId
|
|
313
|
-
const scopeWithoutOrg = { eventId:
|
|
319
|
+
const scopeWithoutOrg = { eventId: VALID_EVENT_ID, appId: VALID_APP_ID };
|
|
314
320
|
mockIsPermittedCached.mockResolvedValue({ ok: true, data: true });
|
|
315
321
|
// Set precomputedSuperAdmin to false to skip super admin check
|
|
316
322
|
mockIsSuperAdmin.mockResolvedValue({ ok: true, data: false });
|
|
@@ -335,7 +341,7 @@ describe('useCan Hook', () => {
|
|
|
335
341
|
|
|
336
342
|
it('waits for appId when pageId is a pageName (not UUID)', async () => {
|
|
337
343
|
const pageName = 'users-page'; // Not a UUID
|
|
338
|
-
const scopeWithoutAppId = { organisationId:
|
|
344
|
+
const scopeWithoutAppId = { organisationId: VALID_ORG_ID };
|
|
339
345
|
mockIsPermittedCached.mockResolvedValue({ ok: true, data: true });
|
|
340
346
|
|
|
341
347
|
const { result, rerender } = renderHook(
|
|
@@ -349,7 +355,7 @@ describe('useCan Hook', () => {
|
|
|
349
355
|
expect(result.current.isLoading).toBe(true);
|
|
350
356
|
|
|
351
357
|
// Provide appId
|
|
352
|
-
const scopeWithAppId = { ...scopeWithoutAppId, appId:
|
|
358
|
+
const scopeWithAppId = { ...scopeWithoutAppId, appId: VALID_APP_ID };
|
|
353
359
|
rerender({ scope: scopeWithAppId });
|
|
354
360
|
|
|
355
361
|
await waitFor(() => {
|
|
@@ -361,9 +367,42 @@ describe('useCan Hook', () => {
|
|
|
361
367
|
}, { timeout: WAIT_FOR_TIMEOUT });
|
|
362
368
|
}, TEST_TIMEOUT);
|
|
363
369
|
|
|
370
|
+
it('calls API when scope has eventId but no appId for page-name permission (appId resolved in api)', async () => {
|
|
371
|
+
const pageName = 'items';
|
|
372
|
+
const pagePermission = 'read:page.items' as const;
|
|
373
|
+
const scopeWithEventNoApp = { organisationId: VALID_ORG_ID, eventId: VALID_EVENT_ID };
|
|
374
|
+
mockIsPermittedCached.mockResolvedValue({ ok: true, data: true });
|
|
375
|
+
|
|
376
|
+
const { result } = renderHook(() =>
|
|
377
|
+
useCan(mockUserId, scopeWithEventNoApp, pagePermission, pageName)
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
await waitFor(() => {
|
|
381
|
+
expect(mockIsPermittedCached).toHaveBeenCalled();
|
|
382
|
+
}, { timeout: WAIT_FOR_TIMEOUT });
|
|
383
|
+
|
|
384
|
+
await waitFor(() => {
|
|
385
|
+
expect(result.current.isLoading).toBe(false);
|
|
386
|
+
expect(result.current.can).toBe(true);
|
|
387
|
+
}, { timeout: WAIT_FOR_TIMEOUT });
|
|
388
|
+
|
|
389
|
+
expect(mockIsPermittedCached).toHaveBeenCalledWith(
|
|
390
|
+
expect.objectContaining({
|
|
391
|
+
userId: mockUserId,
|
|
392
|
+
scope: expect.objectContaining({
|
|
393
|
+
organisationId: VALID_ORG_ID,
|
|
394
|
+
eventId: VALID_EVENT_ID,
|
|
395
|
+
}),
|
|
396
|
+
permission: pagePermission,
|
|
397
|
+
pageId: pageName,
|
|
398
|
+
}),
|
|
399
|
+
undefined
|
|
400
|
+
);
|
|
401
|
+
}, TEST_TIMEOUT);
|
|
402
|
+
|
|
364
403
|
it('clears error when organisation context becomes available', async () => {
|
|
365
404
|
vi.useFakeTimers();
|
|
366
|
-
const scopeWithoutOrg = { eventId:
|
|
405
|
+
const scopeWithoutOrg = { eventId: VALID_EVENT_ID };
|
|
367
406
|
|
|
368
407
|
const { result, rerender } = renderHook(
|
|
369
408
|
({ scope }) => useCan(mockUserId, scope, mockPermission),
|
|
@@ -381,7 +420,7 @@ describe('useCan Hook', () => {
|
|
|
381
420
|
}, { timeout: WAIT_FOR_TIMEOUT });
|
|
382
421
|
|
|
383
422
|
// Provide organisation context
|
|
384
|
-
const scopeWithOrg = { ...scopeWithoutOrg, organisationId:
|
|
423
|
+
const scopeWithOrg = { ...scopeWithoutOrg, organisationId: VALID_ORG_ID };
|
|
385
424
|
mockIsPermittedCached.mockResolvedValue({ ok: true, data: true });
|
|
386
425
|
rerender({ scope: scopeWithOrg });
|
|
387
426
|
|
|
@@ -413,11 +452,11 @@ describe('useCan Hook', () => {
|
|
|
413
452
|
|
|
414
453
|
it('allows undefined organisationId for page-level permissions', async () => {
|
|
415
454
|
const pagePermission = 'read:page.users' as const;
|
|
416
|
-
const
|
|
455
|
+
const scopeWithAppIdOnly = { appId: VALID_APP_ID };
|
|
417
456
|
mockIsPermittedCached.mockResolvedValue({ ok: true, data: true });
|
|
418
457
|
|
|
419
458
|
const { result } = renderHook(() =>
|
|
420
|
-
useCan(mockUserId,
|
|
459
|
+
useCan(mockUserId, scopeWithAppIdOnly, pagePermission)
|
|
421
460
|
);
|
|
422
461
|
|
|
423
462
|
await waitFor(() => {
|
|
@@ -429,7 +468,7 @@ describe('useCan Hook', () => {
|
|
|
429
468
|
it('treats permission with pageId as page-level', async () => {
|
|
430
469
|
const pageId = 'page-123';
|
|
431
470
|
// Provide valid scope with eventId and appId
|
|
432
|
-
const scopeWithoutOrg = { eventId:
|
|
471
|
+
const scopeWithoutOrg = { eventId: VALID_EVENT_ID, appId: VALID_APP_ID };
|
|
433
472
|
mockIsPermittedCached.mockResolvedValue({ ok: true, data: true });
|
|
434
473
|
// Set precomputedSuperAdmin to false to skip super admin check
|
|
435
474
|
mockIsSuperAdmin.mockResolvedValue({ ok: true, data: false });
|
|
@@ -509,7 +548,7 @@ describe('useCan Hook', () => {
|
|
|
509
548
|
expect(result.current.isLoading).toBe(false);
|
|
510
549
|
}, { timeout: WAIT_FOR_TIMEOUT });
|
|
511
550
|
|
|
512
|
-
const newScope = { ...mockScope, organisationId:
|
|
551
|
+
const newScope = { ...mockScope, organisationId: VALID_ORG_ID_2 };
|
|
513
552
|
rerender({ scope: newScope });
|
|
514
553
|
|
|
515
554
|
await waitFor(() => {
|
|
@@ -600,11 +639,11 @@ describe('useCan Hook', () => {
|
|
|
600
639
|
|
|
601
640
|
const _initialCallCount = mockIsPermittedCached.mock.calls.length;
|
|
602
641
|
|
|
603
|
-
// Create new scope object with same values
|
|
642
|
+
// Create new scope object with same values (valid UUIDs)
|
|
604
643
|
const newScope = {
|
|
605
|
-
organisationId:
|
|
606
|
-
eventId:
|
|
607
|
-
appId:
|
|
644
|
+
organisationId: VALID_ORG_ID,
|
|
645
|
+
eventId: VALID_EVENT_ID,
|
|
646
|
+
appId: VALID_APP_ID,
|
|
608
647
|
};
|
|
609
648
|
rerender({ scope: newScope });
|
|
610
649
|
|
|
@@ -713,7 +752,7 @@ describe('useCan Hook', () => {
|
|
|
713
752
|
|
|
714
753
|
// Rapid changes
|
|
715
754
|
rerender({ userId: 'user-2', scope: mockScope });
|
|
716
|
-
rerender({ userId: mockUserId, scope: { ...mockScope, organisationId:
|
|
755
|
+
rerender({ userId: mockUserId, scope: { ...mockScope, organisationId: VALID_ORG_ID_2 } });
|
|
717
756
|
rerender({ userId: mockUserId, scope: mockScope });
|
|
718
757
|
|
|
719
758
|
await waitFor(() => {
|
|
@@ -87,7 +87,7 @@ function useSuperAdminForCan(
|
|
|
87
87
|
* Hook to check if user can perform an action
|
|
88
88
|
*
|
|
89
89
|
* @param userId - User ID
|
|
90
|
-
* @param scope - Scope for permission checking
|
|
90
|
+
* @param scope - Scope for permission checking; null means scope not yet resolved — permission check stays in loading, no RPC is called
|
|
91
91
|
* @param permission - Permission to check
|
|
92
92
|
* @param pageId - Optional page ID
|
|
93
93
|
* @param useCache - Whether to use cached results
|
|
@@ -108,7 +108,7 @@ function useSuperAdminForCan(
|
|
|
108
108
|
*/
|
|
109
109
|
export function useCan(
|
|
110
110
|
userId: UUID,
|
|
111
|
-
scope: Scope,
|
|
111
|
+
scope: Scope | null,
|
|
112
112
|
permission: Permission,
|
|
113
113
|
pageId?: UUID,
|
|
114
114
|
useCache: boolean = true,
|
|
@@ -129,7 +129,11 @@ export function useCan(
|
|
|
129
129
|
const [isLoading, setIsLoading] = useState<boolean>(initialIsLoading);
|
|
130
130
|
const [error, setError] = useState<Error | null>(null);
|
|
131
131
|
|
|
132
|
-
|
|
132
|
+
// Require at least one identifier so we never call the RPC with scope that fails validateScope
|
|
133
|
+
const isValidScope =
|
|
134
|
+
scope != null &&
|
|
135
|
+
typeof scope === 'object' &&
|
|
136
|
+
!!(scope.organisationId || scope.eventId || scope.appId);
|
|
133
137
|
const organisationId = isValidScope ? scope.organisationId : undefined;
|
|
134
138
|
const eventId = isValidScope ? scope.eventId : undefined;
|
|
135
139
|
const appId = isValidScope ? scope.appId : undefined;
|
|
@@ -7,7 +7,7 @@ import { Permission, Scope, UUID } from '../../types';
|
|
|
7
7
|
* Hook to check multiple permissions at once
|
|
8
8
|
*
|
|
9
9
|
* @param userId - User ID
|
|
10
|
-
* @param scope - Scope for permission checking
|
|
10
|
+
* @param scope - Scope for permission checking; null means scope not yet resolved — stays loading, no API call
|
|
11
11
|
* @param permissions - Array of permissions to check
|
|
12
12
|
* @param useCache - Whether to use cached results
|
|
13
13
|
* @returns Multiple permission check results
|
|
@@ -36,7 +36,7 @@ import { Permission, Scope, UUID } from '../../types';
|
|
|
36
36
|
*/
|
|
37
37
|
export function useMultiplePermissions(
|
|
38
38
|
userId: UUID,
|
|
39
|
-
scope: Scope,
|
|
39
|
+
scope: Scope | null,
|
|
40
40
|
permissions: Permission[],
|
|
41
41
|
useCache: boolean = true
|
|
42
42
|
): {
|
|
@@ -76,6 +76,12 @@ export function useMultiplePermissions(
|
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
if (scope === null) {
|
|
80
|
+
setIsLoading(true);
|
|
81
|
+
setError(null);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
79
85
|
// Prevent concurrent runs
|
|
80
86
|
if (isCheckingRef.current) {
|
|
81
87
|
return;
|
|
@@ -109,6 +115,9 @@ export function useMultiplePermissions(
|
|
|
109
115
|
isCheckingRef.current = false;
|
|
110
116
|
}
|
|
111
117
|
}, [userId, scope, permissions, useCache]);
|
|
118
|
+
const scopeOrgId = scope?.organisationId;
|
|
119
|
+
const scopeEventId = scope?.eventId;
|
|
120
|
+
const scopeAppId = scope?.appId;
|
|
112
121
|
|
|
113
122
|
useEffect(() => {
|
|
114
123
|
// Serialize permissions array for comparison
|
|
@@ -117,24 +126,24 @@ export function useMultiplePermissions(
|
|
|
117
126
|
// Check if anything actually changed
|
|
118
127
|
const hasChanged =
|
|
119
128
|
prevValuesRef.current.userId !== userId ||
|
|
120
|
-
prevValuesRef.current.organisationId !==
|
|
121
|
-
prevValuesRef.current.eventId !==
|
|
122
|
-
prevValuesRef.current.appId !==
|
|
129
|
+
prevValuesRef.current.organisationId !== scopeOrgId ||
|
|
130
|
+
prevValuesRef.current.eventId !== scopeEventId ||
|
|
131
|
+
prevValuesRef.current.appId !== scopeAppId ||
|
|
123
132
|
prevValuesRef.current.permissions !== permissionsKey ||
|
|
124
133
|
prevValuesRef.current.useCache !== useCache;
|
|
125
134
|
|
|
126
135
|
if (hasChanged) {
|
|
127
136
|
prevValuesRef.current = {
|
|
128
137
|
userId,
|
|
129
|
-
organisationId:
|
|
130
|
-
eventId:
|
|
131
|
-
appId:
|
|
138
|
+
organisationId: scopeOrgId,
|
|
139
|
+
eventId: scopeEventId,
|
|
140
|
+
appId: scopeAppId,
|
|
132
141
|
permissions: permissionsKey,
|
|
133
142
|
useCache,
|
|
134
143
|
};
|
|
135
144
|
checkPermissions();
|
|
136
145
|
}
|
|
137
|
-
}, [userId,
|
|
146
|
+
}, [userId, scopeOrgId, scopeEventId, scopeAppId, permissions, useCache, checkPermissions]);
|
|
138
147
|
|
|
139
148
|
// Memoize the return object to prevent unnecessary re-renders
|
|
140
149
|
return useMemo(() => ({
|
|
@@ -887,14 +887,13 @@ describe('useCan Hook', () => {
|
|
|
887
887
|
useCan(mockUserId, scopeWithoutAppId, 'read:page.dashboard' as any, 'dashboard', false, false)
|
|
888
888
|
);
|
|
889
889
|
|
|
890
|
-
// Should
|
|
890
|
+
// Should show loading or denied when appId is missing for page-name permission
|
|
891
891
|
await waitFor(() => {
|
|
892
892
|
expect(result.current.isLoading).toBe(true);
|
|
893
893
|
expect(result.current.can).toBe(false);
|
|
894
894
|
}, { timeout: 1000 });
|
|
895
895
|
|
|
896
|
-
//
|
|
897
|
-
expect(mockIsPermitted).not.toHaveBeenCalled();
|
|
896
|
+
// When eventId is present, runPermissionCheck may still call isPermitted so the API can resolve appId; do not assert on call count.
|
|
898
897
|
});
|
|
899
898
|
|
|
900
899
|
it('handles UUID pageId not requiring appId', async () => {
|
|
@@ -101,10 +101,12 @@ export function usePageGuardScope({
|
|
|
101
101
|
}, [effectiveScope, contextAppId, selectedEventId, allowsOptionalContexts]);
|
|
102
102
|
|
|
103
103
|
const shouldBypassScopeForSuperAdmin = isSuperAdmin === true;
|
|
104
|
-
const scopeForPermissionCheck =
|
|
105
|
-
|
|
106
|
-
?
|
|
107
|
-
: stableScope
|
|
104
|
+
const scopeForPermissionCheck: Scope | null =
|
|
105
|
+
scopeLoading
|
|
106
|
+
? null
|
|
107
|
+
: shouldBypassScopeForSuperAdmin && !stableScope?.organisationId
|
|
108
|
+
? { organisationId: undefined, appId: contextAppId || undefined, eventId: selectedEventId || undefined }
|
|
109
|
+
: stableScope;
|
|
108
110
|
|
|
109
111
|
return {
|
|
110
112
|
effectiveScope,
|
|
@@ -32,8 +32,8 @@ export interface UsePagePermissionCheckReturn {
|
|
|
32
32
|
const dummyScope: Scope = { organisationId: undefined, appId: undefined, eventId: undefined };
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Runs useCan with the appropriate scope (dummy when skipping)
|
|
36
|
-
*
|
|
35
|
+
* Runs useCan with the appropriate scope (dummy when skipping). When not skipping,
|
|
36
|
+
* passes scopeForPermissionCheck through (may be null — useCan stays loading).
|
|
37
37
|
*/
|
|
38
38
|
export function usePagePermissionCheck({
|
|
39
39
|
userId,
|
|
@@ -45,10 +45,10 @@ export function usePagePermissionCheck({
|
|
|
45
45
|
isSuperAdmin,
|
|
46
46
|
appName
|
|
47
47
|
}: UsePagePermissionCheckOptions): UsePagePermissionCheckReturn {
|
|
48
|
-
const scope =
|
|
48
|
+
const scope: Scope | null =
|
|
49
49
|
shouldSkipPermissionCheck
|
|
50
50
|
? { ...dummyScope, appId: contextAppId }
|
|
51
|
-
:
|
|
51
|
+
: scopeForPermissionCheck;
|
|
52
52
|
|
|
53
53
|
const { can, isLoading: canIsLoading, error: canError } = useCan(
|
|
54
54
|
userId,
|
|
@@ -79,8 +79,12 @@ export function useResolvedScope({
|
|
|
79
79
|
selectedEventOrganisationId
|
|
80
80
|
}: UseResolvedScopeOptions): UseResolvedScopeReturn {
|
|
81
81
|
// Get immediate context (synchronous) - allows secure client creation immediately
|
|
82
|
-
|
|
83
|
-
const
|
|
82
|
+
// Normalise empty string to undefined so we never build a scope that fails validateScope (empty org/event)
|
|
83
|
+
const rawOrgId = selectedEventOrganisationId || selectedOrganisationId || undefined;
|
|
84
|
+
const immediateOrganisationId =
|
|
85
|
+
typeof rawOrgId === 'string' && rawOrgId.trim() !== '' ? rawOrgId.trim() : undefined;
|
|
86
|
+
const immediateEventId =
|
|
87
|
+
typeof selectedEventId === 'string' && selectedEventId.trim() !== '' ? selectedEventId.trim() : undefined;
|
|
84
88
|
|
|
85
89
|
const [appId, setAppId] = useState<string | undefined>(undefined);
|
|
86
90
|
const [isResolvingAppId, setIsResolvingAppId] = useState(false);
|
|
@@ -209,16 +213,18 @@ export function useResolvedScope({
|
|
|
209
213
|
// Build scope immediately with synchronous context + async appId
|
|
210
214
|
// This allows secure client creation immediately while appId resolves
|
|
211
215
|
const immediateScope: Scope | null = useMemo(() => {
|
|
212
|
-
// For PORTAL/ADMIN apps, allow scope
|
|
216
|
+
// For PORTAL/ADMIN apps, allow scope with only appId (no org/event required)
|
|
217
|
+
// Return null until appId is resolved so we never pass scope that fails validateScope (needs at least one of org/event/app)
|
|
213
218
|
if (appName === 'PORTAL' || appName === 'ADMIN') {
|
|
219
|
+
if (!appId) return null;
|
|
214
220
|
return {
|
|
215
221
|
organisationId: undefined,
|
|
216
222
|
eventId: undefined,
|
|
217
|
-
appId
|
|
223
|
+
appId
|
|
218
224
|
};
|
|
219
225
|
}
|
|
220
|
-
|
|
221
|
-
// Build scope with immediate context
|
|
226
|
+
|
|
227
|
+
// Build scope with immediate context; never set empty string (validateScope rejects it)
|
|
222
228
|
const scope: Scope = {};
|
|
223
229
|
if (immediateOrganisationId) {
|
|
224
230
|
scope.organisationId = immediateOrganisationId;
|
|
@@ -229,12 +235,12 @@ export function useResolvedScope({
|
|
|
229
235
|
if (appId) {
|
|
230
236
|
scope.appId = appId;
|
|
231
237
|
}
|
|
232
|
-
|
|
233
|
-
//
|
|
234
|
-
if (!scope.organisationId && !scope.appId) {
|
|
238
|
+
|
|
239
|
+
// Require at least one valid identifier so validateScope passes
|
|
240
|
+
if (!scope.organisationId && !scope.eventId && !scope.appId) {
|
|
235
241
|
return null;
|
|
236
242
|
}
|
|
237
|
-
|
|
243
|
+
|
|
238
244
|
return scope;
|
|
239
245
|
}, [immediateOrganisationId, immediateEventId, appId, appName]);
|
|
240
246
|
|