@jmruthers/pace-core 0.5.190 → 0.5.193
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/{AuthService-CbP_utw2.d.ts → AuthService-DjnJHDtC.d.ts} +1 -0
- package/dist/{DataTable-ON3IXISJ.js → DataTable-5FU7IESH.js} +7 -6
- package/dist/{DataTable-IVYljGJ6.d.ts → DataTable-Be6dH_dR.d.ts} +1 -1
- package/dist/{PublicPageProvider-C4uxosp6.d.ts → PublicPageProvider-C0Sm_e5k.d.ts} +4 -2
- package/dist/{UnifiedAuthProvider-BYA9qB-o.d.ts → UnifiedAuthProvider-185Ih4dj.d.ts} +2 -0
- package/dist/{UnifiedAuthProvider-X5NXANVI.js → UnifiedAuthProvider-RGJTDE2C.js} +3 -3
- package/dist/{api-I6UCQ5S6.js → api-N774RPUA.js} +2 -2
- package/dist/chunk-6C4YBBJM 5.js +628 -0
- package/dist/chunk-7D4SUZUM.js 2.map +1 -0
- package/dist/{chunk-73HSNNOQ.js → chunk-7EQTDTTJ.js} +47 -74
- package/dist/chunk-7EQTDTTJ.js 2.map +1 -0
- package/dist/chunk-7EQTDTTJ.js.map +1 -0
- package/dist/{chunk-J2XXC7R5.js → chunk-7FLMSG37.js} +409 -244
- package/dist/chunk-7FLMSG37.js 2.map +1 -0
- package/dist/chunk-7FLMSG37.js.map +1 -0
- package/dist/{chunk-NIU6J6OX.js → chunk-BC4IJKSL.js} +23 -32
- package/dist/chunk-BC4IJKSL.js.map +1 -0
- package/dist/{chunk-SDMHPX3X.js → chunk-E3SPN4VZ 5.js } +198 -53
- package/dist/chunk-E3SPN4VZ.js +12917 -0
- package/dist/{chunk-SDMHPX3X.js.map → chunk-E3SPN4VZ.js.map} +1 -1
- package/dist/chunk-E66EQZE6 5.js +37 -0
- package/dist/chunk-E66EQZE6.js 2.map +1 -0
- package/dist/{chunk-DZWK57KZ.js → chunk-G37KK66H.js} +1 -1
- package/dist/{chunk-DZWK57KZ.js.map → chunk-G37KK66H.js.map} +1 -1
- package/dist/{chunk-STYK4OH2.js → chunk-HWIIPPNI.js} +44 -225
- package/dist/chunk-HWIIPPNI.js.map +1 -0
- package/dist/chunk-I7PSE6JW 5.js +191 -0
- package/dist/chunk-I7PSE6JW.js 2.map +1 -0
- package/dist/{chunk-Y4BUBBHD.js → chunk-IIELH4DL.js} +211 -136
- package/dist/chunk-IIELH4DL.js.map +1 -0
- package/dist/{chunk-RUYZKXOD.js → chunk-KNC55RTG.js} +17 -5
- package/dist/chunk-KNC55RTG.js 5.map +1 -0
- package/dist/chunk-KNC55RTG.js.map +1 -0
- package/dist/chunk-KQCRWDSA.js 5.map +1 -0
- package/dist/{chunk-4QYC5L4K.js → chunk-LFNCN2SP.js} +26 -30
- package/dist/chunk-LFNCN2SP.js 2.map +1 -0
- package/dist/chunk-LFNCN2SP.js.map +1 -0
- package/dist/chunk-LMC26NLJ 2.js +84 -0
- package/dist/{chunk-VVBAW5A5.js → chunk-NOAYCWCX 5.js } +118 -110
- package/dist/chunk-NOAYCWCX.js +4993 -0
- package/dist/chunk-NOAYCWCX.js.map +1 -0
- package/dist/chunk-QWWZ5CAQ.js 3.map +1 -0
- package/dist/chunk-QXHPKYJV 3.js +113 -0
- package/dist/chunk-R77UEZ4E 3.js +68 -0
- package/dist/chunk-VBXEHIUJ.js 6.map +1 -0
- package/dist/{chunk-HQVPB5MZ.js → chunk-XNXXZ43G.js} +77 -33
- package/dist/chunk-XNXXZ43G.js.map +1 -0
- package/dist/chunk-ZSAAAMVR 6.js +25 -0
- package/dist/components.d.ts +4 -4
- package/dist/components.js +8 -8
- package/dist/components.js 5.map +1 -0
- package/dist/{database.generated-DI89OQeI.d.ts → database.generated-CzIvgcPu.d.ts} +165 -201
- package/dist/hooks.d.ts +12 -12
- package/dist/hooks.js +9 -9
- package/dist/index.d.ts +11 -11
- package/dist/index.js +20 -27
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +2 -20
- package/dist/rbac/index.js +7 -9
- package/dist/styles/index 2.js +12 -0
- package/dist/styles/index.js 5.map +1 -0
- package/dist/theming/runtime 5.js +19 -0
- package/dist/theming/runtime.js 5.map +1 -0
- package/dist/{types-Bwgl--Xo.d.ts → types-CEpcvwwF.d.ts} +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/{usePublicRouteParams-DxIDS4bC.d.ts → usePublicRouteParams-TZe0gy-4.d.ts} +1 -1
- package/dist/utils.d.ts +8 -8
- package/dist/utils.js +2 -2
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +2 -2
- package/docs/api/classes/RBACAuditManager.md +2 -2
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +2 -2
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +10 -10
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +24 -11
- 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/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +2 -2
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +2 -2
- package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.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/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +2 -2
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +2 -2
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +2 -2
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +2 -2
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +2 -2
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +2 -2
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +2 -2
- package/docs/api/interfaces/RouteConfig.md +2 -2
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +60 -38
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
- 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/UsePublicFileDisplayOptions.md +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +202 -217
- package/docs/migration/README.md +18 -0
- package/docs/migration/database-changes-december-2025.md +768 -0
- package/docs/migration/person-scoped-profiles-migration-guide.md +472 -0
- package/docs/rbac/event-based-apps.md +124 -6
- package/package.json +1 -1
- package/scripts/check-pace-core-compliance.cjs +292 -57
- package/src/__tests__/public-recipe-view.test.ts +10 -10
- package/src/__tests__/rls-policies.test.ts +16 -14
- package/src/components/AddressField/README.md +6 -6
- package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +172 -45
- package/src/components/DataTable/__tests__/DataTable.grouping-aggregation.test.tsx +121 -28
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +9 -8
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +20 -52
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +170 -34
- package/src/components/DataTable/__tests__/keyboard.test.tsx +75 -12
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +75 -11
- package/src/components/DataTable/components/UnifiedTableBody.tsx +85 -14
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +75 -10
- package/src/components/FileDisplay/FileDisplay.test.tsx +2 -1
- package/src/components/FileDisplay/FileDisplay.tsx +16 -4
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +6 -4
- package/src/components/NavigationMenu/NavigationMenu.tsx +1 -10
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +35 -16
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +25 -2
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +97 -68
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +0 -7
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +5 -9
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +0 -1
- package/src/components/PublicLayout/PublicPageProvider.tsx +0 -1
- package/src/components/Select/Select.test.tsx +4 -1
- package/src/components/Select/Select.tsx +60 -15
- package/src/hooks/__tests__/usePermissionCache.simple.test.ts +192 -0
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +741 -0
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +703 -0
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +581 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +23 -15
- package/src/hooks/public/usePublicEvent.ts +8 -8
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/services/useAuthService.ts +21 -3
- package/src/hooks/services/useEventService.ts +21 -3
- package/src/hooks/services/useInactivityService.ts +21 -3
- package/src/hooks/services/useOrganisationService.ts +21 -3
- package/src/hooks/useFileDisplay.ts +18 -26
- package/src/hooks/useQueryCache.ts +6 -6
- package/src/hooks/useSecureDataAccess.test.ts +24 -17
- package/src/hooks/useSecureDataAccess.ts +18 -13
- package/src/providers/__tests__/OrganisationProvider.test.tsx +27 -21
- package/src/providers/services/EventServiceProvider.tsx +0 -8
- package/src/providers/services/UnifiedAuthProvider.tsx +174 -24
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +10 -16
- package/src/rbac/__tests__/isSuperAdmin.real.test.ts +82 -0
- package/src/rbac/adapters.tsx +3 -22
- package/src/rbac/api.test.ts +2 -2
- package/src/rbac/api.ts +7 -1
- package/src/rbac/components/EnhancedNavigationMenu.tsx +2 -15
- package/src/rbac/components/NavigationGuard.tsx +1 -10
- package/src/rbac/components/NavigationProvider.tsx +0 -1
- package/src/rbac/components/PermissionEnforcer.tsx +45 -12
- package/src/rbac/components/SecureDataProvider.tsx +0 -1
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +7 -43
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +4 -11
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +3 -3
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +1 -1
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +1 -1
- package/src/rbac/engine.ts +14 -2
- package/src/rbac/hooks/index.ts +0 -3
- package/src/rbac/hooks/usePermissions.ts +51 -11
- package/src/rbac/hooks/useRBAC.simple.test.ts +95 -0
- package/src/rbac/hooks/useRBAC.ts +3 -13
- package/src/rbac/hooks/useResolvedScope.test.ts +75 -54
- package/src/rbac/hooks/useResolvedScope.ts +58 -33
- package/src/rbac/hooks/useSecureSupabase.ts +4 -9
- package/src/rbac/secureClient.ts +31 -0
- package/src/rbac/utils/__tests__/eventContext.test.ts +2 -2
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +490 -0
- package/src/rbac/utils/eventContext.ts +5 -2
- package/src/services/AuthService.ts +37 -8
- package/src/services/EventService.ts +4 -57
- package/src/services/InactivityService.ts +127 -34
- package/src/services/OrganisationService.ts +160 -149
- package/src/services/__tests__/OrganisationService.pagination.test.ts +34 -8
- package/src/services/__tests__/OrganisationService.test.ts +218 -86
- package/src/types/database.generated.ts +166 -201
- package/src/types/supabase.ts +2 -2
- package/src/utils/__tests__/secureDataAccess.unit.test.ts +3 -2
- package/src/utils/file-reference/index.ts +4 -4
- package/src/utils/google-places/googlePlacesUtils.ts +1 -1
- package/src/utils/google-places/types.ts +1 -1
- package/src/utils/request-deduplication.ts +4 -4
- package/src/utils/security/secureDataAccess.test.ts +1 -1
- package/src/utils/security/secureDataAccess.ts +7 -4
- package/src/utils/storage/README.md +1 -1
- package/dist/chunk-4QYC5L4K.js.map +0 -1
- package/dist/chunk-73HSNNOQ.js.map +0 -1
- package/dist/chunk-HQVPB5MZ.js.map +0 -1
- package/dist/chunk-J2XXC7R5.js.map +0 -1
- package/dist/chunk-NIU6J6OX.js.map +0 -1
- package/dist/chunk-RUYZKXOD.js.map +0 -1
- package/dist/chunk-STYK4OH2.js.map +0 -1
- package/dist/chunk-VVBAW5A5.js.map +0 -1
- package/dist/chunk-Y4BUBBHD.js.map +0 -1
- package/scripts/check-pace-core-compliance.js +0 -512
- package/src/rbac/hooks/useSuperAdminBypass.ts +0 -126
- package/src/utils/context/superAdminOverride.ts +0 -58
- /package/dist/{DataTable-ON3IXISJ.js.map → DataTable-5FU7IESH.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-X5NXANVI.js.map → UnifiedAuthProvider-RGJTDE2C.js.map} +0 -0
- /package/dist/{api-I6UCQ5S6.js.map → api-N774RPUA.js.map} +0 -0
|
@@ -20,6 +20,8 @@ import type {
|
|
|
20
20
|
import { setOrganisationContext } from '../utils/context/organisationContext';
|
|
21
21
|
import { logger } from '../utils/core/logger';
|
|
22
22
|
import { assertUserId, assertOrganisationId } from '../types/core';
|
|
23
|
+
import { isSuperAdmin } from '../rbac/api';
|
|
24
|
+
import type { UUID } from '../rbac/types';
|
|
23
25
|
|
|
24
26
|
// Type for RPC response from data_user_organisation_roles_get
|
|
25
27
|
interface OrganisationRoleRpcResponse {
|
|
@@ -27,6 +29,15 @@ interface OrganisationRoleRpcResponse {
|
|
|
27
29
|
organisation_id: string;
|
|
28
30
|
role: 'org_admin' | 'leader' | 'member' | 'supporter';
|
|
29
31
|
status: 'active' | 'inactive' | 'suspended';
|
|
32
|
+
// Organisation fields from RPC
|
|
33
|
+
name?: string;
|
|
34
|
+
display_name?: string;
|
|
35
|
+
subscription_tier?: string;
|
|
36
|
+
settings?: unknown;
|
|
37
|
+
is_active?: boolean;
|
|
38
|
+
parent_id?: string;
|
|
39
|
+
organisation_created_at?: string;
|
|
40
|
+
organisation_updated_at?: string;
|
|
30
41
|
[key: string]: unknown;
|
|
31
42
|
}
|
|
32
43
|
|
|
@@ -37,6 +48,7 @@ export class OrganisationService extends BaseService implements IOrganisationSer
|
|
|
37
48
|
private _roleMapState: Map<string, string> = new Map();
|
|
38
49
|
private _isLoading = false;
|
|
39
50
|
private _error: Error | null = null;
|
|
51
|
+
private _isSuperAdmin: boolean = false; // Cache super admin status
|
|
40
52
|
private _isContextReady = false;
|
|
41
53
|
private retryCount = 0;
|
|
42
54
|
|
|
@@ -72,17 +84,21 @@ export class OrganisationService extends BaseService implements IOrganisationSer
|
|
|
72
84
|
// Additional methods for testing
|
|
73
85
|
setSelectedOrganisation(organisation: Organisation | null): void {
|
|
74
86
|
// SECURITY: Validate organisation is in user's accessible organisations (only if orgs are loaded)
|
|
87
|
+
// Exception: Super admins can set any organisation (they have global access)
|
|
75
88
|
if (organisation && this._organisations.length > 0) {
|
|
76
|
-
|
|
77
|
-
if (!
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
// Only validate if user is not a super admin (use cached status)
|
|
90
|
+
if (!this._isSuperAdmin) {
|
|
91
|
+
const isValidOrg = this._organisations.some(org => org.id === organisation.id);
|
|
92
|
+
if (!isValidOrg) {
|
|
93
|
+
logger.warn('OrganisationService', 'Attempted to set invalid organisation - not in user\'s accessible organisations', {
|
|
94
|
+
organisationId: organisation.id,
|
|
95
|
+
organisationName: organisation.name,
|
|
96
|
+
accessibleOrgIds: this._organisations.map(o => o.id)
|
|
97
|
+
});
|
|
98
|
+
// Don't set invalid organisation - this prevents security issues
|
|
99
|
+
// If organisations haven't loaded yet, validation will happen in loadUserOrganisations()
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
86
102
|
}
|
|
87
103
|
}
|
|
88
104
|
|
|
@@ -131,6 +147,11 @@ export class OrganisationService extends BaseService implements IOrganisationSer
|
|
|
131
147
|
const wasAuthenticated = !!(this.user && this.session);
|
|
132
148
|
const isAuthenticated = !!(user && session);
|
|
133
149
|
|
|
150
|
+
// Reset super admin cache when user changes
|
|
151
|
+
if (this.user?.id !== user?.id) {
|
|
152
|
+
this._isSuperAdmin = false;
|
|
153
|
+
}
|
|
154
|
+
|
|
134
155
|
this.user = user;
|
|
135
156
|
this.session = session;
|
|
136
157
|
|
|
@@ -358,159 +379,142 @@ export class OrganisationService extends BaseService implements IOrganisationSer
|
|
|
358
379
|
this.notify();
|
|
359
380
|
|
|
360
381
|
try {
|
|
361
|
-
// Get user's organisation
|
|
362
|
-
//
|
|
363
|
-
|
|
382
|
+
// Get user's organisation roles directly from rbac_organisation_roles table
|
|
383
|
+
// This queries the source table directly instead of using the RPC which filters to match core_organisation_memberships view
|
|
384
|
+
// We still filter to active, non-revoked roles for the org selector
|
|
385
|
+
// The join includes organisation data, so we don't need a separate query that might be filtered by RLS
|
|
386
|
+
let memberships, membershipError, organisations: Organisation[] = [];
|
|
364
387
|
try {
|
|
365
|
-
//
|
|
366
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
367
|
-
const timeoutId = setTimeout(() => reject(new Error('RPC call timeout after 10 seconds')), 10000);
|
|
368
|
-
abortSignal.addEventListener('abort', () => {
|
|
369
|
-
clearTimeout(timeoutId);
|
|
370
|
-
reject(new Error('Request aborted'));
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
const rpcPromise = this.supabaseClient.rpc('data_user_organisation_roles_get', {
|
|
375
|
-
p_user_id: this.user.id,
|
|
376
|
-
p_organisation_id: null
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
// Check if request was aborted before making the call
|
|
388
|
+
// Check if request was aborted before making query
|
|
380
389
|
if (abortSignal.aborted) {
|
|
381
390
|
throw new Error('Request aborted');
|
|
382
391
|
}
|
|
392
|
+
|
|
393
|
+
const { data: rolesData, error: rolesError } = await this.supabaseClient
|
|
394
|
+
.from('rbac_organisation_roles')
|
|
395
|
+
.select(`
|
|
396
|
+
id,
|
|
397
|
+
user_id,
|
|
398
|
+
organisation_id,
|
|
399
|
+
role,
|
|
400
|
+
status,
|
|
401
|
+
granted_at,
|
|
402
|
+
granted_by,
|
|
403
|
+
revoked_at,
|
|
404
|
+
revoked_by,
|
|
405
|
+
notes,
|
|
406
|
+
created_at,
|
|
407
|
+
updated_at,
|
|
408
|
+
core_organisations!inner(
|
|
409
|
+
id,
|
|
410
|
+
name,
|
|
411
|
+
display_name,
|
|
412
|
+
subscription_tier,
|
|
413
|
+
settings,
|
|
414
|
+
is_active,
|
|
415
|
+
parent_id,
|
|
416
|
+
created_at,
|
|
417
|
+
updated_at
|
|
418
|
+
)
|
|
419
|
+
`)
|
|
420
|
+
.eq('user_id', this.user.id)
|
|
421
|
+
.eq('status', 'active')
|
|
422
|
+
.is('revoked_at', null);
|
|
383
423
|
|
|
384
|
-
|
|
424
|
+
if (rolesError) {
|
|
425
|
+
logger.error("OrganisationService", "Error loading organisation roles:", rolesError);
|
|
426
|
+
throw rolesError;
|
|
427
|
+
}
|
|
385
428
|
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
|
|
389
|
-
memberships = result.data?.filter((role) =>
|
|
390
|
-
['org_admin', 'leader', 'member', 'supporter'].includes(role.role)
|
|
391
|
-
).map((m) => ({
|
|
429
|
+
// Map to branded types and extract organisation data from the join
|
|
430
|
+
// The join already includes organisation data, so we don't need a separate query
|
|
431
|
+
memberships = rolesData?.map((m) => ({
|
|
392
432
|
...m,
|
|
393
433
|
user_id: assertUserId(m.user_id),
|
|
394
434
|
organisation_id: assertOrganisationId(m.organisation_id),
|
|
395
435
|
})) || [];
|
|
396
|
-
membershipError = result.error;
|
|
397
|
-
} catch (queryError) {
|
|
398
|
-
membershipError = queryError instanceof Error ? queryError : new Error(String(queryError));
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
if (membershipError) {
|
|
402
|
-
logger.error("OrganisationService", "Error loading memberships:", membershipError);
|
|
403
436
|
|
|
404
|
-
//
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
.
|
|
414
|
-
.
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
revoked_by,
|
|
424
|
-
notes,
|
|
425
|
-
created_at,
|
|
426
|
-
updated_at,
|
|
427
|
-
organisations!inner(
|
|
428
|
-
id,
|
|
429
|
-
name,
|
|
430
|
-
display_name,
|
|
431
|
-
subscription_tier,
|
|
432
|
-
settings,
|
|
433
|
-
is_active,
|
|
434
|
-
parent_id,
|
|
435
|
-
created_at,
|
|
436
|
-
updated_at
|
|
437
|
-
)
|
|
438
|
-
`)
|
|
439
|
-
.eq('user_id', this.user.id)
|
|
440
|
-
.eq('status', 'active')
|
|
441
|
-
.is('revoked_at', null)
|
|
442
|
-
.in('role', ['org_admin', 'leader', 'member', 'supporter']);
|
|
443
|
-
|
|
444
|
-
if (fallbackError) {
|
|
445
|
-
logger.error("OrganisationService", "Fallback query also failed:", fallbackError);
|
|
446
|
-
throw membershipError; // Throw original error
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Map to branded types
|
|
450
|
-
memberships = fallbackData?.map((m) => ({
|
|
451
|
-
...m,
|
|
452
|
-
user_id: assertUserId(m.user_id),
|
|
453
|
-
organisation_id: assertOrganisationId(m.organisation_id),
|
|
454
|
-
})) || [];
|
|
455
|
-
membershipError = null;
|
|
456
|
-
} catch (fallbackErr) {
|
|
457
|
-
logger.error("OrganisationService", "Fallback query failed:", fallbackErr);
|
|
458
|
-
throw membershipError; // Throw original error
|
|
437
|
+
// Extract unique organisations from the join results
|
|
438
|
+
// Use a Map to deduplicate by organisation ID
|
|
439
|
+
// Supabase returns joined data nested under the relation name
|
|
440
|
+
const organisationsMap = new Map<string, Organisation>();
|
|
441
|
+
rolesData?.forEach((role: any) => {
|
|
442
|
+
// The join returns organisation data nested under 'core_organisations' key
|
|
443
|
+
const orgData = role.core_organisations;
|
|
444
|
+
if (orgData && role.organisation_id && !organisationsMap.has(role.organisation_id)) {
|
|
445
|
+
organisationsMap.set(role.organisation_id, {
|
|
446
|
+
id: orgData.id,
|
|
447
|
+
name: orgData.name,
|
|
448
|
+
display_name: orgData.display_name,
|
|
449
|
+
subscription_tier: orgData.subscription_tier,
|
|
450
|
+
settings: orgData.settings,
|
|
451
|
+
is_active: orgData.is_active,
|
|
452
|
+
parent_id: orgData.parent_id,
|
|
453
|
+
created_at: orgData.created_at,
|
|
454
|
+
updated_at: orgData.updated_at,
|
|
455
|
+
} as Organisation);
|
|
459
456
|
}
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
organisations = Array.from(organisationsMap.values());
|
|
460
|
+
|
|
461
|
+
// Extract organisations from join results
|
|
462
|
+
} catch (queryError) {
|
|
463
|
+
// Extract error message properly from Supabase error objects
|
|
464
|
+
if (queryError instanceof Error) {
|
|
465
|
+
membershipError = queryError;
|
|
466
|
+
} else if (queryError && typeof queryError === 'object' && 'message' in queryError) {
|
|
467
|
+
membershipError = new Error(String((queryError as any).message));
|
|
460
468
|
} else {
|
|
461
|
-
|
|
469
|
+
membershipError = new Error(String(queryError));
|
|
462
470
|
}
|
|
471
|
+
logger.error("OrganisationService", "Error loading organisation roles:", membershipError);
|
|
472
|
+
throw membershipError;
|
|
463
473
|
}
|
|
464
474
|
|
|
475
|
+
// Check if user is super admin - super admins don't need organisation memberships
|
|
476
|
+
let userIsSuperAdmin = false;
|
|
477
|
+
if (this.user?.id) {
|
|
478
|
+
try {
|
|
479
|
+
userIsSuperAdmin = await isSuperAdmin(this.user.id as UUID);
|
|
480
|
+
this._isSuperAdmin = userIsSuperAdmin; // Cache the result
|
|
481
|
+
} catch (error) {
|
|
482
|
+
logger.warn('OrganisationService', 'Failed to check super admin status', { error });
|
|
483
|
+
// Continue with normal flow if check fails
|
|
484
|
+
this._isSuperAdmin = false;
|
|
485
|
+
}
|
|
486
|
+
} else {
|
|
487
|
+
this._isSuperAdmin = false;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Super admins can proceed without organisation memberships
|
|
465
491
|
if (!memberships || memberships.length === 0) {
|
|
492
|
+
if (userIsSuperAdmin) {
|
|
493
|
+
// Super admin without org memberships - allow empty state
|
|
494
|
+
this._organisations = [];
|
|
495
|
+
this._userMemberships = [];
|
|
496
|
+
this._isLoading = false;
|
|
497
|
+
this._error = null;
|
|
498
|
+
this._isContextReady = true;
|
|
499
|
+
this.notify();
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
466
502
|
throw new Error('User has no active organisation memberships') as OrganisationSecurityError;
|
|
467
503
|
}
|
|
468
504
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
return false;
|
|
482
|
-
}
|
|
483
|
-
// Validate UUID format
|
|
484
|
-
const isValidUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(trimmedId);
|
|
485
|
-
if (!isValidUuid) {
|
|
486
|
-
logger.warn("OrganisationService", "Invalid UUID format:", trimmedId);
|
|
487
|
-
}
|
|
488
|
-
return isValidUuid;
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
if (organisationIds.length === 0) {
|
|
492
|
-
logger.warn("OrganisationService", "No valid organisation IDs found in memberships:", memberships);
|
|
493
|
-
throw new Error('No valid organisation IDs found in memberships') as OrganisationSecurityError;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Check if request was aborted before making organisations query
|
|
497
|
-
if (abortSignal.aborted) {
|
|
498
|
-
throw new Error('Request aborted');
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
const { data: allOrganisations, error: orgError } = await this.supabaseClient
|
|
502
|
-
.from('organisations')
|
|
503
|
-
.select('id, name, display_name, subscription_tier, settings, is_active, parent_id, created_at, updated_at');
|
|
504
|
-
|
|
505
|
-
if (orgError) {
|
|
506
|
-
logger.error("OrganisationService", "Error loading organisations:", orgError);
|
|
507
|
-
throw orgError;
|
|
505
|
+
if (!organisations || organisations.length === 0) {
|
|
506
|
+
if (userIsSuperAdmin) {
|
|
507
|
+
// Super admin without orgs - allow empty state
|
|
508
|
+
this._organisations = [];
|
|
509
|
+
this._userMemberships = [];
|
|
510
|
+
this._isLoading = false;
|
|
511
|
+
this._error = null;
|
|
512
|
+
this._isContextReady = true;
|
|
513
|
+
this.notify();
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
throw new Error('No organisations found in role data') as OrganisationSecurityError;
|
|
508
517
|
}
|
|
509
|
-
|
|
510
|
-
// Filter manually on the client side
|
|
511
|
-
const organisations = allOrganisations?.filter(org =>
|
|
512
|
-
organisationIds.includes(org.id)
|
|
513
|
-
) || [];
|
|
514
518
|
|
|
515
519
|
// Create a map of organisation_id to role from the memberships data
|
|
516
520
|
const roleMap = new Map<string, string>();
|
|
@@ -522,7 +526,19 @@ export class OrganisationService extends BaseService implements IOrganisationSer
|
|
|
522
526
|
const orgs = organisations as Organisation[];
|
|
523
527
|
const activeOrgs = orgs.filter(org => org.is_active);
|
|
524
528
|
|
|
529
|
+
// Filter to active organisations only
|
|
530
|
+
|
|
525
531
|
if (activeOrgs.length === 0) {
|
|
532
|
+
if (userIsSuperAdmin) {
|
|
533
|
+
// Super admin without active orgs - allow empty state
|
|
534
|
+
this._organisations = [];
|
|
535
|
+
this._userMemberships = [];
|
|
536
|
+
this._isLoading = false;
|
|
537
|
+
this._error = null;
|
|
538
|
+
this._isContextReady = true;
|
|
539
|
+
this.notify();
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
526
542
|
throw new Error('User has no access to active organisations') as OrganisationSecurityError;
|
|
527
543
|
}
|
|
528
544
|
|
|
@@ -549,16 +565,13 @@ export class OrganisationService extends BaseService implements IOrganisationSer
|
|
|
549
565
|
initialOrg = validPersistedOrg;
|
|
550
566
|
selectionMethod = 'persisted';
|
|
551
567
|
} else {
|
|
552
|
-
logger.warn("OrganisationService", "Persisted organisation not found in active orgs, clearing cache");
|
|
553
568
|
localStorage.removeItem('pace-core-selected-organisation');
|
|
554
569
|
}
|
|
555
570
|
} else {
|
|
556
|
-
logger.warn("OrganisationService", "Invalid persisted organisation ID, clearing cache");
|
|
557
571
|
localStorage.removeItem('pace-core-selected-organisation');
|
|
558
572
|
}
|
|
559
573
|
}
|
|
560
574
|
} catch (storageError) {
|
|
561
|
-
logger.warn("OrganisationService", "Failed to restore persisted organisation:", storageError);
|
|
562
575
|
// Clear potentially corrupted cache
|
|
563
576
|
localStorage.removeItem('pace-core-selected-organisation');
|
|
564
577
|
}
|
|
@@ -610,10 +623,8 @@ export class OrganisationService extends BaseService implements IOrganisationSer
|
|
|
610
623
|
} catch (err) {
|
|
611
624
|
const error = err as Error;
|
|
612
625
|
// "User has no access to active organisations" is a valid state for users without orgs (e.g., profile pages)
|
|
613
|
-
//
|
|
614
|
-
if (error.message
|
|
615
|
-
logger.warn("OrganisationService", "User has no active organisations (this is expected for users without organisation access):", error);
|
|
616
|
-
} else {
|
|
626
|
+
// Only log actual errors, not expected states
|
|
627
|
+
if (error.message !== 'User has no access to active organisations') {
|
|
617
628
|
logger.error("OrganisationService", "Failed to load organisations:", err);
|
|
618
629
|
}
|
|
619
630
|
this._error = error;
|
|
@@ -88,16 +88,29 @@ describe('OrganisationService Pagination & Validation', () => {
|
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
mockSupabase.from.mockImplementation((table: string) => {
|
|
91
|
-
if (table === '
|
|
91
|
+
if (table === 'rbac_organisation_roles') {
|
|
92
92
|
return {
|
|
93
93
|
select: vi.fn().mockResolvedValue({
|
|
94
|
-
data: [
|
|
94
|
+
data: [
|
|
95
|
+
{
|
|
96
|
+
...mockMembership,
|
|
97
|
+
core_organisations: mockOrganisation
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
...mockMembership2,
|
|
101
|
+
core_organisations: mockOrganisation2
|
|
102
|
+
}
|
|
103
|
+
],
|
|
95
104
|
error: null
|
|
96
|
-
})
|
|
105
|
+
}),
|
|
106
|
+
eq: vi.fn().mockReturnThis(),
|
|
107
|
+
is: vi.fn().mockReturnThis()
|
|
97
108
|
};
|
|
98
109
|
}
|
|
99
110
|
return {
|
|
100
|
-
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
111
|
+
select: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
112
|
+
eq: vi.fn().mockReturnThis(),
|
|
113
|
+
is: vi.fn().mockReturnThis()
|
|
101
114
|
};
|
|
102
115
|
});
|
|
103
116
|
|
|
@@ -175,16 +188,29 @@ describe('OrganisationService Pagination & Validation', () => {
|
|
|
175
188
|
});
|
|
176
189
|
|
|
177
190
|
mockSupabase.from.mockImplementation((table: string) => {
|
|
178
|
-
if (table === '
|
|
191
|
+
if (table === 'rbac_organisation_roles') {
|
|
179
192
|
return {
|
|
180
193
|
select: vi.fn().mockResolvedValue({
|
|
181
|
-
data: [
|
|
194
|
+
data: [
|
|
195
|
+
{
|
|
196
|
+
...mockMembership,
|
|
197
|
+
core_organisations: mockOrganisation
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
...inactiveMembership,
|
|
201
|
+
core_organisations: inactiveOrganisation
|
|
202
|
+
}
|
|
203
|
+
],
|
|
182
204
|
error: null
|
|
183
|
-
})
|
|
205
|
+
}),
|
|
206
|
+
eq: vi.fn().mockReturnThis(),
|
|
207
|
+
is: vi.fn().mockReturnThis()
|
|
184
208
|
};
|
|
185
209
|
}
|
|
186
210
|
return {
|
|
187
|
-
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
211
|
+
select: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
212
|
+
eq: vi.fn().mockReturnThis(),
|
|
213
|
+
is: vi.fn().mockReturnThis()
|
|
188
214
|
};
|
|
189
215
|
});
|
|
190
216
|
|