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