@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.
Files changed (299) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/cursor-rules/00-pace-core-compliance.mdc +34 -2
  3. package/dist/{AuthService-BPvc3Ka0.d.ts → AuthService-Cb34EQs3.d.ts} +9 -1
  4. package/dist/{DataTable-TPTKCX4D.js → DataTable-THFPBKTP.js} +9 -8
  5. package/dist/{PublicPageProvider-DC6kCaqf.d.ts → PublicPageProvider-DEMpysFR.d.ts} +45 -67
  6. package/dist/{UnifiedAuthProvider-CVcTjx-d.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +1 -8
  7. package/dist/{UnifiedAuthProvider-CH6Z342H.js → UnifiedAuthProvider-KAGUYQ4J.js} +5 -4
  8. package/dist/{api-MVVQZLJI.js → api-IAGWF3ZG.js} +10 -10
  9. package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
  10. package/dist/{chunk-SFZUDBL5.js → chunk-2T2IG7T7.js} +70 -56
  11. package/dist/chunk-2T2IG7T7.js.map +1 -0
  12. package/dist/{chunk-MMZ7JXPU.js → chunk-6Z7LTB3D.js} +13 -21
  13. package/dist/{chunk-MMZ7JXPU.js.map → chunk-6Z7LTB3D.js.map} +1 -1
  14. package/dist/{chunk-6J4GEEJR.js → chunk-CNCQDFLN.js} +53 -27
  15. package/dist/chunk-CNCQDFLN.js.map +1 -0
  16. package/dist/chunk-DGUM43GV.js +11 -0
  17. package/dist/{chunk-EHMR7VYL.js → chunk-DWUBLJJM.js} +361 -187
  18. package/dist/chunk-DWUBLJJM.js.map +1 -0
  19. package/dist/{chunk-2UOI2FG5.js → chunk-HFZBI76P.js} +4 -4
  20. package/dist/{chunk-F2IMUDXZ.js → chunk-M7MPQISP.js} +2 -2
  21. package/dist/{chunk-3XC4CPTD.js → chunk-PQBSKX33.js} +244 -5727
  22. package/dist/chunk-PQBSKX33.js.map +1 -0
  23. package/dist/chunk-QRPVRXYT.js +226 -0
  24. package/dist/chunk-QRPVRXYT.js.map +1 -0
  25. package/dist/{chunk-24UVZUZG.js → chunk-RWEBCB47.js} +129 -387
  26. package/dist/chunk-RWEBCB47.js.map +1 -0
  27. package/dist/{chunk-XWQCNGTQ.js → chunk-YDQHOZNA.js} +173 -79
  28. package/dist/chunk-YDQHOZNA.js.map +1 -0
  29. package/dist/{chunk-NECFR5MM.js → chunk-ZNIWI3UC.js} +562 -644
  30. package/dist/chunk-ZNIWI3UC.js.map +1 -0
  31. package/dist/components.d.ts +2 -2
  32. package/dist/components.js +12 -13
  33. package/dist/contextValidator-3JNZKUTX.js +9 -0
  34. package/dist/contextValidator-3JNZKUTX.js.map +1 -0
  35. package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
  36. package/dist/hooks.d.ts +2 -2
  37. package/dist/hooks.js +7 -6
  38. package/dist/hooks.js.map +1 -1
  39. package/dist/index.d.ts +7 -7
  40. package/dist/index.js +21 -16
  41. package/dist/index.js.map +1 -1
  42. package/dist/providers.d.ts +3 -3
  43. package/dist/providers.js +4 -3
  44. package/dist/rbac/index.d.ts +67 -27
  45. package/dist/rbac/index.js +15 -8
  46. package/dist/styles/index.js +1 -1
  47. package/dist/theming/runtime.js +1 -1
  48. package/dist/types.js +1 -1
  49. package/dist/{usePublicRouteParams-1oMokgLF.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +7 -16
  50. package/dist/utils.js +5 -7
  51. package/dist/utils.js.map +1 -1
  52. package/docs/api/README.md +14 -16
  53. package/docs/api/modules.md +3796 -2513
  54. package/docs/components/context-selector.md +126 -0
  55. package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
  56. package/docs/pace-mint-fix-auto-selection.md +218 -0
  57. package/docs/pace-mint-rbac-setup.md +391 -0
  58. package/docs/rbac/secure-client-protection.md +330 -0
  59. package/package.json +3 -3
  60. package/scripts/audit/core/checks/compliance.cjs +72 -0
  61. package/scripts/audit/core/checks/dependencies.cjs +559 -28
  62. package/scripts/audit/core/checks/documentation.cjs +68 -3
  63. package/scripts/audit/core/checks/environment.cjs +2 -14
  64. package/scripts/audit/core/checks/error-handling.cjs +47 -6
  65. package/src/components/ContextSelector/ContextSelector.tsx +384 -0
  66. package/src/components/ContextSelector/index.ts +3 -0
  67. package/src/components/DataTable/components/RowComponent.tsx +19 -19
  68. package/src/components/DataTable/components/UnifiedTableBody.tsx +2 -2
  69. package/src/components/DataTable/hooks/useDataTablePermissions.ts +8 -6
  70. package/src/components/Dialog/Dialog.tsx +29 -1
  71. package/src/components/FileDisplay/FileDisplay.tsx +42 -10
  72. package/src/components/Header/Header.test.tsx +43 -73
  73. package/src/components/Header/Header.tsx +44 -45
  74. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
  75. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
  76. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
  77. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +9 -9
  78. package/src/components/PaceAppLayout/PaceAppLayout.tsx +135 -33
  79. package/src/components/PaceAppLayout/README.md +14 -17
  80. package/src/components/PaceAppLayout/test-setup.tsx +2 -2
  81. package/src/components/index.ts +5 -5
  82. package/src/eslint-rules/pace-core-compliance.cjs +106 -0
  83. package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
  84. package/src/hooks/useAppConfig.ts +15 -30
  85. package/src/hooks/useFileDisplay.ts +77 -50
  86. package/src/index.ts +4 -5
  87. package/src/providers/services/AuthServiceProvider.tsx +17 -7
  88. package/src/providers/services/EventServiceProvider.tsx +33 -5
  89. package/src/providers/services/UnifiedAuthProvider.tsx +90 -134
  90. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
  91. package/src/rbac/adapters.tsx +2 -2
  92. package/src/rbac/api.test.ts +59 -51
  93. package/src/rbac/api.ts +178 -132
  94. package/src/rbac/components/PagePermissionGuard.tsx +38 -10
  95. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
  96. package/src/rbac/hooks/permissions/useAccessLevel.ts +1 -1
  97. package/src/rbac/hooks/permissions/useCan.ts +41 -11
  98. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +1 -1
  99. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +1 -1
  100. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +1 -1
  101. package/src/rbac/hooks/useCan.test.ts +0 -9
  102. package/src/rbac/hooks/useRBAC.test.ts +1 -5
  103. package/src/rbac/hooks/useRBAC.ts +36 -37
  104. package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
  105. package/src/rbac/hooks/useResolvedScope.ts +35 -40
  106. package/src/rbac/hooks/useSecureSupabase.ts +7 -7
  107. package/src/rbac/index.ts +7 -0
  108. package/src/rbac/secureClient.test.ts +22 -18
  109. package/src/rbac/secureClient.ts +103 -16
  110. package/src/rbac/security.ts +0 -17
  111. package/src/rbac/types.ts +1 -0
  112. package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
  113. package/src/rbac/utils/clientSecurity.ts +93 -0
  114. package/src/rbac/utils/contextValidator.ts +77 -168
  115. package/src/services/AuthService.ts +39 -7
  116. package/src/services/EventService.ts +186 -54
  117. package/src/services/OrganisationService.ts +81 -14
  118. package/src/services/__tests__/EventService.test.ts +1 -2
  119. package/src/services/base/BaseService.ts +3 -0
  120. package/src/utils/dynamic/dynamicUtils.ts +7 -4
  121. package/dist/chunk-24UVZUZG.js.map +0 -1
  122. package/dist/chunk-3XC4CPTD.js.map +0 -1
  123. package/dist/chunk-6J4GEEJR.js.map +0 -1
  124. package/dist/chunk-7D4SUZUM.js +0 -38
  125. package/dist/chunk-EHMR7VYL.js.map +0 -1
  126. package/dist/chunk-NECFR5MM.js.map +0 -1
  127. package/dist/chunk-SFZUDBL5.js.map +0 -1
  128. package/dist/chunk-XWQCNGTQ.js.map +0 -1
  129. package/docs/api/classes/ColumnFactory.md +0 -243
  130. package/docs/api/classes/InvalidScopeError.md +0 -73
  131. package/docs/api/classes/Logger.md +0 -178
  132. package/docs/api/classes/MissingUserContextError.md +0 -66
  133. package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
  134. package/docs/api/classes/PermissionDeniedError.md +0 -73
  135. package/docs/api/classes/RBACAuditManager.md +0 -297
  136. package/docs/api/classes/RBACCache.md +0 -322
  137. package/docs/api/classes/RBACEngine.md +0 -171
  138. package/docs/api/classes/RBACError.md +0 -76
  139. package/docs/api/classes/RBACNotInitializedError.md +0 -66
  140. package/docs/api/classes/SecureSupabaseClient.md +0 -163
  141. package/docs/api/classes/StorageUtils.md +0 -328
  142. package/docs/api/enums/FileCategory.md +0 -184
  143. package/docs/api/enums/LogLevel.md +0 -54
  144. package/docs/api/enums/RBACErrorCode.md +0 -228
  145. package/docs/api/enums/RPCFunction.md +0 -118
  146. package/docs/api/interfaces/AddressFieldProps.md +0 -241
  147. package/docs/api/interfaces/AddressFieldRef.md +0 -94
  148. package/docs/api/interfaces/AggregateConfig.md +0 -43
  149. package/docs/api/interfaces/AutocompleteOptions.md +0 -75
  150. package/docs/api/interfaces/AvatarProps.md +0 -128
  151. package/docs/api/interfaces/BadgeProps.md +0 -34
  152. package/docs/api/interfaces/ButtonProps.md +0 -56
  153. package/docs/api/interfaces/CalendarProps.md +0 -73
  154. package/docs/api/interfaces/CardProps.md +0 -69
  155. package/docs/api/interfaces/ColorPalette.md +0 -7
  156. package/docs/api/interfaces/ColorShade.md +0 -66
  157. package/docs/api/interfaces/ComplianceResult.md +0 -30
  158. package/docs/api/interfaces/DataAccessRecord.md +0 -96
  159. package/docs/api/interfaces/DataRecord.md +0 -11
  160. package/docs/api/interfaces/DataTableAction.md +0 -252
  161. package/docs/api/interfaces/DataTableColumn.md +0 -504
  162. package/docs/api/interfaces/DataTableProps.md +0 -625
  163. package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
  164. package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
  165. package/docs/api/interfaces/DatabaseIssue.md +0 -41
  166. package/docs/api/interfaces/EmptyStateConfig.md +0 -61
  167. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
  168. package/docs/api/interfaces/ErrorBoundaryProps.md +0 -147
  169. package/docs/api/interfaces/ErrorBoundaryProviderProps.md +0 -36
  170. package/docs/api/interfaces/ErrorBoundaryState.md +0 -75
  171. package/docs/api/interfaces/EventAppRoleData.md +0 -71
  172. package/docs/api/interfaces/ExportColumn.md +0 -90
  173. package/docs/api/interfaces/ExportOptions.md +0 -126
  174. package/docs/api/interfaces/FileDisplayProps.md +0 -249
  175. package/docs/api/interfaces/FileMetadata.md +0 -129
  176. package/docs/api/interfaces/FileReference.md +0 -118
  177. package/docs/api/interfaces/FileSizeLimits.md +0 -7
  178. package/docs/api/interfaces/FileUploadOptions.md +0 -139
  179. package/docs/api/interfaces/FileUploadProps.md +0 -296
  180. package/docs/api/interfaces/FooterProps.md +0 -107
  181. package/docs/api/interfaces/FormFieldProps.md +0 -166
  182. package/docs/api/interfaces/FormProps.md +0 -113
  183. package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
  184. package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
  185. package/docs/api/interfaces/InputProps.md +0 -56
  186. package/docs/api/interfaces/LabelProps.md +0 -107
  187. package/docs/api/interfaces/LoggerConfig.md +0 -62
  188. package/docs/api/interfaces/LoginFormProps.md +0 -187
  189. package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
  190. package/docs/api/interfaces/NavigationContextType.md +0 -164
  191. package/docs/api/interfaces/NavigationGuardProps.md +0 -139
  192. package/docs/api/interfaces/NavigationItem.md +0 -120
  193. package/docs/api/interfaces/NavigationMenuProps.md +0 -221
  194. package/docs/api/interfaces/NavigationProviderProps.md +0 -117
  195. package/docs/api/interfaces/Organisation.md +0 -140
  196. package/docs/api/interfaces/OrganisationContextType.md +0 -388
  197. package/docs/api/interfaces/OrganisationMembership.md +0 -140
  198. package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
  199. package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
  200. package/docs/api/interfaces/PaceAppLayoutProps.md +0 -409
  201. package/docs/api/interfaces/PaceLoginPageProps.md +0 -49
  202. package/docs/api/interfaces/PageAccessRecord.md +0 -85
  203. package/docs/api/interfaces/PagePermissionContextType.md +0 -140
  204. package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
  205. package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
  206. package/docs/api/interfaces/PaletteData.md +0 -41
  207. package/docs/api/interfaces/ParsedAddress.md +0 -120
  208. package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
  209. package/docs/api/interfaces/ProgressProps.md +0 -42
  210. package/docs/api/interfaces/ProtectedRouteProps.md +0 -78
  211. package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
  212. package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
  213. package/docs/api/interfaces/PublicPageLayoutProps.md +0 -185
  214. package/docs/api/interfaces/QuickFix.md +0 -52
  215. package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
  216. package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
  217. package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
  218. package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
  219. package/docs/api/interfaces/RBACConfig.md +0 -133
  220. package/docs/api/interfaces/RBACContext.md +0 -52
  221. package/docs/api/interfaces/RBACLogger.md +0 -112
  222. package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
  223. package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
  224. package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
  225. package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
  226. package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
  227. package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
  228. package/docs/api/interfaces/RBACResult.md +0 -58
  229. package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
  230. package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
  231. package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
  232. package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
  233. package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
  234. package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
  235. package/docs/api/interfaces/RBACRolesListParams.md +0 -52
  236. package/docs/api/interfaces/RBACRolesListResult.md +0 -74
  237. package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
  238. package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
  239. package/docs/api/interfaces/ResourcePermissions.md +0 -155
  240. package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
  241. package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
  242. package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
  243. package/docs/api/interfaces/RoleManagementResult.md +0 -52
  244. package/docs/api/interfaces/RouteAccessRecord.md +0 -107
  245. package/docs/api/interfaces/RouteConfig.md +0 -134
  246. package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
  247. package/docs/api/interfaces/SecureDataContextType.md +0 -168
  248. package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
  249. package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
  250. package/docs/api/interfaces/SetupIssue.md +0 -41
  251. package/docs/api/interfaces/StorageConfig.md +0 -41
  252. package/docs/api/interfaces/StorageFileInfo.md +0 -74
  253. package/docs/api/interfaces/StorageFileMetadata.md +0 -151
  254. package/docs/api/interfaces/StorageListOptions.md +0 -99
  255. package/docs/api/interfaces/StorageListResult.md +0 -41
  256. package/docs/api/interfaces/StorageUploadOptions.md +0 -101
  257. package/docs/api/interfaces/StorageUploadResult.md +0 -63
  258. package/docs/api/interfaces/StorageUrlOptions.md +0 -60
  259. package/docs/api/interfaces/StyleImport.md +0 -19
  260. package/docs/api/interfaces/SwitchProps.md +0 -34
  261. package/docs/api/interfaces/TabsContentProps.md +0 -9
  262. package/docs/api/interfaces/TabsListProps.md +0 -9
  263. package/docs/api/interfaces/TabsProps.md +0 -9
  264. package/docs/api/interfaces/TabsTriggerProps.md +0 -50
  265. package/docs/api/interfaces/TextareaProps.md +0 -53
  266. package/docs/api/interfaces/ToastActionElement.md +0 -12
  267. package/docs/api/interfaces/ToastProps.md +0 -9
  268. package/docs/api/interfaces/UnifiedAuthContextType.md +0 -823
  269. package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -173
  270. package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
  271. package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
  272. package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -138
  273. package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
  274. package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
  275. package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -84
  276. package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
  277. package/docs/api/interfaces/UsePublicEventReturn.md +0 -71
  278. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
  279. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -123
  280. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -97
  281. package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
  282. package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
  283. package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
  284. package/docs/api/interfaces/UserEventAccess.md +0 -121
  285. package/docs/api/interfaces/UserMenuProps.md +0 -88
  286. package/docs/api/interfaces/UserProfile.md +0 -63
  287. package/src/components/EventSelector/EventSelector.test.tsx +0 -720
  288. package/src/components/EventSelector/EventSelector.tsx +0 -423
  289. package/src/components/EventSelector/index.ts +0 -3
  290. package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
  291. package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -327
  292. package/src/components/OrganisationSelector/index.ts +0 -9
  293. /package/dist/{DataTable-TPTKCX4D.js.map → DataTable-THFPBKTP.js.map} +0 -0
  294. /package/dist/{UnifiedAuthProvider-CH6Z342H.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
  295. /package/dist/{api-MVVQZLJI.js.map → api-IAGWF3ZG.js.map} +0 -0
  296. /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
  297. /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
  298. /package/dist/{chunk-2UOI2FG5.js.map → chunk-HFZBI76P.js.map} +0 -0
  299. /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
- // Load appConfig from database if not provided as prop
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
- // For event-required apps, selectedOrganisation is not in context (org derived from event)
469
- // For org-required apps, selectedOrganisation is available
470
- // CRITICAL FIX: Only set to null if appConfig is loaded AND explicitly requires_event is true
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: rawSelectedOrganisation } = useOrganisations();
757
-
758
- // FIX: For event-required apps, don't pass selectedOrganisation to EventService
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={authService.getUser()}
809
- session={authService.getSession()}
753
+ user={user}
754
+ session={session}
810
755
  >
811
756
  <EventServiceProviderWrapper
812
757
  supabaseClient={supabaseClient}
813
- user={authService.getUser()}
814
- session={authService.getSession()}
758
+ user={user}
759
+ session={session}
815
760
  appName={appName}
816
- appConfig={appConfig}
817
761
  >
818
762
  <InactivityServiceProvider
819
763
  supabaseClient={supabaseClient}
820
- user={authService.getUser()}
821
- session={authService.getSession()}
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
- }, null, undefined, null);
502
+ });
503
503
  expect(handler).toHaveBeenCalledWith(req);
504
504
  expect(result).toEqual({ success: true });
505
505
  });
@@ -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
- }, null, undefined, null);
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
- }, null, undefined, null);
551
+ });
552
552
 
553
553
  if (!hasPermission) {
554
554
  return res.redirect(config.fallbackUrl || '/access-denied');
@@ -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
- getAppConfig: vi.fn()
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: 'page-789'
654
+ pageId: '123e4567-e89b-12d3-a456-426614174000' // Valid UUID format
609
655
  });
610
656
 
611
657
  expect(result).toBe(true);
612
- expect(mockEngine.isPermitted).toHaveBeenCalledWith(
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
- const cacheKey = 'permission:user-123:org-456:null:null:read:users';
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: 'page-789'
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: 'page-789'
725
+ pageId: '123e4567-e89b-12d3-a456-426614174000' // Valid UUID format
690
726
  });
691
727
 
692
728
  expect(result).toBe(true);
693
- expect(mockEngine.isPermitted).toHaveBeenCalledWith(
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
- describe('getAppConfig', () => {
832
- it('returns app configuration', async () => {
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 () => {