@jmruthers/pace-core 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/cursor-rules/00-pace-core-compliance.mdc +34 -2
- package/dist/{AuthService-BPvc3Ka0.d.ts → AuthService-Cb34EQs3.d.ts} +9 -1
- package/dist/{DataTable-TPTKCX4D.js → DataTable-THFPBKTP.js} +9 -8
- package/dist/{PublicPageProvider-DC6kCaqf.d.ts → PublicPageProvider-DEMpysFR.d.ts} +45 -67
- package/dist/{UnifiedAuthProvider-CVcTjx-d.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +1 -8
- package/dist/{UnifiedAuthProvider-CH6Z342H.js → UnifiedAuthProvider-KAGUYQ4J.js} +5 -4
- package/dist/{api-MVVQZLJI.js → api-IAGWF3ZG.js} +10 -10
- package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
- package/dist/{chunk-SFZUDBL5.js → chunk-2T2IG7T7.js} +70 -56
- package/dist/chunk-2T2IG7T7.js.map +1 -0
- package/dist/{chunk-MMZ7JXPU.js → chunk-6Z7LTB3D.js} +13 -21
- package/dist/{chunk-MMZ7JXPU.js.map → chunk-6Z7LTB3D.js.map} +1 -1
- package/dist/{chunk-6J4GEEJR.js → chunk-CNCQDFLN.js} +53 -27
- package/dist/chunk-CNCQDFLN.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/{chunk-EHMR7VYL.js → chunk-DWUBLJJM.js} +361 -187
- package/dist/chunk-DWUBLJJM.js.map +1 -0
- package/dist/{chunk-2UOI2FG5.js → chunk-HFZBI76P.js} +4 -4
- package/dist/{chunk-F2IMUDXZ.js → chunk-M7MPQISP.js} +2 -2
- package/dist/{chunk-3XC4CPTD.js → chunk-PQBSKX33.js} +244 -5727
- package/dist/chunk-PQBSKX33.js.map +1 -0
- package/dist/chunk-QRPVRXYT.js +226 -0
- package/dist/chunk-QRPVRXYT.js.map +1 -0
- package/dist/{chunk-24UVZUZG.js → chunk-RWEBCB47.js} +129 -387
- package/dist/chunk-RWEBCB47.js.map +1 -0
- package/dist/{chunk-XWQCNGTQ.js → chunk-YDQHOZNA.js} +173 -79
- package/dist/chunk-YDQHOZNA.js.map +1 -0
- package/dist/{chunk-NECFR5MM.js → chunk-ZNIWI3UC.js} +562 -644
- package/dist/chunk-ZNIWI3UC.js.map +1 -0
- package/dist/components.d.ts +2 -2
- package/dist/components.js +12 -13
- package/dist/contextValidator-3JNZKUTX.js +9 -0
- package/dist/contextValidator-3JNZKUTX.js.map +1 -0
- package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +7 -6
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +21 -16
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +4 -3
- package/dist/rbac/index.d.ts +67 -27
- package/dist/rbac/index.js +15 -8
- package/dist/styles/index.js +1 -1
- package/dist/theming/runtime.js +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-1oMokgLF.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +7 -16
- package/dist/utils.js +5 -7
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +14 -16
- package/docs/api/modules.md +3796 -2513
- package/docs/components/context-selector.md +126 -0
- package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
- package/docs/pace-mint-fix-auto-selection.md +218 -0
- package/docs/pace-mint-rbac-setup.md +391 -0
- package/docs/rbac/secure-client-protection.md +330 -0
- package/package.json +3 -3
- package/scripts/audit/core/checks/compliance.cjs +72 -0
- package/scripts/audit/core/checks/dependencies.cjs +559 -28
- package/scripts/audit/core/checks/documentation.cjs +68 -3
- package/scripts/audit/core/checks/environment.cjs +2 -14
- package/scripts/audit/core/checks/error-handling.cjs +47 -6
- package/src/components/ContextSelector/ContextSelector.tsx +384 -0
- package/src/components/ContextSelector/index.ts +3 -0
- package/src/components/DataTable/components/RowComponent.tsx +19 -19
- package/src/components/DataTable/components/UnifiedTableBody.tsx +2 -2
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +8 -6
- package/src/components/Dialog/Dialog.tsx +29 -1
- package/src/components/FileDisplay/FileDisplay.tsx +42 -10
- package/src/components/Header/Header.test.tsx +43 -73
- package/src/components/Header/Header.tsx +44 -45
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +9 -9
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +135 -33
- package/src/components/PaceAppLayout/README.md +14 -17
- package/src/components/PaceAppLayout/test-setup.tsx +2 -2
- package/src/components/index.ts +5 -5
- package/src/eslint-rules/pace-core-compliance.cjs +106 -0
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
- package/src/hooks/useAppConfig.ts +15 -30
- package/src/hooks/useFileDisplay.ts +77 -50
- package/src/index.ts +4 -5
- package/src/providers/services/AuthServiceProvider.tsx +17 -7
- package/src/providers/services/EventServiceProvider.tsx +33 -5
- package/src/providers/services/UnifiedAuthProvider.tsx +90 -134
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
- package/src/rbac/adapters.tsx +2 -2
- package/src/rbac/api.test.ts +59 -51
- package/src/rbac/api.ts +178 -132
- package/src/rbac/components/PagePermissionGuard.tsx +38 -10
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
- package/src/rbac/hooks/permissions/useAccessLevel.ts +1 -1
- package/src/rbac/hooks/permissions/useCan.ts +41 -11
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +1 -1
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +1 -1
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +1 -1
- package/src/rbac/hooks/useCan.test.ts +0 -9
- package/src/rbac/hooks/useRBAC.test.ts +1 -5
- package/src/rbac/hooks/useRBAC.ts +36 -37
- package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
- package/src/rbac/hooks/useResolvedScope.ts +35 -40
- package/src/rbac/hooks/useSecureSupabase.ts +7 -7
- package/src/rbac/index.ts +7 -0
- package/src/rbac/secureClient.test.ts +22 -18
- package/src/rbac/secureClient.ts +103 -16
- package/src/rbac/security.ts +0 -17
- package/src/rbac/types.ts +1 -0
- package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
- package/src/rbac/utils/clientSecurity.ts +93 -0
- package/src/rbac/utils/contextValidator.ts +77 -168
- package/src/services/AuthService.ts +39 -7
- package/src/services/EventService.ts +186 -54
- package/src/services/OrganisationService.ts +81 -14
- package/src/services/__tests__/EventService.test.ts +1 -2
- package/src/services/base/BaseService.ts +3 -0
- package/src/utils/dynamic/dynamicUtils.ts +7 -4
- package/dist/chunk-24UVZUZG.js.map +0 -1
- package/dist/chunk-3XC4CPTD.js.map +0 -1
- package/dist/chunk-6J4GEEJR.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js +0 -38
- package/dist/chunk-EHMR7VYL.js.map +0 -1
- package/dist/chunk-NECFR5MM.js.map +0 -1
- package/dist/chunk-SFZUDBL5.js.map +0 -1
- package/dist/chunk-XWQCNGTQ.js.map +0 -1
- package/docs/api/classes/ColumnFactory.md +0 -243
- package/docs/api/classes/InvalidScopeError.md +0 -73
- package/docs/api/classes/Logger.md +0 -178
- package/docs/api/classes/MissingUserContextError.md +0 -66
- package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
- package/docs/api/classes/PermissionDeniedError.md +0 -73
- package/docs/api/classes/RBACAuditManager.md +0 -297
- package/docs/api/classes/RBACCache.md +0 -322
- package/docs/api/classes/RBACEngine.md +0 -171
- package/docs/api/classes/RBACError.md +0 -76
- package/docs/api/classes/RBACNotInitializedError.md +0 -66
- package/docs/api/classes/SecureSupabaseClient.md +0 -163
- package/docs/api/classes/StorageUtils.md +0 -328
- package/docs/api/enums/FileCategory.md +0 -184
- package/docs/api/enums/LogLevel.md +0 -54
- package/docs/api/enums/RBACErrorCode.md +0 -228
- package/docs/api/enums/RPCFunction.md +0 -118
- package/docs/api/interfaces/AddressFieldProps.md +0 -241
- package/docs/api/interfaces/AddressFieldRef.md +0 -94
- package/docs/api/interfaces/AggregateConfig.md +0 -43
- package/docs/api/interfaces/AutocompleteOptions.md +0 -75
- package/docs/api/interfaces/AvatarProps.md +0 -128
- package/docs/api/interfaces/BadgeProps.md +0 -34
- package/docs/api/interfaces/ButtonProps.md +0 -56
- package/docs/api/interfaces/CalendarProps.md +0 -73
- package/docs/api/interfaces/CardProps.md +0 -69
- package/docs/api/interfaces/ColorPalette.md +0 -7
- package/docs/api/interfaces/ColorShade.md +0 -66
- package/docs/api/interfaces/ComplianceResult.md +0 -30
- package/docs/api/interfaces/DataAccessRecord.md +0 -96
- package/docs/api/interfaces/DataRecord.md +0 -11
- package/docs/api/interfaces/DataTableAction.md +0 -252
- package/docs/api/interfaces/DataTableColumn.md +0 -504
- package/docs/api/interfaces/DataTableProps.md +0 -625
- package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
- package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
- package/docs/api/interfaces/DatabaseIssue.md +0 -41
- package/docs/api/interfaces/EmptyStateConfig.md +0 -61
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
- package/docs/api/interfaces/ErrorBoundaryProps.md +0 -147
- package/docs/api/interfaces/ErrorBoundaryProviderProps.md +0 -36
- package/docs/api/interfaces/ErrorBoundaryState.md +0 -75
- package/docs/api/interfaces/EventAppRoleData.md +0 -71
- package/docs/api/interfaces/ExportColumn.md +0 -90
- package/docs/api/interfaces/ExportOptions.md +0 -126
- package/docs/api/interfaces/FileDisplayProps.md +0 -249
- package/docs/api/interfaces/FileMetadata.md +0 -129
- package/docs/api/interfaces/FileReference.md +0 -118
- package/docs/api/interfaces/FileSizeLimits.md +0 -7
- package/docs/api/interfaces/FileUploadOptions.md +0 -139
- package/docs/api/interfaces/FileUploadProps.md +0 -296
- package/docs/api/interfaces/FooterProps.md +0 -107
- package/docs/api/interfaces/FormFieldProps.md +0 -166
- package/docs/api/interfaces/FormProps.md +0 -113
- package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
- package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
- package/docs/api/interfaces/InputProps.md +0 -56
- package/docs/api/interfaces/LabelProps.md +0 -107
- package/docs/api/interfaces/LoggerConfig.md +0 -62
- package/docs/api/interfaces/LoginFormProps.md +0 -187
- package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
- package/docs/api/interfaces/NavigationContextType.md +0 -164
- package/docs/api/interfaces/NavigationGuardProps.md +0 -139
- package/docs/api/interfaces/NavigationItem.md +0 -120
- package/docs/api/interfaces/NavigationMenuProps.md +0 -221
- package/docs/api/interfaces/NavigationProviderProps.md +0 -117
- package/docs/api/interfaces/Organisation.md +0 -140
- package/docs/api/interfaces/OrganisationContextType.md +0 -388
- package/docs/api/interfaces/OrganisationMembership.md +0 -140
- package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
- package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
- package/docs/api/interfaces/PaceAppLayoutProps.md +0 -409
- package/docs/api/interfaces/PaceLoginPageProps.md +0 -49
- package/docs/api/interfaces/PageAccessRecord.md +0 -85
- package/docs/api/interfaces/PagePermissionContextType.md +0 -140
- package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
- package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
- package/docs/api/interfaces/PaletteData.md +0 -41
- package/docs/api/interfaces/ParsedAddress.md +0 -120
- package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
- package/docs/api/interfaces/ProgressProps.md +0 -42
- package/docs/api/interfaces/ProtectedRouteProps.md +0 -78
- package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
- package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
- package/docs/api/interfaces/PublicPageLayoutProps.md +0 -185
- package/docs/api/interfaces/QuickFix.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
- package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
- package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
- package/docs/api/interfaces/RBACConfig.md +0 -133
- package/docs/api/interfaces/RBACContext.md +0 -52
- package/docs/api/interfaces/RBACLogger.md +0 -112
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
- package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
- package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
- package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
- package/docs/api/interfaces/RBACResult.md +0 -58
- package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
- package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
- package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
- package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
- package/docs/api/interfaces/RBACRolesListParams.md +0 -52
- package/docs/api/interfaces/RBACRolesListResult.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
- package/docs/api/interfaces/ResourcePermissions.md +0 -155
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
- package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
- package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
- package/docs/api/interfaces/RoleManagementResult.md +0 -52
- package/docs/api/interfaces/RouteAccessRecord.md +0 -107
- package/docs/api/interfaces/RouteConfig.md +0 -134
- package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
- package/docs/api/interfaces/SecureDataContextType.md +0 -168
- package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
- package/docs/api/interfaces/SetupIssue.md +0 -41
- package/docs/api/interfaces/StorageConfig.md +0 -41
- package/docs/api/interfaces/StorageFileInfo.md +0 -74
- package/docs/api/interfaces/StorageFileMetadata.md +0 -151
- package/docs/api/interfaces/StorageListOptions.md +0 -99
- package/docs/api/interfaces/StorageListResult.md +0 -41
- package/docs/api/interfaces/StorageUploadOptions.md +0 -101
- package/docs/api/interfaces/StorageUploadResult.md +0 -63
- package/docs/api/interfaces/StorageUrlOptions.md +0 -60
- package/docs/api/interfaces/StyleImport.md +0 -19
- package/docs/api/interfaces/SwitchProps.md +0 -34
- package/docs/api/interfaces/TabsContentProps.md +0 -9
- package/docs/api/interfaces/TabsListProps.md +0 -9
- package/docs/api/interfaces/TabsProps.md +0 -9
- package/docs/api/interfaces/TabsTriggerProps.md +0 -50
- package/docs/api/interfaces/TextareaProps.md +0 -53
- package/docs/api/interfaces/ToastActionElement.md +0 -12
- package/docs/api/interfaces/ToastProps.md +0 -9
- package/docs/api/interfaces/UnifiedAuthContextType.md +0 -823
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -173
- package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
- package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -138
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -84
- package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
- package/docs/api/interfaces/UsePublicEventReturn.md +0 -71
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -123
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -97
- package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
- package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
- package/docs/api/interfaces/UserEventAccess.md +0 -121
- package/docs/api/interfaces/UserMenuProps.md +0 -88
- package/docs/api/interfaces/UserProfile.md +0 -63
- package/src/components/EventSelector/EventSelector.test.tsx +0 -720
- package/src/components/EventSelector/EventSelector.tsx +0 -423
- package/src/components/EventSelector/index.ts +0 -3
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -327
- package/src/components/OrganisationSelector/index.ts +0 -9
- /package/dist/{DataTable-TPTKCX4D.js.map → DataTable-THFPBKTP.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-CH6Z342H.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
- /package/dist/{api-MVVQZLJI.js.map → api-IAGWF3ZG.js.map} +0 -0
- /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
- /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
- /package/dist/{chunk-2UOI2FG5.js.map → chunk-HFZBI76P.js.map} +0 -0
- /package/dist/{chunk-F2IMUDXZ.js.map → chunk-M7MPQISP.js.map} +0 -0
|
@@ -25,6 +25,7 @@ import type { Event } from '../../types/event';
|
|
|
25
25
|
import type { AuthError } from '@supabase/supabase-js';
|
|
26
26
|
import type { SessionRestorationState } from '../../types/auth';
|
|
27
27
|
import { logger } from '../../utils/core/logger';
|
|
28
|
+
import { setupRBAC, isRBACInitialized } from '../../rbac/api';
|
|
28
29
|
|
|
29
30
|
// Re-export UserEventAccess type
|
|
30
31
|
/**
|
|
@@ -117,7 +118,6 @@ export interface UnifiedAuthContextType {
|
|
|
117
118
|
handleSignOutNow: () => Promise<void>;
|
|
118
119
|
|
|
119
120
|
// Additional unified properties
|
|
120
|
-
appConfig: { requires_event: boolean } | null;
|
|
121
121
|
isLoading: boolean;
|
|
122
122
|
hasErrors: boolean;
|
|
123
123
|
sessionRestoration: SessionRestorationState;
|
|
@@ -159,8 +159,6 @@ export interface UnifiedAuthProviderProps {
|
|
|
159
159
|
enablePersistence?: boolean;
|
|
160
160
|
requireOrganisationContext?: boolean;
|
|
161
161
|
|
|
162
|
-
// App configuration
|
|
163
|
-
appConfig?: { requires_event: boolean } | null;
|
|
164
162
|
|
|
165
163
|
// Inactivity auto-logout configuration - MANDATORY for security
|
|
166
164
|
idleTimeoutMs: number; // REQUIRED: Inactivity timeout in milliseconds
|
|
@@ -178,7 +176,6 @@ export interface UnifiedAuthProviderProps {
|
|
|
178
176
|
function UnifiedAuthContextProvider({
|
|
179
177
|
children,
|
|
180
178
|
appName,
|
|
181
|
-
appConfig: appConfigProp,
|
|
182
179
|
supabaseClient: supabaseClientProp,
|
|
183
180
|
...props
|
|
184
181
|
}: UnifiedAuthProviderProps) {
|
|
@@ -199,17 +196,7 @@ function UnifiedAuthContextProvider({
|
|
|
199
196
|
restorationError,
|
|
200
197
|
}), [isRestoring, restorationComplete, restorationError]);
|
|
201
198
|
|
|
202
|
-
//
|
|
203
|
-
// Memoize appConfig to prevent object reference changes that cause re-renders
|
|
204
|
-
const [appConfigState, setAppConfigState] = useState<{ requires_event: boolean } | null>(appConfigProp || null);
|
|
205
|
-
const isResolvingAppConfigRef = useRef(false);
|
|
206
|
-
const resolvedAppConfigRef = useRef<{ requires_event: boolean } | null>(null);
|
|
207
|
-
|
|
208
|
-
// Memoize appConfig to ensure stable reference - only recreate if requires_event changes
|
|
209
|
-
const appConfig = useMemo(() => {
|
|
210
|
-
if (!appConfigState) return null;
|
|
211
|
-
return { requires_event: appConfigState.requires_event };
|
|
212
|
-
}, [appConfigState?.requires_event]);
|
|
199
|
+
// App config removed - scope is now page-level only (rbac_app_pages.scope_type)
|
|
213
200
|
|
|
214
201
|
// Try to get event service, but provide fallback if not available
|
|
215
202
|
let eventService;
|
|
@@ -244,6 +231,16 @@ function UnifiedAuthContextProvider({
|
|
|
244
231
|
// Resolve appId immediately when user logs in (don't wait for organisation/event)
|
|
245
232
|
// This makes appId available early for navigation menu filtering
|
|
246
233
|
const [appId, setAppId] = useState<string | undefined>(undefined);
|
|
234
|
+
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
logger.debug('UnifiedAuthContextProvider', `Rendering [AuthService ID:${authService.getInstanceId?.() || 'unknown'}]`, {
|
|
237
|
+
hasUser: !!currentUser,
|
|
238
|
+
userId: currentUser?.id,
|
|
239
|
+
hasSession: !!currentSession,
|
|
240
|
+
isAuth
|
|
241
|
+
});
|
|
242
|
+
}, [currentUser?.id, currentSession?.access_token, isAuth, authService]);
|
|
243
|
+
|
|
247
244
|
const isResolvingAppIdRef = useRef(false);
|
|
248
245
|
const resolvedAppIdRef = useRef<string | undefined>(undefined);
|
|
249
246
|
const resolvedUserIdRef = useRef<string | undefined>(undefined);
|
|
@@ -326,75 +323,6 @@ function UnifiedAuthContextProvider({
|
|
|
326
323
|
}
|
|
327
324
|
}, [isAuth, currentUser?.id, supabase, appName]); // Removed appId from deps - it's the output, not an input. currentUser?.id is stable primitive.
|
|
328
325
|
|
|
329
|
-
// Load appConfig from database if not provided as prop
|
|
330
|
-
useEffect(() => {
|
|
331
|
-
// If appConfig is provided as prop, use it and don't load from database
|
|
332
|
-
if (appConfigProp !== undefined) {
|
|
333
|
-
setAppConfigState(appConfigProp);
|
|
334
|
-
resolvedAppConfigRef.current = appConfigProp;
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// If we've already resolved, don't resolve again
|
|
339
|
-
if (resolvedAppConfigRef.current !== null || isResolvingAppConfigRef.current) {
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Only load if we have supabase and appName
|
|
344
|
-
if (!supabase || !appName) {
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
isResolvingAppConfigRef.current = true;
|
|
349
|
-
|
|
350
|
-
// Load app config from database
|
|
351
|
-
import('../../rbac/api').then(async ({ getAppConfigByName }) => {
|
|
352
|
-
try {
|
|
353
|
-
const config = await getAppConfigByName(appName);
|
|
354
|
-
// Default to requires_event: false if config is null (organisation-based apps)
|
|
355
|
-
const resolvedConfig = config || { requires_event: false };
|
|
356
|
-
|
|
357
|
-
// Only update if the value actually changed to prevent unnecessary re-renders
|
|
358
|
-
if (resolvedAppConfigRef.current?.requires_event !== resolvedConfig.requires_event) {
|
|
359
|
-
resolvedAppConfigRef.current = resolvedConfig;
|
|
360
|
-
setAppConfigState(resolvedConfig);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Debug logging for pace-mint
|
|
364
|
-
if (import.meta.env.DEV && appName === 'MINT') {
|
|
365
|
-
logger.debug('UnifiedAuthProvider', 'App config loaded', {
|
|
366
|
-
appName,
|
|
367
|
-
config: resolvedConfig,
|
|
368
|
-
requiresEvent: resolvedConfig.requires_event
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
} catch (error) {
|
|
372
|
-
logger.warn('UnifiedAuthProvider', 'Failed to load app config, defaulting to organisation-based', {
|
|
373
|
-
error: error instanceof Error ? error.message : String(error),
|
|
374
|
-
appName
|
|
375
|
-
});
|
|
376
|
-
// Default to organisation-based (requires_event: false) on error
|
|
377
|
-
// Only update if not already set to avoid unnecessary re-renders
|
|
378
|
-
if (resolvedAppConfigRef.current?.requires_event !== false) {
|
|
379
|
-
const defaultConfig = { requires_event: false };
|
|
380
|
-
resolvedAppConfigRef.current = defaultConfig;
|
|
381
|
-
setAppConfigState(defaultConfig);
|
|
382
|
-
}
|
|
383
|
-
} finally {
|
|
384
|
-
isResolvingAppConfigRef.current = false;
|
|
385
|
-
}
|
|
386
|
-
}).catch((importError) => {
|
|
387
|
-
logger.error('UnifiedAuthProvider', 'Failed to import RBAC API for app config', importError);
|
|
388
|
-
isResolvingAppConfigRef.current = false;
|
|
389
|
-
// Default to organisation-based on import error
|
|
390
|
-
// Only update if not already set to avoid unnecessary re-renders
|
|
391
|
-
if (resolvedAppConfigRef.current?.requires_event !== false) {
|
|
392
|
-
const defaultConfig = { requires_event: false };
|
|
393
|
-
resolvedAppConfigRef.current = defaultConfig;
|
|
394
|
-
setAppConfigState(defaultConfig);
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
}, [supabase, appName, appConfigProp]);
|
|
398
326
|
|
|
399
327
|
// Subscribe to service state changes to trigger re-renders
|
|
400
328
|
// Use useReducer to force updates when services notify
|
|
@@ -465,30 +393,9 @@ function UnifiedAuthContextProvider({
|
|
|
465
393
|
const rawSelectedOrganisation = organisationService.getSelectedOrganisation();
|
|
466
394
|
const organisationError = organisationService.getError();
|
|
467
395
|
|
|
468
|
-
//
|
|
469
|
-
//
|
|
470
|
-
|
|
471
|
-
// If appConfig is null (still loading) OR requires_event is false/undefined, allow selectedOrganisation
|
|
472
|
-
// This ensures organisation-based apps work correctly even if appConfig hasn't loaded yet or is misconfigured
|
|
473
|
-
// IMPORTANT: If rawSelectedOrganisation exists, prefer it over appConfig to avoid race conditions
|
|
474
|
-
const selectedOrganisation = (appConfig !== null && appConfig?.requires_event === true && !rawSelectedOrganisation)
|
|
475
|
-
? null
|
|
476
|
-
: rawSelectedOrganisation;
|
|
477
|
-
|
|
478
|
-
// Debug logging for pace-mint issue - use useEffect to avoid causing re-renders
|
|
479
|
-
useEffect(() => {
|
|
480
|
-
if (import.meta.env.DEV && appName === 'MINT') {
|
|
481
|
-
logger.debug('UnifiedAuthProvider', 'Organisation state check', {
|
|
482
|
-
rawSelectedOrganisation: rawSelectedOrganisation?.id || null,
|
|
483
|
-
rawSelectedOrganisationType: typeof rawSelectedOrganisation,
|
|
484
|
-
appConfig,
|
|
485
|
-
appConfigRequiresEvent: appConfig?.requires_event,
|
|
486
|
-
selectedOrganisation: selectedOrganisation?.id || null,
|
|
487
|
-
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
488
|
-
checkResult: appConfig?.requires_event === true,
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
}, [appName, rawSelectedOrganisation?.id, appConfig?.requires_event, selectedOrganisation?.id]);
|
|
396
|
+
// Scope is now page-level only - use whatever organisation is selected
|
|
397
|
+
// No app-level scope determination needed
|
|
398
|
+
const selectedOrganisation = rawSelectedOrganisation;
|
|
492
399
|
const hasValidOrganisationContext = organisationService.hasValidOrganisationContext();
|
|
493
400
|
const isContextReady = organisationService.isContextReady();
|
|
494
401
|
|
|
@@ -668,7 +575,6 @@ function UnifiedAuthContextProvider({
|
|
|
668
575
|
// Additional unified properties
|
|
669
576
|
appName,
|
|
670
577
|
appId, // Resolved immediately on login
|
|
671
|
-
appConfig: appConfig,
|
|
672
578
|
isLoading: totalLoading,
|
|
673
579
|
hasErrors: hasErrors,
|
|
674
580
|
sessionRestoration: sessionRestoration,
|
|
@@ -700,7 +606,6 @@ function UnifiedAuthContextProvider({
|
|
|
700
606
|
hasErrors,
|
|
701
607
|
appName,
|
|
702
608
|
appId,
|
|
703
|
-
appConfig,
|
|
704
609
|
sessionRestoration,
|
|
705
610
|
sessionRestorationTimedOut,
|
|
706
611
|
sessionRestorationTimeoutMs,
|
|
@@ -741,28 +646,19 @@ function EventServiceProviderWrapper({
|
|
|
741
646
|
supabaseClient,
|
|
742
647
|
user,
|
|
743
648
|
session,
|
|
744
|
-
appName
|
|
745
|
-
appConfig
|
|
649
|
+
appName
|
|
746
650
|
}: {
|
|
747
651
|
children: React.ReactNode;
|
|
748
652
|
supabaseClient: SupabaseClient;
|
|
749
653
|
user: User | null;
|
|
750
654
|
session: Session | null;
|
|
751
655
|
appName: string;
|
|
752
|
-
appConfig?: { requires_event: boolean } | null;
|
|
753
656
|
}) {
|
|
754
657
|
// Use useOrganisations() hook for better reactivity - it subscribes to service changes
|
|
755
658
|
// This ensures EventServiceProvider re-renders when selectedOrganisation changes
|
|
756
|
-
const { selectedOrganisation
|
|
757
|
-
|
|
758
|
-
//
|
|
759
|
-
// Organisation will be derived from the selected event instead
|
|
760
|
-
// This prevents EventService from filtering events by the wrong organisation
|
|
761
|
-
// CRITICAL FIX: Only set to null if appConfig is loaded AND explicitly requires_event is true AND no org selected
|
|
762
|
-
// If rawSelectedOrganisation exists, prefer it over appConfig to avoid race conditions
|
|
763
|
-
const selectedOrganisation = (appConfig !== null && appConfig?.requires_event === true && !rawSelectedOrganisation)
|
|
764
|
-
? null
|
|
765
|
-
: rawSelectedOrganisation;
|
|
659
|
+
const { selectedOrganisation } = useOrganisations();
|
|
660
|
+
|
|
661
|
+
// Scope is now page-level only - use whatever organisation is selected
|
|
766
662
|
|
|
767
663
|
// Always render EventServiceProvider - it handles null user/session gracefully
|
|
768
664
|
// This ensures EventServiceContext is always available for components calling useEvents()
|
|
@@ -771,6 +667,15 @@ function EventServiceProviderWrapper({
|
|
|
771
667
|
// Event selection is now handled at the application level
|
|
772
668
|
}, []);
|
|
773
669
|
|
|
670
|
+
useEffect(() => {
|
|
671
|
+
logger.debug('EventServiceProviderWrapper', 'Rendering with props', {
|
|
672
|
+
hasUser: !!user,
|
|
673
|
+
userId: user?.id,
|
|
674
|
+
hasSession: !!session,
|
|
675
|
+
selectedOrganisationId: selectedOrganisation?.id
|
|
676
|
+
});
|
|
677
|
+
}, [user?.id, session?.access_token, selectedOrganisation?.id]);
|
|
678
|
+
|
|
774
679
|
return (
|
|
775
680
|
<EventServiceProvider
|
|
776
681
|
supabaseClient={supabaseClient}
|
|
@@ -790,7 +695,6 @@ function ServiceAwareProviders({
|
|
|
790
695
|
children,
|
|
791
696
|
supabaseClient,
|
|
792
697
|
appName,
|
|
793
|
-
appConfig,
|
|
794
698
|
persistState,
|
|
795
699
|
enablePersistence,
|
|
796
700
|
requireOrganisationContext,
|
|
@@ -800,32 +704,71 @@ function ServiceAwareProviders({
|
|
|
800
704
|
renderInactivityWarning,
|
|
801
705
|
dangerouslyDisableInactivity
|
|
802
706
|
}: UnifiedAuthProviderProps) {
|
|
707
|
+
// useAuthService already handles subscription and re-rendering with debouncing
|
|
708
|
+
// This ensures we get fresh user/session values on every render
|
|
803
709
|
const authService = useAuthService();
|
|
804
710
|
|
|
711
|
+
// CRITICAL: Track user/session in state to force re-renders when they change
|
|
712
|
+
// This ensures ServiceAwareProviders re-renders immediately when user logs in
|
|
713
|
+
const [userState, setUserState] = useState(() => authService.getUser());
|
|
714
|
+
const [sessionState, setSessionState] = useState(() => authService.getSession());
|
|
715
|
+
|
|
716
|
+
// Subscribe directly to auth service changes and update state immediately
|
|
717
|
+
useEffect(() => {
|
|
718
|
+
const unsubscribe = authService.subscribe(() => {
|
|
719
|
+
// Update state immediately when auth service notifies (bypasses debounce)
|
|
720
|
+
const newUser = authService.getUser();
|
|
721
|
+
const newSession = authService.getSession();
|
|
722
|
+
|
|
723
|
+
logger.debug('ServiceAwareProviders', 'Auth service notified, updating state', {
|
|
724
|
+
hasUser: !!newUser,
|
|
725
|
+
userId: newUser?.id,
|
|
726
|
+
hasSession: !!newSession
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
setUserState(newUser);
|
|
730
|
+
setSessionState(newSession);
|
|
731
|
+
});
|
|
732
|
+
return unsubscribe;
|
|
733
|
+
}, [authService]);
|
|
734
|
+
|
|
735
|
+
// Use state values instead of reading directly from service
|
|
736
|
+
// This ensures React re-renders when these values change
|
|
737
|
+
const user = userState;
|
|
738
|
+
const session = sessionState;
|
|
739
|
+
|
|
740
|
+
// Log when user/session changes for debugging
|
|
741
|
+
useEffect(() => {
|
|
742
|
+
logger.debug('ServiceAwareProviders', `User/session state [AuthService ID:${authService.getInstanceId?.() || 'unknown'}]`, {
|
|
743
|
+
hasUser: !!user,
|
|
744
|
+
userId: user?.id,
|
|
745
|
+
hasSession: !!session,
|
|
746
|
+
sessionToken: session?.access_token ? 'present' : 'missing'
|
|
747
|
+
});
|
|
748
|
+
}, [user?.id, session?.access_token, authService]);
|
|
749
|
+
|
|
805
750
|
return (
|
|
806
751
|
<OrganisationServiceProvider
|
|
807
752
|
supabaseClient={supabaseClient}
|
|
808
|
-
user={
|
|
809
|
-
session={
|
|
753
|
+
user={user}
|
|
754
|
+
session={session}
|
|
810
755
|
>
|
|
811
756
|
<EventServiceProviderWrapper
|
|
812
757
|
supabaseClient={supabaseClient}
|
|
813
|
-
user={
|
|
814
|
-
session={
|
|
758
|
+
user={user}
|
|
759
|
+
session={session}
|
|
815
760
|
appName={appName}
|
|
816
|
-
appConfig={appConfig}
|
|
817
761
|
>
|
|
818
762
|
<InactivityServiceProvider
|
|
819
763
|
supabaseClient={supabaseClient}
|
|
820
|
-
user={
|
|
821
|
-
session={
|
|
764
|
+
user={user}
|
|
765
|
+
session={session}
|
|
822
766
|
idleTimeoutMs={idleTimeoutMs}
|
|
823
767
|
warnBeforeMs={warnBeforeMs}
|
|
824
768
|
onIdleLogout={onIdleLogout}
|
|
825
769
|
>
|
|
826
770
|
<UnifiedAuthContextProvider
|
|
827
771
|
appName={appName}
|
|
828
|
-
appConfig={appConfig}
|
|
829
772
|
supabaseClient={supabaseClient}
|
|
830
773
|
persistState={persistState}
|
|
831
774
|
enablePersistence={enablePersistence}
|
|
@@ -855,7 +798,6 @@ export function UnifiedAuthProvider({
|
|
|
855
798
|
children,
|
|
856
799
|
supabaseClient,
|
|
857
800
|
appName,
|
|
858
|
-
appConfig = { requires_event: true }, // Default to requiring events
|
|
859
801
|
persistState = true,
|
|
860
802
|
enablePersistence,
|
|
861
803
|
requireOrganisationContext = true,
|
|
@@ -882,15 +824,29 @@ export function UnifiedAuthProvider({
|
|
|
882
824
|
note: 'Ensure you create the Supabase client once and reuse it. Creating multiple clients can cause performance issues and the "Multiple GoTrueClient instances" warning.'
|
|
883
825
|
});
|
|
884
826
|
clientRef.current = supabaseClient;
|
|
827
|
+
} else {
|
|
828
|
+
logger.debug('UnifiedAuthProvider', 'Supabase client reference is stable');
|
|
885
829
|
}
|
|
886
830
|
}, [supabaseClient]);
|
|
887
831
|
|
|
832
|
+
// Initialize RBAC synchronously when supabaseClient is available
|
|
833
|
+
// This ensures RBAC engine is ready BEFORE any child services (EventService, OrganisationService, etc.)
|
|
834
|
+
// are initialized, preventing race conditions where services try to use RBAC before it's ready
|
|
835
|
+
// CRITICAL: This must be synchronous, not in useEffect, to ensure RBAC is ready before children render
|
|
836
|
+
if (supabaseClient && !isRBACInitialized()) {
|
|
837
|
+
try {
|
|
838
|
+
setupRBAC(supabaseClient);
|
|
839
|
+
logger.debug('UnifiedAuthProvider', 'RBAC initialized synchronously');
|
|
840
|
+
} catch (err) {
|
|
841
|
+
logger.error('UnifiedAuthProvider', 'Failed to initialize RBAC', err);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
888
845
|
return (
|
|
889
846
|
<AuthServiceProvider supabaseClient={supabaseClient} appName={appName}>
|
|
890
847
|
<ServiceAwareProviders
|
|
891
848
|
supabaseClient={supabaseClient}
|
|
892
849
|
appName={appName}
|
|
893
|
-
appConfig={appConfig}
|
|
894
850
|
persistState={persistState}
|
|
895
851
|
enablePersistence={enablePersistence}
|
|
896
852
|
requireOrganisationContext={requireOrganisationContext}
|
|
@@ -499,7 +499,7 @@ describe('RBAC Adapters - Comprehensive Tests', () => {
|
|
|
499
499
|
scope: { organisationId: 'org-123', eventId: 'event-123', appId: 'app-123' },
|
|
500
500
|
permission: 'read:users',
|
|
501
501
|
pageId: undefined,
|
|
502
|
-
}
|
|
502
|
+
});
|
|
503
503
|
expect(handler).toHaveBeenCalledWith(req);
|
|
504
504
|
expect(result).toEqual({ success: true });
|
|
505
505
|
});
|
package/src/rbac/adapters.tsx
CHANGED
|
@@ -303,7 +303,7 @@ export function withPermissionGuard<T extends any[]>(
|
|
|
303
303
|
scope: { organisationId, eventId, appId },
|
|
304
304
|
permission: config.permission,
|
|
305
305
|
pageId: config.pageId,
|
|
306
|
-
}
|
|
306
|
+
});
|
|
307
307
|
|
|
308
308
|
if (!hasPermission) {
|
|
309
309
|
throw new Error(`Permission denied: ${config.permission}`);
|
|
@@ -548,7 +548,7 @@ export function createRBACMiddleware(config: {
|
|
|
548
548
|
scope: { organisationId },
|
|
549
549
|
permission: protectedRoute.permission,
|
|
550
550
|
pageId: protectedRoute.pageId,
|
|
551
|
-
}
|
|
551
|
+
});
|
|
552
552
|
|
|
553
553
|
if (!hasPermission) {
|
|
554
554
|
return res.redirect(config.fallbackUrl || '/access-denied');
|
package/src/rbac/api.test.ts
CHANGED
|
@@ -61,6 +61,16 @@ vi.mock('./config', () => ({
|
|
|
61
61
|
}))
|
|
62
62
|
}));
|
|
63
63
|
|
|
64
|
+
// Mock getPageScopeType RPC call
|
|
65
|
+
vi.mock('../utils/core/logger', () => ({
|
|
66
|
+
createLogger: vi.fn(() => ({
|
|
67
|
+
info: vi.fn(),
|
|
68
|
+
warn: vi.fn(),
|
|
69
|
+
error: vi.fn(),
|
|
70
|
+
debug: vi.fn()
|
|
71
|
+
}))
|
|
72
|
+
}));
|
|
73
|
+
|
|
64
74
|
// Mock Supabase client
|
|
65
75
|
const mockSupabase = {
|
|
66
76
|
from: vi.fn(() => ({
|
|
@@ -460,6 +470,42 @@ describe('RBAC API', () => {
|
|
|
460
470
|
let mockEngine: any;
|
|
461
471
|
|
|
462
472
|
beforeEach(() => {
|
|
473
|
+
// Create a mock Supabase client that can handle getPageScopeType queries
|
|
474
|
+
const mockSupabaseForEngine = {
|
|
475
|
+
from: vi.fn((table: string) => {
|
|
476
|
+
if (table === 'rbac_apps') {
|
|
477
|
+
return {
|
|
478
|
+
select: vi.fn(() => ({
|
|
479
|
+
eq: vi.fn(() => ({
|
|
480
|
+
eq: vi.fn(() => ({
|
|
481
|
+
single: vi.fn().mockResolvedValue({ data: { id: 'app-123' }, error: null })
|
|
482
|
+
}))
|
|
483
|
+
}))
|
|
484
|
+
}))
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
if (table === 'rbac_app_pages') {
|
|
488
|
+
return {
|
|
489
|
+
select: vi.fn(() => ({
|
|
490
|
+
eq: vi.fn(() => ({
|
|
491
|
+
eq: vi.fn(() => ({
|
|
492
|
+
maybeSingle: vi.fn().mockResolvedValue({ data: { id: '123e4567-e89b-12d3-a456-426614174000' }, error: null })
|
|
493
|
+
}))
|
|
494
|
+
}))
|
|
495
|
+
}))
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
return mockSupabase.from(table);
|
|
499
|
+
}),
|
|
500
|
+
rpc: vi.fn((fnName: string, params: any) => {
|
|
501
|
+
if (fnName === 'get_page_scope_type') {
|
|
502
|
+
return Promise.resolve({ data: 'organisation', error: null });
|
|
503
|
+
}
|
|
504
|
+
return Promise.resolve({ data: null, error: null });
|
|
505
|
+
}),
|
|
506
|
+
auth: mockSupabase.auth
|
|
507
|
+
};
|
|
508
|
+
|
|
463
509
|
mockEngine = {
|
|
464
510
|
getAccessLevel: vi.fn(),
|
|
465
511
|
getPermissionMap: vi.fn(),
|
|
@@ -467,7 +513,7 @@ describe('RBAC API', () => {
|
|
|
467
513
|
resolveAppContext: vi.fn(),
|
|
468
514
|
getRoleContext: vi.fn(),
|
|
469
515
|
checkSuperAdmin: vi.fn(),
|
|
470
|
-
|
|
516
|
+
supabase: mockSupabaseForEngine as any
|
|
471
517
|
};
|
|
472
518
|
|
|
473
519
|
mockCreateRBACEngine.mockReturnValue(mockEngine);
|
|
@@ -603,24 +649,13 @@ describe('RBAC API', () => {
|
|
|
603
649
|
|
|
604
650
|
const result = await isPermitted({
|
|
605
651
|
userId: 'user-123',
|
|
606
|
-
scope: { organisationId: 'org-456' },
|
|
652
|
+
scope: { organisationId: 'org-456', appId: 'app-123' },
|
|
607
653
|
permission: 'read:users',
|
|
608
|
-
pageId: '
|
|
654
|
+
pageId: '123e4567-e89b-12d3-a456-426614174000' // Valid UUID format
|
|
609
655
|
});
|
|
610
656
|
|
|
611
657
|
expect(result).toBe(true);
|
|
612
|
-
expect(mockEngine.isPermitted).
|
|
613
|
-
{
|
|
614
|
-
userId: 'user-123',
|
|
615
|
-
scope: { organisationId: 'org-456' },
|
|
616
|
-
permission: 'read:users',
|
|
617
|
-
pageId: 'page-789'
|
|
618
|
-
},
|
|
619
|
-
expect.objectContaining({
|
|
620
|
-
userId: 'user-123',
|
|
621
|
-
organisationId: 'org-456'
|
|
622
|
-
})
|
|
623
|
-
);
|
|
658
|
+
expect(mockEngine.isPermitted).toHaveBeenCalled();
|
|
624
659
|
});
|
|
625
660
|
|
|
626
661
|
it('handles engine errors', async () => {
|
|
@@ -661,14 +696,15 @@ describe('RBAC API', () => {
|
|
|
661
696
|
it('returns cached result when available', async () => {
|
|
662
697
|
const { isPermittedCached } = await import('./api');
|
|
663
698
|
|
|
664
|
-
|
|
699
|
+
// Cache key now includes appId when provided
|
|
700
|
+
const cacheKey = 'permission:user-123:org-456:null:app-123:read:users';
|
|
665
701
|
rbacCache.get.mockReturnValue(true);
|
|
666
702
|
|
|
667
703
|
const result = await isPermittedCached({
|
|
668
704
|
userId: 'user-123',
|
|
669
|
-
scope: { organisationId: 'org-456' },
|
|
705
|
+
scope: { organisationId: 'org-456', appId: 'app-123' },
|
|
670
706
|
permission: 'read:users',
|
|
671
|
-
pageId: '
|
|
707
|
+
pageId: '123e4567-e89b-12d3-a456-426614174000' // Valid UUID format
|
|
672
708
|
});
|
|
673
709
|
|
|
674
710
|
expect(result).toBe(true);
|
|
@@ -684,24 +720,13 @@ describe('RBAC API', () => {
|
|
|
684
720
|
|
|
685
721
|
const result = await isPermittedCached({
|
|
686
722
|
userId: 'user-123',
|
|
687
|
-
scope: { organisationId: 'org-456' },
|
|
723
|
+
scope: { organisationId: 'org-456', appId: 'app-123' },
|
|
688
724
|
permission: 'read:users',
|
|
689
|
-
pageId: '
|
|
725
|
+
pageId: '123e4567-e89b-12d3-a456-426614174000' // Valid UUID format
|
|
690
726
|
});
|
|
691
727
|
|
|
692
728
|
expect(result).toBe(true);
|
|
693
|
-
expect(mockEngine.isPermitted).
|
|
694
|
-
{
|
|
695
|
-
userId: 'user-123',
|
|
696
|
-
scope: { organisationId: 'org-456' },
|
|
697
|
-
permission: 'read:users',
|
|
698
|
-
pageId: 'page-789'
|
|
699
|
-
},
|
|
700
|
-
expect.objectContaining({
|
|
701
|
-
userId: 'user-123',
|
|
702
|
-
organisationId: 'org-456'
|
|
703
|
-
})
|
|
704
|
-
);
|
|
729
|
+
expect(mockEngine.isPermitted).toHaveBeenCalled();
|
|
705
730
|
// Check that cache.set was called - pageId presence makes it a page-level check
|
|
706
731
|
expect(rbacCache.set).toHaveBeenCalled();
|
|
707
732
|
const setCall = rbacCache.set.mock.calls[0];
|
|
@@ -828,25 +853,8 @@ describe('RBAC API', () => {
|
|
|
828
853
|
});
|
|
829
854
|
});
|
|
830
855
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
const { getAppConfig } = await import('./api');
|
|
834
|
-
|
|
835
|
-
// getAppConfig now returns null (needs Supabase client)
|
|
836
|
-
const result = await getAppConfig('app-123');
|
|
837
|
-
|
|
838
|
-
expect(result).toBeNull();
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
it('handles missing Supabase client gracefully', async () => {
|
|
842
|
-
const { getAppConfig } = await import('./api');
|
|
843
|
-
|
|
844
|
-
const result = await getAppConfig('app-123');
|
|
845
|
-
|
|
846
|
-
// Should return null when no client is available
|
|
847
|
-
expect(result).toBeNull();
|
|
848
|
-
});
|
|
849
|
-
});
|
|
856
|
+
// getAppConfig has been removed - scope is now page-level only
|
|
857
|
+
// Tests removed as function no longer exists
|
|
850
858
|
|
|
851
859
|
describe('isOrganisationAdmin', () => {
|
|
852
860
|
it('returns true for admin access level', async () => {
|