@jmruthers/pace-core 0.5.121 → 0.5.123
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/dist/{AuthService-D4646R4b.d.ts → AuthService-DYuQPJj6.d.ts} +0 -9
- package/dist/{DataTable-DGZDJUYM.js → DataTable-WTS4IRF2.js} +7 -8
- package/dist/{PublicLoadingSpinner-DgDWTFqn.d.ts → PublicLoadingSpinner-CaoRbHvJ.d.ts} +30 -4
- package/dist/{UnifiedAuthProvider-UACKFATV.js → UnifiedAuthProvider-6C47WIML.js} +3 -4
- package/dist/{chunk-D6BOFXYR.js → chunk-35ZDPMBM.js} +3 -3
- package/dist/{chunk-CGURJ27Z.js → chunk-4MXVZVNS.js} +2 -2
- package/dist/{chunk-ZYJ6O5CA.js → chunk-C43QIDN3.js} +2 -2
- package/dist/{chunk-VKOCWWVY.js → chunk-CX5M4ZAG.js} +1 -6
- package/dist/{chunk-VKOCWWVY.js 3.map → chunk-CX5M4ZAG.js.map} +1 -1
- package/dist/{chunk-HFBOFZ3Z.js → chunk-DHMFMXFV.js} +258 -243
- package/dist/chunk-DHMFMXFV.js.map +1 -0
- package/dist/{chunk-RIEJGKD3.js → chunk-ESJTIADP.js} +15 -6
- package/dist/{chunk-RIEJGKD3.js.map → chunk-ESJTIADP.js.map} +1 -1
- package/dist/{chunk-SMJZMKYN.js → chunk-GEVIB2UB.js} +43 -10
- package/dist/chunk-GEVIB2UB.js.map +1 -0
- package/dist/{chunk-TDNI6ZWL.js → chunk-IJOZZOGT.js} +7 -7
- package/dist/chunk-IJOZZOGT.js.map +1 -0
- package/dist/{chunk-GZRXOUBE.js → chunk-M6DDYFUD.js} +2 -2
- package/dist/chunk-M6DDYFUD.js.map +1 -0
- package/dist/{chunk-B4GZ2BXO.js → chunk-NZGLXZGP.js} +3 -3
- package/dist/{chunk-NZ32EONV.js → chunk-QWNJCQXZ.js} +2 -2
- package/dist/{chunk-FKFHZUGF.js → chunk-XN6GWKMV.js} +43 -56
- package/dist/chunk-XN6GWKMV.js.map +1 -0
- package/dist/{chunk-BHWIUEYH.js → chunk-ZBLK676C.js} +1 -61
- package/dist/chunk-ZBLK676C.js.map +1 -0
- package/dist/{chunk-QPI2CCBA.js → chunk-ZPJMYGEP.js} +149 -96
- package/dist/chunk-ZPJMYGEP.js.map +1 -0
- package/dist/components.d.ts +1 -1
- package/dist/components.js +11 -11
- package/dist/{formatting-B1jSqgl-.d.ts → formatting-DFcCxUEk.d.ts} +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +9 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.js +19 -17
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +2 -3
- package/dist/rbac/index.js +7 -8
- package/dist/styles/index.d.ts +1 -1
- package/dist/styles/index.js +5 -3
- package/dist/theming/runtime.d.ts +73 -1
- package/dist/theming/runtime.js +5 -5
- package/dist/{usePublicRouteParams-BdF8bZgs.d.ts → usePublicRouteParams-Dyt1tzI9.d.ts} +60 -8
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +5 -5
- 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/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +6 -6
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +6 -6
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- 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/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/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.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 +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- 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/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 +1 -1
- 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 +1 -1
- 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/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +7 -7
- package/docs/api/interfaces/PublicErrorBoundaryState.md +5 -5
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +7 -7
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +51 -12
- package/docs/api/interfaces/PublicPageLayoutProps.md +72 -12
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- 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/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/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.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 +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.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 +140 -30
- package/docs/best-practices/README.md +1 -1
- package/docs/implementation-guides/datatable-filtering.md +313 -0
- package/docs/implementation-guides/datatable-rbac-usage.md +317 -0
- package/docs/implementation-guides/hierarchical-datatable.md +850 -0
- package/docs/implementation-guides/large-datasets.md +281 -0
- package/docs/implementation-guides/performance.md +403 -0
- package/docs/implementation-guides/public-pages.md +4 -4
- package/docs/migration/quick-migration-guide.md +320 -0
- package/docs/rbac/quick-start.md +16 -16
- package/docs/troubleshooting/README.md +4 -4
- package/docs/troubleshooting/cake-page-permission-guard-issue-summary.md +1 -1
- package/docs/troubleshooting/debugging.md +1117 -0
- package/docs/troubleshooting/migration.md +918 -0
- package/examples/public-pages/CorrectPublicPageImplementation.tsx +30 -30
- package/examples/public-pages/PublicEventPage.tsx +41 -41
- package/examples/public-pages/PublicPageApp.tsx +33 -33
- package/examples/public-pages/PublicPageUsageExample.tsx +30 -30
- package/package.json +4 -4
- package/src/__tests__/hooks/usePermissions.test.ts +265 -0
- package/src/components/DataTable/DataTable.test.tsx +9 -38
- package/src/components/DataTable/DataTable.tsx +0 -7
- package/src/components/DataTable/components/DataTableCore.tsx +66 -136
- package/src/components/DataTable/components/DataTableModals.tsx +25 -22
- package/src/components/DataTable/components/EditableRow.tsx +118 -42
- package/src/components/DataTable/components/UnifiedTableBody.tsx +129 -76
- package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +33 -14
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +17 -5
- package/src/components/DataTable/utils/exportUtils.ts +3 -2
- package/src/components/Dialog/Dialog.tsx +1 -1
- package/src/components/Dialog/README.md +24 -24
- package/src/components/Dialog/examples/BasicHtmlTest.tsx +2 -2
- package/src/components/Dialog/examples/DebugHtmlExample.tsx +6 -6
- package/src/components/Dialog/examples/HtmlDialogExample.tsx +2 -2
- package/src/components/Dialog/examples/SimpleHtmlTest.tsx +3 -3
- package/src/components/Dialog/examples/__tests__/SimpleHtmlTest.test.tsx +4 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +12 -1
- package/src/components/PublicLayout/EventLogo.tsx +175 -0
- package/src/components/PublicLayout/PublicErrorBoundary.tsx +22 -18
- package/src/components/PublicLayout/PublicLoadingSpinner.tsx +22 -14
- package/src/components/PublicLayout/PublicPageHeader.tsx +133 -40
- package/src/components/PublicLayout/PublicPageLayout.tsx +75 -72
- package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +1 -1
- package/src/components/PublicLayout/__tests__/PublicLoadingSpinner.test.tsx +8 -8
- package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +23 -16
- package/src/components/PublicLayout/__tests__/PublicPageLayout.test.tsx +86 -14
- package/src/examples/CorrectPublicPageImplementation.tsx +30 -30
- package/src/examples/PublicEventPage.tsx +41 -41
- package/src/examples/PublicPageApp.tsx +33 -33
- package/src/examples/PublicPageUsageExample.tsx +30 -30
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +583 -0
- package/src/hooks/__tests__/usePublicRouteParams.unit.test.ts +10 -3
- package/src/hooks/index.ts +1 -1
- package/src/hooks/public/usePublicEventLogo.ts +285 -0
- package/src/hooks/public/usePublicRouteParams.ts +21 -4
- package/src/hooks/useEventTheme.test.ts +119 -43
- package/src/hooks/useEventTheme.ts +84 -55
- package/src/index.ts +3 -1
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +630 -0
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +667 -0
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +647 -0
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +496 -0
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +496 -0
- package/src/rbac/secureClient.ts +4 -2
- package/src/services/EventService.ts +0 -66
- package/src/services/__tests__/EventService.eventColours.test.ts +44 -40
- package/src/styles/index.ts +1 -1
- package/src/theming/__tests__/parseEventColours.test.ts +209 -0
- package/src/theming/parseEventColours.ts +123 -0
- package/src/theming/runtime.ts +3 -0
- package/src/types/__tests__/file-reference.test.ts +447 -0
- package/src/utils/formatDate.test.ts +11 -11
- package/src/utils/formatting.ts +3 -2
- package/dist/chunk-BDZUMRBD.js 3.map +0 -1
- package/dist/chunk-BHWIUEYH.js.map +0 -1
- package/dist/chunk-CGURJ27Z.js.map +0 -1
- package/dist/chunk-FKFHZUGF.js.map +0 -1
- package/dist/chunk-GKHF54DI 2.js +0 -619
- package/dist/chunk-GKHF54DI.js 2.map +0 -1
- package/dist/chunk-GZRXOUBE.js.map +0 -1
- package/dist/chunk-HFBOFZ3Z.js.map +0 -1
- package/dist/chunk-NZ32EONV.js.map +0 -1
- package/dist/chunk-O3NWNXDY 2.js +0 -76
- package/dist/chunk-QPI2CCBA.js.map +0 -1
- package/dist/chunk-SMJZMKYN.js.map +0 -1
- package/dist/chunk-TDNI6ZWL.js 2.map +0 -1
- package/dist/chunk-TDNI6ZWL.js.map +0 -1
- package/dist/chunk-VKOCWWVY.js.map +0 -1
- package/dist/chunk-WP5I5GLN 2.js +0 -1564
- package/dist/index 3.js +0 -856
- package/dist/providers 3.js +0 -38
- package/dist/providers.js 3.map +0 -1
- package/dist/types 3.js +0 -128
- package/dist/types.js 3.map +0 -1
- package/dist/useInactivityTracker-MRUU55XI.js 3.map +0 -1
- package/dist/utils.js 3.map +0 -1
- package/dist/validation 3.js +0 -479
- package/src/styles/semantic.css +0 -24
- /package/dist/{DataTable-DGZDJUYM.js.map → DataTable-WTS4IRF2.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-UACKFATV.js.map → UnifiedAuthProvider-6C47WIML.js.map} +0 -0
- /package/dist/{chunk-D6BOFXYR.js.map → chunk-35ZDPMBM.js.map} +0 -0
- /package/dist/{chunk-CGURJ27Z.js 2.map → chunk-4MXVZVNS.js.map} +0 -0
- /package/dist/{chunk-ZYJ6O5CA.js.map → chunk-C43QIDN3.js.map} +0 -0
- /package/dist/{chunk-B4GZ2BXO.js.map → chunk-NZGLXZGP.js.map} +0 -0
- /package/dist/{chunk-NZ32EONV.js 2.map → chunk-QWNJCQXZ.js.map} +0 -0
package/dist/chunk-WP5I5GLN 2.js
DELETED
|
@@ -1,1564 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createAuditManager,
|
|
3
|
-
emitAuditEvent,
|
|
4
|
-
setGlobalAuditManager
|
|
5
|
-
} from "./chunk-3DBFLLLU.js";
|
|
6
|
-
|
|
7
|
-
// src/rbac/types.ts
|
|
8
|
-
var RBACError = class extends Error {
|
|
9
|
-
constructor(message, code, context) {
|
|
10
|
-
super(message);
|
|
11
|
-
this.code = code;
|
|
12
|
-
this.context = context;
|
|
13
|
-
this.name = "RBACError";
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
var PermissionDeniedError = class extends RBACError {
|
|
17
|
-
constructor(permission, context) {
|
|
18
|
-
super(
|
|
19
|
-
`Permission denied: ${permission}`,
|
|
20
|
-
"PERMISSION_DENIED",
|
|
21
|
-
{ permission, ...context }
|
|
22
|
-
);
|
|
23
|
-
this.name = "PermissionDeniedError";
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
var OrganisationContextRequiredError = class extends RBACError {
|
|
27
|
-
constructor() {
|
|
28
|
-
super(
|
|
29
|
-
"Organisation context is required for this operation",
|
|
30
|
-
"ORGANISATION_CONTEXT_REQUIRED"
|
|
31
|
-
);
|
|
32
|
-
this.name = "OrganisationContextRequiredError";
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
var RBACNotInitializedError = class extends RBACError {
|
|
36
|
-
constructor() {
|
|
37
|
-
super(
|
|
38
|
-
"RBAC system not initialized. Please call setupRBAC(supabase) before using any RBAC components or hooks. See: https://docs.pace-core.dev/rbac/setup",
|
|
39
|
-
"RBAC_NOT_INITIALIZED"
|
|
40
|
-
);
|
|
41
|
-
this.name = "RBACNotInitializedError";
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
var InvalidScopeError = class extends RBACError {
|
|
45
|
-
constructor(scope, reason) {
|
|
46
|
-
super(
|
|
47
|
-
`Invalid scope provided: ${JSON.stringify(scope)}. ${reason}`,
|
|
48
|
-
"INVALID_SCOPE",
|
|
49
|
-
{ scope, reason }
|
|
50
|
-
);
|
|
51
|
-
this.name = "InvalidScopeError";
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
var MissingUserContextError = class extends RBACError {
|
|
55
|
-
constructor() {
|
|
56
|
-
super(
|
|
57
|
-
"User context is required but not available. Make sure to wrap your app with an auth provider.",
|
|
58
|
-
"MISSING_USER_CONTEXT"
|
|
59
|
-
);
|
|
60
|
-
this.name = "MissingUserContextError";
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// src/rbac/cache.ts
|
|
65
|
-
var RBACCache = class {
|
|
66
|
-
constructor() {
|
|
67
|
-
this.cache = /* @__PURE__ */ new Map();
|
|
68
|
-
this.TTL = 60 * 1e3;
|
|
69
|
-
// 60 seconds
|
|
70
|
-
this.invalidationCallbacks = /* @__PURE__ */ new Set();
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Get a value from the cache
|
|
74
|
-
*
|
|
75
|
-
* @param key - Cache key
|
|
76
|
-
* @returns Cached value or null if not found/expired
|
|
77
|
-
*/
|
|
78
|
-
get(key) {
|
|
79
|
-
const entry = this.cache.get(key);
|
|
80
|
-
if (!entry) {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
if (Date.now() > entry.expires) {
|
|
84
|
-
this.cache.delete(key);
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
return entry.data;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Set a value in the cache
|
|
91
|
-
*
|
|
92
|
-
* @param key - Cache key
|
|
93
|
-
* @param data - Data to cache
|
|
94
|
-
* @param ttl - Time to live in milliseconds (defaults to 60s)
|
|
95
|
-
*/
|
|
96
|
-
set(key, data, ttl = this.TTL) {
|
|
97
|
-
const expires = ttl <= 0 ? Date.now() - 1 : Date.now() + ttl;
|
|
98
|
-
this.cache.set(key, {
|
|
99
|
-
data,
|
|
100
|
-
expires
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Delete a specific key from the cache
|
|
105
|
-
*
|
|
106
|
-
* @param key - Cache key to delete
|
|
107
|
-
*/
|
|
108
|
-
delete(key) {
|
|
109
|
-
this.cache.delete(key);
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Invalidate cache entries matching a pattern
|
|
113
|
-
*
|
|
114
|
-
* @param pattern - Pattern to match against cache keys
|
|
115
|
-
*/
|
|
116
|
-
invalidate(pattern) {
|
|
117
|
-
const trimmedPattern = pattern?.trim();
|
|
118
|
-
if (!trimmedPattern) {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
const matcher = this.createMatcher(trimmedPattern);
|
|
122
|
-
const keysToDelete = [];
|
|
123
|
-
for (const key of this.cache.keys()) {
|
|
124
|
-
if (matcher(key)) {
|
|
125
|
-
keysToDelete.push(key);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
keysToDelete.forEach((key) => this.cache.delete(key));
|
|
129
|
-
this.invalidationCallbacks.forEach((callback) => callback(trimmedPattern));
|
|
130
|
-
}
|
|
131
|
-
createMatcher(pattern) {
|
|
132
|
-
if (pattern.includes("*")) {
|
|
133
|
-
const escapedSegments = pattern.split("*").map((segment) => segment.replace(/[|\\{}()[\]^$+?.-]/g, "\\$&"));
|
|
134
|
-
const regexPattern = escapedSegments.join(".*");
|
|
135
|
-
const regex = new RegExp(regexPattern);
|
|
136
|
-
return (key) => regex.test(key);
|
|
137
|
-
}
|
|
138
|
-
return (key) => key.includes(pattern);
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Clear all cache entries
|
|
142
|
-
*/
|
|
143
|
-
clear() {
|
|
144
|
-
this.cache.clear();
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Get cache statistics
|
|
148
|
-
*/
|
|
149
|
-
getStats() {
|
|
150
|
-
return {
|
|
151
|
-
size: this.cache.size,
|
|
152
|
-
ttl: this.TTL,
|
|
153
|
-
keys: Array.from(this.cache.keys())
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Add an invalidation callback
|
|
158
|
-
*
|
|
159
|
-
* @param callback - Function to call when cache is invalidated
|
|
160
|
-
*/
|
|
161
|
-
onInvalidate(callback) {
|
|
162
|
-
this.invalidationCallbacks.add(callback);
|
|
163
|
-
return () => {
|
|
164
|
-
this.invalidationCallbacks.delete(callback);
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Generate cache key for permission check (simplified signature)
|
|
169
|
-
*
|
|
170
|
-
* @param userId - User ID
|
|
171
|
-
* @param permission - Permission string
|
|
172
|
-
* @param organisationId - Organisation ID (optional)
|
|
173
|
-
* @param eventId - Event ID (optional)
|
|
174
|
-
* @param appId - App ID (optional)
|
|
175
|
-
* @param pageId - Page ID (optional)
|
|
176
|
-
* @returns String cache key
|
|
177
|
-
*/
|
|
178
|
-
static generateKey(userId, permission, organisationId, eventId, appId, pageId) {
|
|
179
|
-
const parts = [
|
|
180
|
-
"perm",
|
|
181
|
-
userId,
|
|
182
|
-
organisationId || "null",
|
|
183
|
-
eventId || "null",
|
|
184
|
-
appId || "null",
|
|
185
|
-
permission || "null",
|
|
186
|
-
pageId || "null"
|
|
187
|
-
];
|
|
188
|
-
return parts.join(":");
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Generate cache key for permission check (object signature)
|
|
192
|
-
*
|
|
193
|
-
* @param key - Permission cache key object
|
|
194
|
-
* @returns String cache key
|
|
195
|
-
*/
|
|
196
|
-
static generatePermissionKey(key) {
|
|
197
|
-
const parts = [
|
|
198
|
-
"perm",
|
|
199
|
-
key.userId,
|
|
200
|
-
key.organisationId || "null",
|
|
201
|
-
key.eventId || "null",
|
|
202
|
-
key.appId || "null",
|
|
203
|
-
key.permission || "null",
|
|
204
|
-
key.pageId || "null"
|
|
205
|
-
];
|
|
206
|
-
return parts.join(":");
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Generate cache key for access level
|
|
210
|
-
*
|
|
211
|
-
* @param userId - User ID
|
|
212
|
-
* @param organisationId - Organisation ID
|
|
213
|
-
* @param eventId - Event ID (optional)
|
|
214
|
-
* @param appId - App ID (optional)
|
|
215
|
-
* @returns String cache key
|
|
216
|
-
*/
|
|
217
|
-
static generateAccessLevelKey(userId, organisationId, eventId, appId) {
|
|
218
|
-
const parts = [
|
|
219
|
-
"access",
|
|
220
|
-
userId,
|
|
221
|
-
organisationId,
|
|
222
|
-
eventId || "null",
|
|
223
|
-
appId || "null"
|
|
224
|
-
];
|
|
225
|
-
return parts.join(":");
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Generate cache key for permission map
|
|
229
|
-
*
|
|
230
|
-
* @param userId - User ID
|
|
231
|
-
* @param organisationId - Organisation ID
|
|
232
|
-
* @param eventId - Event ID (optional)
|
|
233
|
-
* @param appId - App ID (optional)
|
|
234
|
-
* @returns String cache key
|
|
235
|
-
*/
|
|
236
|
-
static generatePermissionMapKey(userId, organisationId, eventId, appId) {
|
|
237
|
-
const parts = [
|
|
238
|
-
"map",
|
|
239
|
-
userId,
|
|
240
|
-
organisationId,
|
|
241
|
-
eventId || "null",
|
|
242
|
-
appId || "null"
|
|
243
|
-
];
|
|
244
|
-
return parts.join(":");
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
var rbacCache = new RBACCache();
|
|
248
|
-
var CACHE_PATTERNS = {
|
|
249
|
-
USER: (userId) => `:${userId}:`,
|
|
250
|
-
ORGANISATION: (organisationId) => `:${organisationId}:`,
|
|
251
|
-
EVENT: (eventId) => `:${eventId}:`,
|
|
252
|
-
APP: (appId) => `:${appId}`,
|
|
253
|
-
PERMISSION: (userId, organisationId) => `perm:${userId}:${organisationId}:`
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
// src/rbac/cache-invalidation.ts
|
|
257
|
-
var INVALIDATION_PATTERNS = {
|
|
258
|
-
// User-level invalidation
|
|
259
|
-
USER_ROLES_CHANGED: (userId) => [
|
|
260
|
-
CACHE_PATTERNS.USER(userId),
|
|
261
|
-
`perm:${userId}:*`,
|
|
262
|
-
`access:${userId}:*`,
|
|
263
|
-
`map:${userId}:*`
|
|
264
|
-
],
|
|
265
|
-
// Organisation-level invalidation
|
|
266
|
-
ORGANISATION_PERMISSIONS_CHANGED: (organisationId) => [
|
|
267
|
-
CACHE_PATTERNS.ORGANISATION(organisationId),
|
|
268
|
-
`perm:*:${organisationId}:*`,
|
|
269
|
-
`access:*:${organisationId}:*`,
|
|
270
|
-
`map:*:${organisationId}:*`
|
|
271
|
-
],
|
|
272
|
-
// Event-level invalidation
|
|
273
|
-
EVENT_PERMISSIONS_CHANGED: (eventId) => [
|
|
274
|
-
CACHE_PATTERNS.EVENT(eventId),
|
|
275
|
-
`perm:*:*:${eventId}:*`,
|
|
276
|
-
`access:*:*:${eventId}:*`,
|
|
277
|
-
`map:*:*:${eventId}:*`
|
|
278
|
-
],
|
|
279
|
-
// App-level invalidation
|
|
280
|
-
APP_PERMISSIONS_CHANGED: (appId) => [
|
|
281
|
-
CACHE_PATTERNS.APP(appId),
|
|
282
|
-
`perm:*:*:*:${appId}:*`,
|
|
283
|
-
`access:*:*:*:${appId}`,
|
|
284
|
-
`map:*:*:*:${appId}`
|
|
285
|
-
],
|
|
286
|
-
// Page-level invalidation
|
|
287
|
-
PAGE_PERMISSIONS_CHANGED: (pageId) => [
|
|
288
|
-
`perm:*:*:*:*:${pageId}`,
|
|
289
|
-
`map:*:*:*:*`
|
|
290
|
-
]
|
|
291
|
-
};
|
|
292
|
-
var RBACCacheInvalidationManager = class {
|
|
293
|
-
constructor(supabase) {
|
|
294
|
-
this.invalidationCallbacks = /* @__PURE__ */ new Set();
|
|
295
|
-
this.supabase = supabase;
|
|
296
|
-
this.setupRealtimeSubscriptions();
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* Add a callback for cache invalidation events
|
|
300
|
-
*
|
|
301
|
-
* @param callback - Function to call when cache is invalidated
|
|
302
|
-
* @returns Unsubscribe function
|
|
303
|
-
*/
|
|
304
|
-
onInvalidation(callback) {
|
|
305
|
-
this.invalidationCallbacks.add(callback);
|
|
306
|
-
return () => this.invalidationCallbacks.delete(callback);
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* Invalidate cache for a specific user
|
|
310
|
-
*
|
|
311
|
-
* @param userId - User ID
|
|
312
|
-
* @param reason - Reason for invalidation
|
|
313
|
-
*/
|
|
314
|
-
invalidateUser(userId, reason) {
|
|
315
|
-
const patterns = INVALIDATION_PATTERNS.USER_ROLES_CHANGED(userId);
|
|
316
|
-
this.invalidatePatterns(patterns, reason);
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* Invalidate cache for a specific organisation
|
|
320
|
-
*
|
|
321
|
-
* @param organisationId - Organisation ID
|
|
322
|
-
* @param reason - Reason for invalidation
|
|
323
|
-
*/
|
|
324
|
-
invalidateOrganisation(organisationId, reason) {
|
|
325
|
-
const patterns = INVALIDATION_PATTERNS.ORGANISATION_PERMISSIONS_CHANGED(organisationId);
|
|
326
|
-
this.invalidatePatterns(patterns, reason);
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Invalidate cache for a specific event
|
|
330
|
-
*
|
|
331
|
-
* @param eventId - Event ID
|
|
332
|
-
* @param reason - Reason for invalidation
|
|
333
|
-
*/
|
|
334
|
-
invalidateEvent(eventId, reason) {
|
|
335
|
-
const patterns = INVALIDATION_PATTERNS.EVENT_PERMISSIONS_CHANGED(eventId);
|
|
336
|
-
this.invalidatePatterns(patterns, reason);
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Invalidate cache for a specific app
|
|
340
|
-
*
|
|
341
|
-
* @param appId - App ID
|
|
342
|
-
* @param reason - Reason for invalidation
|
|
343
|
-
*/
|
|
344
|
-
invalidateApp(appId, reason) {
|
|
345
|
-
const patterns = INVALIDATION_PATTERNS.APP_PERMISSIONS_CHANGED(appId);
|
|
346
|
-
this.invalidatePatterns(patterns, reason);
|
|
347
|
-
}
|
|
348
|
-
/**
|
|
349
|
-
* Invalidate cache for a specific page
|
|
350
|
-
*
|
|
351
|
-
* @param pageId - Page ID
|
|
352
|
-
* @param reason - Reason for invalidation
|
|
353
|
-
*/
|
|
354
|
-
invalidatePage(pageId, reason) {
|
|
355
|
-
const patterns = INVALIDATION_PATTERNS.PAGE_PERMISSIONS_CHANGED(pageId);
|
|
356
|
-
this.invalidatePatterns(patterns, reason);
|
|
357
|
-
}
|
|
358
|
-
/**
|
|
359
|
-
* Invalidate cache patterns and notify callbacks
|
|
360
|
-
*
|
|
361
|
-
* @param patterns - Array of cache patterns to invalidate
|
|
362
|
-
* @param reason - Reason for invalidation
|
|
363
|
-
*/
|
|
364
|
-
invalidatePatterns(patterns, reason) {
|
|
365
|
-
console.log(`[RBAC Cache] Invalidating patterns: ${patterns.join(", ")} (${reason})`);
|
|
366
|
-
patterns.forEach((pattern) => {
|
|
367
|
-
rbacCache.invalidate(pattern);
|
|
368
|
-
});
|
|
369
|
-
this.invalidationCallbacks.forEach((callback) => {
|
|
370
|
-
patterns.forEach((pattern) => callback(pattern));
|
|
371
|
-
});
|
|
372
|
-
emitAuditEvent({
|
|
373
|
-
type: "permission_check",
|
|
374
|
-
userId: "system",
|
|
375
|
-
organisationId: "00000000-0000-0000-0000-000000000000",
|
|
376
|
-
permission: "cache:invalidate",
|
|
377
|
-
decision: true,
|
|
378
|
-
source: "api",
|
|
379
|
-
duration_ms: 0,
|
|
380
|
-
metadata: {
|
|
381
|
-
reason,
|
|
382
|
-
patterns,
|
|
383
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
384
|
-
cache_invalidation: true
|
|
385
|
-
}
|
|
386
|
-
}).catch((error) => {
|
|
387
|
-
console.warn("[RBAC Cache] Failed to log cache invalidation audit event:", error);
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
/**
|
|
391
|
-
* Setup realtime subscriptions for automatic cache invalidation
|
|
392
|
-
*/
|
|
393
|
-
setupRealtimeSubscriptions() {
|
|
394
|
-
if (!this.supabase.channel || typeof this.supabase.channel !== "function") {
|
|
395
|
-
console.log("[RBAC Cache] Realtime not available, skipping subscriptions");
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
this.supabase.channel("rbac_organisation_roles_changes").on("postgres_changes", {
|
|
399
|
-
event: "*",
|
|
400
|
-
schema: "public",
|
|
401
|
-
table: "rbac_organisation_roles"
|
|
402
|
-
}, (payload) => {
|
|
403
|
-
const { organisation_id, user_id } = payload.new || payload.old || {};
|
|
404
|
-
if (organisation_id) {
|
|
405
|
-
this.invalidateOrganisation(organisation_id, `organisation_role_${payload.eventType}`);
|
|
406
|
-
}
|
|
407
|
-
if (user_id) {
|
|
408
|
-
this.invalidateUser(user_id, `organisation_role_${payload.eventType}`);
|
|
409
|
-
}
|
|
410
|
-
}).subscribe();
|
|
411
|
-
this.supabase.channel("rbac_event_app_roles_changes").on("postgres_changes", {
|
|
412
|
-
event: "*",
|
|
413
|
-
schema: "public",
|
|
414
|
-
table: "rbac_event_app_roles"
|
|
415
|
-
}, (payload) => {
|
|
416
|
-
const { organisation_id, user_id, event_id, app_id } = payload.new || payload.old || {};
|
|
417
|
-
if (organisation_id) {
|
|
418
|
-
this.invalidateOrganisation(organisation_id, `event_app_role_${payload.eventType}`);
|
|
419
|
-
}
|
|
420
|
-
if (user_id) {
|
|
421
|
-
this.invalidateUser(user_id, `event_app_role_${payload.eventType}`);
|
|
422
|
-
}
|
|
423
|
-
if (event_id) {
|
|
424
|
-
this.invalidateEvent(event_id, `event_app_role_${payload.eventType}`);
|
|
425
|
-
}
|
|
426
|
-
if (app_id) {
|
|
427
|
-
this.invalidateApp(app_id, `event_app_role_${payload.eventType}`);
|
|
428
|
-
}
|
|
429
|
-
}).subscribe();
|
|
430
|
-
this.supabase.channel("rbac_global_roles_changes").on("postgres_changes", {
|
|
431
|
-
event: "*",
|
|
432
|
-
schema: "public",
|
|
433
|
-
table: "rbac_global_roles"
|
|
434
|
-
}, (payload) => {
|
|
435
|
-
const { user_id } = payload.new || payload.old || {};
|
|
436
|
-
if (user_id) {
|
|
437
|
-
this.invalidateUser(user_id, `global_role_${payload.eventType}`);
|
|
438
|
-
}
|
|
439
|
-
}).subscribe();
|
|
440
|
-
this.supabase.channel("rbac_page_permissions_changes").on("postgres_changes", {
|
|
441
|
-
event: "*",
|
|
442
|
-
schema: "public",
|
|
443
|
-
table: "rbac_page_permissions"
|
|
444
|
-
}, (payload) => {
|
|
445
|
-
const { organisation_id, app_page_id, role_id } = payload.new || payload.old || {};
|
|
446
|
-
if (organisation_id) {
|
|
447
|
-
this.invalidateOrganisation(organisation_id, `page_permission_${payload.eventType}`);
|
|
448
|
-
}
|
|
449
|
-
if (app_page_id) {
|
|
450
|
-
this.invalidatePage(app_page_id, `page_permission_${payload.eventType}`);
|
|
451
|
-
}
|
|
452
|
-
}).subscribe();
|
|
453
|
-
}
|
|
454
|
-
/**
|
|
455
|
-
* Manually trigger cache invalidation for all users in an organisation
|
|
456
|
-
*
|
|
457
|
-
* @param organisationId - Organisation ID
|
|
458
|
-
* @param reason - Reason for invalidation
|
|
459
|
-
*/
|
|
460
|
-
async invalidateAllUsersInOrganisation(organisationId, reason) {
|
|
461
|
-
const { data: users } = await this.supabase.from("rbac_organisation_roles").select("user_id").eq("organisation_id", organisationId).eq("is_active", true);
|
|
462
|
-
if (users) {
|
|
463
|
-
users.forEach(({ user_id }) => {
|
|
464
|
-
this.invalidateUser(user_id, reason);
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
/**
|
|
469
|
-
* Clear all cache entries
|
|
470
|
-
*/
|
|
471
|
-
clearAllCache() {
|
|
472
|
-
console.log("[RBAC Cache] Clearing all cache entries");
|
|
473
|
-
rbacCache.clear();
|
|
474
|
-
}
|
|
475
|
-
};
|
|
476
|
-
var globalCacheInvalidationManager = null;
|
|
477
|
-
function initializeCacheInvalidation(supabase) {
|
|
478
|
-
globalCacheInvalidationManager = new RBACCacheInvalidationManager(supabase);
|
|
479
|
-
return globalCacheInvalidationManager;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// src/rbac/errors.ts
|
|
483
|
-
var RATE_LIMIT_STATUS_CODES = /* @__PURE__ */ new Set([429]);
|
|
484
|
-
var AUTH_STATUS_CODES = /* @__PURE__ */ new Set([401]);
|
|
485
|
-
var AUTHZ_STATUS_CODES = /* @__PURE__ */ new Set([403]);
|
|
486
|
-
function normalize(value) {
|
|
487
|
-
if (!value) {
|
|
488
|
-
return "";
|
|
489
|
-
}
|
|
490
|
-
if (typeof value === "string") {
|
|
491
|
-
return value.toLowerCase();
|
|
492
|
-
}
|
|
493
|
-
if (value instanceof Error && typeof value.message === "string") {
|
|
494
|
-
return value.message.toLowerCase();
|
|
495
|
-
}
|
|
496
|
-
return String(value).toLowerCase();
|
|
497
|
-
}
|
|
498
|
-
function categorizeError(error) {
|
|
499
|
-
if (error instanceof PermissionDeniedError) {
|
|
500
|
-
return "authorization_error" /* AUTHORIZATION */;
|
|
501
|
-
}
|
|
502
|
-
if (error instanceof OrganisationContextRequiredError || error instanceof InvalidScopeError) {
|
|
503
|
-
return "validation_error" /* VALIDATION */;
|
|
504
|
-
}
|
|
505
|
-
if (error instanceof MissingUserContextError) {
|
|
506
|
-
return "authentication_error" /* AUTHENTICATION */;
|
|
507
|
-
}
|
|
508
|
-
if (error instanceof RBACError) {
|
|
509
|
-
switch (error.code) {
|
|
510
|
-
case "PERMISSION_DENIED":
|
|
511
|
-
return "authorization_error" /* AUTHORIZATION */;
|
|
512
|
-
case "ORGANISATION_CONTEXT_REQUIRED":
|
|
513
|
-
case "INVALID_SCOPE":
|
|
514
|
-
return "validation_error" /* VALIDATION */;
|
|
515
|
-
case "MISSING_USER_CONTEXT":
|
|
516
|
-
return "authentication_error" /* AUTHENTICATION */;
|
|
517
|
-
default:
|
|
518
|
-
break;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
if (error && typeof error === "object") {
|
|
522
|
-
const status = error.status;
|
|
523
|
-
if (typeof status === "number") {
|
|
524
|
-
if (RATE_LIMIT_STATUS_CODES.has(status)) {
|
|
525
|
-
return "rate_limit_error" /* RATE_LIMIT */;
|
|
526
|
-
}
|
|
527
|
-
if (AUTH_STATUS_CODES.has(status)) {
|
|
528
|
-
return "authentication_error" /* AUTHENTICATION */;
|
|
529
|
-
}
|
|
530
|
-
if (AUTHZ_STATUS_CODES.has(status)) {
|
|
531
|
-
return "authorization_error" /* AUTHORIZATION */;
|
|
532
|
-
}
|
|
533
|
-
if (status >= 500) {
|
|
534
|
-
return "database_error" /* DATABASE */;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
const codeValue = normalize(error.code);
|
|
538
|
-
if (codeValue) {
|
|
539
|
-
if (codeValue.includes("network")) {
|
|
540
|
-
return "network_error" /* NETWORK */;
|
|
541
|
-
}
|
|
542
|
-
if (codeValue.includes("postgres") || codeValue.includes("database") || codeValue.includes("db")) {
|
|
543
|
-
return "database_error" /* DATABASE */;
|
|
544
|
-
}
|
|
545
|
-
if (codeValue.includes("rate")) {
|
|
546
|
-
return "rate_limit_error" /* RATE_LIMIT */;
|
|
547
|
-
}
|
|
548
|
-
if (codeValue.includes("auth")) {
|
|
549
|
-
return "authentication_error" /* AUTHENTICATION */;
|
|
550
|
-
}
|
|
551
|
-
if (codeValue.includes("permission")) {
|
|
552
|
-
return "authorization_error" /* AUTHORIZATION */;
|
|
553
|
-
}
|
|
554
|
-
if (codeValue.includes("scope") || codeValue.includes("invalid")) {
|
|
555
|
-
return "validation_error" /* VALIDATION */;
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
const message = normalize(error);
|
|
560
|
-
if (message.includes("timeout") || message.includes("network") || message.includes("fetch")) {
|
|
561
|
-
return "network_error" /* NETWORK */;
|
|
562
|
-
}
|
|
563
|
-
if (message.includes("postgres") || message.includes("database") || message.includes("connection")) {
|
|
564
|
-
return "database_error" /* DATABASE */;
|
|
565
|
-
}
|
|
566
|
-
if (message.includes("rate limit") || message.includes("too many requests")) {
|
|
567
|
-
return "rate_limit_error" /* RATE_LIMIT */;
|
|
568
|
-
}
|
|
569
|
-
if (message.includes("permission") || message.includes("forbidden")) {
|
|
570
|
-
return "authorization_error" /* AUTHORIZATION */;
|
|
571
|
-
}
|
|
572
|
-
if (message.includes("auth") || message.includes("token") || message.includes("session")) {
|
|
573
|
-
return "authentication_error" /* AUTHENTICATION */;
|
|
574
|
-
}
|
|
575
|
-
if (message.includes("invalid") || message.includes("scope") || message.includes("validation")) {
|
|
576
|
-
return "validation_error" /* VALIDATION */;
|
|
577
|
-
}
|
|
578
|
-
return "unknown_error" /* UNKNOWN */;
|
|
579
|
-
}
|
|
580
|
-
function mapErrorCategoryToSecurityEventType(category) {
|
|
581
|
-
switch (category) {
|
|
582
|
-
case "authorization_error" /* AUTHORIZATION */:
|
|
583
|
-
return "permission_denied";
|
|
584
|
-
case "network_error" /* NETWORK */:
|
|
585
|
-
return "network_error";
|
|
586
|
-
case "database_error" /* DATABASE */:
|
|
587
|
-
return "database_error";
|
|
588
|
-
case "validation_error" /* VALIDATION */:
|
|
589
|
-
return "validation_error";
|
|
590
|
-
case "rate_limit_error" /* RATE_LIMIT */:
|
|
591
|
-
return "rate_limit_error";
|
|
592
|
-
case "authentication_error" /* AUTHENTICATION */:
|
|
593
|
-
return "authentication_error";
|
|
594
|
-
case "unknown_error" /* UNKNOWN */:
|
|
595
|
-
default:
|
|
596
|
-
return "unknown_error";
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// src/rbac/security.ts
|
|
601
|
-
var RBACSecurityValidator = class {
|
|
602
|
-
/**
|
|
603
|
-
* Validate permission string format
|
|
604
|
-
* @param permission - Permission string to validate
|
|
605
|
-
* @returns True if valid, false otherwise
|
|
606
|
-
*/
|
|
607
|
-
static validatePermission(permission) {
|
|
608
|
-
if (typeof permission !== "string" || permission.length === 0) {
|
|
609
|
-
return false;
|
|
610
|
-
}
|
|
611
|
-
const permissionRegex = /^(read|create|update|delete):[a-z0-9._-]+$/;
|
|
612
|
-
return permissionRegex.test(permission);
|
|
613
|
-
}
|
|
614
|
-
/**
|
|
615
|
-
* Validate UUID format
|
|
616
|
-
* @param uuid - UUID string to validate
|
|
617
|
-
* @returns True if valid, false otherwise
|
|
618
|
-
*/
|
|
619
|
-
static validateUUID(uuid) {
|
|
620
|
-
if (typeof uuid !== "string" || uuid.length === 0) {
|
|
621
|
-
return false;
|
|
622
|
-
}
|
|
623
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
624
|
-
return uuidRegex.test(uuid);
|
|
625
|
-
}
|
|
626
|
-
/**
|
|
627
|
-
* Validate scope object
|
|
628
|
-
* @param scope - Scope object to validate
|
|
629
|
-
* @returns True if valid, false otherwise
|
|
630
|
-
*/
|
|
631
|
-
static validateScope(scope) {
|
|
632
|
-
if (!scope || typeof scope !== "object") {
|
|
633
|
-
return false;
|
|
634
|
-
}
|
|
635
|
-
if (scope.organisationId !== void 0) {
|
|
636
|
-
if (typeof scope.organisationId === "string" && scope.organisationId.trim() === "") {
|
|
637
|
-
return false;
|
|
638
|
-
}
|
|
639
|
-
if (scope.organisationId && !this.validateUUID(scope.organisationId)) {
|
|
640
|
-
return false;
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
if (scope.eventId && typeof scope.eventId !== "string") {
|
|
644
|
-
return false;
|
|
645
|
-
}
|
|
646
|
-
if (scope.appId && !this.validateUUID(scope.appId)) {
|
|
647
|
-
return false;
|
|
648
|
-
}
|
|
649
|
-
return !!(scope.organisationId || scope.eventId || scope.appId);
|
|
650
|
-
}
|
|
651
|
-
/**
|
|
652
|
-
* Sanitize input string to prevent injection attacks
|
|
653
|
-
* @param input - Input string to sanitize
|
|
654
|
-
* @returns Sanitized string
|
|
655
|
-
*/
|
|
656
|
-
static sanitizeInput(input) {
|
|
657
|
-
if (typeof input !== "string") {
|
|
658
|
-
return "";
|
|
659
|
-
}
|
|
660
|
-
return input.replace(/<[^>]*>/g, "").replace(/[<>\"'&]/g, "").replace(/[;()]/g, "").replace(/javascript:/gi, "").replace(/data:/gi, "").trim();
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* Validate user ID format
|
|
664
|
-
* @param userId - User ID to validate
|
|
665
|
-
* @returns True if valid, false otherwise
|
|
666
|
-
*/
|
|
667
|
-
static validateUserId(userId) {
|
|
668
|
-
return this.validateUUID(userId);
|
|
669
|
-
}
|
|
670
|
-
/**
|
|
671
|
-
* Check if permission is a wildcard permission
|
|
672
|
-
* @param permission - Permission string to check
|
|
673
|
-
* @returns True if wildcard, false otherwise
|
|
674
|
-
*/
|
|
675
|
-
static isWildcardPermission(permission) {
|
|
676
|
-
return permission.includes("*") || permission.endsWith(":*");
|
|
677
|
-
}
|
|
678
|
-
/**
|
|
679
|
-
* Validate permission hierarchy
|
|
680
|
-
* @param permission - Permission to validate
|
|
681
|
-
* @param requiredOperation - Required operation
|
|
682
|
-
* @returns True if permission matches or is higher in hierarchy
|
|
683
|
-
*/
|
|
684
|
-
static validatePermissionHierarchy(permission, requiredOperation) {
|
|
685
|
-
if (!this.validatePermission(permission)) {
|
|
686
|
-
return false;
|
|
687
|
-
}
|
|
688
|
-
const [operation] = permission.split(":");
|
|
689
|
-
const hierarchy = ["read", "create", "update", "delete"];
|
|
690
|
-
const permissionLevel = hierarchy.indexOf(operation);
|
|
691
|
-
const requiredLevel = hierarchy.indexOf(requiredOperation);
|
|
692
|
-
if (permissionLevel === -1 || requiredLevel === -1) {
|
|
693
|
-
return false;
|
|
694
|
-
}
|
|
695
|
-
return permissionLevel >= requiredLevel;
|
|
696
|
-
}
|
|
697
|
-
/**
|
|
698
|
-
* Rate limiting check (placeholder for future implementation)
|
|
699
|
-
* @param userId - User ID
|
|
700
|
-
* @param operation - Operation being performed
|
|
701
|
-
* @returns True if within rate limit, false otherwise
|
|
702
|
-
*/
|
|
703
|
-
static async checkRateLimit(userId, operation) {
|
|
704
|
-
return true;
|
|
705
|
-
}
|
|
706
|
-
/**
|
|
707
|
-
* Validate context requirements for security
|
|
708
|
-
* @param scope - Scope object
|
|
709
|
-
* @param appId - Application ID
|
|
710
|
-
* @returns True if context is valid, false otherwise
|
|
711
|
-
*/
|
|
712
|
-
static validateContextRequirements(scope, appId) {
|
|
713
|
-
if (!scope.organisationId) {
|
|
714
|
-
return false;
|
|
715
|
-
}
|
|
716
|
-
if (appId && scope.appId === appId) {
|
|
717
|
-
if (!scope.eventId) {
|
|
718
|
-
return false;
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
return true;
|
|
722
|
-
}
|
|
723
|
-
/**
|
|
724
|
-
* Log security event for monitoring
|
|
725
|
-
* @param event - Security event details
|
|
726
|
-
*/
|
|
727
|
-
static logSecurityEvent(event) {
|
|
728
|
-
const securityEvent = {
|
|
729
|
-
...event,
|
|
730
|
-
timestamp: event.timestamp || /* @__PURE__ */ new Date(),
|
|
731
|
-
severity: this.getEventSeverity(event.type)
|
|
732
|
-
};
|
|
733
|
-
if (import.meta.env.MODE === "development") {
|
|
734
|
-
console.warn("[RBAC Security]", securityEvent);
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
/**
|
|
738
|
-
* Get severity level for security event
|
|
739
|
-
* @param eventType - Type of security event
|
|
740
|
-
* @returns Severity level
|
|
741
|
-
*/
|
|
742
|
-
static getEventSeverity(eventType) {
|
|
743
|
-
switch (eventType) {
|
|
744
|
-
case "permission_denied":
|
|
745
|
-
return "low";
|
|
746
|
-
case "invalid_input":
|
|
747
|
-
case "rate_limit_exceeded":
|
|
748
|
-
case "rate_limit_error":
|
|
749
|
-
case "network_error":
|
|
750
|
-
return "medium";
|
|
751
|
-
case "validation_error":
|
|
752
|
-
return "high";
|
|
753
|
-
case "authentication_error":
|
|
754
|
-
case "database_error":
|
|
755
|
-
return "critical";
|
|
756
|
-
case "suspicious_activity":
|
|
757
|
-
case "unknown_error":
|
|
758
|
-
return "high";
|
|
759
|
-
default:
|
|
760
|
-
return "low";
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
};
|
|
764
|
-
var DEFAULT_SECURITY_CONFIG = {
|
|
765
|
-
enableInputValidation: true,
|
|
766
|
-
enableRateLimiting: true,
|
|
767
|
-
enableAuditLogging: true,
|
|
768
|
-
maxPermissionChecksPerMinute: 1e3,
|
|
769
|
-
// Increased from 100 to 1000 for normal app usage
|
|
770
|
-
suspiciousActivityThreshold: 10
|
|
771
|
-
};
|
|
772
|
-
var RBACSecurityMiddleware = class {
|
|
773
|
-
constructor(config = DEFAULT_SECURITY_CONFIG) {
|
|
774
|
-
/**
|
|
775
|
-
* In-memory rate limiting cache (sliding window)
|
|
776
|
-
* Note: For production, this should use Redis or Supabase Edge Functions
|
|
777
|
-
*/
|
|
778
|
-
this.rateLimitCache = /* @__PURE__ */ new Map();
|
|
779
|
-
this.config = config;
|
|
780
|
-
this._startCleanupInterval();
|
|
781
|
-
}
|
|
782
|
-
/**
|
|
783
|
-
* Start periodic cleanup of expired entries
|
|
784
|
-
*/
|
|
785
|
-
_startCleanupInterval() {
|
|
786
|
-
setInterval(() => {
|
|
787
|
-
this.clearExpiredEntries();
|
|
788
|
-
}, 5 * 60 * 1e3);
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* Validate input before processing
|
|
792
|
-
* @param input - Input to validate
|
|
793
|
-
* @param context - Security context
|
|
794
|
-
* @returns Validation result
|
|
795
|
-
*/
|
|
796
|
-
async validateInput(input, context) {
|
|
797
|
-
const errors = [];
|
|
798
|
-
if (!RBACSecurityValidator.validateUserId(context.userId)) {
|
|
799
|
-
errors.push("Invalid user ID format");
|
|
800
|
-
}
|
|
801
|
-
if (!context.organisationId) {
|
|
802
|
-
errors.push("Organisation ID is required");
|
|
803
|
-
} else if (!RBACSecurityValidator.validateUUID(context.organisationId)) {
|
|
804
|
-
errors.push("Invalid organisation ID format");
|
|
805
|
-
}
|
|
806
|
-
if (input.permission && !RBACSecurityValidator.validatePermission(input.permission)) {
|
|
807
|
-
errors.push("Invalid permission format");
|
|
808
|
-
}
|
|
809
|
-
if (input.scope && !RBACSecurityValidator.validateScope(input.scope)) {
|
|
810
|
-
errors.push("Invalid scope format");
|
|
811
|
-
}
|
|
812
|
-
if (this.config.enableInputValidation) {
|
|
813
|
-
if (context.ipAddress && typeof context.ipAddress !== "string") {
|
|
814
|
-
errors.push("Invalid IP address format");
|
|
815
|
-
}
|
|
816
|
-
if (context.userAgent && typeof context.userAgent !== "string") {
|
|
817
|
-
errors.push("Invalid user agent format");
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
if (errors.length > 0) {
|
|
821
|
-
RBACSecurityValidator.logSecurityEvent({
|
|
822
|
-
type: "invalid_input",
|
|
823
|
-
userId: context.userId,
|
|
824
|
-
details: { errors, input: this.sanitizeInput(JSON.stringify(input)) }
|
|
825
|
-
});
|
|
826
|
-
}
|
|
827
|
-
return {
|
|
828
|
-
isValid: errors.length === 0,
|
|
829
|
-
errors
|
|
830
|
-
};
|
|
831
|
-
}
|
|
832
|
-
/**
|
|
833
|
-
* Check rate limiting
|
|
834
|
-
* @param context - Security context
|
|
835
|
-
* @returns Rate limit check result
|
|
836
|
-
*/
|
|
837
|
-
async checkRateLimit(context) {
|
|
838
|
-
if (!this.config.enableRateLimiting) {
|
|
839
|
-
return { isAllowed: true, remaining: this.config.maxPermissionChecksPerMinute };
|
|
840
|
-
}
|
|
841
|
-
const isAllowed = await this._checkRateLimitInternal(context.userId);
|
|
842
|
-
const remaining = isAllowed ? this.config.maxPermissionChecksPerMinute - this._getRequestCount(context.userId) : 0;
|
|
843
|
-
return {
|
|
844
|
-
isAllowed,
|
|
845
|
-
remaining: Math.max(0, remaining)
|
|
846
|
-
};
|
|
847
|
-
}
|
|
848
|
-
async _checkRateLimitInternal(userId) {
|
|
849
|
-
const now = Date.now();
|
|
850
|
-
const windowMs = 60 * 1e3;
|
|
851
|
-
const entries = this.rateLimitCache.get(userId) || [];
|
|
852
|
-
const validEntries = entries.filter((entry) => now - entry.timestamp < windowMs);
|
|
853
|
-
const requestCount = validEntries.length;
|
|
854
|
-
const isAllowed = requestCount < this.config.maxPermissionChecksPerMinute;
|
|
855
|
-
if (isAllowed) {
|
|
856
|
-
validEntries.push({ timestamp: now });
|
|
857
|
-
}
|
|
858
|
-
this.rateLimitCache.set(userId, validEntries);
|
|
859
|
-
return isAllowed;
|
|
860
|
-
}
|
|
861
|
-
_getRequestCount(userId) {
|
|
862
|
-
const now = Date.now();
|
|
863
|
-
const windowMs = 60 * 1e3;
|
|
864
|
-
const entries = this.rateLimitCache.get(userId) || [];
|
|
865
|
-
const validEntries = entries.filter((entry) => now - entry.timestamp < windowMs);
|
|
866
|
-
return validEntries.length;
|
|
867
|
-
}
|
|
868
|
-
/**
|
|
869
|
-
* Clear old rate limit entries to prevent memory leaks
|
|
870
|
-
* Should be called periodically (e.g., every 5 minutes)
|
|
871
|
-
*/
|
|
872
|
-
clearExpiredEntries() {
|
|
873
|
-
const now = Date.now();
|
|
874
|
-
const windowMs = 60 * 1e3;
|
|
875
|
-
for (const [userId, entries] of this.rateLimitCache.entries()) {
|
|
876
|
-
const validEntries = entries.filter((entry) => now - entry.timestamp < windowMs);
|
|
877
|
-
if (validEntries.length === 0) {
|
|
878
|
-
this.rateLimitCache.delete(userId);
|
|
879
|
-
} else {
|
|
880
|
-
this.rateLimitCache.set(userId, validEntries);
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
/**
|
|
885
|
-
* Sanitize input data
|
|
886
|
-
* @param input - Input to sanitize
|
|
887
|
-
* @returns Sanitized input
|
|
888
|
-
*/
|
|
889
|
-
sanitizeInput(input) {
|
|
890
|
-
return RBACSecurityValidator.sanitizeInput(input);
|
|
891
|
-
}
|
|
892
|
-
};
|
|
893
|
-
|
|
894
|
-
// src/rbac/engine.ts
|
|
895
|
-
var RBACEngine = class {
|
|
896
|
-
constructor(supabase, securityConfig) {
|
|
897
|
-
this.supabase = supabase;
|
|
898
|
-
const mergedSecurityConfig = {
|
|
899
|
-
...DEFAULT_SECURITY_CONFIG,
|
|
900
|
-
...securityConfig
|
|
901
|
-
};
|
|
902
|
-
this.securityMiddleware = new RBACSecurityMiddleware(mergedSecurityConfig);
|
|
903
|
-
initializeCacheInvalidation(supabase);
|
|
904
|
-
}
|
|
905
|
-
/**
|
|
906
|
-
* Check if a user has a specific permission
|
|
907
|
-
*
|
|
908
|
-
* This method now delegates to the database RPC function for all the heavy lifting.
|
|
909
|
-
*
|
|
910
|
-
* @param input - Permission check input
|
|
911
|
-
* @param securityContext - Security context for validation (required)
|
|
912
|
-
* @returns Promise resolving to permission result
|
|
913
|
-
*/
|
|
914
|
-
async isPermitted(input, securityContext) {
|
|
915
|
-
const startTime = Date.now();
|
|
916
|
-
const { userId, permission, scope, pageId } = input;
|
|
917
|
-
let cacheHit = false;
|
|
918
|
-
let cacheSource = "rpc";
|
|
919
|
-
try {
|
|
920
|
-
const validation = await this.securityMiddleware.validateInput(input, securityContext);
|
|
921
|
-
if (!validation.isValid) {
|
|
922
|
-
RBACSecurityValidator.logSecurityEvent({
|
|
923
|
-
type: "invalid_input",
|
|
924
|
-
userId,
|
|
925
|
-
details: { errors: validation.errors, input: JSON.stringify(input) }
|
|
926
|
-
});
|
|
927
|
-
return false;
|
|
928
|
-
}
|
|
929
|
-
const rateLimit = await this.securityMiddleware.checkRateLimit(securityContext);
|
|
930
|
-
if (!rateLimit.isAllowed) {
|
|
931
|
-
RBACSecurityValidator.logSecurityEvent({
|
|
932
|
-
type: "rate_limit_exceeded",
|
|
933
|
-
userId,
|
|
934
|
-
details: { remaining: rateLimit.remaining }
|
|
935
|
-
});
|
|
936
|
-
return false;
|
|
937
|
-
}
|
|
938
|
-
if (!RBACSecurityValidator.validateUserId(userId)) {
|
|
939
|
-
RBACSecurityValidator.logSecurityEvent({
|
|
940
|
-
type: "invalid_input",
|
|
941
|
-
userId,
|
|
942
|
-
details: { error: "Invalid user ID format" }
|
|
943
|
-
});
|
|
944
|
-
return false;
|
|
945
|
-
}
|
|
946
|
-
if (!RBACSecurityValidator.validatePermission(permission)) {
|
|
947
|
-
RBACSecurityValidator.logSecurityEvent({
|
|
948
|
-
type: "invalid_input",
|
|
949
|
-
userId,
|
|
950
|
-
details: { error: "Invalid permission format", permission }
|
|
951
|
-
});
|
|
952
|
-
return false;
|
|
953
|
-
}
|
|
954
|
-
if (!RBACSecurityValidator.validateScope(scope)) {
|
|
955
|
-
RBACSecurityValidator.logSecurityEvent({
|
|
956
|
-
type: "invalid_input",
|
|
957
|
-
userId,
|
|
958
|
-
details: { error: "Invalid scope format", scope }
|
|
959
|
-
});
|
|
960
|
-
return false;
|
|
961
|
-
}
|
|
962
|
-
const cacheKey = RBACCache.generateKey(
|
|
963
|
-
userId,
|
|
964
|
-
permission,
|
|
965
|
-
scope.organisationId,
|
|
966
|
-
scope.eventId,
|
|
967
|
-
scope.appId,
|
|
968
|
-
pageId
|
|
969
|
-
);
|
|
970
|
-
const cached = rbacCache.get(cacheKey);
|
|
971
|
-
if (cached !== null) {
|
|
972
|
-
cacheHit = true;
|
|
973
|
-
cacheSource = "memory";
|
|
974
|
-
const duration2 = Date.now() - startTime;
|
|
975
|
-
if (scope.organisationId) {
|
|
976
|
-
const resolvedPageId = await this.resolvePageId(pageId, scope.appId);
|
|
977
|
-
await emitAuditEvent({
|
|
978
|
-
type: "permission_check",
|
|
979
|
-
userId,
|
|
980
|
-
organisationId: scope.organisationId,
|
|
981
|
-
eventId: scope.eventId,
|
|
982
|
-
appId: scope.appId,
|
|
983
|
-
pageId: resolvedPageId,
|
|
984
|
-
permission,
|
|
985
|
-
decision: cached,
|
|
986
|
-
source: "api",
|
|
987
|
-
duration_ms: duration2,
|
|
988
|
-
cache_hit: true,
|
|
989
|
-
cache_source: "memory"
|
|
990
|
-
});
|
|
991
|
-
}
|
|
992
|
-
return cached;
|
|
993
|
-
}
|
|
994
|
-
const { data, error } = await this.supabase.rpc("rbac_check_permission_simplified", {
|
|
995
|
-
p_user_id: userId,
|
|
996
|
-
p_permission: permission,
|
|
997
|
-
p_organisation_id: scope.organisationId || void 0,
|
|
998
|
-
p_event_id: scope.eventId || void 0,
|
|
999
|
-
p_app_id: scope.appId || void 0,
|
|
1000
|
-
p_page_id: pageId || void 0
|
|
1001
|
-
});
|
|
1002
|
-
if (error) {
|
|
1003
|
-
console.error("[RBACEngine] RPC error:", error);
|
|
1004
|
-
const category = categorizeError(error);
|
|
1005
|
-
const eventType = mapErrorCategoryToSecurityEventType(category);
|
|
1006
|
-
const errorDetails = error;
|
|
1007
|
-
RBACSecurityValidator.logSecurityEvent({
|
|
1008
|
-
type: eventType,
|
|
1009
|
-
userId,
|
|
1010
|
-
details: {
|
|
1011
|
-
error: errorDetails?.message || "RPC call failed",
|
|
1012
|
-
code: errorDetails?.code,
|
|
1013
|
-
hint: errorDetails?.hint,
|
|
1014
|
-
details: errorDetails?.details,
|
|
1015
|
-
permission,
|
|
1016
|
-
scope: JSON.stringify(scope),
|
|
1017
|
-
category
|
|
1018
|
-
}
|
|
1019
|
-
});
|
|
1020
|
-
return false;
|
|
1021
|
-
}
|
|
1022
|
-
const hasPermission2 = data === true;
|
|
1023
|
-
rbacCache.set(cacheKey, hasPermission2, 6e4);
|
|
1024
|
-
const duration = Date.now() - startTime;
|
|
1025
|
-
if (scope.organisationId) {
|
|
1026
|
-
const resolvedPageId = await this.resolvePageId(pageId, scope.appId);
|
|
1027
|
-
await emitAuditEvent({
|
|
1028
|
-
type: hasPermission2 ? "permission_check" : "permission_denied",
|
|
1029
|
-
userId,
|
|
1030
|
-
organisationId: scope.organisationId,
|
|
1031
|
-
eventId: scope.eventId,
|
|
1032
|
-
appId: scope.appId,
|
|
1033
|
-
pageId: resolvedPageId,
|
|
1034
|
-
permission,
|
|
1035
|
-
decision: hasPermission2,
|
|
1036
|
-
source: "api",
|
|
1037
|
-
duration_ms: duration,
|
|
1038
|
-
cache_hit: cacheHit,
|
|
1039
|
-
cache_source: cacheSource
|
|
1040
|
-
});
|
|
1041
|
-
}
|
|
1042
|
-
return hasPermission2;
|
|
1043
|
-
} catch (error) {
|
|
1044
|
-
const category = categorizeError(error);
|
|
1045
|
-
const eventType = mapErrorCategoryToSecurityEventType(category);
|
|
1046
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1047
|
-
RBACSecurityValidator.logSecurityEvent({
|
|
1048
|
-
type: eventType,
|
|
1049
|
-
userId,
|
|
1050
|
-
details: {
|
|
1051
|
-
error: errorMessage,
|
|
1052
|
-
permission,
|
|
1053
|
-
scope: JSON.stringify(scope),
|
|
1054
|
-
category
|
|
1055
|
-
}
|
|
1056
|
-
});
|
|
1057
|
-
console.error("[RBACEngine] Permission check failed:", error);
|
|
1058
|
-
return false;
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
/**
|
|
1062
|
-
* Get user's access level in a scope
|
|
1063
|
-
*
|
|
1064
|
-
* This is derived from roles, not permissions.
|
|
1065
|
-
*
|
|
1066
|
-
* @param input - Access level input
|
|
1067
|
-
* @returns Promise resolving to access level
|
|
1068
|
-
*/
|
|
1069
|
-
async getAccessLevel(input) {
|
|
1070
|
-
const { userId, scope } = input;
|
|
1071
|
-
const cacheKey = RBACCache.generateAccessLevelKey(
|
|
1072
|
-
userId,
|
|
1073
|
-
scope.organisationId || "",
|
|
1074
|
-
scope.eventId,
|
|
1075
|
-
scope.appId
|
|
1076
|
-
);
|
|
1077
|
-
const cached = rbacCache.get(cacheKey);
|
|
1078
|
-
if (cached) {
|
|
1079
|
-
return cached;
|
|
1080
|
-
}
|
|
1081
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1082
|
-
const isSuperAdmin2 = await this.checkSuperAdmin(userId);
|
|
1083
|
-
if (isSuperAdmin2) {
|
|
1084
|
-
rbacCache.set(cacheKey, "super", 6e4);
|
|
1085
|
-
return "super";
|
|
1086
|
-
}
|
|
1087
|
-
if (scope.organisationId) {
|
|
1088
|
-
const { data: orgRole } = await this.supabase.from("rbac_organisation_roles").select("role").eq("user_id", userId).eq("organisation_id", scope.organisationId).eq("status", "active").is("revoked_at", null).lte("valid_from", now).or(`valid_to.is.null,valid_to.gte.${now}`).single();
|
|
1089
|
-
if (orgRole?.role === "org_admin") {
|
|
1090
|
-
rbacCache.set(cacheKey, "admin", 6e4);
|
|
1091
|
-
return "admin";
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
if (scope.eventId && scope.appId) {
|
|
1095
|
-
const { data: eventRole } = await this.supabase.from("rbac_event_app_roles").select("role").eq("user_id", userId).eq("event_id", scope.eventId).eq("app_id", scope.appId).eq("status", "active").lte("valid_from", now).or(`valid_to.is.null,valid_to.gte.${now}`).single();
|
|
1096
|
-
if (eventRole?.role === "event_admin") {
|
|
1097
|
-
rbacCache.set(cacheKey, "admin", 6e4);
|
|
1098
|
-
return "admin";
|
|
1099
|
-
}
|
|
1100
|
-
if (eventRole?.role === "planner") {
|
|
1101
|
-
rbacCache.set(cacheKey, "planner", 6e4);
|
|
1102
|
-
return "planner";
|
|
1103
|
-
}
|
|
1104
|
-
if (eventRole?.role === "participant") {
|
|
1105
|
-
rbacCache.set(cacheKey, "participant", 6e4);
|
|
1106
|
-
return "participant";
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
rbacCache.set(cacheKey, "viewer", 6e4);
|
|
1110
|
-
return "viewer";
|
|
1111
|
-
}
|
|
1112
|
-
/**
|
|
1113
|
-
* Get user's permission map for a scope
|
|
1114
|
-
*
|
|
1115
|
-
* This builds a map of page IDs to allowed operations.
|
|
1116
|
-
* Uses the simplified RPC for each permission check.
|
|
1117
|
-
*
|
|
1118
|
-
* @param input - Permission map input
|
|
1119
|
-
* @returns Promise resolving to permission map
|
|
1120
|
-
*/
|
|
1121
|
-
async getPermissionMap(input) {
|
|
1122
|
-
const { userId, scope } = input;
|
|
1123
|
-
const cacheKey = RBACCache.generatePermissionMapKey(
|
|
1124
|
-
userId,
|
|
1125
|
-
scope.organisationId || "",
|
|
1126
|
-
scope.eventId,
|
|
1127
|
-
scope.appId
|
|
1128
|
-
);
|
|
1129
|
-
const isSuperAdmin2 = await this.checkSuperAdmin(userId);
|
|
1130
|
-
if (isSuperAdmin2) {
|
|
1131
|
-
const wildcardMap = { "*": true };
|
|
1132
|
-
rbacCache.set(cacheKey, wildcardMap, 6e4);
|
|
1133
|
-
return wildcardMap;
|
|
1134
|
-
}
|
|
1135
|
-
if (!scope.organisationId) {
|
|
1136
|
-
return {};
|
|
1137
|
-
}
|
|
1138
|
-
const cached = rbacCache.get(cacheKey);
|
|
1139
|
-
if (cached) {
|
|
1140
|
-
return cached;
|
|
1141
|
-
}
|
|
1142
|
-
const permissionMap = {};
|
|
1143
|
-
if (scope.appId) {
|
|
1144
|
-
const { data: pages } = await this.supabase.from("rbac_app_pages").select("id, page_name").eq("app_id", scope.appId);
|
|
1145
|
-
if (pages) {
|
|
1146
|
-
if (!scope.organisationId) {
|
|
1147
|
-
rbacCache.set(cacheKey, permissionMap, 6e4);
|
|
1148
|
-
return permissionMap;
|
|
1149
|
-
}
|
|
1150
|
-
const securityContext = {
|
|
1151
|
-
userId,
|
|
1152
|
-
organisationId: scope.organisationId,
|
|
1153
|
-
// Required
|
|
1154
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
1155
|
-
};
|
|
1156
|
-
for (const page of pages) {
|
|
1157
|
-
for (const operation of ["read", "create", "update", "delete"]) {
|
|
1158
|
-
const permissionString = `${operation}:page.${page.page_name}`;
|
|
1159
|
-
const hasPermission2 = await this.isPermitted(
|
|
1160
|
-
{
|
|
1161
|
-
userId,
|
|
1162
|
-
scope,
|
|
1163
|
-
permission: permissionString,
|
|
1164
|
-
pageId: page.id
|
|
1165
|
-
},
|
|
1166
|
-
securityContext
|
|
1167
|
-
);
|
|
1168
|
-
const permissionKey = permissionString;
|
|
1169
|
-
permissionMap[permissionKey] = hasPermission2;
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
rbacCache.set(cacheKey, permissionMap, 6e4);
|
|
1175
|
-
return permissionMap;
|
|
1176
|
-
}
|
|
1177
|
-
async resolveAppContext(input) {
|
|
1178
|
-
try {
|
|
1179
|
-
const { userId, appName } = input;
|
|
1180
|
-
const { data, error } = await this.supabase.rpc("util_app_resolve", {
|
|
1181
|
-
p_user_id: userId,
|
|
1182
|
-
p_app_name: appName
|
|
1183
|
-
});
|
|
1184
|
-
if (error) {
|
|
1185
|
-
console.error("[RBACEngine] Failed to resolve app context:", error);
|
|
1186
|
-
return null;
|
|
1187
|
-
}
|
|
1188
|
-
if (!data || data.length === 0) {
|
|
1189
|
-
return null;
|
|
1190
|
-
}
|
|
1191
|
-
const appData = data[0];
|
|
1192
|
-
if (!appData?.app_id) {
|
|
1193
|
-
return null;
|
|
1194
|
-
}
|
|
1195
|
-
return {
|
|
1196
|
-
appId: appData.app_id,
|
|
1197
|
-
hasAccess: appData.has_access !== false
|
|
1198
|
-
};
|
|
1199
|
-
} catch (error) {
|
|
1200
|
-
console.error("[RBACEngine] Unexpected error resolving app context:", error);
|
|
1201
|
-
return null;
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
async getRoleContext(input) {
|
|
1205
|
-
const result = {
|
|
1206
|
-
globalRole: null,
|
|
1207
|
-
organisationRole: null,
|
|
1208
|
-
eventAppRole: null
|
|
1209
|
-
};
|
|
1210
|
-
try {
|
|
1211
|
-
const { userId, scope } = input;
|
|
1212
|
-
const { data, error } = await this.supabase.rpc("rbac_permissions_get", {
|
|
1213
|
-
p_user_id: userId,
|
|
1214
|
-
p_organisation_id: scope.organisationId || null,
|
|
1215
|
-
p_event_id: scope.eventId || null,
|
|
1216
|
-
p_app_id: scope.appId || null,
|
|
1217
|
-
p_page_id: null
|
|
1218
|
-
// Optional: can filter to specific page if needed
|
|
1219
|
-
});
|
|
1220
|
-
if (error) {
|
|
1221
|
-
console.error("[RBACEngine] Failed to load role context:", error);
|
|
1222
|
-
return result;
|
|
1223
|
-
}
|
|
1224
|
-
if (!Array.isArray(data)) {
|
|
1225
|
-
return result;
|
|
1226
|
-
}
|
|
1227
|
-
for (const permission of data) {
|
|
1228
|
-
if (permission.permission_type === "all_permissions") {
|
|
1229
|
-
result.globalRole = "super_admin";
|
|
1230
|
-
}
|
|
1231
|
-
if (permission.permission_type === "organisation_access") {
|
|
1232
|
-
result.organisationRole = permission.role_name;
|
|
1233
|
-
}
|
|
1234
|
-
if (permission.permission_type === "event_app_access") {
|
|
1235
|
-
result.eventAppRole = permission.role_name;
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
return result;
|
|
1239
|
-
} catch (error) {
|
|
1240
|
-
console.error("[RBACEngine] Unexpected error loading role context:", error);
|
|
1241
|
-
return result;
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
/**
|
|
1245
|
-
* Check if user is super admin
|
|
1246
|
-
*
|
|
1247
|
-
* @param userId - User ID
|
|
1248
|
-
* @returns Promise resolving to super admin status
|
|
1249
|
-
*/
|
|
1250
|
-
async checkSuperAdmin(userId) {
|
|
1251
|
-
const cacheKey = `super_admin:${userId}`;
|
|
1252
|
-
const cached = rbacCache.get(cacheKey);
|
|
1253
|
-
if (cached !== null) {
|
|
1254
|
-
return cached;
|
|
1255
|
-
}
|
|
1256
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1257
|
-
const { data, error } = await this.supabase.from("rbac_global_roles").select("role").eq("user_id", userId).eq("role", "super_admin").lte("valid_from", now).or(`valid_to.is.null,valid_to.gte.${now}`).limit(1);
|
|
1258
|
-
const isSuperAdmin2 = !error && data && data.length > 0;
|
|
1259
|
-
rbacCache.set(cacheKey, isSuperAdmin2, 6e4);
|
|
1260
|
-
return Boolean(isSuperAdmin2);
|
|
1261
|
-
}
|
|
1262
|
-
/**
|
|
1263
|
-
* Resolve a page ID to UUID if it's a page name
|
|
1264
|
-
*
|
|
1265
|
-
* @param pageId - Page ID (UUID) or page name (string)
|
|
1266
|
-
* @param appId - App ID to look up the page
|
|
1267
|
-
* @returns Resolved page ID (UUID) or original pageId
|
|
1268
|
-
*/
|
|
1269
|
-
async resolvePageId(pageId, appId) {
|
|
1270
|
-
if (!pageId) {
|
|
1271
|
-
return void 0;
|
|
1272
|
-
}
|
|
1273
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1274
|
-
if (uuidRegex.test(pageId)) {
|
|
1275
|
-
return pageId;
|
|
1276
|
-
}
|
|
1277
|
-
if (!appId) {
|
|
1278
|
-
return pageId;
|
|
1279
|
-
}
|
|
1280
|
-
try {
|
|
1281
|
-
const { data: page } = await this.supabase.from("rbac_app_pages").select("id").eq("app_id", appId).eq("page_name", pageId).single();
|
|
1282
|
-
return page?.id || pageId;
|
|
1283
|
-
} catch (error) {
|
|
1284
|
-
console.warn("[RBAC Engine] Failed to resolve page name to UUID:", { pageId, appId, error });
|
|
1285
|
-
return pageId;
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
};
|
|
1289
|
-
function createRBACEngine(supabase, securityConfig) {
|
|
1290
|
-
return new RBACEngine(supabase, securityConfig);
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
// src/rbac/config.ts
|
|
1294
|
-
var RBACConfigManager = class {
|
|
1295
|
-
constructor() {
|
|
1296
|
-
this.config = null;
|
|
1297
|
-
this.logger = null;
|
|
1298
|
-
}
|
|
1299
|
-
setConfig(config) {
|
|
1300
|
-
this.config = config;
|
|
1301
|
-
this.setupLogger();
|
|
1302
|
-
}
|
|
1303
|
-
getConfig() {
|
|
1304
|
-
return this.config;
|
|
1305
|
-
}
|
|
1306
|
-
getLogger() {
|
|
1307
|
-
if (!this.logger) {
|
|
1308
|
-
this.logger = this.createDefaultLogger();
|
|
1309
|
-
}
|
|
1310
|
-
return this.logger;
|
|
1311
|
-
}
|
|
1312
|
-
setupLogger() {
|
|
1313
|
-
if (!this.config) return;
|
|
1314
|
-
const { debug = false, logLevel = "warn" } = this.config;
|
|
1315
|
-
this.logger = {
|
|
1316
|
-
error: (message, ...args) => {
|
|
1317
|
-
console.error(`[RBAC ERROR] ${message}`, ...args);
|
|
1318
|
-
},
|
|
1319
|
-
warn: (message, ...args) => {
|
|
1320
|
-
if (logLevel === "warn" || logLevel === "info" || logLevel === "debug") {
|
|
1321
|
-
console.warn(`[RBAC WARN] ${message}`, ...args);
|
|
1322
|
-
}
|
|
1323
|
-
},
|
|
1324
|
-
info: (message, ...args) => {
|
|
1325
|
-
if (logLevel === "info" || logLevel === "debug") {
|
|
1326
|
-
console.info(`[RBAC INFO] ${message}`, ...args);
|
|
1327
|
-
}
|
|
1328
|
-
},
|
|
1329
|
-
debug: (message, ...args) => {
|
|
1330
|
-
if (debug && logLevel === "debug") {
|
|
1331
|
-
console.debug(`[RBAC DEBUG] ${message}`, ...args);
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
};
|
|
1335
|
-
}
|
|
1336
|
-
createDefaultLogger() {
|
|
1337
|
-
return {
|
|
1338
|
-
error: (message, ...args) => console.error(`[RBAC ERROR] ${message}`, ...args),
|
|
1339
|
-
warn: (message, ...args) => console.warn(`[RBAC WARN] ${message}`, ...args),
|
|
1340
|
-
info: (message, ...args) => console.info(`[RBAC INFO] ${message}`, ...args),
|
|
1341
|
-
debug: (message, ...args) => console.debug(`[RBAC DEBUG] ${message}`, ...args)
|
|
1342
|
-
};
|
|
1343
|
-
}
|
|
1344
|
-
isDebugMode() {
|
|
1345
|
-
return this.config?.debug ?? false;
|
|
1346
|
-
}
|
|
1347
|
-
isDevelopmentMode() {
|
|
1348
|
-
return this.config?.developmentMode ?? false;
|
|
1349
|
-
}
|
|
1350
|
-
getMockPermissions() {
|
|
1351
|
-
return this.config?.mockPermissions ?? null;
|
|
1352
|
-
}
|
|
1353
|
-
};
|
|
1354
|
-
var configManager = new RBACConfigManager();
|
|
1355
|
-
function createRBACConfig(config) {
|
|
1356
|
-
configManager.setConfig(config);
|
|
1357
|
-
return config;
|
|
1358
|
-
}
|
|
1359
|
-
function getRBACConfig() {
|
|
1360
|
-
return configManager.getConfig();
|
|
1361
|
-
}
|
|
1362
|
-
function getRBACLogger() {
|
|
1363
|
-
return configManager.getLogger();
|
|
1364
|
-
}
|
|
1365
|
-
function isDebugMode() {
|
|
1366
|
-
return configManager.isDebugMode();
|
|
1367
|
-
}
|
|
1368
|
-
function isDevelopmentMode() {
|
|
1369
|
-
return configManager.isDevelopmentMode();
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
// src/rbac/api.ts
|
|
1373
|
-
var globalEngine = null;
|
|
1374
|
-
function setupRBAC(supabase, config) {
|
|
1375
|
-
const logger = getRBACLogger();
|
|
1376
|
-
const fullConfig = {
|
|
1377
|
-
supabase,
|
|
1378
|
-
debug: import.meta.env.MODE === "development",
|
|
1379
|
-
logLevel: "warn",
|
|
1380
|
-
developmentMode: import.meta.env.MODE === "development",
|
|
1381
|
-
...config
|
|
1382
|
-
};
|
|
1383
|
-
createRBACConfig(fullConfig);
|
|
1384
|
-
globalEngine = createRBACEngine(supabase, config?.security);
|
|
1385
|
-
const auditManager = createAuditManager(supabase);
|
|
1386
|
-
setGlobalAuditManager(auditManager);
|
|
1387
|
-
logger.info("RBAC system initialized successfully");
|
|
1388
|
-
}
|
|
1389
|
-
function getEngine() {
|
|
1390
|
-
if (!globalEngine) {
|
|
1391
|
-
throw new RBACNotInitializedError();
|
|
1392
|
-
}
|
|
1393
|
-
return globalEngine;
|
|
1394
|
-
}
|
|
1395
|
-
async function getAccessLevel(input) {
|
|
1396
|
-
const engine = getEngine();
|
|
1397
|
-
return engine.getAccessLevel(input);
|
|
1398
|
-
}
|
|
1399
|
-
async function getPermissionMap(input) {
|
|
1400
|
-
const engine = getEngine();
|
|
1401
|
-
return engine.getPermissionMap(input);
|
|
1402
|
-
}
|
|
1403
|
-
async function resolveAppContext(input) {
|
|
1404
|
-
const engine = getEngine();
|
|
1405
|
-
return engine.resolveAppContext(input);
|
|
1406
|
-
}
|
|
1407
|
-
async function getRoleContext(input) {
|
|
1408
|
-
const engine = getEngine();
|
|
1409
|
-
return engine.getRoleContext(input);
|
|
1410
|
-
}
|
|
1411
|
-
async function isPermitted(input) {
|
|
1412
|
-
const engine = getEngine();
|
|
1413
|
-
if (!input.scope.organisationId) {
|
|
1414
|
-
throw new OrganisationContextRequiredError();
|
|
1415
|
-
}
|
|
1416
|
-
const securityContext = {
|
|
1417
|
-
userId: input.userId,
|
|
1418
|
-
organisationId: input.scope.organisationId,
|
|
1419
|
-
// Required - no fallback
|
|
1420
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
1421
|
-
// Optional fields can be omitted
|
|
1422
|
-
};
|
|
1423
|
-
return engine.isPermitted(input, securityContext);
|
|
1424
|
-
}
|
|
1425
|
-
async function isPermittedCached(input) {
|
|
1426
|
-
const { userId, scope, permission, pageId } = input;
|
|
1427
|
-
const cacheKey = RBACCache.generatePermissionKey({
|
|
1428
|
-
userId,
|
|
1429
|
-
organisationId: scope.organisationId,
|
|
1430
|
-
eventId: scope.eventId,
|
|
1431
|
-
appId: scope.appId,
|
|
1432
|
-
permission,
|
|
1433
|
-
pageId
|
|
1434
|
-
});
|
|
1435
|
-
const cached = rbacCache.get(cacheKey);
|
|
1436
|
-
if (cached !== null) {
|
|
1437
|
-
return cached;
|
|
1438
|
-
}
|
|
1439
|
-
const result = await isPermitted(input);
|
|
1440
|
-
rbacCache.set(cacheKey, result);
|
|
1441
|
-
return result;
|
|
1442
|
-
}
|
|
1443
|
-
async function hasPermission(input) {
|
|
1444
|
-
return isPermitted(input);
|
|
1445
|
-
}
|
|
1446
|
-
async function hasAnyPermission(input) {
|
|
1447
|
-
const { permissions, ...baseInput } = input;
|
|
1448
|
-
for (const permission of permissions) {
|
|
1449
|
-
const hasPermission2 = await isPermitted({
|
|
1450
|
-
...baseInput,
|
|
1451
|
-
permission
|
|
1452
|
-
});
|
|
1453
|
-
if (hasPermission2) {
|
|
1454
|
-
return true;
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
return false;
|
|
1458
|
-
}
|
|
1459
|
-
async function hasAllPermissions(input) {
|
|
1460
|
-
const { permissions, ...baseInput } = input;
|
|
1461
|
-
for (const permission of permissions) {
|
|
1462
|
-
const hasPermission2 = await isPermitted({
|
|
1463
|
-
...baseInput,
|
|
1464
|
-
permission
|
|
1465
|
-
});
|
|
1466
|
-
if (!hasPermission2) {
|
|
1467
|
-
return false;
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
return true;
|
|
1471
|
-
}
|
|
1472
|
-
async function isSuperAdmin(userId) {
|
|
1473
|
-
const engine = getEngine();
|
|
1474
|
-
return engine["checkSuperAdmin"](userId);
|
|
1475
|
-
}
|
|
1476
|
-
async function getAppConfig(appId) {
|
|
1477
|
-
console.warn("[RBAC] getAppConfig called without Supabase client - returning null");
|
|
1478
|
-
return null;
|
|
1479
|
-
}
|
|
1480
|
-
async function getAppConfigWithClient(client, appId) {
|
|
1481
|
-
try {
|
|
1482
|
-
const { data, error } = await client.from("rbac_apps").select("requires_event").eq("id", appId).eq("is_active", true).single();
|
|
1483
|
-
if (error || !data) {
|
|
1484
|
-
return null;
|
|
1485
|
-
}
|
|
1486
|
-
return { requires_event: data.requires_event };
|
|
1487
|
-
} catch (err) {
|
|
1488
|
-
console.error("[RBAC] Error fetching app config:", err);
|
|
1489
|
-
return null;
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
async function isOrganisationAdmin(userId, organisationId) {
|
|
1493
|
-
const accessLevel = await getAccessLevel({
|
|
1494
|
-
userId,
|
|
1495
|
-
scope: { organisationId }
|
|
1496
|
-
});
|
|
1497
|
-
return accessLevel === "admin" || accessLevel === "super";
|
|
1498
|
-
}
|
|
1499
|
-
async function isEventAdmin(userId, scope) {
|
|
1500
|
-
if (!scope.eventId || !scope.appId) {
|
|
1501
|
-
return false;
|
|
1502
|
-
}
|
|
1503
|
-
const accessLevel = await getAccessLevel({ userId, scope });
|
|
1504
|
-
return accessLevel === "admin" || accessLevel === "super";
|
|
1505
|
-
}
|
|
1506
|
-
function invalidateUserCache(userId, organisationId) {
|
|
1507
|
-
const patterns = organisationId ? [
|
|
1508
|
-
CACHE_PATTERNS.PERMISSION(userId, organisationId),
|
|
1509
|
-
`access:${userId}:${organisationId}:`,
|
|
1510
|
-
`map:${userId}:${organisationId}:`
|
|
1511
|
-
] : [
|
|
1512
|
-
`perm:${userId}:`,
|
|
1513
|
-
`access:${userId}:`,
|
|
1514
|
-
`map:${userId}:`
|
|
1515
|
-
];
|
|
1516
|
-
patterns.forEach((pattern) => rbacCache.invalidate(pattern));
|
|
1517
|
-
}
|
|
1518
|
-
function invalidateOrganisationCache(organisationId) {
|
|
1519
|
-
rbacCache.invalidate(CACHE_PATTERNS.ORGANISATION(organisationId));
|
|
1520
|
-
}
|
|
1521
|
-
function invalidateEventCache(eventId) {
|
|
1522
|
-
rbacCache.invalidate(CACHE_PATTERNS.EVENT(eventId));
|
|
1523
|
-
}
|
|
1524
|
-
function invalidateAppCache(appId) {
|
|
1525
|
-
rbacCache.invalidate(CACHE_PATTERNS.APP(appId));
|
|
1526
|
-
}
|
|
1527
|
-
function clearCache() {
|
|
1528
|
-
rbacCache.clear();
|
|
1529
|
-
}
|
|
1530
|
-
|
|
1531
|
-
export {
|
|
1532
|
-
createRBACConfig,
|
|
1533
|
-
getRBACConfig,
|
|
1534
|
-
getRBACLogger,
|
|
1535
|
-
isDebugMode,
|
|
1536
|
-
isDevelopmentMode,
|
|
1537
|
-
OrganisationContextRequiredError,
|
|
1538
|
-
RBACCache,
|
|
1539
|
-
rbacCache,
|
|
1540
|
-
CACHE_PATTERNS,
|
|
1541
|
-
RBACEngine,
|
|
1542
|
-
createRBACEngine,
|
|
1543
|
-
setupRBAC,
|
|
1544
|
-
getAccessLevel,
|
|
1545
|
-
getPermissionMap,
|
|
1546
|
-
resolveAppContext,
|
|
1547
|
-
getRoleContext,
|
|
1548
|
-
isPermitted,
|
|
1549
|
-
isPermittedCached,
|
|
1550
|
-
hasPermission,
|
|
1551
|
-
hasAnyPermission,
|
|
1552
|
-
hasAllPermissions,
|
|
1553
|
-
isSuperAdmin,
|
|
1554
|
-
getAppConfig,
|
|
1555
|
-
getAppConfigWithClient,
|
|
1556
|
-
isOrganisationAdmin,
|
|
1557
|
-
isEventAdmin,
|
|
1558
|
-
invalidateUserCache,
|
|
1559
|
-
invalidateOrganisationCache,
|
|
1560
|
-
invalidateEventCache,
|
|
1561
|
-
invalidateAppCache,
|
|
1562
|
-
clearCache
|
|
1563
|
-
};
|
|
1564
|
-
//# sourceMappingURL=chunk-WP5I5GLN.js.map
|