@jmruthers/pace-core 0.5.184 → 0.5.186
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 +38 -0
- package/README.md +60 -1
- package/core-usage-manifest.json +312 -0
- package/dist/{DataTable-QAB34V6K.js → DataTable-IX2NBUTP.js} +6 -6
- package/dist/{DataTable-Bz8ffqyA.d.ts → DataTable-Z9NLVJh0.d.ts} +1 -1
- package/dist/{index-Bl--n7-T.d.ts → PublicPageProvider-DIzEzwKl.d.ts} +23 -10
- package/dist/{UnifiedAuthProvider-7F6T4B6K.js → UnifiedAuthProvider-A4BCQRJY.js} +4 -2
- package/dist/{UnifiedAuthProvider-F86d7dSi.d.ts → UnifiedAuthProvider-BG0AL5eE.d.ts} +2 -1
- package/dist/{api-ROMBCNKU.js → api-BMFCXVQX.js} +2 -2
- package/dist/{chunk-RA3JUFMW.js → chunk-445GEP27.js} +154 -4
- package/dist/{chunk-RA3JUFMW.js.map → chunk-445GEP27.js.map} +1 -1
- package/dist/{chunk-W22JP75J.js → chunk-DAGICKHT.js} +9 -7
- package/dist/chunk-DAGICKHT.js.map +1 -0
- package/dist/{chunk-FUEYYMX5.js → chunk-FXFJRTKI.js} +24 -3
- package/dist/chunk-FXFJRTKI.js.map +1 -0
- package/dist/{chunk-CSOFYHAG.js → chunk-GRIQLQ52.js} +374 -60
- package/dist/chunk-GRIQLQ52.js.map +1 -0
- package/dist/{chunk-NQPMQGS2.js → chunk-HDCUMOOI.js} +497 -399
- package/dist/chunk-HDCUMOOI.js.map +1 -0
- package/dist/chunk-HESYZWZW.js +388 -0
- package/dist/chunk-HESYZWZW.js.map +1 -0
- package/dist/{chunk-QUVSNGIP.js → chunk-HGPQUCBC.js} +34 -9
- package/dist/{chunk-QUVSNGIP.js.map → chunk-HGPQUCBC.js.map} +1 -1
- package/dist/{chunk-PWAHJW4G.js → chunk-OALXJH4Y.js} +86 -33
- package/dist/chunk-OALXJH4Y.js.map +1 -0
- package/dist/{chunk-MI7HBHN3.js → chunk-TC7D3CR3.js} +89 -9
- package/dist/chunk-TC7D3CR3.js.map +1 -0
- package/dist/chunk-THRPYOFK.js +215 -0
- package/dist/chunk-THRPYOFK.js.map +1 -0
- package/dist/{chunk-M7W4CP3M.js → chunk-U6WNSFX5.js} +2 -1
- package/dist/chunk-U6WNSFX5.js.map +1 -0
- package/dist/{chunk-UHNYIBXL.js → chunk-UQWSHFVX.js} +1 -1
- package/dist/chunk-UQWSHFVX.js.map +1 -0
- package/dist/{chunk-QCDXODCA.js → chunk-XAUHJD3L.js} +2 -2
- package/dist/components.d.ts +182 -6
- package/dist/components.js +157 -11
- package/dist/components.js.map +1 -1
- package/dist/{database.generated-CBmg2950.d.ts → database.generated-DI89OQeI.d.ts} +63 -9
- package/dist/eslint-rules/pace-core-compliance.cjs +406 -0
- package/dist/{file-reference-D06mEEWW.d.ts → file-reference-PRTSLxKx.d.ts} +10 -1
- package/dist/hooks.d.ts +52 -15
- package/dist/hooks.js +12 -22
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +12 -12
- package/dist/index.js +82 -18
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +3 -1
- package/dist/rbac/index.d.ts +206 -15
- package/dist/rbac/index.js +28 -6
- package/dist/timezone-_pgH8qrY.d.ts +530 -0
- package/dist/{types-_x1f4QBF.d.ts → types-DUyCRSTj.d.ts} +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-JJczomYq.d.ts → usePublicRouteParams-D71QLlg4.d.ts} +114 -3
- package/dist/utils.d.ts +110 -152
- package/dist/utils.js +128 -138
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +60 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/Logger.md +178 -0
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +2 -2
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +2 -2
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +5 -5
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +54 -0
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +18 -2
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +30 -0
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +85 -0
- package/docs/api/interfaces/DatabaseIssue.md +41 -0
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +6 -6
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +48 -8
- package/docs/api/interfaces/FileUploadProps.md +46 -13
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +9 -9
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +62 -0
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +36 -23
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +6 -6
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/QuickFix.md +52 -0
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +4 -4
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +7 -7
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +5 -5
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +55 -0
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +41 -0
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseFormDialogOptions.md +62 -0
- package/docs/api/interfaces/UseFormDialogReturn.md +117 -0
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +746 -50
- package/docs/api-reference/components.md +26 -12
- package/docs/api-reference/hooks.md +111 -0
- package/docs/api-reference/rpc-functions.md +1 -1
- package/docs/api-reference/utilities.md +184 -0
- package/docs/getting-started/installation-guide.md +75 -16
- package/docs/getting-started/quick-start.md +61 -11
- package/docs/implementation-guides/authentication.md +88 -12
- package/docs/implementation-guides/file-reference-system.md +26 -3
- package/docs/implementation-guides/file-upload-storage.md +30 -1
- package/docs/rbac/README.md +1 -0
- package/docs/rbac/compliance/compliance-guide.md +544 -0
- package/docs/rbac/getting-started.md +158 -33
- package/docs/standards/pace-core-compliance.md +432 -0
- package/eslint-config-pace-core.cjs +93 -0
- package/package.json +15 -3
- package/scripts/analyze-bundle.js +232 -0
- package/scripts/build-css.js +56 -0
- package/scripts/build-docs-incremental.js +1015 -0
- package/scripts/check-pace-core-compliance.cjs +2353 -0
- package/scripts/check-pace-core-compliance.js +512 -0
- package/scripts/generate-docs.js +157 -0
- package/scripts/setup-build-cache.js +73 -0
- package/scripts/utils/command-runner.js +131 -0
- package/scripts/utils/env.js +33 -0
- package/scripts/utils/index.js +10 -0
- package/scripts/utils/logger.js +88 -0
- package/scripts/utils/path-helpers.js +37 -0
- package/scripts/validate-formats.js +133 -0
- package/scripts/validate-master.js +155 -0
- package/scripts/validate-pre-publish.js +140 -0
- package/scripts/validate-theme.js +142 -0
- package/src/components/Calendar/Calendar.tsx +8 -1
- package/src/components/Card/Card.tsx +47 -8
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +314 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +126 -0
- package/src/components/DatePickerWithTimezone/README.md +135 -0
- package/src/components/DatePickerWithTimezone/index.ts +10 -0
- package/src/components/DateTimeField/DateTimeField.test.tsx +358 -0
- package/src/components/DateTimeField/DateTimeField.tsx +232 -0
- package/src/components/DateTimeField/README.md +148 -0
- package/src/components/DateTimeField/index.ts +10 -0
- package/src/components/FileUpload/FileUpload.test.tsx +2 -0
- package/src/components/FileUpload/FileUpload.tsx +10 -1
- package/src/components/Header/Header.test.tsx +47 -18
- package/src/components/Header/Header.tsx +22 -7
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +29 -20
- package/src/components/PaceAppLayout/README.md +9 -0
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +37 -8
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +146 -5
- package/src/components/index.ts +8 -0
- package/src/eslint-rules/pace-core-compliance.cjs +406 -0
- package/src/eslint-rules/pace-core-compliance.js +640 -0
- package/src/hooks/__tests__/useFormDialog.test.ts +478 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useFileReference.test.ts +2 -0
- package/src/hooks/useFormDialog.ts +147 -0
- package/src/hooks/usePreventTabReload.ts +106 -0
- package/src/hooks/useSecureDataAccess.ts +2 -2
- package/src/index.ts +27 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +6 -5
- package/src/providers/services/UnifiedAuthProvider.tsx +24 -3
- package/src/rbac/__tests__/rbac-role-isolation.test.ts +456 -0
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +3 -0
- package/src/rbac/compliance/database-validator.ts +165 -0
- package/src/rbac/compliance/index.ts +38 -0
- package/src/rbac/compliance/quick-fix-suggestions.ts +209 -0
- package/src/rbac/compliance/runtime-compliance.ts +77 -0
- package/src/rbac/compliance/setup-validator.ts +131 -0
- package/src/rbac/components/PagePermissionGuard.tsx +8 -64
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +35 -21
- package/src/rbac/docs/event-based-apps.md +285 -0
- package/src/rbac/errors.ts +11 -0
- package/src/rbac/hooks/useRoleManagement.ts +292 -12
- package/src/rbac/index.ts +30 -0
- package/src/services/OrganisationService.ts +4 -0
- package/src/styles/core.css +5 -5
- package/src/types/database.generated.ts +63 -9
- package/src/types/file-reference.ts +9 -0
- package/src/utils/__tests__/timezone.test.ts +345 -0
- package/src/utils/file-reference/__tests__/file-reference.test.ts +60 -4
- package/src/utils/file-reference/index.ts +13 -2
- package/src/utils/formatting/formatDateTimeTimezone.test.ts +167 -0
- package/src/utils/formatting/formatting.ts +179 -0
- package/src/utils/index.ts +27 -1
- package/src/utils/location/index.ts +16 -0
- package/src/utils/location/location.test.ts +286 -0
- package/src/utils/location/location.ts +175 -0
- package/src/utils/security/secureDataAccess.ts +1 -1
- package/src/utils/storage/helpers.ts +68 -0
- package/src/utils/timezone/index.ts +17 -0
- package/src/utils/timezone/timezone.test.ts +349 -0
- package/src/utils/timezone/timezone.ts +281 -0
- package/dist/chunk-CSOFYHAG.js.map +0 -1
- package/dist/chunk-FUEYYMX5.js.map +0 -1
- package/dist/chunk-HKIT6O7W.js +0 -198
- package/dist/chunk-HKIT6O7W.js.map +0 -1
- package/dist/chunk-KUEN3HFB.js +0 -94
- package/dist/chunk-KUEN3HFB.js.map +0 -1
- package/dist/chunk-M7W4CP3M.js.map +0 -1
- package/dist/chunk-MI7HBHN3.js.map +0 -1
- package/dist/chunk-NQPMQGS2.js.map +0 -1
- package/dist/chunk-PWAHJW4G.js.map +0 -1
- package/dist/chunk-UHNYIBXL.js.map +0 -1
- package/dist/chunk-W22JP75J.js.map +0 -1
- package/dist/formatting-5wETwiGF.d.ts +0 -162
- /package/dist/{DataTable-QAB34V6K.js.map → DataTable-IX2NBUTP.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-7F6T4B6K.js.map → UnifiedAuthProvider-A4BCQRJY.js.map} +0 -0
- /package/dist/{api-ROMBCNKU.js.map → api-BMFCXVQX.js.map} +0 -0
- /package/dist/{chunk-QCDXODCA.js.map → chunk-XAUHJD3L.js.map} +0 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# Event-Based Apps with RBAC
|
|
2
|
+
|
|
3
|
+
This guide explains how to use the RBAC system for event-based applications where the organization context is automatically derived from the event context.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Event-based apps are applications that operate within the context of a specific event. Since events inherently belong to an organization, these apps don't need explicit organization context - it is automatically resolved from the event by the RBAC components.
|
|
8
|
+
|
|
9
|
+
## Key Concepts
|
|
10
|
+
|
|
11
|
+
### Automatic Organization Resolution
|
|
12
|
+
The RBAC components automatically resolve the organization from the event context when needed. You only need to provide the `eventId` (and optionally `appId`), and the system handles the rest.
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
// The components automatically resolve organisationId from eventId
|
|
16
|
+
const scope: Scope = {
|
|
17
|
+
eventId: 'event-123',
|
|
18
|
+
appId: 'app-456' // optional
|
|
19
|
+
// organisationId is automatically resolved
|
|
20
|
+
};
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Permission Hierarchy
|
|
24
|
+
For event-based apps, the permission hierarchy is:
|
|
25
|
+
1. **Page-level permissions** (most specific)
|
|
26
|
+
2. **Event-app permissions** (event + app specific)
|
|
27
|
+
3. **Organization permissions** (automatically resolved from event)
|
|
28
|
+
4. **Global permissions** (least specific)
|
|
29
|
+
|
|
30
|
+
## Components
|
|
31
|
+
|
|
32
|
+
The same RBAC components work for both organization-based and event-based apps. They automatically detect the context and resolve the organization when needed.
|
|
33
|
+
|
|
34
|
+
### PermissionEnforcer
|
|
35
|
+
Use this component for general permission enforcement in event-based apps:
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { PermissionEnforcer } from '@jmruthers/pace-core/rbac';
|
|
39
|
+
|
|
40
|
+
function EventDashboard() {
|
|
41
|
+
return (
|
|
42
|
+
<PermissionEnforcer
|
|
43
|
+
permissions={['read:events', 'update:participants']}
|
|
44
|
+
operation="dashboard"
|
|
45
|
+
>
|
|
46
|
+
<div>Event Dashboard Content</div>
|
|
47
|
+
</PermissionEnforcer>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### PagePermissionGuard
|
|
53
|
+
Use this component for page-level permission enforcement:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { PagePermissionGuard } from '@jmruthers/pace-core/rbac';
|
|
57
|
+
|
|
58
|
+
function EventSettingsPage() {
|
|
59
|
+
return (
|
|
60
|
+
<PagePermissionGuard
|
|
61
|
+
pageName="settings"
|
|
62
|
+
operation="read"
|
|
63
|
+
>
|
|
64
|
+
<div>Event Settings</div>
|
|
65
|
+
</PagePermissionGuard>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### NavigationGuard
|
|
71
|
+
Use this component for navigation-level permission enforcement:
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { NavigationGuard } from '@jmruthers/pace-core/rbac';
|
|
75
|
+
|
|
76
|
+
const navigationItems = [
|
|
77
|
+
{
|
|
78
|
+
id: 'dashboard',
|
|
79
|
+
name: 'Dashboard',
|
|
80
|
+
path: '/event/dashboard',
|
|
81
|
+
permissions: ['read:events']
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'settings',
|
|
85
|
+
name: 'Settings',
|
|
86
|
+
path: '/event/settings',
|
|
87
|
+
permissions: ['update:events']
|
|
88
|
+
}
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
function EventNavigation() {
|
|
92
|
+
return (
|
|
93
|
+
<nav>
|
|
94
|
+
{navigationItems.map(item => (
|
|
95
|
+
<NavigationGuard
|
|
96
|
+
key={item.id}
|
|
97
|
+
navigationItem={item}
|
|
98
|
+
>
|
|
99
|
+
<Link to={item.path}>{item.name}</Link>
|
|
100
|
+
</NavigationGuard>
|
|
101
|
+
))}
|
|
102
|
+
</nav>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Hooks
|
|
108
|
+
|
|
109
|
+
### useCan with Event Context
|
|
110
|
+
You can use the standard `useCan` hook with event-based scopes:
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
import { useCan } from '@jmruthers/pace-core/rbac';
|
|
114
|
+
|
|
115
|
+
function EventComponent() {
|
|
116
|
+
const { selectedEventId } = useUnifiedAuth();
|
|
117
|
+
|
|
118
|
+
const { can: canManageEvents } = useCan(
|
|
119
|
+
userId,
|
|
120
|
+
{ eventId: selectedEventId },
|
|
121
|
+
'update:events'
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<div>
|
|
126
|
+
{canManageEvents && (
|
|
127
|
+
<button>Manage Event</button>
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Setup
|
|
135
|
+
|
|
136
|
+
### 1. Event Context Provider
|
|
137
|
+
Ensure your app has event context available:
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
import { UnifiedAuthProvider } from '@jmruthers/pace-core';
|
|
141
|
+
|
|
142
|
+
function App() {
|
|
143
|
+
return (
|
|
144
|
+
<UnifiedAuthProvider>
|
|
145
|
+
{/* Your event-based app components */}
|
|
146
|
+
</UnifiedAuthProvider>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### 2. Event Selection
|
|
152
|
+
Make sure the event is selected in the auth context:
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
import { useUnifiedAuth } from '@jmruthers/pace-core';
|
|
156
|
+
|
|
157
|
+
function EventSelector() {
|
|
158
|
+
const { setSelectedEventId } = useUnifiedAuth();
|
|
159
|
+
|
|
160
|
+
const handleEventSelect = (eventId: string) => {
|
|
161
|
+
setSelectedEventId(eventId);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<select onChange={(e) => handleEventSelect(e.target.value)}>
|
|
166
|
+
<option value="event-1">Event 1</option>
|
|
167
|
+
<option value="event-2">Event 2</option>
|
|
168
|
+
</select>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Permission Types
|
|
174
|
+
|
|
175
|
+
### Event-App Permissions
|
|
176
|
+
These permissions are specific to an event and app combination:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const EVENT_APP_PERMISSIONS = {
|
|
180
|
+
// Event management
|
|
181
|
+
MANAGE_EVENT: 'update:event',
|
|
182
|
+
READ_EVENT: 'read:event',
|
|
183
|
+
UPDATE_EVENT: 'update:event',
|
|
184
|
+
|
|
185
|
+
// Participant management
|
|
186
|
+
MANAGE_PARTICIPANTS: 'update:participants',
|
|
187
|
+
READ_PARTICIPANTS: 'read:participants',
|
|
188
|
+
CREATE_PARTICIPANTS: 'create:participants',
|
|
189
|
+
UPDATE_PARTICIPANTS: 'update:participants',
|
|
190
|
+
DELETE_PARTICIPANTS: 'delete:participants',
|
|
191
|
+
|
|
192
|
+
// App-specific permissions
|
|
193
|
+
MANAGE_APP: 'update:app',
|
|
194
|
+
READ_APP: 'read:app',
|
|
195
|
+
UPDATE_APP: 'update:app',
|
|
196
|
+
};
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Page Permissions
|
|
200
|
+
Page permissions are specific to individual pages within an event app:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const PAGE_PERMISSIONS = {
|
|
204
|
+
DASHBOARD_READ: 'read:page.dashboard',
|
|
205
|
+
DASHBOARD_WRITE: 'write:page.dashboard',
|
|
206
|
+
SETTINGS_READ: 'read:page.settings',
|
|
207
|
+
SETTINGS_WRITE: 'write:page.settings',
|
|
208
|
+
PARTICIPANTS_READ: 'read:page.participants',
|
|
209
|
+
PARTICIPANTS_WRITE: 'write:page.participants',
|
|
210
|
+
};
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Best Practices
|
|
214
|
+
|
|
215
|
+
### 1. Use Event-Based Components
|
|
216
|
+
Always use the event-based components (`EventPermissionEnforcer`, `EventPagePermissionGuard`, `EventNavigationGuard`) instead of the organization-based ones for event apps.
|
|
217
|
+
|
|
218
|
+
### 2. Provide Explicit Scopes
|
|
219
|
+
When possible, provide explicit scopes to avoid context resolution overhead:
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
<EventPermissionEnforcer
|
|
223
|
+
scope={{ eventId: 'event-123', appId: 'app-456' }}
|
|
224
|
+
permissions={['read:events']}
|
|
225
|
+
>
|
|
226
|
+
<div>Content</div>
|
|
227
|
+
</EventPermissionEnforcer>
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 3. Handle Loading States
|
|
231
|
+
Event-based components automatically handle loading states while resolving the organization from the event:
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
<EventPermissionEnforcer
|
|
235
|
+
permissions={['read:events']}
|
|
236
|
+
loading={<div>Loading permissions...</div>}
|
|
237
|
+
>
|
|
238
|
+
<div>Content</div>
|
|
239
|
+
</EventPermissionEnforcer>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### 4. Error Handling
|
|
243
|
+
Handle cases where the organization cannot be resolved from the event:
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
<EventPermissionEnforcer
|
|
247
|
+
permissions={['read:events']}
|
|
248
|
+
onDenied={(permission, reason) => {
|
|
249
|
+
console.error(`Permission denied: ${permission}, reason: ${reason}`);
|
|
250
|
+
}}
|
|
251
|
+
>
|
|
252
|
+
<div>Content</div>
|
|
253
|
+
</EventPermissionEnforcer>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Migration from Organization-Based Apps
|
|
257
|
+
|
|
258
|
+
If you're migrating from organization-based apps to event-based apps:
|
|
259
|
+
|
|
260
|
+
1. Replace `PermissionEnforcer` with `EventPermissionEnforcer`
|
|
261
|
+
2. Replace `PagePermissionGuard` with `EventPagePermissionGuard`
|
|
262
|
+
3. Replace `NavigationGuard` with `EventNavigationGuard`
|
|
263
|
+
4. Remove explicit organization context from your components
|
|
264
|
+
5. Ensure event context is available in your app
|
|
265
|
+
|
|
266
|
+
## Troubleshooting
|
|
267
|
+
|
|
268
|
+
### Common Issues
|
|
269
|
+
|
|
270
|
+
1. **"Event context is required" error**: Make sure `selectedEventId` is set in the auth context
|
|
271
|
+
2. **"Could not resolve organization from event context" error**: Verify the event exists and has a valid `organisation_id`
|
|
272
|
+
3. **Permission checks failing**: Ensure the user has the appropriate event-app roles assigned
|
|
273
|
+
|
|
274
|
+
### Debug Mode
|
|
275
|
+
|
|
276
|
+
Enable debug mode to see detailed permission resolution:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import { createRBACConfig } from '@jmruthers/pace-core/rbac';
|
|
280
|
+
|
|
281
|
+
const config = createRBACConfig({
|
|
282
|
+
debug: true,
|
|
283
|
+
logLevel: 'debug'
|
|
284
|
+
});
|
|
285
|
+
```
|
package/src/rbac/errors.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
OrganisationContextRequiredError,
|
|
5
5
|
InvalidScopeError,
|
|
6
6
|
MissingUserContextError,
|
|
7
|
+
RBACNotInitializedError,
|
|
7
8
|
} from './types';
|
|
8
9
|
|
|
9
10
|
export enum RBACErrorCategory {
|
|
@@ -154,3 +155,13 @@ export function mapErrorCategoryToSecurityEventType(category: RBACErrorCategory)
|
|
|
154
155
|
return 'unknown_error';
|
|
155
156
|
}
|
|
156
157
|
}
|
|
158
|
+
|
|
159
|
+
// Re-export error classes for convenience
|
|
160
|
+
export {
|
|
161
|
+
RBACError,
|
|
162
|
+
PermissionDeniedError,
|
|
163
|
+
OrganisationContextRequiredError,
|
|
164
|
+
InvalidScopeError,
|
|
165
|
+
MissingUserContextError,
|
|
166
|
+
RBACNotInitializedError,
|
|
167
|
+
};
|
|
@@ -13,25 +13,46 @@
|
|
|
13
13
|
* import { useRoleManagement } from '@jmruthers/pace-core/rbac';
|
|
14
14
|
*
|
|
15
15
|
* function UserRolesComponent() {
|
|
16
|
-
* const {
|
|
16
|
+
* const {
|
|
17
|
+
* revokeEventAppRole,
|
|
18
|
+
* grantEventAppRole,
|
|
19
|
+
* grantGlobalRole,
|
|
20
|
+
* revokeGlobalRole,
|
|
21
|
+
* grantOrganisationRole,
|
|
22
|
+
* revokeOrganisationRole,
|
|
23
|
+
* isLoading,
|
|
24
|
+
* error
|
|
25
|
+
* } = useRoleManagement();
|
|
17
26
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* appId: roleData.app_id,
|
|
24
|
-
* role: roleData.role
|
|
27
|
+
* // Grant a global role
|
|
28
|
+
* const handleGrantGlobalRole = async () => {
|
|
29
|
+
* const result = await grantGlobalRole({
|
|
30
|
+
* user_id: userId,
|
|
31
|
+
* role: 'super_admin'
|
|
25
32
|
* });
|
|
33
|
+
* if (result.success) {
|
|
34
|
+
* toast({ title: 'Role granted successfully' });
|
|
35
|
+
* }
|
|
36
|
+
* };
|
|
26
37
|
*
|
|
38
|
+
* // Grant an organisation role
|
|
39
|
+
* const handleGrantOrgRole = async () => {
|
|
40
|
+
* const result = await grantOrganisationRole({
|
|
41
|
+
* user_id: userId,
|
|
42
|
+
* organisation_id: orgId,
|
|
43
|
+
* role: 'org_admin'
|
|
44
|
+
* });
|
|
27
45
|
* if (result.success) {
|
|
28
|
-
* toast({ title: 'Role
|
|
29
|
-
* } else {
|
|
30
|
-
* toast({ title: 'Failed to revoke role', variant: 'destructive' });
|
|
46
|
+
* toast({ title: 'Role granted successfully' });
|
|
31
47
|
* }
|
|
32
48
|
* };
|
|
33
49
|
*
|
|
34
|
-
* return
|
|
50
|
+
* return (
|
|
51
|
+
* <div>
|
|
52
|
+
* <button onClick={handleGrantGlobalRole}>Grant Super Admin</button>
|
|
53
|
+
* <button onClick={handleGrantOrgRole}>Grant Org Admin</button>
|
|
54
|
+
* </div>
|
|
55
|
+
* );
|
|
35
56
|
* }
|
|
36
57
|
* ```
|
|
37
58
|
*/
|
|
@@ -48,6 +69,17 @@ export interface EventAppRoleData {
|
|
|
48
69
|
role: 'viewer' | 'participant' | 'planner' | 'event_admin';
|
|
49
70
|
}
|
|
50
71
|
|
|
72
|
+
export interface OrganisationRoleData {
|
|
73
|
+
user_id: UUID;
|
|
74
|
+
organisation_id: UUID;
|
|
75
|
+
role: 'supporter' | 'member' | 'leader' | 'org_admin';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface GlobalRoleData {
|
|
79
|
+
user_id: UUID;
|
|
80
|
+
role: 'super_admin';
|
|
81
|
+
}
|
|
82
|
+
|
|
51
83
|
export interface RevokeEventAppRoleParams extends EventAppRoleData {
|
|
52
84
|
revoked_by?: UUID;
|
|
53
85
|
}
|
|
@@ -58,6 +90,26 @@ export interface GrantEventAppRoleParams extends EventAppRoleData {
|
|
|
58
90
|
valid_to?: string | null;
|
|
59
91
|
}
|
|
60
92
|
|
|
93
|
+
export interface RevokeOrganisationRoleParams extends OrganisationRoleData {
|
|
94
|
+
revoked_by?: UUID;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface GrantOrganisationRoleParams extends OrganisationRoleData {
|
|
98
|
+
granted_by?: UUID;
|
|
99
|
+
valid_from?: string;
|
|
100
|
+
valid_to?: string | null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface RevokeGlobalRoleParams extends GlobalRoleData {
|
|
104
|
+
revoked_by?: UUID;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface GrantGlobalRoleParams extends GlobalRoleData {
|
|
108
|
+
granted_by?: UUID;
|
|
109
|
+
valid_from?: string;
|
|
110
|
+
valid_to?: string | null;
|
|
111
|
+
}
|
|
112
|
+
|
|
61
113
|
export interface RoleManagementResult {
|
|
62
114
|
success: boolean;
|
|
63
115
|
message?: string;
|
|
@@ -244,10 +296,238 @@ export function useRoleManagement() {
|
|
|
244
296
|
}
|
|
245
297
|
}, [user?.id, supabase]);
|
|
246
298
|
|
|
299
|
+
/**
|
|
300
|
+
* Grant a global role using the unified RPC function
|
|
301
|
+
*
|
|
302
|
+
* This function uses the `rbac_role_grant` RPC which:
|
|
303
|
+
* - Runs with SECURITY DEFINER privileges
|
|
304
|
+
* - Includes proper permission checks
|
|
305
|
+
* - Automatically populates audit fields (granted_by, timestamps)
|
|
306
|
+
* - Complies with Row-Level Security policies
|
|
307
|
+
*
|
|
308
|
+
* @param params - Role grant parameters
|
|
309
|
+
* @returns Promise resolving to operation result with role ID
|
|
310
|
+
*/
|
|
311
|
+
const grantGlobalRole = useCallback(async (
|
|
312
|
+
params: GrantGlobalRoleParams
|
|
313
|
+
): Promise<RoleManagementResult> => {
|
|
314
|
+
setIsLoading(true);
|
|
315
|
+
setError(null);
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
const { data, error: rpcError } = await supabase.rpc('rbac_role_grant', {
|
|
319
|
+
p_user_id: params.user_id,
|
|
320
|
+
p_role_type: 'global',
|
|
321
|
+
p_role_name: params.role,
|
|
322
|
+
p_context_id: null, // Global roles don't need context
|
|
323
|
+
p_granted_by: params.granted_by || user?.id || undefined
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
if (rpcError) {
|
|
327
|
+
throw new Error(rpcError.message || 'Failed to grant role');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// rbac_role_grant returns a table with success, message, role_id, error_code
|
|
331
|
+
const result = Array.isArray(data) && data.length > 0 ? data[0] : null;
|
|
332
|
+
|
|
333
|
+
if (!result || !result.success) {
|
|
334
|
+
return {
|
|
335
|
+
success: false,
|
|
336
|
+
error: result?.message || result?.error_code || 'Failed to grant role',
|
|
337
|
+
message: result?.message
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
success: true,
|
|
343
|
+
message: result.message || 'Role granted successfully',
|
|
344
|
+
roleId: result.role_id
|
|
345
|
+
};
|
|
346
|
+
} catch (err) {
|
|
347
|
+
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
|
|
348
|
+
setError(err instanceof Error ? err : new Error(errorMessage));
|
|
349
|
+
return {
|
|
350
|
+
success: false,
|
|
351
|
+
error: errorMessage
|
|
352
|
+
};
|
|
353
|
+
} finally {
|
|
354
|
+
setIsLoading(false);
|
|
355
|
+
}
|
|
356
|
+
}, [user?.id, supabase]);
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Revoke a global role using the unified RPC function
|
|
360
|
+
*
|
|
361
|
+
* This function uses the `rbac_role_revoke` RPC which:
|
|
362
|
+
* - Runs with SECURITY DEFINER privileges
|
|
363
|
+
* - Includes proper permission checks
|
|
364
|
+
* - Automatically populates audit fields (revoked_by, timestamps)
|
|
365
|
+
* - Complies with Row-Level Security policies
|
|
366
|
+
*
|
|
367
|
+
* @param params - Role revocation parameters
|
|
368
|
+
* @returns Promise resolving to operation result
|
|
369
|
+
*/
|
|
370
|
+
const revokeGlobalRole = useCallback(async (
|
|
371
|
+
params: RevokeGlobalRoleParams
|
|
372
|
+
): Promise<RoleManagementResult> => {
|
|
373
|
+
setIsLoading(true);
|
|
374
|
+
setError(null);
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
const { data, error: rpcError } = await supabase.rpc('rbac_role_revoke', {
|
|
378
|
+
p_user_id: params.user_id,
|
|
379
|
+
p_role_type: 'global',
|
|
380
|
+
p_role_name: params.role,
|
|
381
|
+
p_context_id: null, // Global roles don't need context
|
|
382
|
+
p_revoked_by: params.revoked_by || user?.id || undefined
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
if (rpcError) {
|
|
386
|
+
throw new Error(rpcError.message || 'Failed to revoke role');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// rbac_role_revoke returns a table with success, message, revoked_count, error_code
|
|
390
|
+
const result = Array.isArray(data) && data.length > 0 ? data[0] : null;
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
success: result?.success === true,
|
|
394
|
+
message: result?.message || undefined,
|
|
395
|
+
error: result?.success === false ? (result?.message || result?.error_code || 'Unknown error') : undefined
|
|
396
|
+
};
|
|
397
|
+
} catch (err) {
|
|
398
|
+
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
|
|
399
|
+
setError(err instanceof Error ? err : new Error(errorMessage));
|
|
400
|
+
return {
|
|
401
|
+
success: false,
|
|
402
|
+
error: errorMessage
|
|
403
|
+
};
|
|
404
|
+
} finally {
|
|
405
|
+
setIsLoading(false);
|
|
406
|
+
}
|
|
407
|
+
}, [user?.id, supabase]);
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Grant an organisation role using the unified RPC function
|
|
411
|
+
*
|
|
412
|
+
* This function uses the `rbac_role_grant` RPC which:
|
|
413
|
+
* - Runs with SECURITY DEFINER privileges
|
|
414
|
+
* - Includes proper permission checks
|
|
415
|
+
* - Automatically populates audit fields (granted_by, timestamps)
|
|
416
|
+
* - Complies with Row-Level Security policies
|
|
417
|
+
*
|
|
418
|
+
* @param params - Role grant parameters
|
|
419
|
+
* @returns Promise resolving to operation result with role ID
|
|
420
|
+
*/
|
|
421
|
+
const grantOrganisationRole = useCallback(async (
|
|
422
|
+
params: GrantOrganisationRoleParams
|
|
423
|
+
): Promise<RoleManagementResult> => {
|
|
424
|
+
setIsLoading(true);
|
|
425
|
+
setError(null);
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
const { data, error: rpcError } = await supabase.rpc('rbac_role_grant', {
|
|
429
|
+
p_user_id: params.user_id,
|
|
430
|
+
p_role_type: 'organisation',
|
|
431
|
+
p_role_name: params.role,
|
|
432
|
+
p_context_id: params.organisation_id, // Organisation ID as context
|
|
433
|
+
p_granted_by: params.granted_by || user?.id || undefined
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
if (rpcError) {
|
|
437
|
+
throw new Error(rpcError.message || 'Failed to grant role');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// rbac_role_grant returns a table with success, message, role_id, error_code
|
|
441
|
+
const result = Array.isArray(data) && data.length > 0 ? data[0] : null;
|
|
442
|
+
|
|
443
|
+
if (!result || !result.success) {
|
|
444
|
+
return {
|
|
445
|
+
success: false,
|
|
446
|
+
error: result?.message || result?.error_code || 'Failed to grant role',
|
|
447
|
+
message: result?.message
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
success: true,
|
|
453
|
+
message: result.message || 'Role granted successfully',
|
|
454
|
+
roleId: result.role_id
|
|
455
|
+
};
|
|
456
|
+
} catch (err) {
|
|
457
|
+
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
|
|
458
|
+
setError(err instanceof Error ? err : new Error(errorMessage));
|
|
459
|
+
return {
|
|
460
|
+
success: false,
|
|
461
|
+
error: errorMessage
|
|
462
|
+
};
|
|
463
|
+
} finally {
|
|
464
|
+
setIsLoading(false);
|
|
465
|
+
}
|
|
466
|
+
}, [user?.id, supabase]);
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Revoke an organisation role using the unified RPC function
|
|
470
|
+
*
|
|
471
|
+
* This function uses the `rbac_role_revoke` RPC which:
|
|
472
|
+
* - Runs with SECURITY DEFINER privileges
|
|
473
|
+
* - Includes proper permission checks
|
|
474
|
+
* - Automatically populates audit fields (revoked_by, timestamps)
|
|
475
|
+
* - Complies with Row-Level Security policies
|
|
476
|
+
*
|
|
477
|
+
* @param params - Role revocation parameters
|
|
478
|
+
* @returns Promise resolving to operation result
|
|
479
|
+
*/
|
|
480
|
+
const revokeOrganisationRole = useCallback(async (
|
|
481
|
+
params: RevokeOrganisationRoleParams
|
|
482
|
+
): Promise<RoleManagementResult> => {
|
|
483
|
+
setIsLoading(true);
|
|
484
|
+
setError(null);
|
|
485
|
+
|
|
486
|
+
try {
|
|
487
|
+
const { data, error: rpcError } = await supabase.rpc('rbac_role_revoke', {
|
|
488
|
+
p_user_id: params.user_id,
|
|
489
|
+
p_role_type: 'organisation',
|
|
490
|
+
p_role_name: params.role,
|
|
491
|
+
p_context_id: params.organisation_id, // Organisation ID as context
|
|
492
|
+
p_revoked_by: params.revoked_by || user?.id || undefined
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
if (rpcError) {
|
|
496
|
+
throw new Error(rpcError.message || 'Failed to revoke role');
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// rbac_role_revoke returns a table with success, message, revoked_count, error_code
|
|
500
|
+
const result = Array.isArray(data) && data.length > 0 ? data[0] : null;
|
|
501
|
+
|
|
502
|
+
return {
|
|
503
|
+
success: result?.success === true,
|
|
504
|
+
message: result?.message || undefined,
|
|
505
|
+
error: result?.success === false ? (result?.message || result?.error_code || 'Unknown error') : undefined
|
|
506
|
+
};
|
|
507
|
+
} catch (err) {
|
|
508
|
+
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
|
|
509
|
+
setError(err instanceof Error ? err : new Error(errorMessage));
|
|
510
|
+
return {
|
|
511
|
+
success: false,
|
|
512
|
+
error: errorMessage
|
|
513
|
+
};
|
|
514
|
+
} finally {
|
|
515
|
+
setIsLoading(false);
|
|
516
|
+
}
|
|
517
|
+
}, [user?.id, supabase]);
|
|
518
|
+
|
|
247
519
|
return {
|
|
520
|
+
// Event app roles (existing)
|
|
248
521
|
revokeEventAppRole,
|
|
249
522
|
grantEventAppRole,
|
|
250
523
|
revokeRoleById,
|
|
524
|
+
// Global roles (new)
|
|
525
|
+
grantGlobalRole,
|
|
526
|
+
revokeGlobalRole,
|
|
527
|
+
// Organisation roles (new)
|
|
528
|
+
grantOrganisationRole,
|
|
529
|
+
revokeOrganisationRole,
|
|
530
|
+
// Shared state
|
|
251
531
|
isLoading,
|
|
252
532
|
error
|
|
253
533
|
};
|
package/src/rbac/index.ts
CHANGED
|
@@ -113,3 +113,33 @@ export {
|
|
|
113
113
|
|
|
114
114
|
// Permissions
|
|
115
115
|
export * from './permissions';
|
|
116
|
+
|
|
117
|
+
// Compliance
|
|
118
|
+
export {
|
|
119
|
+
isRBACInitialized,
|
|
120
|
+
validateRBACSetup,
|
|
121
|
+
getSetupIssues,
|
|
122
|
+
type SetupIssue,
|
|
123
|
+
type ComplianceResult,
|
|
124
|
+
} from './compliance/setup-validator';
|
|
125
|
+
|
|
126
|
+
export {
|
|
127
|
+
checkRuntimeCompliance,
|
|
128
|
+
validateAndWarn,
|
|
129
|
+
type RuntimeComplianceResult,
|
|
130
|
+
} from './compliance/runtime-compliance';
|
|
131
|
+
|
|
132
|
+
export {
|
|
133
|
+
validateDatabaseConfiguration,
|
|
134
|
+
type DatabaseComplianceResult,
|
|
135
|
+
type DatabaseIssue,
|
|
136
|
+
} from './compliance/database-validator';
|
|
137
|
+
|
|
138
|
+
export {
|
|
139
|
+
getQuickFixes,
|
|
140
|
+
getCustomAuthCodeFixes,
|
|
141
|
+
getDuplicateConfigFixes,
|
|
142
|
+
getUnprotectedPageFixes,
|
|
143
|
+
getDirectSupabaseAuthFixes,
|
|
144
|
+
type QuickFix,
|
|
145
|
+
} from './compliance/quick-fix-suggestions';
|
|
@@ -519,6 +519,7 @@ export class OrganisationService extends BaseService implements IOrganisationSer
|
|
|
519
519
|
|
|
520
520
|
// Auto-select organisation: try persisted, then primary, then first
|
|
521
521
|
let initialOrg: Organisation | null = null;
|
|
522
|
+
let selectionMethod: 'persisted' | 'admin' | 'first' = 'first';
|
|
522
523
|
|
|
523
524
|
// 1. Try to restore from localStorage
|
|
524
525
|
try {
|
|
@@ -530,6 +531,7 @@ export class OrganisationService extends BaseService implements IOrganisationSer
|
|
|
530
531
|
const validPersistedOrg = activeOrgs.find(org => org.id === persistedOrg.id);
|
|
531
532
|
if (validPersistedOrg) {
|
|
532
533
|
initialOrg = validPersistedOrg;
|
|
534
|
+
selectionMethod = 'persisted';
|
|
533
535
|
} else {
|
|
534
536
|
logger.warn("OrganisationService", "Persisted organisation not found in active orgs, clearing cache");
|
|
535
537
|
localStorage.removeItem('pace-core-selected-organisation');
|
|
@@ -552,6 +554,7 @@ export class OrganisationService extends BaseService implements IOrganisationSer
|
|
|
552
554
|
const foundOrg = organisations.find((org) => org.id === adminMembership.organisation_id);
|
|
553
555
|
if (foundOrg) {
|
|
554
556
|
initialOrg = foundOrg;
|
|
557
|
+
selectionMethod = 'admin';
|
|
555
558
|
}
|
|
556
559
|
}
|
|
557
560
|
}
|
|
@@ -559,6 +562,7 @@ export class OrganisationService extends BaseService implements IOrganisationSer
|
|
|
559
562
|
// 3. Fall back to first organisation
|
|
560
563
|
if (!initialOrg) {
|
|
561
564
|
initialOrg = activeOrgs[0];
|
|
565
|
+
selectionMethod = 'first';
|
|
562
566
|
}
|
|
563
567
|
|
|
564
568
|
if (!initialOrg) {
|