@jmruthers/pace-core 0.6.3 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/{DataTable-THFPBKTP.js → DataTable-AOVNCPTX.js} +8 -8
  2. package/dist/{PublicPageProvider-DEMpysFR.d.ts → PublicPageProvider-QTFVrL-Z.d.ts} +65 -83
  3. package/dist/{UnifiedAuthProvider-KAGUYQ4J.js → UnifiedAuthProvider-4SBX4LU5.js} +4 -4
  4. package/dist/{api-IAGWF3ZG.js → api-O6HTBX5Y.js} +3 -3
  5. package/dist/{chunk-ZNIWI3UC.js → chunk-6COVEUS7.js} +141 -107
  6. package/dist/chunk-6COVEUS7.js.map +1 -0
  7. package/dist/{chunk-QRPVRXYT.js → chunk-AFVQODI2.js} +38 -1
  8. package/dist/{chunk-QRPVRXYT.js.map → chunk-AFVQODI2.js.map} +1 -1
  9. package/dist/{chunk-RWEBCB47.js → chunk-EFN2EIMK.js} +2 -2
  10. package/dist/{chunk-CNCQDFLN.js → chunk-G7QEZTYQ.js} +31 -31
  11. package/dist/{chunk-CNCQDFLN.js.map → chunk-G7QEZTYQ.js.map} +1 -1
  12. package/dist/{chunk-YDQHOZNA.js → chunk-HU2C6SSC.js} +29 -18
  13. package/dist/chunk-HU2C6SSC.js.map +1 -0
  14. package/dist/{chunk-DWUBLJJM.js → chunk-IHB5DR3H.js} +184 -53
  15. package/dist/chunk-IHB5DR3H.js.map +1 -0
  16. package/dist/{chunk-PQBSKX33.js → chunk-IVOFDYWT.js} +364 -208
  17. package/dist/chunk-IVOFDYWT.js.map +1 -0
  18. package/dist/{chunk-6SOIHG6Z.js → chunk-JGRYX5UX.js} +120 -20
  19. package/dist/chunk-JGRYX5UX.js.map +1 -0
  20. package/dist/{chunk-6Z7LTB3D.js → chunk-NTM7ZSB6.js} +4 -4
  21. package/dist/chunk-NTM7ZSB6.js.map +1 -0
  22. package/dist/{chunk-HFZBI76P.js → chunk-RGAWHO7N.js} +4 -4
  23. package/dist/chunk-RGAWHO7N.js.map +1 -0
  24. package/dist/{chunk-2T2IG7T7.js → chunk-UPPMRMYG.js} +3 -3
  25. package/dist/{chunk-2T2IG7T7.js.map → chunk-UPPMRMYG.js.map} +1 -1
  26. package/dist/components.d.ts +2 -3
  27. package/dist/components.js +24 -28
  28. package/dist/components.js.map +1 -1
  29. package/dist/{contextValidator-3JNZKUTX.js → contextValidator-5OGXSPKS.js} +2 -2
  30. package/dist/hooks.d.ts +3 -3
  31. package/dist/hooks.js +41 -139
  32. package/dist/hooks.js.map +1 -1
  33. package/dist/index.d.ts +27 -18
  34. package/dist/index.js +41 -50
  35. package/dist/index.js.map +1 -1
  36. package/dist/providers.js +3 -3
  37. package/dist/rbac/index.d.ts +16 -9
  38. package/dist/rbac/index.js +6 -6
  39. package/dist/{usePublicRouteParams-i3qtoBgg.d.ts → usePublicRouteParams-ClnV4tnv.d.ts} +8 -8
  40. package/dist/utils.js +1 -1
  41. package/docs/api/modules.md +210 -100
  42. package/package.json +8 -4
  43. package/scripts/audit/core/checks/dependencies.cjs +9 -0
  44. package/scripts/validate-master.js +1 -1
  45. package/src/components/DataTable/__tests__/keyboard.test.tsx +15 -2
  46. package/src/components/DataTable/components/ImportModal.tsx +4 -6
  47. package/src/components/DataTable/components/ViewRowModal.tsx +4 -4
  48. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +455 -96
  49. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +122 -58
  50. package/src/components/DataTable/core/DataTableContext.tsx +1 -1
  51. package/src/components/DateTimeField/DateTimeField.tsx +17 -19
  52. package/src/components/DateTimeField/README.md +5 -2
  53. package/src/components/Dialog/Dialog.test.tsx +248 -228
  54. package/src/components/Dialog/Dialog.tsx +455 -325
  55. package/src/components/Dialog/index.ts +3 -3
  56. package/src/components/FileDisplay/FileDisplay.test.tsx +41 -0
  57. package/src/components/FileDisplay/FileDisplay.tsx +5 -5
  58. package/src/components/Form/Form.test.tsx +3 -2
  59. package/src/components/Form/Form.tsx +4 -5
  60. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +28 -28
  61. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +40 -54
  62. package/src/components/LoginForm/LoginForm.tsx +2 -2
  63. package/src/components/NavigationMenu/NavigationMenu.tsx +2 -2
  64. package/src/components/PaceAppLayout/PaceAppLayout.tsx +54 -42
  65. package/src/components/PaceAppLayout/README.md +10 -9
  66. package/src/components/PaceAppLayout/test-setup.tsx +40 -31
  67. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +61 -0
  68. package/src/components/PasswordChange/PasswordChangeForm.tsx +20 -13
  69. package/src/components/PublicLayout/PublicLayout.test.tsx +7 -3
  70. package/src/components/PublicLayout/PublicPageLayout.tsx +5 -8
  71. package/src/components/UserMenu/UserMenu.test.tsx +38 -6
  72. package/src/components/UserMenu/UserMenu.tsx +36 -34
  73. package/src/components/index.ts +3 -4
  74. package/src/hooks/useEventTheme.ts +4 -4
  75. package/src/hooks/useEvents.ts +11 -7
  76. package/src/hooks/useKeyboardShortcuts.ts +1 -1
  77. package/src/hooks/useOrganisationPermissions.ts +4 -4
  78. package/src/hooks/useOrganisations.ts +13 -7
  79. package/src/index.ts +11 -1
  80. package/src/rbac/README.md +20 -20
  81. package/src/rbac/hooks/useRBAC.test.ts +21 -3
  82. package/src/rbac/hooks/useRBAC.ts +4 -3
  83. package/src/rbac/hooks/useResourcePermissions.test.ts +125 -30
  84. package/src/rbac/hooks/useResourcePermissions.ts +57 -29
  85. package/src/rbac/permissions.ts +17 -17
  86. package/src/rbac/utils/contextValidator.ts +36 -0
  87. package/src/services/AuthService.ts +2 -5
  88. package/src/services/EventService.ts +99 -2
  89. package/src/services/InactivityService.ts +139 -58
  90. package/src/styles/core.css +4 -0
  91. package/src/utils/formatting/formatTime.test.ts +3 -2
  92. package/dist/chunk-6SOIHG6Z.js.map +0 -1
  93. package/dist/chunk-6Z7LTB3D.js.map +0 -1
  94. package/dist/chunk-DWUBLJJM.js.map +0 -1
  95. package/dist/chunk-HFZBI76P.js.map +0 -1
  96. package/dist/chunk-PQBSKX33.js.map +0 -1
  97. package/dist/chunk-YDQHOZNA.js.map +0 -1
  98. package/dist/chunk-ZNIWI3UC.js.map +0 -1
  99. /package/dist/{DataTable-THFPBKTP.js.map → DataTable-AOVNCPTX.js.map} +0 -0
  100. /package/dist/{UnifiedAuthProvider-KAGUYQ4J.js.map → UnifiedAuthProvider-4SBX4LU5.js.map} +0 -0
  101. /package/dist/{api-IAGWF3ZG.js.map → api-O6HTBX5Y.js.map} +0 -0
  102. /package/dist/{chunk-RWEBCB47.js.map → chunk-EFN2EIMK.js.map} +0 -0
  103. /package/dist/{contextValidator-3JNZKUTX.js.map → contextValidator-5OGXSPKS.js.map} +0 -0
@@ -96,6 +96,9 @@ async function getOrganisationFromEvent(supabase, eventId) {
96
96
 
97
97
  // src/rbac/utils/contextValidator.ts
98
98
  var log = createLogger("ContextValidator");
99
+ function allowsOptionalContexts(appName) {
100
+ return appName === "PORTAL" || appName === "ADMIN";
101
+ }
99
102
  var ContextValidator = class {
100
103
  /**
101
104
  * Derive organisation ID from event ID
@@ -123,6 +126,17 @@ var ContextValidator = class {
123
126
  const effectiveScopeType = pageScopeType;
124
127
  if (effectiveScopeType === "both") {
125
128
  if (!scope.organisationId && !scope.eventId) {
129
+ if (allowsOptionalContexts(appName)) {
130
+ return {
131
+ isValid: true,
132
+ resolvedScope: {
133
+ organisationId: void 0,
134
+ eventId: void 0,
135
+ appId: scope.appId
136
+ },
137
+ error: null
138
+ };
139
+ }
126
140
  return {
127
141
  isValid: false,
128
142
  resolvedScope: null,
@@ -150,6 +164,17 @@ var ContextValidator = class {
150
164
  }
151
165
  if (effectiveScopeType === "event") {
152
166
  if (!scope.eventId) {
167
+ if (allowsOptionalContexts(appName)) {
168
+ return {
169
+ isValid: true,
170
+ resolvedScope: {
171
+ organisationId: scope.organisationId,
172
+ eventId: void 0,
173
+ appId: scope.appId
174
+ },
175
+ error: null
176
+ };
177
+ }
153
178
  return {
154
179
  isValid: false,
155
180
  resolvedScope: null,
@@ -189,6 +214,17 @@ var ContextValidator = class {
189
214
  }
190
215
  if (effectiveScopeType === "organisation") {
191
216
  if (!scope.organisationId) {
217
+ if (allowsOptionalContexts(appName)) {
218
+ return {
219
+ isValid: true,
220
+ resolvedScope: {
221
+ organisationId: void 0,
222
+ eventId: scope.eventId,
223
+ appId: scope.appId
224
+ },
225
+ error: null
226
+ };
227
+ }
192
228
  return {
193
229
  isValid: false,
194
230
  resolvedScope: null,
@@ -218,9 +254,10 @@ export {
218
254
  RBACError,
219
255
  PermissionDeniedError,
220
256
  OrganisationContextRequiredError,
257
+ EventContextRequiredError,
221
258
  RBACNotInitializedError,
222
259
  InvalidScopeError,
223
260
  MissingUserContextError,
224
261
  ContextValidator
225
262
  };
226
- //# sourceMappingURL=chunk-QRPVRXYT.js.map
263
+ //# sourceMappingURL=chunk-AFVQODI2.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/rbac/types.ts","../src/rbac/utils/eventContext.ts","../src/rbac/utils/contextValidator.ts"],"sourcesContent":["/**\n * RBAC (Role-Based Access Control) Types - Build Contract Compliant\n * @package @jmruthers/pace-core\n * @module RBAC/Types\n * @since 1.0.0\n * \n * This module defines the core types for the RBAC system that match the build contract exactly.\n * All types are designed to be framework-agnostic and provide strong typing for permission operations.\n */\n\nimport type React from 'react';\nimport type { AppId, PageId } from '../types/core';\n\n// ============================================================================\n// CORE TYPES\n// ============================================================================\n\nexport type UUID = string;\n\nexport type Operation = 'read' | 'create' | 'update' | 'delete';\n\nexport type Permission = `${Operation}:${string}`; // e.g. \"read:base.events\" or \"create:team.members\"\n\nexport type AccessLevel =\n | 'viewer'\n | 'participant'\n | 'planner'\n | 'admin'\n | 'super';\n\n/**\n * Scope defines the context for permission checks.\n * Can include organisation, event, and/or app identifiers.\n */\nexport type Scope = {\n organisationId?: UUID;\n eventId?: string; // event_id is text/varchar\n appId?: AppId | UUID;\n};\n\n/**\n * Permission check request parameters.\n * Defines who (userId) is checking what permission in what context (scope).\n */\nexport type PermissionCheck = {\n userId: UUID;\n scope: Scope;\n permission: Permission;\n pageId?: PageId | UUID;\n};\n\nexport type PermissionMap = Record<Permission, boolean> & Partial<Record<'*', boolean>>;\n\n// ============================================================================\n// ROLE TYPES\n// ============================================================================\n\nexport type GlobalRole = 'super_admin';\n\nexport type OrganisationRole = 'supporter' | 'member' | 'leader' | 'org_admin';\n\nexport type EventAppRole = 'viewer' | 'participant' | 'planner' | 'event_admin';\n\n// ============================================================================\n// DATABASE TYPES\n// ============================================================================\n\nexport interface RBACGlobalRole {\n id: UUID;\n user_id: UUID;\n role: GlobalRole;\n granted_at: string;\n granted_by: UUID | null;\n valid_from: string;\n valid_to: string | null;\n}\n\nexport interface RBACOrganisationRole {\n id: UUID;\n user_id: UUID;\n organisation_id: UUID;\n role: OrganisationRole;\n status: 'active' | 'inactive' | 'suspended';\n granted_at: string;\n granted_by: UUID | null;\n revoked_at: string | null;\n revoked_by: UUID | null;\n notes: string | null;\n created_at: string;\n updated_at: string;\n valid_from: string;\n valid_to: string | null;\n}\n\nexport interface RBACEventAppRole {\n id: UUID;\n user_id: UUID;\n event_id: string;\n role: EventAppRole;\n status: 'active' | 'inactive' | 'suspended';\n granted_at: string;\n granted_by: UUID | null;\n organisation_id: UUID;\n app_id: UUID;\n valid_from: string;\n valid_to: string | null;\n}\n\nexport interface RBACPagePermission {\n id: UUID;\n app_page_id: UUID;\n operation: Operation;\n role_name: string;\n allowed: boolean;\n created_at: string;\n updated_at: string;\n organisation_id: UUID;\n}\n\nexport interface RBACAppPage {\n id: UUID;\n page_name: string;\n page_description: string | null;\n created_at: string;\n updated_at: string;\n created_by: UUID | null;\n updated_by: UUID | null;\n app_id: UUID;\n scope_type: 'event' | 'organisation' | 'both'; // Required - single source of truth for page scoping\n}\n\nexport interface RBACApp {\n id: UUID;\n name: string;\n display_name: string;\n description: string | null;\n requires_event: boolean;\n is_active: boolean;\n created_at: string;\n updated_at: string;\n created_by: UUID | null;\n updated_by: UUID | null;\n}\n\n// ============================================================================\n// AUDIT EVENT TYPES\n// ============================================================================\n\nexport type AuditEventType = \n | 'permission_check'\n | 'permission_denied'\n | 'role_granted'\n | 'role_denied'\n | 'rls_denied';\n\nexport type AuditEventSource = 'api' | 'ui' | 'middleware' | 'rls';\n\nexport interface RBACAuditEvent {\n id: UUID;\n event_type: AuditEventType;\n user_id: UUID;\n organisation_id: UUID | null; // Nullable to properly track missing context cases (should be rare since organisationId is required)\n event_id?: string;\n app_id?: UUID;\n page_id?: UUID;\n permission?: string;\n decision?: boolean;\n source?: AuditEventSource;\n bypass?: boolean;\n duration_ms?: number;\n metadata: Record<string, any>;\n created_at: string;\n}\n\nexport interface RBACAppContext {\n appId: UUID;\n hasAccess: boolean;\n}\n\nexport interface RBACRoleContext {\n globalRole: GlobalRole | null;\n organisationRole: OrganisationRole | null;\n eventAppRole: EventAppRole | null;\n}\n\n// ============================================================================\n// CACHE TYPES\n// ============================================================================\n\nexport interface CacheEntry<T> {\n data: T;\n expires: number;\n}\n\nexport interface PermissionCacheKey {\n userId: UUID;\n organisationId?: UUID;\n eventId?: string;\n appId?: UUID;\n permission?: Permission;\n pageId?: UUID | string;\n}\n\n// ============================================================================\n// API TYPES\n// ============================================================================\n\nexport interface GetAccessLevelInput {\n userId: UUID;\n scope: Scope;\n}\n\nexport interface GetPermissionMapInput {\n userId: UUID;\n scope: Scope;\n}\n\nexport interface IsPermittedInput extends PermissionCheck {}\n\n// ============================================================================\n// HOOK TYPES\n// ============================================================================\n\nexport interface UsePermissionsReturn {\n permissions: PermissionMap;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n}\n\nexport interface UseCanReturn {\n can: boolean;\n isLoading: boolean;\n error: Error | null;\n check: () => Promise<void>;\n}\n\n// ============================================================================\n// ADAPTER TYPES\n// ============================================================================\n\nexport interface PermissionGuardConfig {\n permission: Permission;\n pageId?: UUID;\n}\n\nexport interface WithPermissionGuardOptions {\n permission: Permission;\n pageId?: UUID;\n fallback?: React.ReactNode;\n onDenied?: () => void;\n}\n\n// ============================================================================\n// HOOK RETURN TYPES\n// ============================================================================\n\nexport interface UserRBACContext {\n user: any; // User from auth context\n globalRole: GlobalRole | null;\n organisationRole: OrganisationRole | null;\n eventAppRole: EventAppRole | null;\n hasGlobalPermission: (permission: Permission) => boolean;\n isSuperAdmin: boolean;\n isOrgAdmin: boolean;\n isEventAdmin: boolean;\n canManageOrganisation: boolean;\n canManageEvent: boolean;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport interface RBACPermission {\n permission_type: string;\n role_name: string;\n [key: string]: any;\n}\n\n// ============================================================================\n// COMPONENT TYPES\n// ============================================================================\n\nexport interface RBACGuardProps {\n children: React.ReactNode;\n operation: Operation;\n pageId?: UUID;\n fallback?: React.ReactNode;\n}\n\nexport interface RoleBasedContentProps {\n children: React.ReactNode;\n globalRoles?: GlobalRole[];\n organisationRoles?: OrganisationRole[];\n eventAppRoles?: EventAppRole[];\n fallback?: React.ReactNode;\n}\n\n// ============================================================================\n// ERROR TYPES\n// ============================================================================\n\nexport class RBACError extends Error {\n constructor(\n message: string,\n public code: string,\n public context?: Record<string, any>\n ) {\n super(message);\n this.name = 'RBACError';\n }\n}\n\nexport class PermissionDeniedError extends RBACError {\n constructor(permission: Permission, context?: Record<string, any>) {\n super(\n `Permission denied: ${permission}`,\n 'PERMISSION_DENIED',\n { permission, ...context }\n );\n this.name = 'PermissionDeniedError';\n }\n}\n\nexport class OrganisationContextRequiredError extends RBACError {\n constructor() {\n super(\n 'Organisation context is required for this operation',\n 'ORGANISATION_CONTEXT_REQUIRED'\n );\n this.name = 'OrganisationContextRequiredError';\n }\n}\n\nexport class EventContextRequiredError extends RBACError {\n constructor() {\n super(\n 'Event context is required for this operation',\n 'EVENT_CONTEXT_REQUIRED'\n );\n this.name = 'EventContextRequiredError';\n }\n}\n\nexport class RBACNotInitializedError extends RBACError {\n constructor() {\n super(\n 'RBAC system not initialized. Please call setupRBAC(supabase) before using any RBAC components or hooks. See: https://docs.pace-core.dev/rbac/setup',\n 'RBAC_NOT_INITIALIZED'\n );\n this.name = 'RBACNotInitializedError';\n }\n}\n\nexport class InvalidScopeError extends RBACError {\n constructor(scope: Scope, reason: string) {\n super(\n `Invalid scope provided: ${JSON.stringify(scope)}. ${reason}`,\n 'INVALID_SCOPE',\n { scope, reason }\n );\n this.name = 'InvalidScopeError';\n }\n}\n\nexport class MissingUserContextError extends RBACError {\n constructor() {\n super(\n 'User context is required but not available. Make sure to wrap your app with an auth provider.',\n 'MISSING_USER_CONTEXT'\n );\n this.name = 'MissingUserContextError';\n }\n}\n","/**\n * Event Context Utilities for RBAC\n * @package @jmruthers/pace-core\n * @module RBAC/EventContext\n * @since 1.0.0\n * \n * This module provides utilities for event-based RBAC operations where\n * the organization context is derived from the event context.\n */\n\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport { Database } from '../../types/database';\nimport { UUID, Scope } from '../types';\n\n/**\n * Cache for organisation derivation from event\n * Key: eventId, Value: organisationId | null\n * Maximum cache size to prevent memory leaks\n */\nconst orgDerivationCache = new Map<string, UUID | null>();\nconst MAX_CACHE_SIZE = 100; // Limit cache to 100 entries\n\n/**\n * Clear cache entry for a specific event (useful if event's org changes)\n * @param eventId - Event ID to clear from cache\n */\nexport function clearOrgDerivationCache(eventId: string): void {\n orgDerivationCache.delete(eventId);\n}\n\n/**\n * Clear all cached organisation derivations\n */\nexport function clearAllOrgDerivationCache(): void {\n orgDerivationCache.clear();\n}\n\n/**\n * Get organization ID from event ID\n * \n * Uses caching to avoid repeated database queries for the same event.\n * Cache is limited to prevent memory leaks.\n * \n * @param supabase - Supabase client\n * @param eventId - Event ID\n * @returns Promise resolving to organization ID or null\n */\nexport async function getOrganisationFromEvent(\n supabase: SupabaseClient<Database>,\n eventId: string\n): Promise<UUID | null> {\n // Check cache first\n if (orgDerivationCache.has(eventId)) {\n return orgDerivationCache.get(eventId) ?? null;\n }\n\n // Query database\n const { data, error } = await supabase\n .from('core_events')\n .select('organisation_id')\n .eq('event_id', eventId)\n .single() as { data: { organisation_id: string } | null; error: any };\n\n let organisationId: UUID | null = null;\n\n if (error || !data) {\n organisationId = null;\n } else if (data.organisation_id) {\n organisationId = data.organisation_id;\n } else {\n // organisation_id is null or undefined\n organisationId = null;\n }\n\n // Cache the result (with size limit to prevent memory leaks)\n if (orgDerivationCache.size >= MAX_CACHE_SIZE) {\n // Remove oldest entry (first key in Map)\n const firstKey = orgDerivationCache.keys().next().value;\n if (firstKey) {\n orgDerivationCache.delete(firstKey);\n }\n }\n orgDerivationCache.set(eventId, organisationId);\n\n return organisationId;\n}\n\n/**\n * Create a complete scope from event context\n * \n * @param supabase - Supabase client\n * @param eventId - Event ID\n * @param appId - Optional app ID\n * @returns Promise resolving to complete scope\n */\nexport async function createScopeFromEvent(\n supabase: SupabaseClient<Database>,\n eventId: string,\n appId?: UUID\n): Promise<Scope | null> {\n const organisationId = await getOrganisationFromEvent(supabase, eventId);\n \n if (!organisationId) {\n return null;\n }\n\n return {\n organisationId,\n eventId,\n appId\n };\n}\n\n/**\n * Check if a scope is event-based (has eventId but no explicit organisationId)\n * \n * @param scope - Permission scope\n * @returns True if scope is event-based\n */\nexport function isEventBasedScope(scope: Scope): boolean {\n return !scope.organisationId && !!scope.eventId;\n}\n\n/**\n * Validate that an event-based scope has the required context\n * \n * @param scope - Permission scope\n * @returns True if scope is valid for event-based operations\n */\nexport function isValidEventBasedScope(scope: Scope): boolean {\n return isEventBasedScope(scope) && !!scope.eventId;\n}\n","/**\n * Context Validator for RBAC\n * @package @jmruthers/pace-core\n * @module RBAC/ContextValidator\n * @since 1.0.0\n * \n * Centralized validation for RBAC context requirements based on app configuration.\n * Enforces app-specific context rules with single primary context:\n * - requires_event = TRUE: Event is PRIMARY context, org derived from event (org not required in input)\n * - requires_event = FALSE: Organisation is PRIMARY context, event optional\n * - PORTAL/ADMIN apps: Both contexts optional (allows users to view/edit own profiles, super admin access)\n * \n * Key principle: Only one primary context is required based on app config. The other is derived or optional.\n */\n\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport { Database } from '../../types/database';\nimport { UUID, Scope } from '../types';\nimport { EventContextRequiredError, OrganisationContextRequiredError } from '../types';\nimport { getOrganisationFromEvent } from './eventContext';\nimport { createLogger } from '../../utils/core/logger';\n\nconst log = createLogger('ContextValidator');\n\n/**\n * Page scope type - determines what context is required for a page\n * This is the single source of truth for page scoping.\n */\nexport type PageScopeType = 'event' | 'organisation' | 'both';\n\n/**\n * Check if an app allows optional contexts (both organisation and event optional)\n * @param appName - App name to check\n * @returns True if app allows optional contexts\n */\nfunction allowsOptionalContexts(appName?: string): boolean {\n return appName === 'PORTAL' || appName === 'ADMIN';\n}\n\nexport interface ContextValidationResult {\n isValid: boolean;\n resolvedScope: Scope | null;\n error: Error | null;\n}\n\n/**\n * Context Validator class\n * \n * Validates and resolves RBAC scope based on app configuration requirements.\n */\nexport class ContextValidator {\n\n /**\n * Derive organisation ID from event ID\n * \n * @param supabase - Supabase client\n * @param eventId - Event ID\n * @returns Organisation ID or null\n */\n static async deriveOrgFromEvent(\n supabase: SupabaseClient<Database>,\n eventId: string\n ): Promise<UUID | null> {\n return getOrganisationFromEvent(supabase, eventId);\n }\n\n /**\n * Resolve scope based on page-level scope_type\n * \n * This method handles page-level scoping. All pages have explicit scope_type set.\n * Used for hybrid apps like pace-mint that have both event and organisation pages.\n * \n * @param scope - Current scope\n * @param pageScopeType - Page scope type ('event', 'organisation', or 'both')\n * @param appName - App name (for PORTAL/ADMIN special case)\n * @param supabase - Supabase client (for deriving org from event)\n * @returns Resolved scope with all required context\n */\n static async resolveScopeForPage(\n scope: Scope,\n pageScopeType: PageScopeType,\n appName?: string,\n supabase?: SupabaseClient<Database> | null\n ): Promise<ContextValidationResult> {\n // Use page-level scope (single source of truth)\n const effectiveScopeType = pageScopeType;\n \n // Handle 'both' scope - requires both contexts available, but can use either\n if (effectiveScopeType === 'both') {\n // For 'both' pages, we need at least one context (org or event)\n // Both will be checked during permission evaluation\n if (!scope.organisationId && !scope.eventId) {\n return {\n isValid: false,\n resolvedScope: null,\n error: new Error('Page requires either organisation or event context')\n };\n }\n \n // Derive org from event if event is provided but org is not\n let organisationId = scope.organisationId;\n if (!organisationId && scope.eventId && supabase) {\n try {\n const derivedOrgId = await this.deriveOrgFromEvent(supabase, scope.eventId);\n organisationId = derivedOrgId || undefined;\n } catch (error) {\n log.warn('Failed to derive org from event for both-scope page:', error);\n // Continue without org - permission check will handle it\n }\n }\n \n return {\n isValid: true,\n resolvedScope: {\n organisationId,\n eventId: scope.eventId,\n appId: scope.appId\n },\n error: null\n };\n }\n \n // Handle 'event' scope - requires event context\n if (effectiveScopeType === 'event') {\n if (!scope.eventId) {\n return {\n isValid: false,\n resolvedScope: null,\n error: new EventContextRequiredError()\n };\n }\n \n // Derive organisationId from event if not provided\n let organisationId: UUID | undefined = scope.organisationId;\n if (!organisationId && supabase && scope.eventId) {\n try {\n const derivedOrgId = await this.deriveOrgFromEvent(supabase, scope.eventId);\n organisationId = derivedOrgId || undefined;\n if (!organisationId) {\n return {\n isValid: false,\n resolvedScope: null,\n error: new Error('Could not resolve organisation from event context')\n };\n }\n } catch (error) {\n log.error('Failed to derive org from event:', error);\n return {\n isValid: false,\n resolvedScope: null,\n error: error instanceof Error ? error : new Error('Failed to derive organisation from event')\n };\n }\n }\n \n return {\n isValid: true,\n resolvedScope: {\n organisationId,\n eventId: scope.eventId,\n appId: scope.appId\n },\n error: null\n };\n }\n \n // Handle 'organisation' scope - requires organisation context\n if (effectiveScopeType === 'organisation') {\n if (!scope.organisationId) {\n return {\n isValid: false,\n resolvedScope: null,\n error: new OrganisationContextRequiredError()\n };\n }\n \n return {\n isValid: true,\n resolvedScope: {\n organisationId: scope.organisationId,\n eventId: scope.eventId, // Event is optional for org-scoped pages\n appId: scope.appId\n },\n error: null\n };\n }\n \n // Fallback (should not happen)\n return {\n isValid: false,\n resolvedScope: null,\n error: new Error('Invalid scope type')\n };\n }\n\n}\n\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/rbac/types.ts","../src/rbac/utils/eventContext.ts","../src/rbac/utils/contextValidator.ts"],"sourcesContent":["/**\n * RBAC (Role-Based Access Control) Types - Build Contract Compliant\n * @package @jmruthers/pace-core\n * @module RBAC/Types\n * @since 1.0.0\n * \n * This module defines the core types for the RBAC system that match the build contract exactly.\n * All types are designed to be framework-agnostic and provide strong typing for permission operations.\n */\n\nimport type React from 'react';\nimport type { AppId, PageId } from '../types/core';\n\n// ============================================================================\n// CORE TYPES\n// ============================================================================\n\nexport type UUID = string;\n\nexport type Operation = 'read' | 'create' | 'update' | 'delete';\n\nexport type Permission = `${Operation}:${string}`; // e.g. \"read:base.events\" or \"create:team.members\"\n\nexport type AccessLevel =\n | 'viewer'\n | 'participant'\n | 'planner'\n | 'admin'\n | 'super';\n\n/**\n * Scope defines the context for permission checks.\n * Can include organisation, event, and/or app identifiers.\n */\nexport type Scope = {\n organisationId?: UUID;\n eventId?: string; // event_id is text/varchar\n appId?: AppId | UUID;\n};\n\n/**\n * Permission check request parameters.\n * Defines who (userId) is checking what permission in what context (scope).\n */\nexport type PermissionCheck = {\n userId: UUID;\n scope: Scope;\n permission: Permission;\n pageId?: PageId | UUID;\n};\n\nexport type PermissionMap = Record<Permission, boolean> & Partial<Record<'*', boolean>>;\n\n// ============================================================================\n// ROLE TYPES\n// ============================================================================\n\nexport type GlobalRole = 'super_admin';\n\nexport type OrganisationRole = 'supporter' | 'member' | 'leader' | 'org_admin';\n\nexport type EventAppRole = 'viewer' | 'participant' | 'planner' | 'event_admin';\n\n// ============================================================================\n// DATABASE TYPES\n// ============================================================================\n\nexport interface RBACGlobalRole {\n id: UUID;\n user_id: UUID;\n role: GlobalRole;\n granted_at: string;\n granted_by: UUID | null;\n valid_from: string;\n valid_to: string | null;\n}\n\nexport interface RBACOrganisationRole {\n id: UUID;\n user_id: UUID;\n organisation_id: UUID;\n role: OrganisationRole;\n status: 'active' | 'inactive' | 'suspended';\n granted_at: string;\n granted_by: UUID | null;\n revoked_at: string | null;\n revoked_by: UUID | null;\n notes: string | null;\n created_at: string;\n updated_at: string;\n valid_from: string;\n valid_to: string | null;\n}\n\nexport interface RBACEventAppRole {\n id: UUID;\n user_id: UUID;\n event_id: string;\n role: EventAppRole;\n status: 'active' | 'inactive' | 'suspended';\n granted_at: string;\n granted_by: UUID | null;\n organisation_id: UUID;\n app_id: UUID;\n valid_from: string;\n valid_to: string | null;\n}\n\nexport interface RBACPagePermission {\n id: UUID;\n app_page_id: UUID;\n operation: Operation;\n role_name: string;\n allowed: boolean;\n created_at: string;\n updated_at: string;\n organisation_id: UUID;\n}\n\nexport interface RBACAppPage {\n id: UUID;\n page_name: string;\n page_description: string | null;\n created_at: string;\n updated_at: string;\n created_by: UUID | null;\n updated_by: UUID | null;\n app_id: UUID;\n scope_type: 'event' | 'organisation' | 'both'; // Required - single source of truth for page scoping\n}\n\nexport interface RBACApp {\n id: UUID;\n name: string;\n display_name: string;\n description: string | null;\n requires_event: boolean;\n is_active: boolean;\n created_at: string;\n updated_at: string;\n created_by: UUID | null;\n updated_by: UUID | null;\n}\n\n// ============================================================================\n// AUDIT EVENT TYPES\n// ============================================================================\n\nexport type AuditEventType = \n | 'permission_check'\n | 'permission_denied'\n | 'role_granted'\n | 'role_denied'\n | 'rls_denied';\n\nexport type AuditEventSource = 'api' | 'ui' | 'middleware' | 'rls';\n\nexport interface RBACAuditEvent {\n id: UUID;\n event_type: AuditEventType;\n user_id: UUID;\n organisation_id: UUID | null; // Nullable to properly track missing context cases (should be rare since organisationId is required)\n event_id?: string;\n app_id?: UUID;\n page_id?: UUID;\n permission?: string;\n decision?: boolean;\n source?: AuditEventSource;\n bypass?: boolean;\n duration_ms?: number;\n metadata: Record<string, any>;\n created_at: string;\n}\n\nexport interface RBACAppContext {\n appId: UUID;\n hasAccess: boolean;\n}\n\nexport interface RBACRoleContext {\n globalRole: GlobalRole | null;\n organisationRole: OrganisationRole | null;\n eventAppRole: EventAppRole | null;\n}\n\n// ============================================================================\n// CACHE TYPES\n// ============================================================================\n\nexport interface CacheEntry<T> {\n data: T;\n expires: number;\n}\n\nexport interface PermissionCacheKey {\n userId: UUID;\n organisationId?: UUID;\n eventId?: string;\n appId?: UUID;\n permission?: Permission;\n pageId?: UUID | string;\n}\n\n// ============================================================================\n// API TYPES\n// ============================================================================\n\nexport interface GetAccessLevelInput {\n userId: UUID;\n scope: Scope;\n}\n\nexport interface GetPermissionMapInput {\n userId: UUID;\n scope: Scope;\n}\n\nexport interface IsPermittedInput extends PermissionCheck {}\n\n// ============================================================================\n// HOOK TYPES\n// ============================================================================\n\nexport interface UsePermissionsReturn {\n permissions: PermissionMap;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n}\n\nexport interface UseCanReturn {\n can: boolean;\n isLoading: boolean;\n error: Error | null;\n check: () => Promise<void>;\n}\n\n// ============================================================================\n// ADAPTER TYPES\n// ============================================================================\n\nexport interface PermissionGuardConfig {\n permission: Permission;\n pageId?: UUID;\n}\n\nexport interface WithPermissionGuardOptions {\n permission: Permission;\n pageId?: UUID;\n fallback?: React.ReactNode;\n onDenied?: () => void;\n}\n\n// ============================================================================\n// HOOK RETURN TYPES\n// ============================================================================\n\nexport interface UserRBACContext {\n user: any; // User from auth context\n globalRole: GlobalRole | null;\n organisationRole: OrganisationRole | null;\n eventAppRole: EventAppRole | null;\n hasGlobalPermission: (permission: Permission) => boolean;\n isSuperAdmin: boolean;\n isOrgAdmin: boolean;\n isEventAdmin: boolean;\n canManageOrganisation: boolean;\n canManageEvent: boolean;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport interface RBACPermission {\n permission_type: string;\n role_name: string;\n [key: string]: any;\n}\n\n// ============================================================================\n// COMPONENT TYPES\n// ============================================================================\n\nexport interface RBACGuardProps {\n children: React.ReactNode;\n operation: Operation;\n pageId?: UUID;\n fallback?: React.ReactNode;\n}\n\nexport interface RoleBasedContentProps {\n children: React.ReactNode;\n globalRoles?: GlobalRole[];\n organisationRoles?: OrganisationRole[];\n eventAppRoles?: EventAppRole[];\n fallback?: React.ReactNode;\n}\n\n// ============================================================================\n// ERROR TYPES\n// ============================================================================\n\nexport class RBACError extends Error {\n constructor(\n message: string,\n public code: string,\n public context?: Record<string, any>\n ) {\n super(message);\n this.name = 'RBACError';\n }\n}\n\nexport class PermissionDeniedError extends RBACError {\n constructor(permission: Permission, context?: Record<string, any>) {\n super(\n `Permission denied: ${permission}`,\n 'PERMISSION_DENIED',\n { permission, ...context }\n );\n this.name = 'PermissionDeniedError';\n }\n}\n\nexport class OrganisationContextRequiredError extends RBACError {\n constructor() {\n super(\n 'Organisation context is required for this operation',\n 'ORGANISATION_CONTEXT_REQUIRED'\n );\n this.name = 'OrganisationContextRequiredError';\n }\n}\n\nexport class EventContextRequiredError extends RBACError {\n constructor() {\n super(\n 'Event context is required for this operation',\n 'EVENT_CONTEXT_REQUIRED'\n );\n this.name = 'EventContextRequiredError';\n }\n}\n\nexport class RBACNotInitializedError extends RBACError {\n constructor() {\n super(\n 'RBAC system not initialized. Please call setupRBAC(supabase) before using any RBAC components or hooks. See: https://docs.pace-core.dev/rbac/setup',\n 'RBAC_NOT_INITIALIZED'\n );\n this.name = 'RBACNotInitializedError';\n }\n}\n\nexport class InvalidScopeError extends RBACError {\n constructor(scope: Scope, reason: string) {\n super(\n `Invalid scope provided: ${JSON.stringify(scope)}. ${reason}`,\n 'INVALID_SCOPE',\n { scope, reason }\n );\n this.name = 'InvalidScopeError';\n }\n}\n\nexport class MissingUserContextError extends RBACError {\n constructor() {\n super(\n 'User context is required but not available. Make sure to wrap your app with an auth provider.',\n 'MISSING_USER_CONTEXT'\n );\n this.name = 'MissingUserContextError';\n }\n}\n","/**\n * Event Context Utilities for RBAC\n * @package @jmruthers/pace-core\n * @module RBAC/EventContext\n * @since 1.0.0\n * \n * This module provides utilities for event-based RBAC operations where\n * the organization context is derived from the event context.\n */\n\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport { Database } from '../../types/database';\nimport { UUID, Scope } from '../types';\n\n/**\n * Cache for organisation derivation from event\n * Key: eventId, Value: organisationId | null\n * Maximum cache size to prevent memory leaks\n */\nconst orgDerivationCache = new Map<string, UUID | null>();\nconst MAX_CACHE_SIZE = 100; // Limit cache to 100 entries\n\n/**\n * Clear cache entry for a specific event (useful if event's org changes)\n * @param eventId - Event ID to clear from cache\n */\nexport function clearOrgDerivationCache(eventId: string): void {\n orgDerivationCache.delete(eventId);\n}\n\n/**\n * Clear all cached organisation derivations\n */\nexport function clearAllOrgDerivationCache(): void {\n orgDerivationCache.clear();\n}\n\n/**\n * Get organization ID from event ID\n * \n * Uses caching to avoid repeated database queries for the same event.\n * Cache is limited to prevent memory leaks.\n * \n * @param supabase - Supabase client\n * @param eventId - Event ID\n * @returns Promise resolving to organization ID or null\n */\nexport async function getOrganisationFromEvent(\n supabase: SupabaseClient<Database>,\n eventId: string\n): Promise<UUID | null> {\n // Check cache first\n if (orgDerivationCache.has(eventId)) {\n return orgDerivationCache.get(eventId) ?? null;\n }\n\n // Query database\n const { data, error } = await supabase\n .from('core_events')\n .select('organisation_id')\n .eq('event_id', eventId)\n .single() as { data: { organisation_id: string } | null; error: any };\n\n let organisationId: UUID | null = null;\n\n if (error || !data) {\n organisationId = null;\n } else if (data.organisation_id) {\n organisationId = data.organisation_id;\n } else {\n // organisation_id is null or undefined\n organisationId = null;\n }\n\n // Cache the result (with size limit to prevent memory leaks)\n if (orgDerivationCache.size >= MAX_CACHE_SIZE) {\n // Remove oldest entry (first key in Map)\n const firstKey = orgDerivationCache.keys().next().value;\n if (firstKey) {\n orgDerivationCache.delete(firstKey);\n }\n }\n orgDerivationCache.set(eventId, organisationId);\n\n return organisationId;\n}\n\n/**\n * Create a complete scope from event context\n * \n * @param supabase - Supabase client\n * @param eventId - Event ID\n * @param appId - Optional app ID\n * @returns Promise resolving to complete scope\n */\nexport async function createScopeFromEvent(\n supabase: SupabaseClient<Database>,\n eventId: string,\n appId?: UUID\n): Promise<Scope | null> {\n const organisationId = await getOrganisationFromEvent(supabase, eventId);\n \n if (!organisationId) {\n return null;\n }\n\n return {\n organisationId,\n eventId,\n appId\n };\n}\n\n/**\n * Check if a scope is event-based (has eventId but no explicit organisationId)\n * \n * @param scope - Permission scope\n * @returns True if scope is event-based\n */\nexport function isEventBasedScope(scope: Scope): boolean {\n return !scope.organisationId && !!scope.eventId;\n}\n\n/**\n * Validate that an event-based scope has the required context\n * \n * @param scope - Permission scope\n * @returns True if scope is valid for event-based operations\n */\nexport function isValidEventBasedScope(scope: Scope): boolean {\n return isEventBasedScope(scope) && !!scope.eventId;\n}\n","/**\n * Context Validator for RBAC\n * @package @jmruthers/pace-core\n * @module RBAC/ContextValidator\n * @since 1.0.0\n * \n * Centralized validation for RBAC context requirements based on app configuration.\n * Enforces app-specific context rules with single primary context:\n * - requires_event = TRUE: Event is PRIMARY context, org derived from event (org not required in input)\n * - requires_event = FALSE: Organisation is PRIMARY context, event optional\n * - PORTAL/ADMIN apps: Both contexts optional (allows users to view/edit own profiles, super admin access)\n * \n * Key principle: Only one primary context is required based on app config. The other is derived or optional.\n */\n\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport { Database } from '../../types/database';\nimport { UUID, Scope } from '../types';\nimport { EventContextRequiredError, OrganisationContextRequiredError } from '../types';\nimport { getOrganisationFromEvent } from './eventContext';\nimport { createLogger } from '../../utils/core/logger';\n\nconst log = createLogger('ContextValidator');\n\n/**\n * Page scope type - determines what context is required for a page\n * This is the single source of truth for page scoping.\n */\nexport type PageScopeType = 'event' | 'organisation' | 'both';\n\n/**\n * Check if an app allows optional contexts (both organisation and event optional)\n * @param appName - App name to check\n * @returns True if app allows optional contexts\n */\nfunction allowsOptionalContexts(appName?: string): boolean {\n return appName === 'PORTAL' || appName === 'ADMIN';\n}\n\nexport interface ContextValidationResult {\n isValid: boolean;\n resolvedScope: Scope | null;\n error: Error | null;\n}\n\n/**\n * Context Validator class\n * \n * Validates and resolves RBAC scope based on app configuration requirements.\n */\nexport class ContextValidator {\n\n /**\n * Derive organisation ID from event ID\n * \n * @param supabase - Supabase client\n * @param eventId - Event ID\n * @returns Organisation ID or null\n */\n static async deriveOrgFromEvent(\n supabase: SupabaseClient<Database>,\n eventId: string\n ): Promise<UUID | null> {\n return getOrganisationFromEvent(supabase, eventId);\n }\n\n /**\n * Resolve scope based on page-level scope_type\n * \n * This method handles page-level scoping. All pages have explicit scope_type set.\n * Used for hybrid apps like pace-mint that have both event and organisation pages.\n * \n * @param scope - Current scope\n * @param pageScopeType - Page scope type ('event', 'organisation', or 'both')\n * @param appName - App name (for PORTAL/ADMIN special case)\n * @param supabase - Supabase client (for deriving org from event)\n * @returns Resolved scope with all required context\n */\n static async resolveScopeForPage(\n scope: Scope,\n pageScopeType: PageScopeType,\n appName?: string,\n supabase?: SupabaseClient<Database> | null\n ): Promise<ContextValidationResult> {\n // Use page-level scope (single source of truth)\n const effectiveScopeType = pageScopeType;\n \n // Handle 'both' scope - requires both contexts available, but can use either\n if (effectiveScopeType === 'both') {\n // For 'both' pages, we need at least one context (org or event)\n // Both will be checked during permission evaluation\n // For PORTAL/ADMIN apps, both contexts are optional\n if (!scope.organisationId && !scope.eventId) {\n if (allowsOptionalContexts(appName)) {\n return {\n isValid: true,\n resolvedScope: {\n organisationId: undefined,\n eventId: undefined,\n appId: scope.appId\n },\n error: null\n };\n }\n return {\n isValid: false,\n resolvedScope: null,\n error: new Error('Page requires either organisation or event context')\n };\n }\n \n // Derive org from event if event is provided but org is not\n let organisationId = scope.organisationId;\n if (!organisationId && scope.eventId && supabase) {\n try {\n const derivedOrgId = await this.deriveOrgFromEvent(supabase, scope.eventId);\n organisationId = derivedOrgId || undefined;\n } catch (error) {\n log.warn('Failed to derive org from event for both-scope page:', error);\n // Continue without org - permission check will handle it\n }\n }\n \n return {\n isValid: true,\n resolvedScope: {\n organisationId,\n eventId: scope.eventId,\n appId: scope.appId\n },\n error: null\n };\n }\n \n // Handle 'event' scope - requires event context\n if (effectiveScopeType === 'event') {\n if (!scope.eventId) {\n // For PORTAL/ADMIN apps, event context is optional\n if (allowsOptionalContexts(appName)) {\n return {\n isValid: true,\n resolvedScope: {\n organisationId: scope.organisationId,\n eventId: undefined,\n appId: scope.appId\n },\n error: null\n };\n }\n return {\n isValid: false,\n resolvedScope: null,\n error: new EventContextRequiredError()\n };\n }\n \n // Derive organisationId from event if not provided\n let organisationId: UUID | undefined = scope.organisationId;\n if (!organisationId && supabase && scope.eventId) {\n try {\n const derivedOrgId = await this.deriveOrgFromEvent(supabase, scope.eventId);\n organisationId = derivedOrgId || undefined;\n if (!organisationId) {\n return {\n isValid: false,\n resolvedScope: null,\n error: new Error('Could not resolve organisation from event context')\n };\n }\n } catch (error) {\n log.error('Failed to derive org from event:', error);\n return {\n isValid: false,\n resolvedScope: null,\n error: error instanceof Error ? error : new Error('Failed to derive organisation from event')\n };\n }\n }\n \n return {\n isValid: true,\n resolvedScope: {\n organisationId,\n eventId: scope.eventId,\n appId: scope.appId\n },\n error: null\n };\n }\n \n // Handle 'organisation' scope - requires organisation context\n if (effectiveScopeType === 'organisation') {\n if (!scope.organisationId) {\n // For PORTAL/ADMIN apps, organisation context is optional\n if (allowsOptionalContexts(appName)) {\n return {\n isValid: true,\n resolvedScope: {\n organisationId: undefined,\n eventId: scope.eventId,\n appId: scope.appId\n },\n error: null\n };\n }\n return {\n isValid: false,\n resolvedScope: null,\n error: new OrganisationContextRequiredError()\n };\n }\n \n return {\n isValid: true,\n resolvedScope: {\n organisationId: scope.organisationId,\n eventId: scope.eventId, // Event is optional for org-scoped pages\n appId: scope.appId\n },\n error: null\n };\n }\n \n // Fallback (should not happen)\n return {\n isValid: false,\n resolvedScope: null,\n error: new Error('Invalid scope type')\n };\n }\n\n}\n\n"],"mappings":"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","names":[]}
@@ -11,7 +11,7 @@ import {
11
11
  PermissionDeniedError,
12
12
  RBACError,
13
13
  RBACNotInitializedError
14
- } from "./chunk-QRPVRXYT.js";
14
+ } from "./chunk-AFVQODI2.js";
15
15
  import {
16
16
  createLogger
17
17
  } from "./chunk-PWLANIRT.js";
@@ -1990,4 +1990,4 @@ export {
1990
1990
  invalidateAppCache,
1991
1991
  clearCache
1992
1992
  };
1993
- //# sourceMappingURL=chunk-RWEBCB47.js.map
1993
+ //# sourceMappingURL=chunk-EFN2EIMK.js.map
@@ -5,22 +5,22 @@ import {
5
5
  useMultiplePermissions,
6
6
  useResolvedScope,
7
7
  useSecureSupabase
8
- } from "./chunk-YDQHOZNA.js";
8
+ } from "./chunk-HU2C6SSC.js";
9
9
  import {
10
10
  useOrganisationSecurity
11
- } from "./chunk-6Z7LTB3D.js";
11
+ } from "./chunk-NTM7ZSB6.js";
12
12
  import {
13
13
  useUnifiedAuth
14
- } from "./chunk-DWUBLJJM.js";
14
+ } from "./chunk-IHB5DR3H.js";
15
15
  import {
16
16
  RBACCache,
17
17
  getRBACConfig,
18
18
  getRBACLogger,
19
19
  rbacCache
20
- } from "./chunk-RWEBCB47.js";
20
+ } from "./chunk-EFN2EIMK.js";
21
21
  import {
22
22
  RBACNotInitializedError
23
- } from "./chunk-QRPVRXYT.js";
23
+ } from "./chunk-AFVQODI2.js";
24
24
  import {
25
25
  createLogger,
26
26
  logger
@@ -192,7 +192,7 @@ var PagePermissionGuardComponent = ({
192
192
  const checkSuperAdmin = async () => {
193
193
  const startTime = Date.now();
194
194
  try {
195
- const { isSuperAdmin: checkSuperAdmin2 } = await import("./api-IAGWF3ZG.js");
195
+ const { isSuperAdmin: checkSuperAdmin2 } = await import("./api-O6HTBX5Y.js");
196
196
  const timeoutPromise = new Promise((_, reject) => {
197
197
  setTimeout(() => reject(new Error("Super admin check timeout")), 1e4);
198
198
  });
@@ -1380,7 +1380,7 @@ function withPermissionGuard(config, handler) {
1380
1380
  if (!userId || !organisationId) {
1381
1381
  throw new Error("User context required for permission check");
1382
1382
  }
1383
- const { isPermitted: isPermitted2 } = await import("./api-IAGWF3ZG.js");
1383
+ const { isPermitted: isPermitted2 } = await import("./api-O6HTBX5Y.js");
1384
1384
  const hasPermission2 = await isPermitted2({
1385
1385
  userId,
1386
1386
  scope: { organisationId, eventId, appId },
@@ -1403,7 +1403,7 @@ function withAccessLevelGuard(minLevel, handler) {
1403
1403
  if (!userId || !organisationId) {
1404
1404
  throw new Error("User context required for access level check");
1405
1405
  }
1406
- const { getAccessLevel: getAccessLevel2 } = await import("./api-IAGWF3ZG.js");
1406
+ const { getAccessLevel: getAccessLevel2 } = await import("./api-O6HTBX5Y.js");
1407
1407
  const accessLevel = await getAccessLevel2({
1408
1408
  userId,
1409
1409
  scope: { organisationId, eventId, appId }
@@ -1428,7 +1428,7 @@ function withRoleGuard(config, handler) {
1428
1428
  throw new Error("User context required for role check");
1429
1429
  }
1430
1430
  if (config.globalRoles && config.globalRoles.length > 0) {
1431
- const { isSuperAdmin } = await import("./api-IAGWF3ZG.js");
1431
+ const { isSuperAdmin } = await import("./api-O6HTBX5Y.js");
1432
1432
  const isSuper = await isSuperAdmin(userId);
1433
1433
  if (isSuper) {
1434
1434
  if (organisationId) {
@@ -1454,14 +1454,14 @@ function withRoleGuard(config, handler) {
1454
1454
  }
1455
1455
  }
1456
1456
  if (config.organisationRoles && config.organisationRoles.length > 0) {
1457
- const { isOrganisationAdmin } = await import("./api-IAGWF3ZG.js");
1457
+ const { isOrganisationAdmin } = await import("./api-O6HTBX5Y.js");
1458
1458
  const isOrgAdmin = await isOrganisationAdmin(userId, organisationId);
1459
1459
  if (!isOrgAdmin && config.requireAll !== false) {
1460
1460
  throw new Error(`Organisation admin role required`);
1461
1461
  }
1462
1462
  }
1463
1463
  if (config.eventAppRoles && config.eventAppRoles.length > 0 && eventId && appId) {
1464
- const { isEventAdmin } = await import("./api-IAGWF3ZG.js");
1464
+ const { isEventAdmin } = await import("./api-O6HTBX5Y.js");
1465
1465
  const isEventAdminUser = await isEventAdmin(userId, { organisationId, eventId, appId });
1466
1466
  if (!isEventAdminUser && config.requireAll !== false) {
1467
1467
  throw new Error(`Event admin role required`);
@@ -1501,7 +1501,7 @@ function createRBACMiddleware(config) {
1501
1501
  );
1502
1502
  if (protectedRoute) {
1503
1503
  try {
1504
- const { isPermitted: isPermitted2 } = await import("./api-IAGWF3ZG.js");
1504
+ const { isPermitted: isPermitted2 } = await import("./api-O6HTBX5Y.js");
1505
1505
  const hasPermission2 = await isPermitted2({
1506
1506
  userId,
1507
1507
  scope: { organisationId },
@@ -1528,7 +1528,7 @@ function createRBACExpressMiddleware(config) {
1528
1528
  return res.status(401).json({ error: "User context required" });
1529
1529
  }
1530
1530
  try {
1531
- const { isPermitted: isPermitted2 } = await import("./api-IAGWF3ZG.js");
1531
+ const { isPermitted: isPermitted2 } = await import("./api-O6HTBX5Y.js");
1532
1532
  const hasPermission2 = await isPermitted2({
1533
1533
  userId,
1534
1534
  scope: { organisationId, eventId, appId },
@@ -1626,31 +1626,31 @@ var EVENT_APP_PERMISSIONS = {
1626
1626
  DELETE_EVENT_SETTINGS: "delete:event.settings"
1627
1627
  };
1628
1628
  var PAGE_PERMISSIONS = {
1629
- // General page access
1629
+ // General page access (generic - used for wildcard checks)
1630
1630
  READ_PAGE: "read:page",
1631
1631
  CREATE_PAGE: "create:page",
1632
1632
  UPDATE_PAGE: "update:page",
1633
1633
  DELETE_PAGE: "delete:page",
1634
1634
  // Admin pages
1635
- READ_ADMIN: "read:admin",
1636
- CREATE_ADMIN: "create:admin",
1637
- UPDATE_ADMIN: "update:admin",
1638
- DELETE_ADMIN: "delete:admin",
1635
+ READ_ADMIN: "read:page.admin",
1636
+ CREATE_ADMIN: "create:page.admin",
1637
+ UPDATE_ADMIN: "update:page.admin",
1638
+ DELETE_ADMIN: "delete:page.admin",
1639
1639
  // Dashboard pages
1640
- READ_DASHBOARD: "read:dashboard",
1641
- CREATE_DASHBOARD: "create:dashboard",
1642
- UPDATE_DASHBOARD: "update:dashboard",
1643
- DELETE_DASHBOARD: "delete:dashboard",
1640
+ READ_DASHBOARD: "read:page.dashboard",
1641
+ CREATE_DASHBOARD: "create:page.dashboard",
1642
+ UPDATE_DASHBOARD: "update:page.dashboard",
1643
+ DELETE_DASHBOARD: "delete:page.dashboard",
1644
1644
  // Settings pages
1645
- READ_SETTINGS: "read:settings",
1646
- CREATE_SETTINGS: "create:settings",
1647
- UPDATE_SETTINGS: "update:settings",
1648
- DELETE_SETTINGS: "delete:settings",
1645
+ READ_SETTINGS: "read:page.settings",
1646
+ CREATE_SETTINGS: "create:page.settings",
1647
+ UPDATE_SETTINGS: "update:page.settings",
1648
+ DELETE_SETTINGS: "delete:page.settings",
1649
1649
  // Reports pages
1650
- READ_REPORTS: "read:reports",
1651
- CREATE_REPORTS: "create:reports",
1652
- UPDATE_REPORTS: "update:reports",
1653
- DELETE_REPORTS: "delete:reports"
1650
+ READ_REPORTS: "read:page.reports",
1651
+ CREATE_REPORTS: "create:page.reports",
1652
+ UPDATE_REPORTS: "update:page.reports",
1653
+ DELETE_REPORTS: "delete:page.reports"
1654
1654
  };
1655
1655
  function isValidPermission(permission) {
1656
1656
  const pattern = /^(read|create|update|delete):[a-z0-9]+(\.[a-z0-9]+)*$|^(read|create|update|delete):\*$/;
@@ -2050,4 +2050,4 @@ export {
2050
2050
  getDirectSupabaseAuthFixes,
2051
2051
  getQuickFixes
2052
2052
  };
2053
- //# sourceMappingURL=chunk-CNCQDFLN.js.map
2053
+ //# sourceMappingURL=chunk-G7QEZTYQ.js.map