@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
package/src/rbac/api.ts CHANGED
@@ -30,7 +30,6 @@ import { createLogger } from '../utils/core/logger';
30
30
  import { enablePerformanceMonitoring } from './performance';
31
31
  import { getOrCreateRequest } from './request-deduplication';
32
32
  import { ContextValidator } from './utils/contextValidator';
33
- import type { AppConfig } from './utils/contextValidator';
34
33
 
35
34
  const log = createLogger('RBACAPI');
36
35
 
@@ -92,6 +91,15 @@ export function setupRBAC(supabase: SupabaseClient<Database>, config?: Partial<R
92
91
 
93
92
  }
94
93
 
94
+ /**
95
+ * Check if RBAC system is initialized
96
+ *
97
+ * @returns True if RBAC is initialized
98
+ */
99
+ export function isRBACInitialized(): boolean {
100
+ return globalEngine !== null;
101
+ }
102
+
95
103
  /**
96
104
  * Get the global RBAC engine
97
105
  *
@@ -126,7 +134,6 @@ export async function getAccessLevel(
126
134
  userId: UUID;
127
135
  scope: Scope;
128
136
  },
129
- appConfig?: AppConfig | null,
130
137
  appName?: string
131
138
  ): Promise<AccessLevel> {
132
139
  try {
@@ -138,19 +145,12 @@ export async function getAccessLevel(
138
145
  return 'super';
139
146
  }
140
147
 
141
- // Fetch app config if not provided
142
- let resolvedAppConfig: AppConfig | null = appConfig ?? null;
143
- let resolvedAppName = appName;
144
-
145
- if (!resolvedAppConfig && input.scope.appId) {
146
- resolvedAppConfig = await getAppConfig(input.scope.appId);
147
- }
148
-
149
- // Validate context using ContextValidator
150
- const validation = await ContextValidator.resolveRequiredContext(
148
+ // For functions without pageId, default to organisation scope validation
149
+ // This is a safe default - most operations require organisation context
150
+ const validation = await ContextValidator.resolveScopeForPage(
151
151
  input.scope,
152
- resolvedAppConfig,
153
- resolvedAppName,
152
+ 'organisation', // Default to organisation scope when no page context
153
+ appName,
154
154
  engine['supabase']
155
155
  );
156
156
 
@@ -194,25 +194,17 @@ export async function getPermissionMap(
194
194
  userId: UUID;
195
195
  scope: Scope;
196
196
  },
197
- appConfig?: AppConfig | null,
198
197
  appName?: string
199
198
  ): Promise<PermissionMap> {
200
199
  try {
201
200
  const engine = getEngine();
202
201
 
203
- // Fetch app config if not provided
204
- let resolvedAppConfig: AppConfig | null = appConfig ?? null;
205
- let resolvedAppName = appName;
206
-
207
- if (!resolvedAppConfig && input.scope.appId) {
208
- resolvedAppConfig = await getAppConfig(input.scope.appId);
209
- }
210
-
211
- // Validate context using ContextValidator
212
- const validation = await ContextValidator.resolveRequiredContext(
202
+ // For functions without pageId, default to organisation scope validation
203
+ // This is a safe default - most operations require organisation context
204
+ const validation = await ContextValidator.resolveScopeForPage(
213
205
  input.scope,
214
- resolvedAppConfig,
215
- resolvedAppName,
206
+ 'organisation', // Default to organisation scope when no page context
207
+ appName,
216
208
  engine['supabase']
217
209
  );
218
210
 
@@ -249,24 +241,15 @@ export async function getRoleContext(
249
241
  userId: UUID;
250
242
  scope: Scope;
251
243
  },
252
- appConfig?: AppConfig | null,
253
244
  appName?: string
254
245
  ): Promise<RBACRoleContext> {
255
246
  const engine = getEngine();
256
247
 
257
- // Fetch app config if not provided
258
- let resolvedAppConfig: AppConfig | null = appConfig ?? null;
259
- let resolvedAppName = appName;
260
-
261
- if (!resolvedAppConfig && input.scope.appId) {
262
- resolvedAppConfig = await getAppConfig(input.scope.appId);
263
- }
264
-
265
- // Validate context using ContextValidator
266
- const validation = await ContextValidator.resolveRequiredContext(
248
+ // For functions without pageId, default to organisation scope validation
249
+ const validation = await ContextValidator.resolveScopeForPage(
267
250
  input.scope,
268
- resolvedAppConfig,
269
- resolvedAppName,
251
+ 'organisation', // Default to organisation scope when no page context
252
+ appName,
270
253
  engine['supabase']
271
254
  );
272
255
 
@@ -301,7 +284,6 @@ export async function getRoleContext(
301
284
  */
302
285
  export async function isPermitted(
303
286
  input: PermissionCheck,
304
- appConfig?: AppConfig | null,
305
287
  appName?: string,
306
288
  /**
307
289
  * Pre-computed super admin status to avoid duplicate checks.
@@ -329,15 +311,8 @@ export async function isPermitted(
329
311
  }
330
312
  // If precomputedSuperAdmin === false, skip check and proceed with permission check
331
313
 
332
- // Fetch app config if not provided and we have appId
333
- let resolvedAppConfig: AppConfig | null = appConfig ?? null;
314
+ // Get app name if not provided (for PORTAL/ADMIN special case)
334
315
  let resolvedAppName = appName;
335
-
336
- if (!resolvedAppConfig && input.scope.appId) {
337
- resolvedAppConfig = await getAppConfig(input.scope.appId);
338
- }
339
-
340
- // If we have appId but no appName, try to get it from the database
341
316
  if (!resolvedAppName && input.scope.appId) {
342
317
  try {
343
318
  const { data } = await engine['supabase']
@@ -354,10 +329,34 @@ export async function isPermitted(
354
329
  }
355
330
  }
356
331
 
357
- // Validate context using ContextValidator
358
- const validation = await ContextValidator.resolveRequiredContext(
332
+ // Get page scope type (required for all permission checks)
333
+ // All pages must have scope_type set - this is the single source of truth
334
+ let pageScopeType: 'event' | 'organisation' | 'both';
335
+ if (input.pageId) {
336
+ try {
337
+ const scopeType = await getPageScopeType(
338
+ input.pageId,
339
+ input.scope.appId,
340
+ resolvedAppName
341
+ );
342
+ if (!scopeType) {
343
+ throw new Error(`Page ${input.pageId} does not have scope_type set`);
344
+ }
345
+ pageScopeType = scopeType;
346
+ } catch (err) {
347
+ log.error('Failed to get page scope type:', err);
348
+ throw new Error(`Failed to determine page scope type: ${err instanceof Error ? err.message : String(err)}`);
349
+ }
350
+ } else {
351
+ // No pageId provided - default to organisation scope
352
+ // This should rarely happen, but provides a safe fallback
353
+ pageScopeType = 'organisation';
354
+ }
355
+
356
+ // Validate context using page-level scope (single source of truth)
357
+ const validation = await ContextValidator.resolveScopeForPage(
359
358
  input.scope,
360
- resolvedAppConfig,
359
+ pageScopeType,
361
360
  resolvedAppName,
362
361
  engine['supabase']
363
362
  );
@@ -369,7 +368,64 @@ export async function isPermitted(
369
368
  // Use resolved scope for permission check
370
369
  const validatedScope = validation.resolvedScope;
371
370
 
372
- // Create security context from validated scope
371
+ // Handle 'both' scope pages - check both scopes and return union
372
+ if (pageScopeType === 'both' && input.pageId) {
373
+ // Check permission with event scope
374
+ const eventScope: Scope = {
375
+ organisationId: validatedScope.organisationId, // Org derived from event
376
+ eventId: validatedScope.eventId,
377
+ appId: validatedScope.appId
378
+ };
379
+
380
+ // Check permission with organisation scope (if org is available separately)
381
+ // For 'both' pages, we check the permission in both contexts and return the union
382
+ // Higher permission wins (true > false, admin > user, etc.)
383
+
384
+ const eventSecurityContext: SecurityContext = {
385
+ userId: input.userId,
386
+ organisationId: eventScope.organisationId || null,
387
+ timestamp: new Date(),
388
+ };
389
+
390
+ const eventInput: PermissionCheck = {
391
+ ...input,
392
+ scope: eventScope
393
+ };
394
+
395
+ const hasEventPermission = await engine.isPermitted(eventInput, eventSecurityContext);
396
+
397
+ // Also check with organisation scope if we have a separate org context
398
+ // (This handles cases where user has org permissions separate from event)
399
+ if (validatedScope.organisationId && validatedScope.eventId) {
400
+ const orgScope: Scope = {
401
+ organisationId: validatedScope.organisationId,
402
+ eventId: undefined, // Clear event for org-only check
403
+ appId: validatedScope.appId
404
+ };
405
+
406
+ const orgSecurityContext: SecurityContext = {
407
+ userId: input.userId,
408
+ organisationId: orgScope.organisationId || null,
409
+ timestamp: new Date(),
410
+ };
411
+
412
+ const orgInput: PermissionCheck = {
413
+ ...input,
414
+ scope: orgScope
415
+ };
416
+
417
+ const hasOrgPermission = await engine.isPermitted(orgInput, orgSecurityContext);
418
+
419
+ // Return union (true if either scope grants permission)
420
+ // For access levels, the database function handles the "higher wins" logic
421
+ return hasEventPermission || hasOrgPermission;
422
+ }
423
+
424
+ // If only event scope available, return event permission
425
+ return hasEventPermission;
426
+ }
427
+
428
+ // Standard permission check for single-scope pages
373
429
  const securityContext: SecurityContext = {
374
430
  userId: input.userId,
375
431
  organisationId: validatedScope.organisationId || null,
@@ -393,13 +449,11 @@ export async function isPermitted(
393
449
  * and checks cache before making new requests. Uses session cache for page-level checks.
394
450
  *
395
451
  * @param input - Permission check input
396
- * @param appConfig - Optional app configuration
397
- * @param appName - Optional app name
452
+ * @param appName - Optional app name (for PORTAL/ADMIN special case)
398
453
  * @returns Promise resolving to permission result
399
454
  */
400
455
  export async function isPermittedCached(
401
456
  input: PermissionCheck,
402
- appConfig?: AppConfig | null,
403
457
  appName?: string
404
458
  ): Promise<boolean> {
405
459
  const { userId, scope, permission, pageId } = input;
@@ -424,7 +478,7 @@ export async function isPermittedCached(
424
478
  // Check permission with context validation
425
479
  // Note: We can't pass precomputedSuperAdmin here because getOrCreateRequest doesn't support it
426
480
  // The super admin check in isPermitted will be cached, so it's not a huge performance hit
427
- const result = await isPermitted(checkInput, appConfig, appName, null);
481
+ const result = await isPermitted(checkInput, appName, null);
428
482
 
429
483
  // Determine if this is a page-level check (has pageId or permission contains 'page.')
430
484
  const isPageLevelCheck = !!pageId || permission.includes('page.');
@@ -443,7 +497,7 @@ export async function isPermittedCached(
443
497
  * @returns Promise<boolean> - True if user has permission
444
498
  */
445
499
  export async function hasPermission(input: PermissionCheck): Promise<boolean> {
446
- return isPermitted(input, null, undefined, null);
500
+ return isPermitted(input);
447
501
  }
448
502
 
449
503
  /**
@@ -464,7 +518,7 @@ export async function hasAnyPermission(input: {
464
518
  const hasPermission = await isPermitted({
465
519
  ...baseInput,
466
520
  permission,
467
- }, null, undefined, null);
521
+ });
468
522
 
469
523
  if (hasPermission) {
470
524
  return true;
@@ -492,7 +546,7 @@ export async function hasAllPermissions(input: {
492
546
  const hasPermission = await isPermitted({
493
547
  ...baseInput,
494
548
  permission,
495
- }, null, undefined, null);
549
+ });
496
550
 
497
551
  if (!hasPermission) {
498
552
  return false;
@@ -513,89 +567,81 @@ export async function isSuperAdmin(userId: UUID): Promise<boolean> {
513
567
  return engine['checkSuperAdmin'](userId);
514
568
  }
515
569
 
570
+
516
571
  /**
517
- * Get app configuration including requires_event setting
572
+ * Get page scope type (effective scope for a page)
518
573
  *
519
- * @param appId - App ID
520
- * @returns Promise resolving to app configuration
574
+ * Returns the scope type for a page. After migration, all pages have explicit scope_type set.
575
+ * This is the single source of truth for page scoping.
576
+ *
577
+ * @param pageId - Page ID (UUID) or page name
578
+ * @param appId - App ID (required if pageId is a page name)
579
+ * @param appName - App name (alternative to appId, used to resolve appId)
580
+ * @returns Promise resolving to page scope type: 'event', 'organisation', or 'both'
521
581
  */
522
- export async function getAppConfig(appId: UUID): Promise<AppConfig | null> {
523
- try {
524
- const engine = getEngine();
525
- return getAppConfigWithClient(engine['supabase'], appId);
526
- } catch (err) {
527
- // RBAC not initialized - return null gracefully
528
- if (err instanceof RBACNotInitializedError) {
529
- return null;
530
- }
531
- throw err;
532
- }
533
- }
534
-
535
- export async function getAppConfigWithClient(client: SupabaseClient | null | undefined, appId: UUID): Promise<AppConfig | null> {
536
- // Return null if client is not available
537
- if (!client) {
538
- return null;
539
- }
540
-
541
- // Cache key for app config - cache for 5 minutes (app config rarely changes)
542
- const cacheKey = `app_config:${appId}`;
543
-
544
- // Check cache first
545
- const cached = rbacCache.get<AppConfig>(cacheKey, true);
546
- if (cached !== null) {
547
- return cached;
548
- }
582
+ export async function getPageScopeType(
583
+ pageId: UUID | string,
584
+ appId?: UUID,
585
+ appName?: string
586
+ ): Promise<'event' | 'organisation' | 'both'> {
587
+ const engine = getEngine();
549
588
 
550
589
  try {
551
- const { data, error } = await client
552
- .from('rbac_apps')
553
- .select('requires_event, name')
554
- .eq('id', appId)
555
- .eq('is_active', true)
556
- .single() as { data: { requires_event: boolean; name: string } | null; error: any };
557
-
558
- if (error || !data) {
559
- return null;
590
+ // Resolve appId if not provided
591
+ let resolvedAppId = appId;
592
+ if (!resolvedAppId && appName) {
593
+ // Get appId directly from app name
594
+ const { data: app } = await engine['supabase']
595
+ .from('rbac_apps')
596
+ .select('id')
597
+ .eq('name', appName)
598
+ .eq('is_active', true)
599
+ .single() as { data: { id: UUID } | null; error: any };
600
+ resolvedAppId = app?.id;
560
601
  }
561
-
562
- const appConfig: AppConfig = { requires_event: data.requires_event ?? false };
563
602
 
564
- // Cache the result for 5 minutes (300000ms) with session cache enabled
565
- // App config rarely changes, so we can cache it longer
566
- rbacCache.set(cacheKey, appConfig, 5 * 60 * 1000, true);
603
+ if (!resolvedAppId) {
604
+ throw new Error(`Could not resolve appId for page ${pageId}`);
605
+ }
567
606
 
568
- return appConfig;
569
- } catch (err) {
570
- log.error('Error fetching app config:', err);
571
- return null;
572
- }
573
- }
574
-
575
- /**
576
- * Get app configuration by app name
577
- *
578
- * @param appName - App name
579
- * @returns Promise resolving to app configuration
580
- */
581
- export async function getAppConfigByName(appName: string): Promise<AppConfig | null> {
582
- const engine = getEngine();
583
- try {
607
+ // Resolve pageId if it's a page name
608
+ let resolvedPageId: UUID | string = pageId;
609
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
610
+ if (!uuidRegex.test(pageId)) {
611
+ // It's a page name, resolve to UUID
612
+ const { data: page } = await engine['supabase']
613
+ .from('rbac_app_pages')
614
+ .select('id')
615
+ .eq('app_id', resolvedAppId)
616
+ .eq('page_name', pageId)
617
+ .maybeSingle() as { data: { id: UUID } | null; error: any };
618
+ resolvedPageId = page?.id || pageId;
619
+ }
620
+
621
+ // If still not a UUID, can't proceed
622
+ if (!uuidRegex.test(resolvedPageId)) {
623
+ throw new Error(`Could not resolve pageId ${pageId} to a valid UUID`);
624
+ }
625
+
626
+ // Call the database function to get scope type (always returns a value)
584
627
  const { data, error } = await engine['supabase']
585
- .from('rbac_apps')
586
- .select('requires_event, name')
587
- .eq('name', appName)
588
- .eq('is_active', true)
589
- .single() as { data: { requires_event: boolean; name: string } | null; error: any };
590
-
591
- if (error || !data) {
592
- return null;
628
+ .rpc('get_page_scope_type' as any, {
629
+ p_page_id: resolvedPageId
630
+ }) as { data: 'event' | 'organisation' | 'both' | null; error: any };
631
+
632
+ if (error) {
633
+ log.error('Error fetching page scope type:', { pageId, appId, error });
634
+ throw new Error(`Failed to get page scope type: ${error.message}`);
593
635
  }
594
-
595
- return { requires_event: data.requires_event ?? false };
636
+
637
+ if (!data) {
638
+ throw new Error(`Page ${resolvedPageId} does not have scope_type set`);
639
+ }
640
+
641
+ return data;
596
642
  } catch (err) {
597
- log.error('Error fetching app config by name:', err);
598
- return null;
643
+ log.error('Error fetching page scope type:', err);
644
+ throw err instanceof Error ? err : new Error(`Failed to get page scope type: ${String(err)}`);
599
645
  }
600
646
  }
601
647
 
@@ -141,6 +141,7 @@ const PagePermissionGuardComponent = ({
141
141
  const { user, selectedOrganisation, selectedEvent, supabase, appId: contextAppId, appName } = useUnifiedAuth();
142
142
 
143
143
  const [hasChecked, setHasChecked] = useState(false);
144
+ const hasLoggedSuperAdminRef = useRef(false);
144
145
 
145
146
  // Determine the page ID for permission checking
146
147
  const effectivePageId = useMemo((): string => {
@@ -396,10 +397,11 @@ const PagePermissionGuardComponent = ({
396
397
 
397
398
 
398
399
 
399
- // Debug logging for permission check state
400
+ // Debug logging for permission check state (only log when state actually changes, not on every render)
401
+ const lastLogStateRef = useRef<string>('');
400
402
  useEffect(() => {
401
403
  if (process.env.NODE_ENV === 'development') {
402
- console.log('[PagePermissionGuard] Permission check state', {
404
+ const currentState = JSON.stringify({
403
405
  pageName,
404
406
  userId: user?.id,
405
407
  isSuperAdmin,
@@ -408,10 +410,25 @@ const PagePermissionGuardComponent = ({
408
410
  canIsLoading,
409
411
  hasChecked,
410
412
  hasValidUser,
411
- effectiveCan,
412
- stableScope,
413
- effectiveScope
413
+ effectiveCan
414
414
  });
415
+ // Only log if state actually changed
416
+ if (currentState !== lastLogStateRef.current) {
417
+ lastLogStateRef.current = currentState;
418
+ console.log('[PagePermissionGuard] Permission check state', {
419
+ pageName,
420
+ userId: user?.id,
421
+ isSuperAdmin,
422
+ isLoading,
423
+ scopeLoading,
424
+ canIsLoading,
425
+ hasChecked,
426
+ hasValidUser,
427
+ effectiveCan,
428
+ stableScope,
429
+ effectiveScope
430
+ });
431
+ }
415
432
  }
416
433
  }, [pageName, user?.id, isSuperAdmin, isLoading, scopeLoading, canIsLoading, hasChecked, hasValidUser, effectiveCan, stableScope, effectiveScope]);
417
434
 
@@ -439,13 +456,24 @@ const PagePermissionGuardComponent = ({
439
456
 
440
457
  // CRITICAL: Super admins bypass all checks - show content immediately once confirmed
441
458
  // This must be checked AFTER all hooks are called to avoid React hooks rule violations
459
+ // Log super admin access only once when it's first confirmed (not on every render)
460
+ useEffect(() => {
461
+ if (isSuperAdmin === true && hasValidUser && !hasLoggedSuperAdminRef.current && process.env.NODE_ENV === 'development') {
462
+ hasLoggedSuperAdminRef.current = true;
463
+ console.log('[PagePermissionGuard] Super admin access granted - bypassing all checks', {
464
+ pageName,
465
+ userId: user?.id,
466
+ operation
467
+ });
468
+ }
469
+ // Reset log flag if super admin status changes back to false/null
470
+ if (isSuperAdmin !== true) {
471
+ hasLoggedSuperAdminRef.current = false;
472
+ }
473
+ }, [isSuperAdmin, hasValidUser, pageName, user?.id, operation]);
474
+
442
475
  if (isSuperAdmin === true && hasValidUser) {
443
476
  // Super admin confirmed - grant access immediately
444
- console.log('[PagePermissionGuard] Super admin access granted - bypassing all checks', {
445
- pageName,
446
- userId: user?.id,
447
- operation
448
- });
449
477
  return <>{children}</>;
450
478
  }
451
479
 
@@ -33,14 +33,15 @@ vi.mock('../useResolvedScope', () => ({
33
33
  }));
34
34
 
35
35
  vi.mock('../../secureClient', () => ({
36
- createSecureClient: vi.fn()
36
+ createSecureClient: vi.fn(),
37
+ fromSupabaseClient: vi.fn()
37
38
  }));
38
39
 
39
40
  import { useUnifiedAuth } from '../../../providers/services/UnifiedAuthProvider';
40
41
  import { useOrganisations } from '../../../hooks/useOrganisations';
41
42
  import { useEvents } from '../../../hooks/useEvents';
42
43
  import { useResolvedScope } from '../useResolvedScope';
43
- import { createSecureClient } from '../../secureClient';
44
+ import { createSecureClient, fromSupabaseClient } from '../../secureClient';
44
45
  import type { SupabaseClient } from '@supabase/supabase-js';
45
46
  import type { Database } from '../../../types/database';
46
47
 
@@ -68,6 +69,7 @@ describe('useSecureSupabase Hook', () => {
68
69
  const mockUseEvents = vi.mocked(useEvents);
69
70
  const mockUseResolvedScope = vi.mocked(useResolvedScope);
70
71
  const mockCreateSecureClient = vi.mocked(createSecureClient);
72
+ const mockFromSupabaseClient = vi.mocked(fromSupabaseClient);
71
73
 
72
74
  beforeEach(() => {
73
75
  vi.clearAllMocks();
@@ -131,6 +133,9 @@ describe('useSecureSupabase Hook', () => {
131
133
  });
132
134
 
133
135
  mockCreateSecureClient.mockReturnValue(mockSecureClient as any);
136
+ // When fromSupabaseClient is used (when baseClient/authSupabase is available),
137
+ // it should return a secure client that wraps the existing client
138
+ mockFromSupabaseClient.mockReturnValue(mockSecureClient as any);
134
139
  });
135
140
 
136
141
  afterEach(() => {
@@ -171,14 +176,15 @@ describe('useSecureSupabase Hook', () => {
171
176
  const { result } = renderHook(() => useSecureSupabase());
172
177
 
173
178
  await waitFor(() => {
174
- expect(mockCreateSecureClient).toHaveBeenCalled();
179
+ // When authSupabase is available, the hook uses fromSupabaseClient
180
+ expect(mockFromSupabaseClient).toHaveBeenCalled();
175
181
  }, { timeout: 2000 });
176
182
 
177
183
  expect(result.current).toBe(mockSupabaseClient);
178
- const call = mockCreateSecureClient.mock.calls[0];
179
- expect(call[2]).toBe(uniqueOrgId);
180
- expect(call[3]).toBe(mockEventId);
181
- expect(call[4]).toBe(mockAppId);
184
+ const call = mockFromSupabaseClient.mock.calls[0];
185
+ expect(call[1]).toBe(uniqueOrgId);
186
+ expect(call[2]).toBe(mockEventId);
187
+ expect(call[3]).toBe(mockAppId);
182
188
  });
183
189
 
184
190
  it('should return base client when event is loading', () => {
@@ -265,7 +271,8 @@ describe('useSecureSupabase Hook', () => {
265
271
  // The cache is working correctly by reusing the client
266
272
  } else {
267
273
  // Client was created - verify it was called
268
- expect(mockCreateSecureClient).toHaveBeenCalled();
274
+ // When authSupabase is available, the hook uses fromSupabaseClient
275
+ expect(mockFromSupabaseClient).toHaveBeenCalled();
269
276
  }
270
277
 
271
278
  // Store the first client instance
@@ -316,11 +323,12 @@ describe('useSecureSupabase Hook', () => {
316
323
  const { result: result1, rerender: rerender1 } = renderHook(() => useSecureSupabase());
317
324
 
318
325
  await waitFor(() => {
319
- expect(mockCreateSecureClient).toHaveBeenCalled();
326
+ // When authSupabase is available, the hook uses fromSupabaseClient
327
+ expect(mockFromSupabaseClient).toHaveBeenCalled();
320
328
  }, { timeout: 2000 });
321
329
 
322
330
  // Clear the mock to count new calls
323
- mockCreateSecureClient.mockClear();
331
+ mockFromSupabaseClient.mockClear();
324
332
 
325
333
  // Change organisation - this should create a new client with different cache key
326
334
  const uniqueOrgId2 = `org-${Date.now()}-2`;
@@ -347,7 +355,8 @@ describe('useSecureSupabase Hook', () => {
347
355
  rerender1();
348
356
 
349
357
  await waitFor(() => {
350
- expect(mockCreateSecureClient).toHaveBeenCalled();
358
+ // When authSupabase is available, the hook uses fromSupabaseClient
359
+ expect(mockFromSupabaseClient).toHaveBeenCalled();
351
360
  }, { timeout: 2000 });
352
361
  });
353
362
  });
@@ -448,13 +457,14 @@ describe('useSecureSupabase Hook', () => {
448
457
  const { result } = renderHook(() => useSecureSupabase());
449
458
 
450
459
  await waitFor(() => {
451
- expect(mockCreateSecureClient).toHaveBeenCalled();
460
+ // When authSupabase is available, the hook uses fromSupabaseClient
461
+ expect(mockFromSupabaseClient).toHaveBeenCalled();
452
462
  }, { timeout: 2000 });
453
463
 
454
- const call = mockCreateSecureClient.mock.calls[0];
455
- expect(call[2]).toBe(uniqueOrgId);
456
- expect(call[3]).toBe(mockEventId);
457
- expect(call[4]).toBe(customAppId);
464
+ const call = mockFromSupabaseClient.mock.calls[0];
465
+ expect(call[1]).toBe(uniqueOrgId);
466
+ expect(call[2]).toBe(mockEventId);
467
+ expect(call[3]).toBe(customAppId);
458
468
  });
459
469
 
460
470
  it('should work without event context', async () => {
@@ -493,13 +503,14 @@ describe('useSecureSupabase Hook', () => {
493
503
  const { result } = renderHook(() => useSecureSupabase());
494
504
 
495
505
  await waitFor(() => {
496
- expect(mockCreateSecureClient).toHaveBeenCalled();
506
+ // When authSupabase is available, the hook uses fromSupabaseClient
507
+ expect(mockFromSupabaseClient).toHaveBeenCalled();
497
508
  }, { timeout: 2000 });
498
509
 
499
- const call = mockCreateSecureClient.mock.calls[0];
500
- expect(call[2]).toBe(uniqueOrgId);
501
- expect(call[3]).toBeUndefined();
502
- expect(call[4]).toBe(mockAppId);
510
+ const call = mockFromSupabaseClient.mock.calls[0];
511
+ expect(call[1]).toBe(uniqueOrgId);
512
+ expect(call[2]).toBeUndefined();
513
+ expect(call[3]).toBe(mockAppId);
503
514
  });
504
515
  });
505
516
 
@@ -80,7 +80,7 @@ export function useAccessLevel(userId: UUID, scope: Scope): {
80
80
  return;
81
81
  }
82
82
 
83
- const level = await getAccessLevel({ userId, scope }, null, appName);
83
+ const level = await getAccessLevel({ userId, scope }, appName);
84
84
  setAccessLevel(level);
85
85
  } catch (err) {
86
86
  const error = err instanceof Error ? err : new Error('Failed to fetch access level');