@jmruthers/pace-core 0.5.183 → 0.5.185
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-BABf6JCh.d.ts} +21 -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-CSOFYHAG.js → chunk-AISXLWGZ.js} +374 -60
- package/dist/chunk-AISXLWGZ.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-QETLRQI6.js → chunk-HC67NW5K.js} +380 -360
- package/dist/chunk-HC67NW5K.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-UHNYIBXL.js → chunk-IXSNYUCT.js} +1 -1
- package/dist/chunk-IXSNYUCT.js.map +1 -0
- package/dist/{chunk-MI7HBHN3.js → chunk-MX3EIJGQ.js} +4 -3
- package/dist/{chunk-MI7HBHN3.js.map → chunk-MX3EIJGQ.js.map} +1 -1
- package/dist/{chunk-PWAHJW4G.js → chunk-OKI34GZD.js} +86 -33
- package/dist/chunk-OKI34GZD.js.map +1 -0
- package/dist/{chunk-W22JP75J.js → chunk-STTZQK2I.js} +3 -3
- 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-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/eslint-rules/pace-core-compliance.cjs +406 -0
- package/dist/{file-reference-D06mEEWW.d.ts → file-reference-BjR39ktt.d.ts} +7 -1
- package/dist/hooks.d.ts +7 -14
- package/dist/hooks.js +10 -22
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.js +79 -16
- 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 +205 -14
- 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 +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-JJczomYq.d.ts → usePublicRouteParams-CvnC3d-e.d.ts} +113 -2
- package/dist/utils.d.ts +109 -151
- 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 +24 -8
- package/docs/api/interfaces/FileUploadProps.md +24 -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 +1 -1
- 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 +738 -42
- 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 +2 -1
- package/docs/implementation-guides/file-upload-storage.md +21 -0
- 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/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.tsx +3 -0
- package/src/components/Header/Header.test.tsx +47 -18
- package/src/components/Header/Header.tsx +24 -6
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +29 -20
- package/src/components/PaceAppLayout/README.md +9 -0
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -1
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +37 -8
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +12 -4
- 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 +2 -0
- package/src/hooks/useFileReference.test.ts +1 -0
- package/src/hooks/useFormDialog.ts +147 -0
- 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__/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/types/file-reference.ts +6 -0
- package/src/utils/__tests__/timezone.test.ts +345 -0
- package/src/utils/file-reference/__tests__/file-reference.test.ts +2 -0
- package/src/utils/file-reference/index.ts +1 -0
- 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/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-PWAHJW4G.js.map +0 -1
- package/dist/chunk-QETLRQI6.js.map +0 -1
- package/dist/chunk-UHNYIBXL.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-W22JP75J.js.map → chunk-STTZQK2I.js.map} +0 -0
- /package/dist/{chunk-QCDXODCA.js.map → chunk-XAUHJD3L.js.map} +0 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RBAC Setup Validator
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module RBAC/Compliance/SetupValidator
|
|
5
|
+
* @since 1.0.0
|
|
6
|
+
*
|
|
7
|
+
* This module provides utilities to validate RBAC setup state.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { getRBACConfig } from '../config';
|
|
11
|
+
import { RBACNotInitializedError } from '../errors';
|
|
12
|
+
|
|
13
|
+
export interface SetupIssue {
|
|
14
|
+
type: 'not-initialized' | 'missing-config' | 'invalid-config' | 'missing-provider-context';
|
|
15
|
+
message: string;
|
|
16
|
+
recommendation: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ComplianceResult {
|
|
20
|
+
isCompliant: boolean;
|
|
21
|
+
issues: SetupIssue[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check if RBAC system is initialized
|
|
26
|
+
*
|
|
27
|
+
* @returns true if RBAC is initialized, false otherwise
|
|
28
|
+
*/
|
|
29
|
+
export function isRBACInitialized(): boolean {
|
|
30
|
+
try {
|
|
31
|
+
const config = getRBACConfig();
|
|
32
|
+
return config !== null && config.supabase !== null;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (error instanceof RBACNotInitializedError) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
// Re-throw unexpected errors
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get setup issues
|
|
44
|
+
*
|
|
45
|
+
* @returns Array of setup issues
|
|
46
|
+
*/
|
|
47
|
+
export function getSetupIssues(): SetupIssue[] {
|
|
48
|
+
const issues: SetupIssue[] = [];
|
|
49
|
+
|
|
50
|
+
const config = getRBACConfig();
|
|
51
|
+
|
|
52
|
+
if (!config) {
|
|
53
|
+
issues.push({
|
|
54
|
+
type: 'not-initialized',
|
|
55
|
+
message: 'RBAC system has not been initialized. setupRBAC() has not been called.',
|
|
56
|
+
recommendation: 'Call setupRBAC(supabase) before using any RBAC features. This should be done in your main entry point (main.tsx or App.tsx) before rendering the app.'
|
|
57
|
+
});
|
|
58
|
+
return issues;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!config.supabase) {
|
|
62
|
+
issues.push({
|
|
63
|
+
type: 'missing-config',
|
|
64
|
+
message: 'RBAC configuration is missing Supabase client.',
|
|
65
|
+
recommendation: 'Ensure setupRBAC() is called with a valid Supabase client instance.'
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return issues;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check if UnifiedAuthProvider context is available
|
|
74
|
+
*
|
|
75
|
+
* This function can be called from React components to check if the context
|
|
76
|
+
* is available. It uses React's useContext hook, so it must be called from
|
|
77
|
+
* within a React component.
|
|
78
|
+
*
|
|
79
|
+
* @returns true if context is available, false otherwise
|
|
80
|
+
* @throws Error if called outside React component context
|
|
81
|
+
*/
|
|
82
|
+
export function isUnifiedAuthContextAvailable(): boolean {
|
|
83
|
+
try {
|
|
84
|
+
// This will only work if called from within a React component
|
|
85
|
+
// We can't import useContext here directly as it would require React
|
|
86
|
+
// Instead, we'll check if the context can be accessed
|
|
87
|
+
// Note: This is a best-effort check and may not work in all scenarios
|
|
88
|
+
return true; // Context availability is checked by useUnifiedAuth hook itself
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get provider context setup issues
|
|
96
|
+
*
|
|
97
|
+
* This provides guidance on common provider setup problems.
|
|
98
|
+
* Actual context availability must be checked at component level.
|
|
99
|
+
*
|
|
100
|
+
* @returns Array of setup issues related to provider context
|
|
101
|
+
*/
|
|
102
|
+
export function getProviderContextIssues(): SetupIssue[] {
|
|
103
|
+
const issues: SetupIssue[] = [];
|
|
104
|
+
|
|
105
|
+
// Check if RBAC is initialized (prerequisite for provider context)
|
|
106
|
+
if (!isRBACInitialized()) {
|
|
107
|
+
issues.push({
|
|
108
|
+
type: 'not-initialized',
|
|
109
|
+
message: 'RBAC system must be initialized before provider context can be used.',
|
|
110
|
+
recommendation: 'Call setupRBAC(supabase) before rendering UnifiedAuthProvider.'
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return issues;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Validate RBAC setup
|
|
119
|
+
*
|
|
120
|
+
* @returns Compliance result with issues and recommendations
|
|
121
|
+
*/
|
|
122
|
+
export function validateRBACSetup(): ComplianceResult {
|
|
123
|
+
const issues = getSetupIssues();
|
|
124
|
+
const providerIssues = getProviderContextIssues();
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
isCompliant: issues.length === 0 && providerIssues.length === 0,
|
|
128
|
+
issues: [...issues, ...providerIssues]
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
@@ -72,7 +72,6 @@ import { useCan } from '../hooks';
|
|
|
72
72
|
import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
|
|
73
73
|
import { UUID, Permission, Scope } from '../types';
|
|
74
74
|
import { createScopeFromEvent } from '../utils/eventContext';
|
|
75
|
-
import { getCurrentAppName } from '../../utils/app/appNameResolver';
|
|
76
75
|
import { getRBACLogger } from '../config';
|
|
77
76
|
|
|
78
77
|
export interface PagePermissionGuardProps {
|
|
@@ -129,16 +128,16 @@ const PagePermissionGuardComponent = ({
|
|
|
129
128
|
onDenied,
|
|
130
129
|
loading = <DefaultLoading />
|
|
131
130
|
}: PagePermissionGuardProps) => {
|
|
132
|
-
// Generate a unique instance ID for debugging
|
|
133
|
-
const instanceId = useMemo(() => Math.random().toString(36).substr(2, 9), []);
|
|
134
|
-
|
|
135
131
|
// Track render count for debugging
|
|
136
132
|
const renderCountRef = useRef(0);
|
|
137
133
|
renderCountRef.current += 1;
|
|
138
134
|
|
|
135
|
+
// Generate a unique instance ID for debugging (must be called before any conditional returns)
|
|
136
|
+
const instanceId = useMemo(() => Math.random().toString(36).substr(2, 9), []);
|
|
139
137
|
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
// Use UnifiedAuth hook - if context is not available, it will throw and ErrorBoundary will handle it
|
|
139
|
+
// This is better than checking for context and returning early, which causes infinite loops
|
|
140
|
+
const { user, selectedOrganisation, selectedEvent, supabase, appId: contextAppId } = useUnifiedAuth();
|
|
142
141
|
|
|
143
142
|
const [hasChecked, setHasChecked] = useState(false);
|
|
144
143
|
const [checkError, setCheckError] = useState<Error | null>(null);
|
|
@@ -179,64 +178,9 @@ const PagePermissionGuardComponent = ({
|
|
|
179
178
|
return;
|
|
180
179
|
}
|
|
181
180
|
|
|
182
|
-
// Get app ID from
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
// Try to resolve from database
|
|
186
|
-
if (supabaseRef.current) {
|
|
187
|
-
const appName = getCurrentAppName();
|
|
188
|
-
if (appName) {
|
|
189
|
-
try {
|
|
190
|
-
const { data: app, error } = await supabaseRef.current
|
|
191
|
-
.from('rbac_apps')
|
|
192
|
-
.select('id, name, is_active')
|
|
193
|
-
.eq('name', appName)
|
|
194
|
-
.eq('is_active', true)
|
|
195
|
-
.single() as { data: { id: string; name: string; is_active: boolean } | null; error: any };
|
|
196
|
-
|
|
197
|
-
if (signal.aborted) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (error) {
|
|
202
|
-
const logger = getRBACLogger();
|
|
203
|
-
logger.error('Database error resolving app ID:', error);
|
|
204
|
-
if (signal.aborted) {
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
const { data: inactiveApp } = await supabaseRef.current
|
|
208
|
-
.from('rbac_apps')
|
|
209
|
-
.select('id, name, is_active')
|
|
210
|
-
.eq('name', appName)
|
|
211
|
-
.single() as { data: { id: string; name: string; is_active: boolean } | null };
|
|
212
|
-
|
|
213
|
-
if (signal.aborted) {
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (inactiveApp) {
|
|
218
|
-
logger.error(`App "${appName}" exists but is inactive (is_active: ${inactiveApp.is_active})`);
|
|
219
|
-
} else {
|
|
220
|
-
logger.error(`App "${appName}" not found in rbac_apps table`);
|
|
221
|
-
}
|
|
222
|
-
} else if (app) {
|
|
223
|
-
appId = app.id;
|
|
224
|
-
} else {
|
|
225
|
-
const logger = getRBACLogger();
|
|
226
|
-
logger.error('No app data returned for:', appName);
|
|
227
|
-
}
|
|
228
|
-
} catch (error) {
|
|
229
|
-
if (signal.aborted) {
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
const logger = getRBACLogger();
|
|
233
|
-
logger.error('Unexpected error resolving app ID:', error);
|
|
234
|
-
}
|
|
235
|
-
} else {
|
|
236
|
-
const logger = getRBACLogger();
|
|
237
|
-
logger.error('No app name found. Make sure to call setRBACAppName() in your app setup.');
|
|
238
|
-
}
|
|
239
|
-
}
|
|
181
|
+
// Get app ID from UnifiedAuth context (already resolved on login)
|
|
182
|
+
// This is much faster than querying the database
|
|
183
|
+
const appId = contextAppId;
|
|
240
184
|
|
|
241
185
|
if (signal.aborted) {
|
|
242
186
|
return;
|
|
@@ -80,6 +80,7 @@ describe('PagePermissionGuard Component', () => {
|
|
|
80
80
|
user: mockUser,
|
|
81
81
|
selectedOrganisation: { id: 'org-123' },
|
|
82
82
|
selectedEvent: { event_id: 'event-123' },
|
|
83
|
+
appId: 'app-123', // Required for scope resolution
|
|
83
84
|
supabase: {
|
|
84
85
|
from: vi.fn().mockReturnValue({
|
|
85
86
|
select: vi.fn().mockReturnValue({
|
|
@@ -343,14 +344,17 @@ describe('PagePermissionGuard Component', () => {
|
|
|
343
344
|
</PagePermissionGuard>
|
|
344
345
|
);
|
|
345
346
|
|
|
347
|
+
// When there's an error and no permission, component shows fallback
|
|
346
348
|
await waitFor(() => {
|
|
347
|
-
expect(screen.
|
|
348
|
-
}, { interval: 10 });
|
|
349
|
+
expect(screen.getByTestId('test-fallback')).toBeInTheDocument();
|
|
350
|
+
}, { interval: 10, timeout: 2000 });
|
|
349
351
|
});
|
|
350
352
|
});
|
|
351
353
|
|
|
352
354
|
describe('App ID Resolution', () => {
|
|
353
355
|
it('resolves app ID from database', async () => {
|
|
356
|
+
// When appId is already provided from useUnifiedAuth, getCurrentAppName is not called
|
|
357
|
+
// The component uses appId from context directly
|
|
354
358
|
mockUseCan.mockReturnValue({
|
|
355
359
|
can: true,
|
|
356
360
|
isLoading: false,
|
|
@@ -368,9 +372,11 @@ describe('PagePermissionGuard Component', () => {
|
|
|
368
372
|
|
|
369
373
|
await waitFor(() => {
|
|
370
374
|
expect(screen.getByTestId('test-component')).toBeInTheDocument();
|
|
371
|
-
}, { interval: 10 });
|
|
375
|
+
}, { interval: 10, timeout: 2000 });
|
|
372
376
|
|
|
373
|
-
|
|
377
|
+
// appId is provided from useUnifiedAuth context, so getCurrentAppName is not called
|
|
378
|
+
// The component uses contextAppId directly (line 183 of PagePermissionGuard)
|
|
379
|
+
expect(mockGetCurrentAppName).not.toHaveBeenCalled();
|
|
374
380
|
});
|
|
375
381
|
|
|
376
382
|
it('handles app resolution errors in test environment', async () => {
|
|
@@ -432,10 +438,12 @@ describe('PagePermissionGuard Component', () => {
|
|
|
432
438
|
mockGetCurrentAppName.mockReturnValue('test-app');
|
|
433
439
|
|
|
434
440
|
// Mock database returning invalid app ID
|
|
441
|
+
// In test mode, validation is skipped, so component should work normally
|
|
435
442
|
mockUseUnifiedAuthFn.mockReturnValue({
|
|
436
443
|
user: mockUser,
|
|
437
444
|
selectedOrganisation: { id: 'org-123' },
|
|
438
445
|
selectedEvent: { event_id: 'event-123' },
|
|
446
|
+
appId: 'app-123',
|
|
439
447
|
supabase: {
|
|
440
448
|
from: vi.fn().mockReturnValue({
|
|
441
449
|
select: vi.fn().mockReturnValue({
|
|
@@ -452,9 +460,11 @@ describe('PagePermissionGuard Component', () => {
|
|
|
452
460
|
} as any
|
|
453
461
|
});
|
|
454
462
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
463
|
+
mockUseCan.mockReturnValue({
|
|
464
|
+
can: true,
|
|
465
|
+
isLoading: false,
|
|
466
|
+
error: null
|
|
467
|
+
});
|
|
458
468
|
|
|
459
469
|
render(
|
|
460
470
|
<PagePermissionGuard
|
|
@@ -466,12 +476,10 @@ describe('PagePermissionGuard Component', () => {
|
|
|
466
476
|
</PagePermissionGuard>
|
|
467
477
|
);
|
|
468
478
|
|
|
479
|
+
// In test mode, validation is skipped, so component should render normally
|
|
469
480
|
await waitFor(() => {
|
|
470
|
-
expect(screen.
|
|
471
|
-
}, { interval: 10 });
|
|
472
|
-
|
|
473
|
-
// Restore NODE_ENV
|
|
474
|
-
process.env.NODE_ENV = originalEnv;
|
|
481
|
+
expect(screen.getByTestId('test-component')).toBeInTheDocument();
|
|
482
|
+
}, { interval: 10, timeout: 2000 });
|
|
475
483
|
});
|
|
476
484
|
});
|
|
477
485
|
|
|
@@ -550,6 +558,7 @@ describe('PagePermissionGuard Component', () => {
|
|
|
550
558
|
user: mockUser,
|
|
551
559
|
selectedOrganisation: { id: 'org-123' },
|
|
552
560
|
selectedEvent: null,
|
|
561
|
+
appId: 'app-123',
|
|
553
562
|
supabase: {
|
|
554
563
|
from: vi.fn().mockReturnValue({
|
|
555
564
|
select: vi.fn().mockReturnValue({
|
|
@@ -603,6 +612,7 @@ describe('PagePermissionGuard Component', () => {
|
|
|
603
612
|
user: mockUser,
|
|
604
613
|
selectedOrganisation: null,
|
|
605
614
|
selectedEvent: { event_id: 'event-123' },
|
|
615
|
+
appId: 'app-123',
|
|
606
616
|
supabase: {
|
|
607
617
|
from: vi.fn().mockReturnValue({
|
|
608
618
|
select: vi.fn().mockReturnValue({
|
|
@@ -653,7 +663,7 @@ describe('PagePermissionGuard Component', () => {
|
|
|
653
663
|
expect.objectContaining({
|
|
654
664
|
organisationId: 'resolved-org',
|
|
655
665
|
eventId: 'event-123',
|
|
656
|
-
appId: 'app-123'
|
|
666
|
+
appId: 'app-123' // Component uses appId from context, not from resolved scope
|
|
657
667
|
}),
|
|
658
668
|
'read:page.dashboard',
|
|
659
669
|
'dashboard',
|
|
@@ -666,6 +676,7 @@ describe('PagePermissionGuard Component', () => {
|
|
|
666
676
|
user: mockUser,
|
|
667
677
|
selectedOrganisation: null,
|
|
668
678
|
selectedEvent: { event_id: 'event-123' },
|
|
679
|
+
appId: undefined, // Not available for error case
|
|
669
680
|
supabase: {} as any
|
|
670
681
|
});
|
|
671
682
|
|
|
@@ -692,6 +703,7 @@ describe('PagePermissionGuard Component', () => {
|
|
|
692
703
|
user: mockUser,
|
|
693
704
|
selectedOrganisation: null,
|
|
694
705
|
selectedEvent: null,
|
|
706
|
+
appId: undefined,
|
|
695
707
|
supabase: null
|
|
696
708
|
});
|
|
697
709
|
|
|
@@ -809,8 +821,8 @@ describe('PagePermissionGuard Component', () => {
|
|
|
809
821
|
);
|
|
810
822
|
|
|
811
823
|
await waitFor(() => {
|
|
812
|
-
expect(screen.
|
|
813
|
-
}, { interval: 10 });
|
|
824
|
+
expect(screen.getByTestId('test-fallback')).toBeInTheDocument();
|
|
825
|
+
}, { interval: 10, timeout: 2000 });
|
|
814
826
|
|
|
815
827
|
expect(onDeniedSpy).toHaveBeenCalledWith(mockPageName, mockOperation);
|
|
816
828
|
});
|
|
@@ -864,8 +876,8 @@ describe('PagePermissionGuard Component', () => {
|
|
|
864
876
|
);
|
|
865
877
|
|
|
866
878
|
await waitFor(() => {
|
|
867
|
-
expect(screen.
|
|
868
|
-
}, { interval: 10 });
|
|
879
|
+
expect(screen.getByTestId('test-fallback')).toBeInTheDocument();
|
|
880
|
+
}, { interval: 10, timeout: 2000 });
|
|
869
881
|
|
|
870
882
|
expect(consoleSpy).not.toHaveBeenCalledWith(
|
|
871
883
|
expect.stringContaining('STRICT MODE VIOLATION')
|
|
@@ -895,8 +907,8 @@ describe('PagePermissionGuard Component', () => {
|
|
|
895
907
|
);
|
|
896
908
|
|
|
897
909
|
await waitFor(() => {
|
|
898
|
-
expect(screen.
|
|
899
|
-
}, { interval: 10 });
|
|
910
|
+
expect(screen.getByTestId('test-fallback')).toBeInTheDocument();
|
|
911
|
+
}, { interval: 10, timeout: 2000 });
|
|
900
912
|
|
|
901
913
|
expect(consoleSpy).not.toHaveBeenCalledWith(
|
|
902
914
|
expect.stringContaining('Page access attempt')
|
|
@@ -910,8 +922,9 @@ describe('PagePermissionGuard Component', () => {
|
|
|
910
922
|
it('handles missing user gracefully', async () => {
|
|
911
923
|
mockUseUnifiedAuthFn.mockReturnValue({
|
|
912
924
|
user: null,
|
|
913
|
-
|
|
914
|
-
|
|
925
|
+
selectedOrganisation: { id: 'org-123' },
|
|
926
|
+
selectedEvent: { event_id: 'event-123' },
|
|
927
|
+
appId: 'app-123',
|
|
915
928
|
supabase: {
|
|
916
929
|
from: vi.fn().mockReturnValue({
|
|
917
930
|
select: vi.fn().mockReturnValue({
|
|
@@ -966,6 +979,7 @@ describe('PagePermissionGuard Component', () => {
|
|
|
966
979
|
user: mockUser,
|
|
967
980
|
selectedOrganisation: { id: 'org-123' },
|
|
968
981
|
selectedEvent: { event_id: 'event-123' },
|
|
982
|
+
appId: undefined, // Not resolved due to database error
|
|
969
983
|
supabase: {
|
|
970
984
|
from: vi.fn().mockReturnValue({
|
|
971
985
|
select: vi.fn().mockReturnValue({
|
|
@@ -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
|
+
```
|