@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
@@ -13,8 +13,6 @@ import { useEffect, useState, useRef, useMemo } from 'react';
13
13
  import { SupabaseClient } from '@supabase/supabase-js';
14
14
  import type { Database } from '../../types/database';
15
15
  import type { Scope } from '../types';
16
- import { ContextValidator } from '../utils/contextValidator';
17
- import type { AppConfig } from '../utils/contextValidator';
18
16
  import { getCurrentAppName } from '../../utils/app/appNameResolver';
19
17
  import { createLogger } from '../../utils/core/logger';
20
18
 
@@ -22,12 +20,12 @@ const log = createLogger('useResolvedScope');
22
20
 
23
21
  // Cache app config to avoid repeated database queries
24
22
  // App config rarely changes during a session, so we can cache it
25
- const appConfigCache = new Map<string, { appId: string; appConfig: AppConfig; timestamp: number }>();
23
+ const appIdCache = new Map<string, { appId: string; timestamp: number }>();
26
24
  const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
27
25
 
28
26
  // Export function to clear cache (for testing)
29
27
  export function clearAppConfigCache(): void {
30
- appConfigCache.clear();
28
+ appIdCache.clear();
31
29
  }
32
30
 
33
31
  export interface UseResolvedScopeOptions {
@@ -138,12 +136,11 @@ export function useResolvedScope({
138
136
  setError(null);
139
137
 
140
138
  try {
141
- // Get app name and config
139
+ // Get app name and resolve appId
142
140
  const appName = getCurrentAppName();
143
141
  let appId: string | undefined = undefined;
144
- let appConfig: AppConfig | null = null;
145
142
 
146
- // Try to resolve app config from database (with caching)
143
+ // Try to resolve appId from database (with caching)
147
144
  // Only query if user is authenticated (RLS policies require authentication)
148
145
  if (supabase && appName) {
149
146
  try {
@@ -156,19 +153,18 @@ export function useResolvedScope({
156
153
  log.debug(`Skipping app resolution for "${appName}" - user not authenticated`);
157
154
  } else {
158
155
  // Check cache first
159
- const cached = appConfigCache.get(appName);
156
+ const cached = appIdCache.get(appName);
160
157
  const now = Date.now();
161
158
  if (cached && (now - cached.timestamp) < CACHE_TTL) {
162
159
  appId = cached.appId;
163
- appConfig = cached.appConfig;
164
160
  } else {
165
161
  // Cache miss or expired - fetch from database
166
162
  const { data: app, error } = await supabase
167
163
  .from('rbac_apps')
168
- .select('id, name, requires_event, is_active')
164
+ .select('id, name, is_active')
169
165
  .eq('name', appName)
170
166
  .eq('is_active', true)
171
- .single() as { data: { id: string; name: string; requires_event: boolean; is_active: boolean } | null; error: any };
167
+ .single() as { data: { id: string; name: string; is_active: boolean } | null; error: any };
172
168
 
173
169
  if (error) {
174
170
  // HTTP 406 is expected when not authenticated (RLS blocks query)
@@ -197,9 +193,8 @@ export function useResolvedScope({
197
193
  }
198
194
  } else if (app) {
199
195
  appId = app.id;
200
- appConfig = { requires_event: app.requires_event ?? false };
201
196
  // Only cache successful lookups of active apps
202
- appConfigCache.set(appName, { appId, appConfig, timestamp: now });
197
+ appIdCache.set(appName, { appId, timestamp: now });
203
198
  }
204
199
  }
205
200
  }
@@ -208,7 +203,7 @@ export function useResolvedScope({
208
203
  // Don't log 406 errors as they're expected when not authenticated
209
204
  const errorMessage = error instanceof Error ? error.message : String(error);
210
205
  if (!errorMessage.includes('406') && !errorMessage.includes('PGRST116')) {
211
- log.error('Unexpected error resolving app config:', error);
206
+ log.error('Unexpected error resolving app ID:', error);
212
207
  } else {
213
208
  log.debug('App resolution skipped - authentication required');
214
209
  }
@@ -216,44 +211,44 @@ export function useResolvedScope({
216
211
  }
217
212
 
218
213
  // Build initial scope from available context
219
- // For event-required apps: Only use eventId (org will be derived)
220
- // For org-required apps: Only use organisationId (event is optional)
214
+ // Scope is now page-level only - use whatever context is available
215
+ // Default to organisation scope if both are available (safest default)
221
216
  const initialScope: Scope = {
222
- organisationId: appConfig?.requires_event ? undefined : (selectedOrganisationId || undefined),
217
+ organisationId: selectedOrganisationId || undefined,
223
218
  eventId: selectedEventId || undefined,
224
219
  appId: appId
225
220
  };
226
221
 
227
- // Use ContextValidator to resolve required context
228
- // For PORTAL, always allow scope resolution even if appConfig is null
229
- const validation = await ContextValidator.resolveRequiredContext(
222
+ // For PORTAL/ADMIN apps, allow scope without org/event
223
+ if (appName === 'PORTAL' || appName === 'ADMIN') {
224
+ if (!cancelled) {
225
+ const optionalContextScope: Scope = {
226
+ organisationId: undefined,
227
+ eventId: undefined,
228
+ appId: appId || undefined
229
+ };
230
+ setResolvedScope(optionalContextScope);
231
+ setError(null);
232
+ setIsLoading(false);
233
+ }
234
+ return;
235
+ }
236
+
237
+ // For other apps, default to organisation scope validation (safest default)
238
+ // Page-level scope will be validated during permission checks
239
+ // ContextValidator is already imported at the top
240
+ const { ContextValidator } = await import('../utils/contextValidator');
241
+ const validation = await ContextValidator.resolveScopeForPage(
230
242
  initialScope,
231
- appConfig,
243
+ 'organisation', // Default to organisation scope when no page context
232
244
  appName || undefined,
233
245
  supabase
234
246
  );
235
247
 
236
248
  if (!validation.isValid) {
237
- // For PORTAL/ADMIN apps, allow scope without org/event even if validation fails
238
- if (appName === 'PORTAL' || appName === 'ADMIN') {
239
- if (!cancelled) {
240
- // For PORTAL/ADMIN, we need at least an appId. If we don't have it from the query,
241
- // we'll set it to undefined and let the component handle it (it can use contextAppId)
242
- const optionalContextScope: Scope = {
243
- organisationId: undefined,
244
- eventId: undefined,
245
- appId: appId || undefined // appId might be undefined if query failed, that's OK
246
- };
247
- setResolvedScope(optionalContextScope);
248
- setError(null);
249
- setIsLoading(false);
250
- }
251
- return;
252
- }
253
-
254
- // For event-required apps: if validation fails but we have an eventId, return scope with eventId
249
+ // If validation fails but we have an eventId, return scope with eventId
255
250
  // The organisation will be derived later during permission checks
256
- if (appConfig?.requires_event && selectedEventId) {
251
+ if (selectedEventId) {
257
252
  if (!cancelled) {
258
253
  const eventScope: Scope = {
259
254
  organisationId: undefined, // Will be derived from event during permission check
@@ -295,13 +295,13 @@ export function useSecureSupabase(
295
295
  isSuperAdmin
296
296
  )
297
297
  : createSecureClient(
298
- config.url,
299
- config.key,
300
- effectiveOrganisationId as any, // organisationId is string | null, UUID is string alias
301
- eventId,
302
- appId as any, // appId is string | undefined, UUID is string alias
303
- isSuperAdmin // Pass super admin status for conditional filtering
304
- );
298
+ config.url,
299
+ config.key,
300
+ effectiveOrganisationId as any, // organisationId is string | null, UUID is string alias
301
+ eventId,
302
+ appId as any, // appId is string | undefined, UUID is string alias
303
+ isSuperAdmin // Pass super admin status for conditional filtering
304
+ );
305
305
 
306
306
  // Cache the client for reuse
307
307
  secureClientCache.set(cacheKey, secureClient);
package/src/rbac/index.ts CHANGED
@@ -52,6 +52,13 @@ export {
52
52
  fromSupabaseClient,
53
53
  } from './secureClient';
54
54
 
55
+ // Client security utilities
56
+ export {
57
+ isSecureClient,
58
+ warnIfInsecureClient,
59
+ SECURE_CLIENT_SYMBOL,
60
+ } from './utils/clientSecurity';
61
+
55
62
  // Cache
56
63
  export {
57
64
  RBACCache,
@@ -360,32 +360,36 @@ describe('createSecureClient', () => {
360
360
  describe('fromSupabaseClient', () => {
361
361
  it('converts existing Supabase client to secure client', () => {
362
362
  const mockSupabase = createMockSupabaseClient();
363
- expect(() => {
364
- fromSupabaseClient(
365
- mockSupabase as any,
366
- 'org-123' as UUID
367
- );
368
- }).toThrow('fromSupabaseClient is not supported. Use createSecureClient instead.');
363
+ const secureClient = fromSupabaseClient(
364
+ mockSupabase as any,
365
+ 'org-123' as UUID
366
+ );
367
+
368
+ expect(secureClient).toBeInstanceOf(SecureSupabaseClient);
369
+ expect(secureClient.getOrganisationId()).toBe('org-123');
369
370
  });
370
371
 
371
372
  it('preserves original client configuration', () => {
372
373
  const mockSupabase = createMockSupabaseClient();
373
- expect(() => {
374
- fromSupabaseClient(
375
- mockSupabase as any,
376
- 'org-123' as UUID,
377
- 'event-456',
378
- 'app-789' as UUID
379
- );
380
- }).toThrow('fromSupabaseClient is not supported. Use createSecureClient instead.');
374
+ const secureClient = fromSupabaseClient(
375
+ mockSupabase as any,
376
+ 'org-123' as UUID,
377
+ 'event-456',
378
+ 'app-789' as UUID
379
+ );
380
+
381
+ expect(secureClient).toBeInstanceOf(SecureSupabaseClient);
382
+ expect(secureClient.getOrganisationId()).toBe('org-123');
383
+ expect(secureClient.getEventId()).toBe('event-456');
384
+ expect(secureClient.getAppId()).toBe('app-789');
381
385
  });
382
386
 
383
- it('validates organisation context', () => {
387
+ it('handles empty organisation context', () => {
384
388
  const mockSupabase = createMockSupabaseClient();
389
+ const secureClient = fromSupabaseClient(mockSupabase as any, '' as UUID);
385
390
 
386
- expect(() => {
387
- fromSupabaseClient(mockSupabase as any, '' as UUID);
388
- }).toThrow('fromSupabaseClient is not supported. Use createSecureClient instead.');
391
+ expect(secureClient).toBeInstanceOf(SecureSupabaseClient);
392
+ expect(secureClient.getOrganisationId()).toBe('');
389
393
  });
390
394
  });
391
395
 
@@ -12,6 +12,7 @@ import { createClient, SupabaseClient } from '@supabase/supabase-js';
12
12
  import { Database } from '../types/database';
13
13
  import { UUID } from './types';
14
14
  import { OrganisationContextRequiredError } from './types';
15
+ import { markClientAsSecure } from './utils/clientSecurity';
15
16
 
16
17
  /**
17
18
  * Secure Supabase Client that enforces organisation context
@@ -34,6 +35,10 @@ export class SecureSupabaseClient {
34
35
  private appId?: UUID;
35
36
  private isSuperAdmin: boolean;
36
37
  private usesExistingClient: boolean = false;
38
+
39
+ // Cache for RPC function signatures to avoid repeated database queries
40
+ // Maps function name -> Set of parameter names it accepts
41
+ private static rpcSignatureCache = new Map<string, Set<string>>();
37
42
 
38
43
  /**
39
44
  * RPC functions that are safe to call without organisation context.
@@ -91,6 +96,9 @@ export class SecureSupabaseClient {
91
96
  // Override functions.invoke to exclude custom headers for Edge Functions
92
97
  // Edge Functions may not have CORS configured to accept custom headers
93
98
  this.setupEdgeFunctionHandling();
99
+
100
+ // Mark the client as secure
101
+ markClientAsSecure(this.supabase);
94
102
  }
95
103
 
96
104
  /**
@@ -125,22 +133,42 @@ export class SecureSupabaseClient {
125
133
  // Some RPCs are global (not organisation-scoped) but still require auth.uid() from JWT.
126
134
  // Allow these even without organisation context.
127
135
  this.validateContextForRpc(fn);
136
+
137
+ // SYSTEMIC FIX: Use opt-in whitelist approach instead of brittle blacklist.
138
+ // PostgREST matches RPCs by *exact* parameter signature; sending unexpected params results in:
139
+ // - HTTP 404 + PGRST202 "Could not find the function ... with parameters ..."
140
+ //
141
+ // By default, we don't inject context unless the function is explicitly whitelisted.
142
+ // This prevents PGRST202 errors and makes the system more maintainable.
143
+ const acceptedParams = this.getRpcAcceptedParams(fn);
128
144
 
129
- // Inject context into RPC calls
130
- // Only include organisation_id if it's available (super-admins may not have it)
131
- // IMPORTANT:
132
- // Do NOT overwrite explicitly provided RPC parameters.
145
+ // Only inject context parameters that:
146
+ // 1. The function accepts (according to our whitelist)
147
+ // 2. Are not already explicitly provided
148
+ // 3. We have values for
149
+ // IMPORTANT: Do NOT overwrite explicitly provided RPC parameters.
133
150
  // Some RPCs legitimately take `p_app_id`/`p_event_id` as the *target* entity,
134
151
  // which may differ from the current RBAC scope app/event.
135
152
  const safeArgs = (args ?? {}) as Record<string, unknown>;
136
- const contextArgs = {
137
- ...safeArgs,
138
- ...(this.organisationId && safeArgs.p_organisation_id === undefined
139
- ? { p_organisation_id: this.organisationId }
140
- : {}),
141
- ...(this.eventId && safeArgs.p_event_id === undefined ? { p_event_id: this.eventId } : {}),
142
- ...(this.appId && safeArgs.p_app_id === undefined ? { p_app_id: this.appId } : {}),
143
- };
153
+ const contextArgs: Record<string, unknown> = { ...safeArgs };
154
+
155
+ if (acceptedParams.has('p_organisation_id') &&
156
+ this.organisationId &&
157
+ safeArgs.p_organisation_id === undefined) {
158
+ contextArgs.p_organisation_id = this.organisationId;
159
+ }
160
+
161
+ if (acceptedParams.has('p_event_id') &&
162
+ this.eventId &&
163
+ safeArgs.p_event_id === undefined) {
164
+ contextArgs.p_event_id = this.eventId;
165
+ }
166
+
167
+ if (acceptedParams.has('p_app_id') &&
168
+ this.appId &&
169
+ safeArgs.p_app_id === undefined) {
170
+ contextArgs.p_app_id = this.appId;
171
+ }
144
172
 
145
173
  return originalRpc(fn as any, contextArgs, options);
146
174
  };
@@ -193,6 +221,8 @@ export class SecureSupabaseClient {
193
221
  // Override select to add organisation filter
194
222
  query.select = (columns?: string) => {
195
223
  const result = originalSelect(columns);
224
+ // Store table name on query object so we can access it in filter methods
225
+ (result as any)._tableName = tableName;
196
226
  return this.addOrganisationFilter(result, tableName);
197
227
  };
198
228
 
@@ -326,12 +356,15 @@ export class SecureSupabaseClient {
326
356
  // For rbac_user_profiles, use conditional filtering based on super admin status
327
357
  if (tableName === 'rbac_user_profiles') {
328
358
  // Super admins: No org filter (see all users via RLS)
329
- // Non-super-admins: Apply org filter as defense in depth (RLS also filters)
330
359
  if (this.isSuperAdmin) {
331
360
  return query; // No filter - RLS handles access control
332
361
  }
333
- // Apply org filter for non-super-admins as additional security layer
334
- return query.eq('organisation_id', this.organisationId);
362
+
363
+ // For non-super-admins: Apply org filter, but allow NULL organisation_id
364
+ // User profiles can have organisation_id = NULL (users not yet assigned to an org)
365
+ // We use .or() to allow either matching organisation_id OR NULL
366
+ // RLS policies will still enforce access control
367
+ return query.or(`organisation_id.eq.${this.organisationId},organisation_id.is.null`);
335
368
  }
336
369
 
337
370
  // For all other tables, apply organisation filter
@@ -455,7 +488,7 @@ export class SecureSupabaseClient {
455
488
  getClient(): SupabaseClient<Database> {
456
489
  // Return a proxy that intercepts functions.invoke calls to use edge function client
457
490
  // This avoids CORS issues with Edge Functions while keeping the main client intact
458
- return new Proxy(this.supabase, {
491
+ const proxiedClient = new Proxy(this.supabase, {
459
492
  get: (target, prop) => {
460
493
  if (prop === 'functions' && this.edgeFunctionClient) {
461
494
  // Return the edge function client's functions for invoke calls
@@ -466,6 +499,60 @@ export class SecureSupabaseClient {
466
499
  return (target as any)[prop];
467
500
  }
468
501
  }) as SupabaseClient<Database>;
502
+
503
+ // Mark the proxied client as secure
504
+ markClientAsSecure(proxiedClient);
505
+
506
+ return proxiedClient;
507
+ }
508
+
509
+ /**
510
+ * Get the set of parameter names that an RPC function accepts.
511
+ * Uses a static whitelist of RPCs that we know accept context parameters.
512
+ *
513
+ * This is an opt-in approach: by default, we don't inject context unless
514
+ * the function is explicitly whitelisted. This prevents PGRST202 errors from
515
+ * injecting unexpected parameters.
516
+ *
517
+ * @param fn - The RPC function name
518
+ * @returns Set of parameter names the function accepts
519
+ */
520
+ private getRpcAcceptedParams(fn: string): Set<string> {
521
+ // Check cache first
522
+ if (SecureSupabaseClient.rpcSignatureCache.has(fn)) {
523
+ return SecureSupabaseClient.rpcSignatureCache.get(fn)!;
524
+ }
525
+
526
+ // Whitelist of RPCs that accept context parameters
527
+ // Format: function name -> Set of parameters it accepts
528
+ //
529
+ // SYSTEMIC FIX: This is an opt-in approach. By default, we don't inject context
530
+ // unless the function is explicitly whitelisted. This prevents PGRST202 errors
531
+ // from injecting unexpected parameters.
532
+ //
533
+ // To add a new RPC:
534
+ // 1. Check the function signature in the database:
535
+ // SELECT pg_get_function_identity_arguments(oid) FROM pg_proc WHERE proname = 'function_name';
536
+ // 2. Add it here with the parameters it accepts
537
+ const rpcContextWhitelist: Record<string, Set<string>> = {
538
+ // RPCs that accept all three context parameters
539
+ 'rbac_roles_list': new Set(['p_organisation_id', 'p_event_id', 'p_app_id']),
540
+
541
+ // RPCs that accept only p_organisation_id (not p_app_id or p_event_id)
542
+ 'data_file_reference_by_category_list': new Set(['p_organisation_id']),
543
+
544
+ // Add more RPCs here as we discover them
545
+ // Format: 'function_name': new Set(['p_organisation_id', 'p_event_id', 'p_app_id']),
546
+ };
547
+
548
+ // Default: empty set (no context injection) unless whitelisted
549
+ // This is the safe default - prevents PGRST202 errors
550
+ const acceptedParams = rpcContextWhitelist[fn] || new Set<string>();
551
+
552
+ // Cache the result to avoid repeated lookups
553
+ SecureSupabaseClient.rpcSignatureCache.set(fn, acceptedParams);
554
+
555
+ return acceptedParams;
469
556
  }
470
557
  }
471
558
 
@@ -9,8 +9,6 @@
9
9
 
10
10
  import { UUID, Permission, Scope } from './types';
11
11
  import { createLogger } from '../utils/core/logger';
12
- import { ContextValidator } from './utils/contextValidator';
13
- import type { AppConfig } from './utils/contextValidator';
14
12
 
15
13
  const log = createLogger('RBACSecurity');
16
14
 
@@ -161,21 +159,6 @@ export class RBACSecurityValidator {
161
159
  return true;
162
160
  }
163
161
 
164
- /**
165
- * Validate context requirements for security
166
- * @param scope - Scope object
167
- * @param appConfig - App configuration
168
- * @param appName - App name (for PORTAL special case)
169
- * @returns True if context is valid, false otherwise
170
- */
171
- static async validateContextRequirements(
172
- scope: Scope,
173
- appConfig?: AppConfig | null,
174
- appName?: string
175
- ): Promise<boolean> {
176
- const validation = await ContextValidator.validateScope(scope, appConfig || null, appName);
177
- return validation.isValid;
178
- }
179
162
 
180
163
  /**
181
164
  * Log security event for monitoring
package/src/rbac/types.ts CHANGED
@@ -126,6 +126,7 @@ export interface RBACAppPage {
126
126
  created_by: UUID | null;
127
127
  updated_by: UUID | null;
128
128
  app_id: UUID;
129
+ scope_type: 'event' | 'organisation' | 'both'; // Required - single source of truth for page scoping
129
130
  }
130
131
 
131
132
  export interface RBACApp {