@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
@@ -23,6 +23,8 @@ type AuthStateSubscription = {
23
23
  };
24
24
 
25
25
  export class AuthService extends BaseService implements IAuthService {
26
+ private static instanceCount = 0;
27
+ private instanceId: number;
26
28
  private user: User | null = null;
27
29
  private session: Session | null = null;
28
30
  private authLoading = false;
@@ -43,12 +45,23 @@ export class AuthService extends BaseService implements IAuthService {
43
45
 
44
46
  constructor(supabaseClient: SupabaseClient, appName?: string) {
45
47
  super();
48
+ this.instanceId = ++AuthService.instanceCount;
46
49
  this.supabaseClient = supabaseClient;
47
50
  this.appName = appName;
51
+ logger.debug('AuthService', `Instance created [ID:${this.instanceId}]`, { appName });
52
+ }
53
+
54
+ getInstanceId(): number {
55
+ return this.instanceId;
48
56
  }
49
57
 
50
58
  // Auth state getters
51
59
  getUser(): User | null {
60
+ if (this.user) {
61
+ logger.debug('AuthService', `getUser() [ID:${this.instanceId}] returning user: ${this.user.id}`);
62
+ } else {
63
+ logger.debug('AuthService', `getUser() [ID:${this.instanceId}] returning null`);
64
+ }
52
65
  return this.user;
53
66
  }
54
67
 
@@ -398,6 +411,11 @@ export class AuthService extends BaseService implements IAuthService {
398
411
  const subscription = this.supabaseClient.auth.onAuthStateChange(
399
412
  (event: AuthChangeEvent, session: SupabaseSession | null) => {
400
413
  try {
414
+ logger.debug('AuthService', `Auth state change [ID:${this.instanceId}]`, {
415
+ event,
416
+ hasSession: !!session,
417
+ userId: session?.user?.id
418
+ });
401
419
  // Handle different auth events
402
420
  if (event === 'SIGNED_OUT') {
403
421
  this.session = null;
@@ -407,15 +425,15 @@ export class AuthService extends BaseService implements IAuthService {
407
425
  // Automatic session tracking (non-blocking)
408
426
  if (session?.user) {
409
427
  this.trackSession('logout', session).catch(err => {
410
- logger.warn('AuthService', 'Failed to track logout session:', err);
428
+ logger.warn('AuthService', `Failed to track logout session [ID:${this.instanceId}]:`, err);
411
429
  });
412
430
  }
413
431
  } else if (event === 'SIGNED_IN' || event === 'TOKEN_REFRESHED') {
414
432
  this.session = session;
415
433
  this.user = session?.user ?? null;
416
434
 
417
- // Only clear auth error if we have a valid session
418
- if (session) {
435
+ // Ensure state is set before non-blocking tracking calls
436
+ if (session?.user) {
419
437
  this.authError = null;
420
438
  }
421
439
 
@@ -423,7 +441,7 @@ export class AuthService extends BaseService implements IAuthService {
423
441
  // Only track on SIGNED_IN, not TOKEN_REFRESHED (to avoid duplicate login records)
424
442
  if (event === 'SIGNED_IN' && session?.user) {
425
443
  this.trackSession('login', session).catch(err => {
426
- logger.warn('AuthService', 'Failed to track login session:', err);
444
+ logger.warn('AuthService', `Failed to track login session [ID:${this.instanceId}]:`, err);
427
445
  });
428
446
  }
429
447
  } else if (event === 'INITIAL_SESSION') {
@@ -452,16 +470,30 @@ export class AuthService extends BaseService implements IAuthService {
452
470
  // This ensures ProtectedRoute waits for session restoration to complete
453
471
  // before checking authentication state
454
472
  this.authLoading = false;
473
+
474
+ // Final check: Ensure state matches what we just logged
475
+ logger.debug('AuthService', `State synchronized after INITIAL_SESSION [ID:${this.instanceId}]`, {
476
+ hasUser: !!this.user,
477
+ userId: this.user?.id
478
+ });
479
+
455
480
  this.notify();
456
481
  return; // Return early to avoid setting loading to false again below
457
482
  }
458
483
 
459
- // For other events (SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED), set loading to false
460
- // INITIAL_SESSION is handled above and returns early
484
+ // For other events (SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED), set loading to false and notify
461
485
  this.authLoading = false;
486
+
487
+ // Final check: Ensure state matches what we just logged
488
+ logger.debug('AuthService', `State synchronized after event [ID:${this.instanceId}]`, {
489
+ event,
490
+ hasUser: !!this.user,
491
+ userId: this.user?.id
492
+ });
493
+
462
494
  this.notify();
463
495
  } catch (error) {
464
- logger.warn('AuthService', 'Error in auth state change handler:', error);
496
+ logger.warn('AuthService', `Error in auth state change handler [ID:${this.instanceId}]:`, error);
465
497
  this.authLoading = false;
466
498
  this.notify();
467
499
  }
@@ -16,11 +16,12 @@ import { Organisation } from '../types/organisation';
16
16
  import { assertOrganisationId } from '../types/core';
17
17
  import { logger } from '../utils/core/logger';
18
18
  import { secureStorage } from '../utils/security/secureStorage';
19
- import { isSuperAdmin, getAppConfigByName } from '../rbac/api';
19
+ import { isSuperAdmin } from '../rbac/api';
20
20
  import type { UUID } from '../rbac/types';
21
- import type { AppConfig } from '../rbac/utils/contextValidator';
22
21
 
23
22
  export class EventService extends BaseService implements IEventService {
23
+ private static instanceCount = 0;
24
+ private instanceId: number;
24
25
  private events: Event[] = [];
25
26
  private selectedEvent: Event | null = null;
26
27
  private _isLoading = false; // Start as false to avoid blocking UI
@@ -34,7 +35,7 @@ export class EventService extends BaseService implements IEventService {
34
35
  private selectedOrganisation: Organisation | null = null;
35
36
  private setSelectedEventId: ((eventId: string | null) => void) | null = null;
36
37
  private isSuperAdmin: boolean = false; // Track super admin status for conditional validation
37
- private appConfig: AppConfig | null = null; // Cache app config to avoid repeated lookups
38
+ // App config removed - scope is now page-level only (rbac_app_pages.scope_type)
38
39
 
39
40
  // Internal state management
40
41
  private isInitializedRef = false;
@@ -51,12 +52,22 @@ export class EventService extends BaseService implements IEventService {
51
52
  setSelectedEventId: (eventId: string | null) => void
52
53
  ) {
53
54
  super();
55
+ this.instanceId = ++EventService.instanceCount;
54
56
  this.supabaseClient = supabaseClient;
55
57
  this.user = user;
56
58
  this.session = session;
57
59
  this.appName = appName;
58
60
  this.selectedOrganisation = selectedOrganisation;
59
61
  this.setSelectedEventId = setSelectedEventId;
62
+ logger.debug('EventService', `Instance created [ID:${this.instanceId}]`, {
63
+ appName,
64
+ hasUser: !!user,
65
+ userId: user?.id
66
+ });
67
+ }
68
+
69
+ getInstanceId(): number {
70
+ return this.instanceId;
60
71
  }
61
72
 
62
73
  // Helper method to get user-scoped storage key
@@ -97,6 +108,12 @@ export class EventService extends BaseService implements IEventService {
97
108
  this.resetInitialization();
98
109
  this.isInitializedRef = false;
99
110
  this.isFetchingRef = false;
111
+
112
+ logger.debug('EventService', `User changed [ID:${this.instanceId}]`, {
113
+ previousUserId,
114
+ newUserId,
115
+ willInitialize: newUserId !== null
116
+ });
100
117
  }
101
118
 
102
119
  this.supabaseClient = supabaseClient;
@@ -106,19 +123,43 @@ export class EventService extends BaseService implements IEventService {
106
123
  this.selectedOrganisation = selectedOrganisation;
107
124
  this.setSelectedEventId = setSelectedEventId;
108
125
 
109
- // Clear app config cache when app name changes
110
- if (previousAppName !== appName) {
111
- this.appConfig = null;
112
- }
126
+ // App name changed - state will be reset by updateDependencies
113
127
 
114
128
  // Update super admin status when user changes
115
129
  // This allows super admins to select events from any organisation
130
+ // RBAC should be initialized synchronously by UnifiedAuthProvider, so this should always work
116
131
  if (user?.id) {
117
132
  try {
118
- this.isSuperAdmin = await isSuperAdmin(user.id as UUID);
133
+ const { isRBACInitialized, isSuperAdmin: checkSuperAdmin, setupRBAC } = await import('../rbac/api');
134
+
135
+ // Ensure RBAC is initialized if possible (defensive check for edge cases)
136
+ if (!isRBACInitialized() && this.supabaseClient) {
137
+ setupRBAC(this.supabaseClient);
138
+ }
139
+
140
+ if (isRBACInitialized()) {
141
+ this.isSuperAdmin = await checkSuperAdmin(user.id as UUID);
142
+ logger.debug('EventService', 'Super admin status updated in updateDependencies', {
143
+ userId: user.id,
144
+ isSuperAdmin: this.isSuperAdmin
145
+ });
146
+ } else {
147
+ // RBAC not initialized - this should be rare since UnifiedAuthProvider initializes it synchronously
148
+ // Keep existing value (don't reset to false) to avoid clearing a previously determined super admin status
149
+ logger.warn('EventService', 'RBAC not initialized in updateDependencies, keeping existing super admin status', {
150
+ userId: user.id,
151
+ existingIsSuperAdmin: this.isSuperAdmin,
152
+ note: 'RBAC should be initialized by UnifiedAuthProvider. This may indicate EventService is being used outside the provider.'
153
+ });
154
+ }
119
155
  } catch (error) {
120
- logger.warn('EventService', 'Failed to check super admin status', { error });
121
- this.isSuperAdmin = false; // Default to false on error
156
+ logger.warn('EventService', 'Failed to check super admin status in updateDependencies', {
157
+ error,
158
+ userId: user.id,
159
+ existingIsSuperAdmin: this.isSuperAdmin
160
+ });
161
+ // Don't reset to false on error - keep existing value to avoid blocking super admins
162
+ // The error might be transient, and we don't want to clear a valid super admin status
122
163
  }
123
164
  } else {
124
165
  this.isSuperAdmin = false;
@@ -131,14 +172,26 @@ export class EventService extends BaseService implements IEventService {
131
172
  this.resetInitialization(); // Reset BaseService's isInitialized flag
132
173
  this.isInitializedRef = false;
133
174
  this.isFetchingRef = false;
175
+
176
+ // SECURITY: Super admins can see events regardless of organisation context
177
+ // Do not clear events for super admins when organisation context is removed
178
+ const shouldClearEvents = !this.isSuperAdmin;
179
+
134
180
  // Clear events ONLY when switching between different organisations (not when org first becomes available)
135
181
  if (previousOrgId !== null && newOrgId !== null && previousOrgId !== newOrgId) {
136
- this.events = [];
137
- this.selectedEvent = null;
182
+ if (shouldClearEvents) {
183
+ this.events = [];
184
+ // Use setSelectedEvent(null) to preserve userClearedEventRef flag if user explicitly cleared
185
+ // This prevents auto-selection from re-selecting the event after org switch
186
+ this.setSelectedEvent(null);
187
+ }
138
188
  } else if (previousOrgId !== null && newOrgId === null) {
139
- // Organisation was removed - clear events
140
- this.events = [];
141
- this.selectedEvent = null;
189
+ // Organisation was removed - clear events if not super admin
190
+ if (shouldClearEvents) {
191
+ this.events = [];
192
+ // Use setSelectedEvent(null) to preserve userClearedEventRef flag if user explicitly cleared
193
+ this.setSelectedEvent(null);
194
+ }
142
195
  }
143
196
  }
144
197
 
@@ -220,7 +273,7 @@ export class EventService extends BaseService implements IEventService {
220
273
  const persistedEvent = events.find(event => event.event_id === persistedEventId);
221
274
 
222
275
  if (persistedEvent) {
223
- // Use setSelectedEvent() to go through same path as EventSelector
276
+ // Use setSelectedEvent() to ensure consistent behavior
224
277
  // This ensures consistent behavior and proper notification
225
278
  // Theme will be applied by useEventTheme hook once user navigates away from login
226
279
  this.setSelectedEvent(persistedEvent);
@@ -315,11 +368,13 @@ export class EventService extends BaseService implements IEventService {
315
368
  protected async doInitialize(): Promise<void> {
316
369
  // Skip if already initialized
317
370
  if (this.isInitializedRef) {
371
+ logger.debug('EventService', 'Skipping initialization - already initialized');
318
372
  return;
319
373
  }
320
374
 
321
375
  // Skip if already fetching
322
376
  if (this.isFetchingRef) {
377
+ logger.debug('EventService', 'Skipping initialization - already fetching');
323
378
  return;
324
379
  }
325
380
 
@@ -336,14 +391,27 @@ export class EventService extends BaseService implements IEventService {
336
391
  // For event-required apps, selectedOrganisation may be null (org derived from event)
337
392
  // For org-required apps, selectedOrganisation is required
338
393
  if (!this.user) {
394
+ logger.debug('EventService', 'Skipping initialization - no user');
339
395
  return;
340
396
  }
341
397
 
398
+ logger.debug('EventService', 'Initializing', {
399
+ userId: this.user.id,
400
+ appName: this.appName,
401
+ hasSelectedOrganisation: !!this.selectedOrganisation,
402
+ hasSupabaseClient: !!this.supabaseClient,
403
+ hasSession: !!this.session
404
+ });
405
+
342
406
  // Initial setup - fetch events on initialization
343
407
  await this.fetchEvents(false);
344
408
 
345
409
  // Mark as initialized after successful fetch
346
410
  this.isInitializedRef = true;
411
+ logger.debug('EventService', 'Initialization complete', {
412
+ eventsCount: this.events.length,
413
+ hasError: !!this.error
414
+ });
347
415
  }
348
416
 
349
417
  protected doCleanup(): void {
@@ -372,18 +440,7 @@ export class EventService extends BaseService implements IEventService {
372
440
  let isMounted = true;
373
441
 
374
442
  try {
375
- // Load app config if not already cached (only once per app)
376
- if (!this.appConfig && this.appName) {
377
- try {
378
- this.appConfig = await getAppConfigByName(this.appName);
379
- } catch (configError) {
380
- logger.warn('EventService', 'Failed to load app config, defaulting to event-required', {
381
- error: configError
382
- });
383
- // Default to event-required for safety
384
- this.appConfig = { requires_event: true };
385
- }
386
- }
443
+ // Scope is now page-level only - no app-level config needed
387
444
 
388
445
  // Determine organisationId for RPC call
389
446
  // For event-required apps: org is derived from selectedEvent (if available), or null to get all accessible events
@@ -392,58 +449,119 @@ export class EventService extends BaseService implements IEventService {
392
449
  let organisationIdForRpc: string | null = null;
393
450
 
394
451
  // Check if user is super admin first
395
- let userIsSuperAdmin = false;
452
+ // RBAC should be initialized synchronously by UnifiedAuthProvider before EventService is used
453
+ // Use cached value as fallback for edge cases (e.g., EventService used outside UnifiedAuthProvider)
454
+ let userIsSuperAdmin = this.isSuperAdmin; // Start with cached value from updateDependencies
396
455
  try {
397
- userIsSuperAdmin = await isSuperAdmin(this.user.id as UUID);
456
+ const { isRBACInitialized, isSuperAdmin: checkSuperAdmin, setupRBAC } = await import('../rbac/api');
457
+
458
+ // Ensure RBAC is initialized if possible (defensive check for edge cases)
459
+ if (!isRBACInitialized() && this.supabaseClient) {
460
+ setupRBAC(this.supabaseClient);
461
+ }
462
+
463
+ // Check super admin status if RBAC is ready
464
+ // RBAC should always be initialized by UnifiedAuthProvider, but we check defensively
465
+ if (isRBACInitialized()) {
466
+ userIsSuperAdmin = await checkSuperAdmin(this.user.id as UUID);
467
+ // Update cached value for future use
468
+ this.isSuperAdmin = userIsSuperAdmin;
469
+ logger.debug('EventService', 'Super admin check completed', {
470
+ userId: this.user.id,
471
+ isSuperAdmin: userIsSuperAdmin
472
+ });
473
+ } else {
474
+ // RBAC not initialized - this should be rare since UnifiedAuthProvider initializes it synchronously
475
+ // Use cached value from updateDependencies as fallback
476
+ // If cached value is true, trust it to avoid blocking super admins
477
+ if (this.isSuperAdmin) {
478
+ userIsSuperAdmin = true;
479
+ logger.warn('EventService', 'RBAC not initialized, using cached super admin status', {
480
+ userId: this.user.id,
481
+ cachedIsSuperAdmin: this.isSuperAdmin,
482
+ note: 'RBAC should be initialized by UnifiedAuthProvider. This may indicate EventService is being used outside the provider.'
483
+ });
484
+ } else {
485
+ logger.warn('EventService', 'RBAC not initialized, using cached non-super-admin status', {
486
+ userId: this.user.id,
487
+ cachedIsSuperAdmin: this.isSuperAdmin,
488
+ note: 'RBAC should be initialized by UnifiedAuthProvider. This may indicate EventService is being used outside the provider.'
489
+ });
490
+ }
491
+ }
492
+
398
493
  if (userIsSuperAdmin) {
399
494
  // Super admin: Pass null to see all events across all organisations
400
495
  organisationIdForRpc = null;
401
496
  } else {
402
- // Not super admin: determine org from context based on app type
497
+ // Not super admin: determine org from available context
498
+ // Scope is now page-level only - use whatever context is available
403
499
  if (this.selectedEvent) {
404
500
  // If event is already selected, use its organisation
405
501
  organisationIdForRpc = this.selectedEvent.organisation_id;
406
- } else if (this.appConfig?.requires_event === true) {
407
- // Event-required app with no selected event yet: pass null to get all accessible events
408
- // The RPC will filter by event app roles, returning all events the user has access to
409
- organisationIdForRpc = null;
410
502
  } else if (this.selectedOrganisation) {
411
- // Org-required app: use selected organisation
503
+ // Use selected organisation
412
504
  organisationIdForRpc = this.selectedOrganisation.id;
413
505
  } else {
414
- // No context available - this shouldn't happen for authenticated users
415
- logger.warn('EventService', 'No organisation context available for event fetch', {
506
+ // No context available - pass null to get all accessible events via event-app roles
507
+ // This allows users with event-app roles to see their events even without org context
508
+ logger.debug('EventService', 'No organisation context available, fetching all accessible events', {
416
509
  hasSelectedEvent: !!this.selectedEvent,
417
- hasSelectedOrganisation: !!this.selectedOrganisation,
418
- appRequiresEvent: this.appConfig?.requires_event
510
+ hasSelectedOrganisation: !!this.selectedOrganisation
419
511
  });
420
- organisationIdForRpc = null; // Will return empty list
512
+ organisationIdForRpc = null; // Will return events user has access to via event-app roles
421
513
  }
422
514
  }
423
515
  } catch (superAdminCheckError) {
424
- // If super admin check fails, fall back to organisation-scoped query
425
- logger.warn('EventService', 'Failed to check super admin status, using organisation-scoped query', {
426
- error: superAdminCheckError
427
- });
428
- // Fallback: use available context
429
- if (this.selectedEvent) {
430
- organisationIdForRpc = this.selectedEvent.organisation_id;
431
- } else if (this.appConfig?.requires_event === true) {
432
- // Event-required app: pass null to get all accessible events
433
- organisationIdForRpc = null;
434
- } else if (this.selectedOrganisation) {
435
- organisationIdForRpc = this.selectedOrganisation.id;
516
+ // If super admin check fails, use cached value as fallback
517
+ // If cached value is true, trust it to avoid blocking super admins
518
+ if (this.isSuperAdmin) {
519
+ userIsSuperAdmin = true;
520
+ organisationIdForRpc = null; // Super admin gets all events
521
+ logger.warn('EventService', 'Super admin check failed, using cached super admin status', {
522
+ error: superAdminCheckError,
523
+ cachedIsSuperAdmin: this.isSuperAdmin
524
+ });
525
+ } else {
526
+ // Fallback: use available context
527
+ logger.warn('EventService', 'Failed to check super admin status, using organisation-scoped query', {
528
+ error: superAdminCheckError,
529
+ cachedIsSuperAdmin: this.isSuperAdmin
530
+ });
531
+ if (this.selectedEvent) {
532
+ organisationIdForRpc = this.selectedEvent.organisation_id;
533
+ } else if (this.selectedOrganisation) {
534
+ organisationIdForRpc = this.selectedOrganisation.id;
535
+ } else {
536
+ // No context - pass null to get all accessible events via event-app roles
537
+ organisationIdForRpc = null;
538
+ }
436
539
  }
437
540
  }
438
541
 
439
542
  // Call the RPC function following the established pattern
440
543
  // For super admins, pass null for p_organisation_id to see all events
544
+ logger.debug('EventService', 'Fetching events', {
545
+ userId: this.user.id,
546
+ organisationIdForRpc,
547
+ appName: this.appName,
548
+ hasSelectedEvent: !!this.selectedEvent,
549
+ hasSelectedOrganisation: !!this.selectedOrganisation,
550
+ isSuperAdmin: userIsSuperAdmin
551
+ });
552
+
441
553
  let { data, error: rpcError } = await this.supabaseClient.rpc('data_user_events_get', {
442
554
  p_user_id: this.user.id,
443
555
  p_organisation_id: organisationIdForRpc,
444
556
  p_app_name: this.appName
445
557
  });
446
558
 
559
+ logger.debug('EventService', 'RPC response', {
560
+ dataLength: data?.length || 0,
561
+ hasError: !!rpcError,
562
+ error: rpcError
563
+ });
564
+
447
565
  if (rpcError) {
448
566
  logger.error('EventService', 'RPC error fetching events:', rpcError);
449
567
  throw new Error(rpcError.message || 'Failed to fetch events');
@@ -482,7 +600,21 @@ export class EventService extends BaseService implements IEventService {
482
600
  updated_at: new Date().toISOString()
483
601
  }));
484
602
 
485
- this.events = transformedEvents;
603
+ // Sort events by event_date descending (newest first)
604
+ // Handle null dates by putting them at the end
605
+ const sortedEvents = [...transformedEvents].sort((a, b) => {
606
+ // If both have dates, sort descending (newest first)
607
+ if (a.event_date && b.event_date) {
608
+ return new Date(b.event_date).getTime() - new Date(a.event_date).getTime();
609
+ }
610
+ // If only one has a date, prioritize the one with a date
611
+ if (a.event_date && !b.event_date) return -1;
612
+ if (!a.event_date && b.event_date) return 1;
613
+ // If neither has a date, maintain original order
614
+ return 0;
615
+ });
616
+
617
+ this.events = sortedEvents;
486
618
  this.error = null;
487
619
 
488
620
  // Reset auto-selection ref for new events
@@ -20,7 +20,6 @@ import type {
20
20
  import { setOrganisationContext } from '../utils/context/organisationContext';
21
21
  import { logger } from '../utils/core/logger';
22
22
  import { assertUserId, assertOrganisationId } from '../types/core';
23
- import { isSuperAdmin } from '../rbac/api';
24
23
  import type { UUID } from '../rbac/types';
25
24
 
26
25
  // Type for RPC response from data_user_organisation_roles_get
@@ -42,6 +41,8 @@ interface OrganisationRoleRpcResponse {
42
41
  }
43
42
 
44
43
  export class OrganisationService extends BaseService implements IOrganisationService {
44
+ private static instanceCount = 0;
45
+ private instanceId: number;
45
46
  private _selectedOrganisation: Organisation | null = null;
46
47
  private _organisations: Organisation[] = [];
47
48
  private _userMemberships: OrganisationMembership[] = [];
@@ -65,9 +66,18 @@ export class OrganisationService extends BaseService implements IOrganisationSer
65
66
 
66
67
  constructor(supabaseClient: SupabaseClient, user: User | null, session: Session | null) {
67
68
  super();
69
+ this.instanceId = ++OrganisationService.instanceCount;
68
70
  this.supabaseClient = supabaseClient;
69
71
  this.user = user;
70
72
  this.session = session;
73
+ logger.debug('OrganisationService', `Instance created [ID:${this.instanceId}]`, {
74
+ hasUser: !!user,
75
+ userId: user?.id
76
+ });
77
+ }
78
+
79
+ getInstanceId(): number {
80
+ return this.instanceId;
71
81
  }
72
82
 
73
83
  // Interface implementation
@@ -144,23 +154,43 @@ export class OrganisationService extends BaseService implements IOrganisationSer
144
154
 
145
155
  // Update dependencies
146
156
  updateDependencies(user: User | null, session: Session | null): void {
147
- const wasAuthenticated = !!(this.user && this.session);
148
- const isAuthenticated = !!(user && session);
157
+ const previousUserId = this.user?.id || null;
158
+ const newUserId = user?.id || null;
149
159
 
150
- // Reset super admin cache when user changes
151
- if (this.user?.id !== user?.id) {
160
+ // Only reset if the User ID has actually changed (null -> ID or ID -> different ID)
161
+ const userChanged = previousUserId !== newUserId;
162
+ const needsRetry = this._error !== null && !this.isLoadingRef;
163
+ const isEmpty = newUserId !== null && this._organisations.length === 0 && !this.isLoadingRef;
164
+
165
+ if (userChanged || needsRetry || isEmpty) {
166
+ if (userChanged) {
167
+ logger.debug('OrganisationService', `User changed [ID:${this.instanceId}], resetting initialization`, {
168
+ previousUserId,
169
+ newUserId
170
+ });
171
+ } else if (needsRetry) {
172
+ logger.debug('OrganisationService', `Previous error detected [ID:${this.instanceId}], retrying initialization`);
173
+ } else if (isEmpty) {
174
+ logger.debug('OrganisationService', `No organisations found [ID:${this.instanceId}], retrying initialization`);
175
+ }
176
+
152
177
  this._isSuperAdmin = false;
178
+ this.resetInitialization();
179
+
180
+ // Only clear all state if the user actually changed
181
+ if (userChanged) {
182
+ this._organisations = [];
183
+ this._userMemberships = [];
184
+ this._roleMapState = new Map();
185
+ this._selectedOrganisation = null;
186
+ this._isContextReady = false;
187
+ }
188
+ this.lastLoadTimeRef = 0; // Allow immediate reload
153
189
  }
154
190
 
155
191
  this.user = user;
156
192
  this.session = session;
157
193
 
158
- // If user logs out, allow re-initialization when they log back in
159
- if (wasAuthenticated && !isAuthenticated) {
160
- // Reset BaseService initialization state to allow re-initialization
161
- this.resetInitialization();
162
- }
163
-
164
194
  this.notify();
165
195
  }
166
196
 
@@ -260,6 +290,13 @@ export class OrganisationService extends BaseService implements IOrganisationSer
260
290
 
261
291
  // Lifecycle methods
262
292
  async initialize(): Promise<void> {
293
+ // SECURITY: Only initialize if we have an authenticated user
294
+ // This prevents premature initialization during early auth states
295
+ if (!this.user) {
296
+ logger.debug('OrganisationService', 'Skipping initialization - no user');
297
+ return;
298
+ }
299
+
263
300
  await super.initialize();
264
301
 
265
302
  // Don't load if already loading (prevents duplicate loads during rapid auth events)
@@ -351,8 +388,9 @@ export class OrganisationService extends BaseService implements IOrganisationSer
351
388
  }
352
389
 
353
390
  // Prevent rapid retries - minimum 2 seconds between attempts
391
+ // Skip this check if we don't have any organisations yet
354
392
  const now = Date.now();
355
- if (now - this.lastLoadTimeRef < 2000) {
393
+ if (this._organisations.length > 0 && now - this.lastLoadTimeRef < 2000) {
356
394
  // Ensure loading state is correct
357
395
  if (this._organisations.length > 0 || this._selectedOrganisation) {
358
396
  this._isLoading = false;
@@ -377,6 +415,10 @@ export class OrganisationService extends BaseService implements IOrganisationSer
377
415
  this._isLoading = true;
378
416
  this._error = null;
379
417
  this.notify();
418
+
419
+ logger.debug('OrganisationService', 'Loading organisations for user', {
420
+ userId: this.user.id
421
+ });
380
422
 
381
423
  try {
382
424
  // Get user's organisation roles directly from rbac_organisation_roles table
@@ -458,6 +500,11 @@ export class OrganisationService extends BaseService implements IOrganisationSer
458
500
 
459
501
  organisations = Array.from(organisationsMap.values());
460
502
 
503
+ logger.debug('OrganisationService', 'Query results', {
504
+ membershipsCount: memberships.length,
505
+ organisationsCount: organisations.length
506
+ });
507
+
461
508
  // Extract organisations from join results
462
509
  } catch (queryError) {
463
510
  // Extract error message properly from Supabase error objects
@@ -476,7 +523,20 @@ export class OrganisationService extends BaseService implements IOrganisationSer
476
523
  let userIsSuperAdmin = false;
477
524
  if (this.user?.id) {
478
525
  try {
479
- userIsSuperAdmin = await isSuperAdmin(this.user.id as UUID);
526
+ // Dynamic import to avoid circular dependencies and ensure RBAC is initialized
527
+ const { isSuperAdmin: checkSuperAdmin, isRBACInitialized, setupRBAC } = await import('../rbac/api');
528
+
529
+ // Ensure RBAC is initialized if possible
530
+ if (!isRBACInitialized() && this.supabaseClient) {
531
+ setupRBAC(this.supabaseClient);
532
+ }
533
+
534
+ if (isRBACInitialized()) {
535
+ userIsSuperAdmin = await checkSuperAdmin(this.user.id as UUID);
536
+ } else {
537
+ userIsSuperAdmin = false;
538
+ }
539
+
480
540
  this._isSuperAdmin = userIsSuperAdmin; // Cache the result
481
541
  } catch (error) {
482
542
  logger.warn('OrganisationService', 'Failed to check super admin status', { error });
@@ -542,7 +602,14 @@ export class OrganisationService extends BaseService implements IOrganisationSer
542
602
  throw new Error('User has no access to active organisations') as OrganisationSecurityError;
543
603
  }
544
604
 
545
- this._organisations = activeOrgs;
605
+ // Sort organisations alphabetically by display_name
606
+ const sortedOrgs = [...activeOrgs].sort((a, b) => {
607
+ const nameA = (a.display_name || a.name || '').toLowerCase();
608
+ const nameB = (b.display_name || b.name || '').toLowerCase();
609
+ return nameA.localeCompare(nameB);
610
+ });
611
+
612
+ this._organisations = sortedOrgs;
546
613
  // Memberships already have branded types from earlier mapping
547
614
  this._userMemberships = memberships as OrganisationMembership[];
548
615