@jmruthers/pace-core 0.6.2 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/cursor-rules/00-pace-core-compliance.mdc +34 -2
- package/dist/{AuthService-BPvc3Ka0.d.ts → AuthService-Cb34EQs3.d.ts} +9 -1
- package/dist/{DataTable-TPTKCX4D.js → DataTable-E7YQZD7D.js} +9 -8
- package/dist/{PublicPageProvider-DC6kCaqf.d.ts → PublicPageProvider-DEMpysFR.d.ts} +45 -67
- package/dist/{UnifiedAuthProvider-CVcTjx-d.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +1 -8
- package/dist/{UnifiedAuthProvider-CH6Z342H.js → UnifiedAuthProvider-QPXO24B4.js} +5 -4
- package/dist/{api-MVVQZLJI.js → api-6LVZTHDS.js} +10 -10
- package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
- package/dist/chunk-36LVWXB2.js +227 -0
- package/dist/chunk-36LVWXB2.js.map +1 -0
- package/dist/{chunk-24UVZUZG.js → chunk-3LPHPB62.js} +129 -387
- package/dist/chunk-3LPHPB62.js.map +1 -0
- package/dist/{chunk-2UOI2FG5.js → chunk-5EC5MEWX.js} +4 -4
- package/dist/{chunk-3XC4CPTD.js → chunk-7JPAB3T5.js} +244 -5727
- package/dist/chunk-7JPAB3T5.js.map +1 -0
- package/dist/{chunk-6J4GEEJR.js → chunk-ATKZM7RX.js} +53 -27
- package/dist/chunk-ATKZM7RX.js.map +1 -0
- package/dist/{chunk-EHMR7VYL.js → chunk-AVMLPIM7.js} +443 -189
- package/dist/chunk-AVMLPIM7.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/{chunk-NECFR5MM.js → chunk-I6DAQMWX.js} +575 -647
- package/dist/chunk-I6DAQMWX.js.map +1 -0
- package/dist/{chunk-F2IMUDXZ.js → chunk-M7MPQISP.js} +2 -2
- package/dist/{chunk-XWQCNGTQ.js → chunk-NN6WWZ5U.js} +173 -79
- package/dist/chunk-NN6WWZ5U.js.map +1 -0
- package/dist/{chunk-MMZ7JXPU.js → chunk-OEWDTMG7.js} +13 -21
- package/dist/{chunk-MMZ7JXPU.js.map → chunk-OEWDTMG7.js.map} +1 -1
- package/dist/{chunk-SFZUDBL5.js → chunk-YKRAFF5K.js} +70 -56
- package/dist/chunk-YKRAFF5K.js.map +1 -0
- package/dist/components.d.ts +2 -2
- package/dist/components.js +12 -13
- package/dist/contextValidator-OOPCLPZW.js +9 -0
- package/dist/contextValidator-OOPCLPZW.js.map +1 -0
- package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +7 -6
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +21 -16
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +4 -3
- package/dist/rbac/index.d.ts +67 -27
- package/dist/rbac/index.js +15 -8
- package/dist/styles/index.js +1 -1
- package/dist/theming/runtime.js +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-1oMokgLF.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +7 -16
- package/dist/utils.js +5 -7
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +14 -16
- package/docs/api/modules.md +3796 -2513
- package/docs/components/context-selector.md +126 -0
- package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
- package/docs/pace-mint-fix-auto-selection.md +218 -0
- package/docs/pace-mint-rbac-setup.md +391 -0
- package/docs/rbac/secure-client-protection.md +330 -0
- package/package.json +10 -5
- package/scripts/audit/core/checks/compliance.cjs +72 -0
- package/scripts/audit/core/checks/dependencies.cjs +568 -28
- package/scripts/audit/core/checks/documentation.cjs +68 -3
- package/scripts/audit/core/checks/environment.cjs +2 -14
- package/scripts/audit/core/checks/error-handling.cjs +47 -6
- package/src/components/ContextSelector/ContextSelector.tsx +384 -0
- package/src/components/ContextSelector/index.ts +3 -0
- package/src/components/DataTable/components/RowComponent.tsx +19 -19
- package/src/components/DataTable/components/UnifiedTableBody.tsx +2 -2
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +8 -6
- package/src/components/Dialog/Dialog.tsx +29 -1
- package/src/components/FileDisplay/FileDisplay.tsx +42 -10
- package/src/components/Header/Header.test.tsx +43 -73
- package/src/components/Header/Header.tsx +44 -45
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +9 -9
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +157 -36
- package/src/components/PaceAppLayout/README.md +14 -17
- package/src/components/PaceAppLayout/test-setup.tsx +2 -2
- package/src/components/index.ts +5 -5
- package/src/eslint-rules/pace-core-compliance.cjs +106 -0
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
- package/src/hooks/useAppConfig.ts +15 -30
- package/src/hooks/useFileDisplay.ts +77 -50
- package/src/index.ts +4 -5
- package/src/providers/services/AuthServiceProvider.tsx +17 -7
- package/src/providers/services/EventServiceProvider.tsx +33 -5
- package/src/providers/services/UnifiedAuthProvider.tsx +90 -134
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
- package/src/rbac/adapters.tsx +2 -2
- package/src/rbac/api.test.ts +59 -51
- package/src/rbac/api.ts +178 -132
- package/src/rbac/components/PagePermissionGuard.tsx +38 -10
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
- package/src/rbac/hooks/permissions/useAccessLevel.ts +1 -1
- package/src/rbac/hooks/permissions/useCan.ts +41 -11
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +1 -1
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +1 -1
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +1 -1
- package/src/rbac/hooks/useCan.test.ts +0 -9
- package/src/rbac/hooks/useRBAC.test.ts +1 -5
- package/src/rbac/hooks/useRBAC.ts +36 -37
- package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
- package/src/rbac/hooks/useResolvedScope.ts +35 -40
- package/src/rbac/hooks/useSecureSupabase.ts +7 -7
- package/src/rbac/index.ts +7 -0
- package/src/rbac/secureClient.test.ts +22 -18
- package/src/rbac/secureClient.ts +103 -16
- package/src/rbac/security.ts +0 -17
- package/src/rbac/types.ts +1 -0
- package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
- package/src/rbac/utils/clientSecurity.ts +93 -0
- package/src/rbac/utils/contextValidator.ts +77 -168
- package/src/services/AuthService.ts +39 -7
- package/src/services/EventService.ts +285 -56
- package/src/services/OrganisationService.ts +81 -14
- package/src/services/__tests__/EventService.test.ts +1 -2
- package/src/services/base/BaseService.ts +3 -0
- package/src/utils/dynamic/dynamicUtils.ts +7 -4
- package/dist/chunk-24UVZUZG.js.map +0 -1
- package/dist/chunk-3XC4CPTD.js.map +0 -1
- package/dist/chunk-6J4GEEJR.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js +0 -38
- package/dist/chunk-EHMR7VYL.js.map +0 -1
- package/dist/chunk-NECFR5MM.js.map +0 -1
- package/dist/chunk-SFZUDBL5.js.map +0 -1
- package/dist/chunk-XWQCNGTQ.js.map +0 -1
- package/docs/api/classes/ColumnFactory.md +0 -243
- package/docs/api/classes/InvalidScopeError.md +0 -73
- package/docs/api/classes/Logger.md +0 -178
- package/docs/api/classes/MissingUserContextError.md +0 -66
- package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
- package/docs/api/classes/PermissionDeniedError.md +0 -73
- package/docs/api/classes/RBACAuditManager.md +0 -297
- package/docs/api/classes/RBACCache.md +0 -322
- package/docs/api/classes/RBACEngine.md +0 -171
- package/docs/api/classes/RBACError.md +0 -76
- package/docs/api/classes/RBACNotInitializedError.md +0 -66
- package/docs/api/classes/SecureSupabaseClient.md +0 -163
- package/docs/api/classes/StorageUtils.md +0 -328
- package/docs/api/enums/FileCategory.md +0 -184
- package/docs/api/enums/LogLevel.md +0 -54
- package/docs/api/enums/RBACErrorCode.md +0 -228
- package/docs/api/enums/RPCFunction.md +0 -118
- package/docs/api/interfaces/AddressFieldProps.md +0 -241
- package/docs/api/interfaces/AddressFieldRef.md +0 -94
- package/docs/api/interfaces/AggregateConfig.md +0 -43
- package/docs/api/interfaces/AutocompleteOptions.md +0 -75
- package/docs/api/interfaces/AvatarProps.md +0 -128
- package/docs/api/interfaces/BadgeProps.md +0 -34
- package/docs/api/interfaces/ButtonProps.md +0 -56
- package/docs/api/interfaces/CalendarProps.md +0 -73
- package/docs/api/interfaces/CardProps.md +0 -69
- package/docs/api/interfaces/ColorPalette.md +0 -7
- package/docs/api/interfaces/ColorShade.md +0 -66
- package/docs/api/interfaces/ComplianceResult.md +0 -30
- package/docs/api/interfaces/DataAccessRecord.md +0 -96
- package/docs/api/interfaces/DataRecord.md +0 -11
- package/docs/api/interfaces/DataTableAction.md +0 -252
- package/docs/api/interfaces/DataTableColumn.md +0 -504
- package/docs/api/interfaces/DataTableProps.md +0 -625
- package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
- package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
- package/docs/api/interfaces/DatabaseIssue.md +0 -41
- package/docs/api/interfaces/EmptyStateConfig.md +0 -61
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
- package/docs/api/interfaces/ErrorBoundaryProps.md +0 -147
- package/docs/api/interfaces/ErrorBoundaryProviderProps.md +0 -36
- package/docs/api/interfaces/ErrorBoundaryState.md +0 -75
- package/docs/api/interfaces/EventAppRoleData.md +0 -71
- package/docs/api/interfaces/ExportColumn.md +0 -90
- package/docs/api/interfaces/ExportOptions.md +0 -126
- package/docs/api/interfaces/FileDisplayProps.md +0 -249
- package/docs/api/interfaces/FileMetadata.md +0 -129
- package/docs/api/interfaces/FileReference.md +0 -118
- package/docs/api/interfaces/FileSizeLimits.md +0 -7
- package/docs/api/interfaces/FileUploadOptions.md +0 -139
- package/docs/api/interfaces/FileUploadProps.md +0 -296
- package/docs/api/interfaces/FooterProps.md +0 -107
- package/docs/api/interfaces/FormFieldProps.md +0 -166
- package/docs/api/interfaces/FormProps.md +0 -113
- package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
- package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
- package/docs/api/interfaces/InputProps.md +0 -56
- package/docs/api/interfaces/LabelProps.md +0 -107
- package/docs/api/interfaces/LoggerConfig.md +0 -62
- package/docs/api/interfaces/LoginFormProps.md +0 -187
- package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
- package/docs/api/interfaces/NavigationContextType.md +0 -164
- package/docs/api/interfaces/NavigationGuardProps.md +0 -139
- package/docs/api/interfaces/NavigationItem.md +0 -120
- package/docs/api/interfaces/NavigationMenuProps.md +0 -221
- package/docs/api/interfaces/NavigationProviderProps.md +0 -117
- package/docs/api/interfaces/Organisation.md +0 -140
- package/docs/api/interfaces/OrganisationContextType.md +0 -388
- package/docs/api/interfaces/OrganisationMembership.md +0 -140
- package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
- package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
- package/docs/api/interfaces/PaceAppLayoutProps.md +0 -409
- package/docs/api/interfaces/PaceLoginPageProps.md +0 -49
- package/docs/api/interfaces/PageAccessRecord.md +0 -85
- package/docs/api/interfaces/PagePermissionContextType.md +0 -140
- package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
- package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
- package/docs/api/interfaces/PaletteData.md +0 -41
- package/docs/api/interfaces/ParsedAddress.md +0 -120
- package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
- package/docs/api/interfaces/ProgressProps.md +0 -42
- package/docs/api/interfaces/ProtectedRouteProps.md +0 -78
- package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
- package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
- package/docs/api/interfaces/PublicPageLayoutProps.md +0 -185
- package/docs/api/interfaces/QuickFix.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
- package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
- package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
- package/docs/api/interfaces/RBACConfig.md +0 -133
- package/docs/api/interfaces/RBACContext.md +0 -52
- package/docs/api/interfaces/RBACLogger.md +0 -112
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
- package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
- package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
- package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
- package/docs/api/interfaces/RBACResult.md +0 -58
- package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
- package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
- package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
- package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
- package/docs/api/interfaces/RBACRolesListParams.md +0 -52
- package/docs/api/interfaces/RBACRolesListResult.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
- package/docs/api/interfaces/ResourcePermissions.md +0 -155
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
- package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
- package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
- package/docs/api/interfaces/RoleManagementResult.md +0 -52
- package/docs/api/interfaces/RouteAccessRecord.md +0 -107
- package/docs/api/interfaces/RouteConfig.md +0 -134
- package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
- package/docs/api/interfaces/SecureDataContextType.md +0 -168
- package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
- package/docs/api/interfaces/SetupIssue.md +0 -41
- package/docs/api/interfaces/StorageConfig.md +0 -41
- package/docs/api/interfaces/StorageFileInfo.md +0 -74
- package/docs/api/interfaces/StorageFileMetadata.md +0 -151
- package/docs/api/interfaces/StorageListOptions.md +0 -99
- package/docs/api/interfaces/StorageListResult.md +0 -41
- package/docs/api/interfaces/StorageUploadOptions.md +0 -101
- package/docs/api/interfaces/StorageUploadResult.md +0 -63
- package/docs/api/interfaces/StorageUrlOptions.md +0 -60
- package/docs/api/interfaces/StyleImport.md +0 -19
- package/docs/api/interfaces/SwitchProps.md +0 -34
- package/docs/api/interfaces/TabsContentProps.md +0 -9
- package/docs/api/interfaces/TabsListProps.md +0 -9
- package/docs/api/interfaces/TabsProps.md +0 -9
- package/docs/api/interfaces/TabsTriggerProps.md +0 -50
- package/docs/api/interfaces/TextareaProps.md +0 -53
- package/docs/api/interfaces/ToastActionElement.md +0 -12
- package/docs/api/interfaces/ToastProps.md +0 -9
- package/docs/api/interfaces/UnifiedAuthContextType.md +0 -823
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -173
- package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
- package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -138
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -84
- package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
- package/docs/api/interfaces/UsePublicEventReturn.md +0 -71
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -123
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -97
- package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
- package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
- package/docs/api/interfaces/UserEventAccess.md +0 -121
- package/docs/api/interfaces/UserMenuProps.md +0 -88
- package/docs/api/interfaces/UserProfile.md +0 -63
- package/src/components/EventSelector/EventSelector.test.tsx +0 -720
- package/src/components/EventSelector/EventSelector.tsx +0 -423
- package/src/components/EventSelector/index.ts +0 -3
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -327
- package/src/components/OrganisationSelector/index.ts +0 -9
- /package/dist/{DataTable-TPTKCX4D.js.map → DataTable-E7YQZD7D.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-CH6Z342H.js.map → UnifiedAuthProvider-QPXO24B4.js.map} +0 -0
- /package/dist/{api-MVVQZLJI.js.map → api-6LVZTHDS.js.map} +0 -0
- /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
- /package/dist/{chunk-2UOI2FG5.js.map → chunk-5EC5MEWX.js.map} +0 -0
- /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
- /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
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
153
|
-
|
|
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
|
-
//
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
215
|
-
|
|
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
|
-
//
|
|
258
|
-
|
|
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
|
-
|
|
269
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
358
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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
|
|
572
|
+
* Get page scope type (effective scope for a page)
|
|
518
573
|
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
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
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
-
|
|
565
|
-
|
|
566
|
-
|
|
603
|
+
if (!resolvedAppId) {
|
|
604
|
+
throw new Error(`Could not resolve appId for page ${pageId}`);
|
|
605
|
+
}
|
|
567
606
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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
|
-
.
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
-
|
|
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
|
|
598
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
179
|
-
expect(call[
|
|
180
|
-
expect(call[
|
|
181
|
-
expect(call[
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
460
|
+
// When authSupabase is available, the hook uses fromSupabaseClient
|
|
461
|
+
expect(mockFromSupabaseClient).toHaveBeenCalled();
|
|
452
462
|
}, { timeout: 2000 });
|
|
453
463
|
|
|
454
|
-
const call =
|
|
455
|
-
expect(call[
|
|
456
|
-
expect(call[
|
|
457
|
-
expect(call[
|
|
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
|
-
|
|
506
|
+
// When authSupabase is available, the hook uses fromSupabaseClient
|
|
507
|
+
expect(mockFromSupabaseClient).toHaveBeenCalled();
|
|
497
508
|
}, { timeout: 2000 });
|
|
498
509
|
|
|
499
|
-
const call =
|
|
500
|
-
expect(call[
|
|
501
|
-
expect(call[
|
|
502
|
-
expect(call[
|
|
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 },
|
|
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');
|