@memberjunction/server 6.2.0-edge.3 → 6.2.0-edge.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.
- package/README.md +20 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
- package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
- package/dist/agentSessions/BridgeHostInstance.js +30 -0
- package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
- package/dist/agentSessions/HostInstance.d.ts +75 -11
- package/dist/agentSessions/HostInstance.d.ts.map +1 -1
- package/dist/agentSessions/HostInstance.js +99 -28
- package/dist/agentSessions/HostInstance.js.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +236 -27
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +682 -54
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +48 -3
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +112 -9
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
- package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
- package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
- package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
- package/dist/agentSessions/index.d.ts +2 -0
- package/dist/agentSessions/index.d.ts.map +1 -1
- package/dist/agentSessions/index.js +10 -3
- package/dist/agentSessions/index.js.map +1 -1
- package/dist/apiRoot.d.ts +32 -0
- package/dist/apiRoot.d.ts.map +1 -0
- package/dist/apiRoot.js +67 -0
- package/dist/apiRoot.js.map +1 -0
- package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.js +25 -0
- package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
- package/dist/auth/LoginScreenEngine.d.ts +22 -0
- package/dist/auth/LoginScreenEngine.d.ts.map +1 -0
- package/dist/auth/LoginScreenEngine.js +57 -0
- package/dist/auth/LoginScreenEngine.js.map +1 -0
- package/dist/auth/actionAuthorization.d.ts +40 -0
- package/dist/auth/actionAuthorization.d.ts.map +1 -0
- package/dist/auth/actionAuthorization.js +94 -0
- package/dist/auth/actionAuthorization.js.map +1 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +4 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +3 -2
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +6 -3
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/newUsers.d.ts +10 -0
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +80 -11
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/config.d.ts +2514 -22
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +166 -4
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +8 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +14 -11
- package/dist/context.js.map +1 -1
- package/dist/coreRoutes.d.ts +29 -0
- package/dist/coreRoutes.d.ts.map +1 -0
- package/dist/coreRoutes.js +142 -0
- package/dist/coreRoutes.js.map +1 -0
- package/dist/generated/graphql-schemas/__mj.d.ts +504 -3
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +2456 -7
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +43 -8
- package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
- package/dist/generic/CacheInvalidationResolver.js +59 -19
- package/dist/generic/CacheInvalidationResolver.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/PushStatusResolver.js +1 -1
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +123 -48
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +293 -305
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +16 -0
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/globalCors.d.ts +33 -0
- package/dist/globalCors.d.ts.map +1 -0
- package/dist/globalCors.js +58 -0
- package/dist/globalCors.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +219 -278
- package/dist/index.js.map +1 -1
- package/dist/isolatedProvider.d.ts +20 -0
- package/dist/isolatedProvider.d.ts.map +1 -0
- package/dist/isolatedProvider.js +22 -0
- package/dist/isolatedProvider.js.map +1 -0
- package/dist/multiTenancy/index.d.ts.map +1 -1
- package/dist/multiTenancy/index.js +22 -2
- package/dist/multiTenancy/index.js.map +1 -1
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
- package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
- package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js +139 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js +99 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts +100 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts.map +1 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.js +173 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.js.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
- package/dist/realtimeSessions/index.d.ts +19 -0
- package/dist/realtimeSessions/index.d.ts.map +1 -0
- package/dist/realtimeSessions/index.js +19 -0
- package/dist/realtimeSessions/index.js.map +1 -0
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts +30 -0
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -0
- package/dist/realtimeSessions/ownedRealtimeSession.js +47 -0
- package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -0
- package/dist/realtimeSessions/sessionAccess.d.ts +92 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionAccess.js +94 -0
- package/dist/realtimeSessions/sessionAccess.js.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
- package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
- package/dist/realtimeSessions/verificationCore.d.ts +243 -0
- package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationCore.js +356 -0
- package/dist/realtimeSessions/verificationCore.js.map +1 -0
- package/dist/realtimeSessions/verificationPages.d.ts +66 -0
- package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPages.js +110 -0
- package/dist/realtimeSessions/verificationPages.js.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.js +249 -0
- package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.js +435 -0
- package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +31 -5
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +75 -14
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts +14 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +21 -5
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/ArtifactFileResolver.d.ts +5 -1
- package/dist/resolvers/ArtifactFileResolver.d.ts.map +1 -1
- package/dist/resolvers/ArtifactFileResolver.js +9 -7
- package/dist/resolvers/ArtifactFileResolver.js.map +1 -1
- package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
- package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
- package/dist/resolvers/CacheStatsResolver.js +113 -2
- package/dist/resolvers/CacheStatsResolver.js.map +1 -1
- package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
- package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
- package/dist/resolvers/ClientToolRequestResolver.js +28 -10
- package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.d.ts +8 -4
- package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.js +6 -6
- package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
- package/dist/resolvers/DatasetResolver.d.ts +24 -3
- package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
- package/dist/resolvers/DatasetResolver.js +57 -8
- package/dist/resolvers/DatasetResolver.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +2 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +3 -2
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts +27 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +148 -2
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts +82 -11
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +279 -101
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.d.ts +33 -7
- package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.js +117 -38
- package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +6 -3
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.js +7 -1
- package/dist/resolvers/HumanHandoffResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +4 -2
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +9 -4
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/QueryResolver.d.ts +2 -0
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +13 -0
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts +54 -2
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +155 -10
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +157 -33
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +404 -116
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +100 -14
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +12 -11
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts +12 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.js +15 -2
- package/dist/resolvers/SearchKnowledgeStreamResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +2 -3
- package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +6 -2
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.d.ts +2 -1
- package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.js +28 -1
- package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.d.ts +24 -0
- package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.js +65 -13
- package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts +19 -2
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +102 -24
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
- package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
- package/dist/resolvers/UserAvatarResolver.js +124 -0
- package/dist/resolvers/UserAvatarResolver.js.map +1 -0
- package/dist/resolvers/UserResolver.d.ts.map +1 -1
- package/dist/resolvers/UserResolver.js +2 -17
- package/dist/resolvers/UserResolver.js.map +1 -1
- package/dist/resolvers/agent-run-data-guard.d.ts +31 -0
- package/dist/resolvers/agent-run-data-guard.d.ts.map +1 -0
- package/dist/resolvers/agent-run-data-guard.js +72 -0
- package/dist/resolvers/agent-run-data-guard.js.map +1 -0
- package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
- package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
- package/dist/resolvers/agentVisionConsentAudit.js +63 -0
- package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
- package/dist/resolvers/avatarInputValidation.d.ts +40 -0
- package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
- package/dist/resolvers/avatarInputValidation.js +97 -0
- package/dist/resolvers/avatarInputValidation.js.map +1 -0
- package/dist/resolvers/currentUserRoles.d.ts +29 -0
- package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
- package/dist/resolvers/currentUserRoles.js +30 -0
- package/dist/resolvers/currentUserRoles.js.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.d.ts +6 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.js +21 -0
- package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.d.ts +49 -0
- package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -0
- package/dist/rest/RealtimeRecordingHandler.js +383 -0
- package/dist/rest/RealtimeRecordingHandler.js.map +1 -0
- package/dist/rest/ViewOperationsHandler.d.ts +3 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +21 -12
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts +6 -0
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +6 -0
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/dist/serverExtensionReservedRoots.d.ts +12 -10
- package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
- package/dist/serverExtensionReservedRoots.js +14 -17
- package/dist/serverExtensionReservedRoots.js.map +1 -1
- package/dist/services/MJStorageBlobStore.d.ts +8 -1
- package/dist/services/MJStorageBlobStore.d.ts.map +1 -1
- package/dist/services/MJStorageBlobStore.js +25 -19
- package/dist/services/MJStorageBlobStore.js.map +1 -1
- package/dist/services/StartTaskGraphDispatcher.js +2 -2
- package/dist/services/StartTaskGraphDispatcher.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +5 -3
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +27 -5
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphAgentRunner.d.ts +14 -2
- package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphAgentRunner.js +157 -9
- package/dist/services/TaskGraphAgentRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +57 -4
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +607 -18
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/sharedCache.d.ts +105 -0
- package/dist/sharedCache.d.ts.map +1 -0
- package/dist/sharedCache.js +279 -0
- package/dist/sharedCache.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +99 -96
- package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
- package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
- package/src/__tests__/BridgeHostInstance.test.ts +232 -0
- package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
- package/src/__tests__/DatasetResolver.itemFilters.test.ts +201 -0
- package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +433 -0
- package/src/__tests__/FileResolver.storageAccess.test.ts +562 -0
- package/src/__tests__/GetDataContextDataResolver.sqlItems.test.ts +416 -0
- package/src/__tests__/HostInstance.test.ts +84 -0
- package/src/__tests__/HumanHandoffResolver.test.ts +17 -0
- package/src/__tests__/ListOperationsResolver.adhocScreen.test.ts +137 -0
- package/src/__tests__/LoginScreenEngine.test.ts +86 -0
- package/src/__tests__/MJStorageBlobStore.storageGate.test.ts +63 -0
- package/src/__tests__/MeetingResolver.test.ts +75 -1
- package/src/__tests__/RealtimeBridgeResolver.test.ts +415 -6
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
- package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
- package/src/__tests__/RealtimeRelay.test.ts +876 -0
- package/src/__tests__/RecordingAssemblyScheduler.test.ts +178 -0
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +133 -2
- package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
- package/src/__tests__/ResolverBase.balancedClause.test.ts +155 -0
- package/src/__tests__/ResolverBase.clauseUnscopedRead.test.ts +127 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +212 -6
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +5 -2
- package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +170 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +6 -4
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +258 -0
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
- package/src/__tests__/RunTemplateResolver.test.ts +132 -0
- package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
- package/src/__tests__/RunViewResolver.cacheCheckScreen.test.ts +165 -0
- package/src/__tests__/SearchKnowledgeStreamResolver.test.ts +161 -0
- package/src/__tests__/SessionJanitor.test.ts +1203 -21
- package/src/__tests__/SessionManager.test.ts +240 -17
- package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
- package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +25 -0
- package/src/__tests__/TaskGraphAgentRunner.test.ts +266 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +403 -22
- package/src/__tests__/TaskGraphFrameResolver.test.ts +22 -0
- package/src/__tests__/TestQuerySQLResolver.test.ts +274 -0
- package/src/__tests__/TransactionGroupResolver.refusals.test.ts +98 -1
- package/src/__tests__/UpdateMJDataContextItem.oldValues.test.ts +220 -0
- package/src/__tests__/UserAvatarResolver.test.ts +255 -0
- package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
- package/src/__tests__/ViewOperationsHandler.clauseScreen.test.ts +157 -0
- package/src/__tests__/actionAuthorization.test.ts +130 -0
- package/src/__tests__/agent-run-data-guard.test.ts +130 -0
- package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
- package/src/__tests__/apiRootBoot.test.ts +202 -0
- package/src/__tests__/avatarInputValidation.test.ts +160 -0
- package/src/__tests__/cacheInvalidationRecordData.test.ts +64 -0
- package/src/__tests__/conversationRunEvents.test.ts +56 -14
- package/src/__tests__/globalCors.test.ts +217 -0
- package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
- package/src/__tests__/magicLinkService.consume.test.ts +6 -0
- package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
- package/src/__tests__/multiTenancy.postgresRewrite.test.ts +181 -0
- package/src/__tests__/multiTenancy.security.test.ts +51 -0
- package/src/__tests__/multiTenancy.test.ts +1 -1
- package/src/__tests__/newUsers.template.test.ts +151 -0
- package/src/__tests__/newUsers.test.ts +74 -5
- package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
- package/src/__tests__/ownedRealtimeSession.test.ts +39 -0
- package/src/__tests__/pushStatusResolver.filter.test.ts +7 -0
- package/src/__tests__/realtimeRecordingHandler.test.ts +652 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
- package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
- package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
- package/src/__tests__/realtimeSessions/helpers.ts +4 -0
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
- package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
- package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
- package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
- package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
- package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
- package/src/__tests__/resolverBase.fls.test.ts +87 -4
- package/src/__tests__/serverExtensionHostOverlay.test.ts +66 -0
- package/src/__tests__/serverExtensionReservedRoots.test.ts +11 -96
- package/src/__tests__/sharedCache.test.ts +312 -0
- package/src/__tests__/widgetGuestElevation.test.ts +44 -0
- package/src/agentSessions/BridgeHostInstance.ts +32 -0
- package/src/agentSessions/HostInstance.ts +124 -28
- package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
- package/src/agentSessions/SessionJanitor.ts +821 -59
- package/src/agentSessions/SessionManager.ts +138 -26
- package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
- package/src/agentSessions/index.ts +10 -3
- package/src/apiRoot.ts +78 -0
- package/src/auth/AuthProviderCatalogRouter.ts +26 -0
- package/src/auth/LoginScreenEngine.ts +50 -0
- package/src/auth/actionAuthorization.ts +116 -0
- package/src/auth/index.ts +4 -8
- package/src/auth/magicLink/MagicLinkRouter.ts +3 -2
- package/src/auth/magicLink/MagicLinkService.ts +6 -3
- package/src/auth/newUsers.ts +99 -11
- package/src/config.ts +171 -5
- package/src/context.ts +15 -10
- package/src/coreRoutes.ts +166 -0
- package/src/generated/graphql-schemas/__mj.ts +1815 -57
- package/src/generic/CacheInvalidationResolver.ts +73 -18
- package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
- package/src/generic/PushStatusResolver.ts +1 -1
- package/src/generic/ResolverBase.ts +335 -326
- package/src/generic/RunViewResolver.ts +21 -0
- package/src/globalCors.ts +71 -0
- package/src/index.ts +231 -300
- package/src/isolatedProvider.ts +22 -0
- package/src/multiTenancy/index.ts +24 -2
- package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
- package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
- package/src/realtimeSessions/README.md +179 -0
- package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
- package/src/realtimeSessions/RealtimeSessionVerificationService.ts +168 -0
- package/src/realtimeSessions/RealtimeVerifyRouter.ts +109 -0
- package/src/realtimeSessions/RecordingAssemblyScheduler.ts +234 -0
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
- package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
- package/src/realtimeSessions/index.ts +18 -0
- package/src/realtimeSessions/ownedRealtimeSession.ts +55 -0
- package/src/realtimeSessions/sessionAccess.ts +145 -0
- package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
- package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
- package/src/realtimeSessions/verificationCore.ts +502 -0
- package/src/realtimeSessions/verificationPages.ts +149 -0
- package/src/realtimeSessions/verificationPolicy.ts +321 -0
- package/src/realtimeSessions/verificationWorkflow.ts +702 -0
- package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
- package/src/resolvers/ActionResolver.ts +84 -21
- package/src/resolvers/AdhocQueryResolver.ts +24 -6
- package/src/resolvers/ArtifactFileResolver.ts +9 -8
- package/src/resolvers/CacheStatsResolver.ts +89 -2
- package/src/resolvers/ClientToolRequestResolver.ts +25 -7
- package/src/resolvers/ConversationRunEventsResolver.ts +15 -11
- package/src/resolvers/DatasetResolver.ts +70 -10
- package/src/resolvers/EntityRecordNameResolver.ts +3 -2
- package/src/resolvers/FetchEntityVectorsResolver.ts +179 -2
- package/src/resolvers/FileResolver.ts +321 -106
- package/src/resolvers/GetDataContextDataResolver.ts +139 -41
- package/src/resolvers/GetDataResolver.ts +6 -3
- package/src/resolvers/HumanHandoffResolver.ts +8 -0
- package/src/resolvers/IntegrationDiscoveryResolver.ts +4 -2
- package/src/resolvers/MCPResolver.ts +8 -5
- package/src/resolvers/QueryResolver.ts +11 -0
- package/src/resolvers/RealtimeBridgeResolver.ts +163 -11
- package/src/resolvers/RealtimeClientSessionResolver.ts +511 -129
- package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
- package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
- package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
- package/src/resolvers/RunAIAgentResolver.ts +116 -14
- package/src/resolvers/RunTemplateResolver.ts +13 -12
- package/src/resolvers/SearchKnowledgeStreamResolver.ts +19 -3
- package/src/resolvers/SqlLoggingConfigResolver.ts +2 -4
- package/src/resolvers/SyncDataResolver.ts +6 -2
- package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
- package/src/resolvers/TaskGraphFrameResolver.ts +33 -1
- package/src/resolvers/TestQuerySQLResolver.ts +68 -13
- package/src/resolvers/TransactionGroupResolver.ts +131 -27
- package/src/resolvers/UserAvatarResolver.ts +111 -0
- package/src/resolvers/UserResolver.ts +2 -16
- package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
- package/src/resolvers/agent-run-data-guard.ts +89 -0
- package/src/resolvers/agentVisionConsentAudit.ts +82 -0
- package/src/resolvers/avatarInputValidation.ts +123 -0
- package/src/resolvers/currentUserRoles.ts +32 -1
- package/src/resolvers/meetings/meetingMutations.ts +21 -0
- package/src/rest/RealtimeRecordingHandler.ts +464 -0
- package/src/rest/ViewOperationsHandler.ts +29 -13
- package/src/rest/setupRESTEndpoints.ts +6 -0
- package/src/serverExtensionReservedRoots.ts +14 -17
- package/src/services/MJStorageBlobStore.ts +31 -30
- package/src/services/StartTaskGraphDispatcher.ts +2 -2
- package/src/services/TaskGraphActionRunner.ts +25 -5
- package/src/services/TaskGraphAgentRunner.ts +188 -11
- package/src/services/TaskGraphContinuationDeliverer.ts +713 -17
- package/src/sharedCache.ts +305 -0
- package/src/types.ts +7 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tenant PreRunView hook checks the caller's ExtraFilter and builds
|
|
3
|
+
* `(<ExtraFilter>) AND (<tenant predicate>)`. On PostgreSQL the provider then rewrites the whole
|
|
4
|
+
* filter (identifier quoting, T-SQL date functions, boolean literals), and that rewrite can change
|
|
5
|
+
* the caller's part after the hook checked it. These tests run the real hook, the real PostgreSQL
|
|
6
|
+
* rewrite and the real view query builder, with only ExecuteSQL recorded, and pin that the tenant
|
|
7
|
+
* predicate stays a separate AND term whatever the rewrite does to the caller's term. The last
|
|
8
|
+
* block runs the same path with no hook and pins that the per-term rewrite keeps a SQL Server
|
|
9
|
+
* bracket name whole.
|
|
10
|
+
*/
|
|
11
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
12
|
+
import {
|
|
13
|
+
ClearAllDataHooks,
|
|
14
|
+
Metadata,
|
|
15
|
+
RegisterDataHook,
|
|
16
|
+
UserInfo,
|
|
17
|
+
type EntityInfo,
|
|
18
|
+
type IMetadataProvider,
|
|
19
|
+
} from '@memberjunction/core';
|
|
20
|
+
import { PostgreSQLDataProvider } from '@memberjunction/postgresql-dataprovider';
|
|
21
|
+
import type { SQLParserDialect } from '@memberjunction/sql-dialect';
|
|
22
|
+
import { IsBalancedSQLFragment, SplitTopLevelAndTerms } from '@memberjunction/sql-parser';
|
|
23
|
+
import { CreateTenantPreRunViewHook } from '../multiTenancy/index.js';
|
|
24
|
+
import type { MultiTenancyConfig } from '../config.js';
|
|
25
|
+
|
|
26
|
+
const TENANT_TERM = `(("OrganizationID" = 't1'))`;
|
|
27
|
+
|
|
28
|
+
/** The real PostgreSQL provider over one entity fixture, with every executed statement recorded. */
|
|
29
|
+
class RecordingPostgreSQLProvider extends PostgreSQLDataProvider {
|
|
30
|
+
public readonly ExecutedSQL: string[] = [];
|
|
31
|
+
|
|
32
|
+
constructor(private readonly testEntities: EntityInfo[]) {
|
|
33
|
+
super();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public override get Entities(): EntityInfo[] {
|
|
37
|
+
return this.testEntities;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public override async ExecuteSQL<T>(sql: string): Promise<T[]> {
|
|
41
|
+
this.ExecutedSQL.push(sql);
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A tenant-scoped entity with a boolean field, which the PostgreSQL rewrite coerces. */
|
|
47
|
+
function customersEntity(): EntityInfo {
|
|
48
|
+
const field = (name: string, tsType: string) => ({ Name: name, CodeName: name, TSType: tsType, IsVirtual: false });
|
|
49
|
+
const fields = [field('ID', 'string'), field('Name', 'string'), field('OrganizationID', 'string'), field('IsActive', 'boolean')];
|
|
50
|
+
return {
|
|
51
|
+
ID: 'ENT-CUSTOMERS',
|
|
52
|
+
Name: 'Customers',
|
|
53
|
+
SchemaName: 'app',
|
|
54
|
+
BaseView: 'vwCustomers',
|
|
55
|
+
Fields: fields,
|
|
56
|
+
PrimaryKeys: [fields[0]],
|
|
57
|
+
FirstPrimaryKey: fields[0],
|
|
58
|
+
FieldByName: (n: string) => fields.find((f) => f.Name.toLowerCase() === n.trim().toLowerCase()),
|
|
59
|
+
DatetimeFields: [],
|
|
60
|
+
ExternalDataSourceID: null,
|
|
61
|
+
UserViewMaxRows: 0,
|
|
62
|
+
EnableFieldLevelSecurity: false,
|
|
63
|
+
AuditViewRuns: false,
|
|
64
|
+
GetUserPermisions: () => ({ CanRead: true }),
|
|
65
|
+
GetEffectiveRowFilterWhereClause: () => '',
|
|
66
|
+
} as unknown as EntityInfo;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function tenantConfig(): MultiTenancyConfig {
|
|
70
|
+
return {
|
|
71
|
+
enabled: true,
|
|
72
|
+
contextSource: 'header',
|
|
73
|
+
tenantHeader: 'x-tenant-id',
|
|
74
|
+
scopingStrategy: 'allowlist',
|
|
75
|
+
scopedEntities: ['Customers'],
|
|
76
|
+
autoExcludeCoreEntities: true,
|
|
77
|
+
defaultTenantColumn: 'OrganizationID',
|
|
78
|
+
entityColumnMappings: {},
|
|
79
|
+
adminRoles: ['Admin'],
|
|
80
|
+
writeProtection: 'strict',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The WHERE clause of the one statement the provider ran. */
|
|
85
|
+
function whereOf(provider: RecordingPostgreSQLProvider): string {
|
|
86
|
+
expect(provider.ExecutedSQL).toHaveLength(1);
|
|
87
|
+
const sql = provider.ExecutedSQL[0];
|
|
88
|
+
return sql.substring(sql.indexOf(' WHERE ') + ' WHERE '.length);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Whether `term` is one of the top-level AND terms inside the one pair of parentheses around
|
|
93
|
+
* `where`. Only then does it narrow every row the query returns.
|
|
94
|
+
*/
|
|
95
|
+
function isTopLevelAndTerm(where: string, term: string, dialect: SQLParserDialect): boolean {
|
|
96
|
+
const inner = where.slice(1, -1);
|
|
97
|
+
const wrapped = where.startsWith('(') && where.endsWith(')') && IsBalancedSQLFragment(inner, dialect).IsBalanced;
|
|
98
|
+
return wrapped && (SplitTopLevelAndTerms(inner, dialect) ?? []).some((t) => t.trim() === term);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
describe('Multi-tenancy on PostgreSQL — the provider rewrite cannot reach the tenant predicate', () => {
|
|
102
|
+
let provider: RecordingPostgreSQLProvider;
|
|
103
|
+
let savedProvider: IMetadataProvider;
|
|
104
|
+
let user: UserInfo;
|
|
105
|
+
|
|
106
|
+
beforeEach(() => {
|
|
107
|
+
provider = new RecordingPostgreSQLProvider([customersEntity()]);
|
|
108
|
+
savedProvider = Metadata.Provider;
|
|
109
|
+
Metadata.Provider = provider;
|
|
110
|
+
ClearAllDataHooks();
|
|
111
|
+
RegisterDataHook('PreRunView', CreateTenantPreRunViewHook(tenantConfig()));
|
|
112
|
+
user = new UserInfo(provider, { ID: 'U1', Email: 'u1@example.com' });
|
|
113
|
+
user.TenantContext = { TenantID: 't1', Source: 'header' };
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
afterEach(() => {
|
|
117
|
+
ClearAllDataHooks();
|
|
118
|
+
Metadata.Provider = savedProvider;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('keeps the tenant predicate a separate AND term when the rewrite changes the caller term', async () => {
|
|
122
|
+
// The rewrite turns the caller's quoted text into boolean literals and changes where its
|
|
123
|
+
// parentheses close. Each term is rewritten and wrapped on its own, so that change stays
|
|
124
|
+
// inside the caller's term.
|
|
125
|
+
const result = await provider.RunView({ EntityName: 'Customers', ExtraFilter: `"IsActive" = '1) AND ("IsActive" = 1'` }, user);
|
|
126
|
+
|
|
127
|
+
expect(result.Success).toBe(true);
|
|
128
|
+
expect(whereOf(provider)).toBe(`((("IsActive" = TRUE) AND ("IsActive" = TRUE)) AND ${TENANT_TERM})`);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('keeps the tenant predicate its own AND term after a quote the boolean rewrite drops, followed by a top-level OR', async () => {
|
|
132
|
+
// The rewrite drops the quote, so text the hook read inside a string literal becomes code
|
|
133
|
+
// with a top-level OR. The tenant predicate must still narrow every row.
|
|
134
|
+
const result = await provider.RunView({ EntityName: 'Customers', ExtraFilter: `"IsActive" = '1) OR (1=1) OR ("IsActive" = 1'` }, user);
|
|
135
|
+
|
|
136
|
+
expect(result.Success).toBe(true);
|
|
137
|
+
const where = whereOf(provider);
|
|
138
|
+
expect(isTopLevelAndTerm(where, TENANT_TERM, provider.Dialect), `tenant predicate is not a top-level AND term of ${where}`).toBe(true);
|
|
139
|
+
expect(where).toBe(`((("IsActive" = TRUE) OR (1=1) OR ("IsActive" = TRUE)) AND ${TENANT_TERM})`);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('refuses a caller term that the rewrite leaves unbalanced, and runs no SQL', async () => {
|
|
143
|
+
const result = await provider.RunView({ EntityName: 'Customers', ExtraFilter: `"IsActive" = '1) AND (1 = 1'` }, user);
|
|
144
|
+
|
|
145
|
+
expect(provider.ExecutedSQL).toEqual([]);
|
|
146
|
+
expect(result.Success).toBe(false);
|
|
147
|
+
expect(result.ErrorMessage).toMatch(/ExtraFilter/);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('leaves an ordinary filter and the tenant predicate as written, each in its own term', async () => {
|
|
151
|
+
const result = await provider.RunView({ EntityName: 'Customers', ExtraFilter: `"IsActive" = 1 OR "Name" = 'a'` }, user);
|
|
152
|
+
|
|
153
|
+
expect(result.Success).toBe(true);
|
|
154
|
+
expect(whereOf(provider)).toBe(`((("IsActive" = TRUE OR "Name" = 'a')) AND ${TENANT_TERM})`);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('PostgreSQL ExtraFilter rewrite with no PreRunView hook', () => {
|
|
159
|
+
let provider: RecordingPostgreSQLProvider;
|
|
160
|
+
let savedProvider: IMetadataProvider;
|
|
161
|
+
|
|
162
|
+
beforeEach(() => {
|
|
163
|
+
provider = new RecordingPostgreSQLProvider([customersEntity()]);
|
|
164
|
+
savedProvider = Metadata.Provider;
|
|
165
|
+
Metadata.Provider = provider;
|
|
166
|
+
ClearAllDataHooks();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
afterEach(() => {
|
|
170
|
+
Metadata.Provider = savedProvider;
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('keeps a SQL Server bracket name that contains AND whole when it rewrites each AND term', async () => {
|
|
174
|
+
const user = new UserInfo(provider, { ID: 'U1', Email: 'u1@example.com' });
|
|
175
|
+
|
|
176
|
+
const result = await provider.RunView({ EntityName: 'Customers', ExtraFilter: '[Profit and Loss] > 0 AND [IsActive] = 1' }, user);
|
|
177
|
+
|
|
178
|
+
expect(result.Success).toBe(true);
|
|
179
|
+
expect(whereOf(provider)).toBe('(("Profit and Loss" > 0) AND ("IsActive" = TRUE))');
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -518,6 +518,57 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
518
518
|
});
|
|
519
519
|
});
|
|
520
520
|
|
|
521
|
+
// ─── Client ExtraFilter containment ───────────────────────────────────
|
|
522
|
+
|
|
523
|
+
describe('client ExtraFilter must stay inside its parentheses', () => {
|
|
524
|
+
// The hook builds `(<ExtraFilter>) AND (<tenant>)`. A filter that closes a parenthesis it did
|
|
525
|
+
// not open turns that into `(1=1) OR (1=1) AND (<tenant>)`: AND binds tighter than OR, so the
|
|
526
|
+
// tenant predicate would narrow only one side of an OR the caller controls.
|
|
527
|
+
const escapes = [
|
|
528
|
+
'1=1) OR (1=1',
|
|
529
|
+
"Name = 'a') OR (Name = 'b'",
|
|
530
|
+
'1=1)) OR ((1=1',
|
|
531
|
+
];
|
|
532
|
+
|
|
533
|
+
it.each(escapes)('refuses the ExtraFilter %j instead of ANDing the tenant predicate onto it', (extraFilter) => {
|
|
534
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
535
|
+
const params = { EntityName: 'Customers', ExtraFilter: extraFilter } as RunViewParams;
|
|
536
|
+
|
|
537
|
+
expect(() => hook(params, makeUser('tenant-abc'))).toThrow(/parenthesis/);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it('refuses an ExtraFilter that ends inside an unterminated string literal', () => {
|
|
541
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
542
|
+
const params = { EntityName: 'Customers', ExtraFilter: "Name = 'a" } as RunViewParams;
|
|
543
|
+
|
|
544
|
+
expect(() => hook(params, makeUser('tenant-abc'))).toThrow(/unterminated/);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it('refuses an ExtraFilter that contains a comment', () => {
|
|
548
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
549
|
+
const params = { EntityName: 'Customers', ExtraFilter: "Name = 'a' -- note\n" } as RunViewParams;
|
|
550
|
+
|
|
551
|
+
expect(() => hook(params, makeUser('tenant-abc'))).toThrow(/comment/);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it('applies the PostgreSQL rules too when the provider dialect is unknown', () => {
|
|
555
|
+
// The mocked Metadata.Provider here is not a database provider, so the hook reads the filter
|
|
556
|
+
// with every supported dialect.
|
|
557
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
558
|
+
const params = { EntityName: 'Customers', ExtraFilter: 'Name = $$x$$' } as RunViewParams;
|
|
559
|
+
|
|
560
|
+
expect(() => hook(params, makeUser('tenant-abc'))).toThrow(/dollar/);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it('keeps a balanced ExtraFilter and the tenant predicate as separate parenthesized terms', () => {
|
|
564
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
565
|
+
const params = { EntityName: 'Customers', ExtraFilter: "Name = 'a) OR (b' OR Name = 'c'" } as RunViewParams;
|
|
566
|
+
|
|
567
|
+
const result = hook(params, makeUser('tenant-abc')) as RunViewParams;
|
|
568
|
+
expect(result.ExtraFilter).toBe("(Name = 'a) OR (b' OR Name = 'c') AND ([OrganizationID] = 'tenant-abc')");
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
|
|
521
572
|
// ─── PreSave security edge cases ──────────────────────────────────────
|
|
522
573
|
|
|
523
574
|
describe('PreSave security', () => {
|
|
@@ -87,7 +87,7 @@ describe('Multi-Tenancy Hooks', () => {
|
|
|
87
87
|
|
|
88
88
|
const result = hook(params, user);
|
|
89
89
|
expect((result as RunViewParams).ExtraFilter).toBe(
|
|
90
|
-
"(Status = 'Active') AND [OrganizationID] = 'tenant-abc'"
|
|
90
|
+
"(Status = 'Active') AND ([OrganizationID] = 'tenant-abc')"
|
|
91
91
|
);
|
|
92
92
|
});
|
|
93
93
|
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for userHandling.newUserTemplate: NewUserBase.createNewUser builds the new user as a copy of a
|
|
3
|
+
* template user through the record-cloning engine, and falls back to a plain user whenever the template
|
|
4
|
+
* can't be used (missing, not flagged IsTemplate, or the copy fails), so sign-in never breaks.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
mockConfig,
|
|
10
|
+
mockClone,
|
|
11
|
+
mockRunView,
|
|
12
|
+
mockLogError,
|
|
13
|
+
mockProvider,
|
|
14
|
+
savedUsers,
|
|
15
|
+
} = vi.hoisted(() => {
|
|
16
|
+
const savedUsers: Array<Record<string, unknown>> = [];
|
|
17
|
+
const makeUser = () => {
|
|
18
|
+
const user: Record<string, unknown> & { NewRecord: () => void; Load: (id: string) => Promise<boolean>; Save: () => Promise<boolean> } = {
|
|
19
|
+
NewRecord: vi.fn(() => {
|
|
20
|
+
user.ID = 'plain-user-id';
|
|
21
|
+
user.Created = 'plain';
|
|
22
|
+
}),
|
|
23
|
+
Load: vi.fn(async (id: string) => {
|
|
24
|
+
user.ID = id;
|
|
25
|
+
user.Created = 'template';
|
|
26
|
+
return true;
|
|
27
|
+
}),
|
|
28
|
+
Save: vi.fn(async () => {
|
|
29
|
+
savedUsers.push({ ...user });
|
|
30
|
+
return true;
|
|
31
|
+
}),
|
|
32
|
+
LatestResult: null,
|
|
33
|
+
};
|
|
34
|
+
return user;
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
mockConfig: {
|
|
38
|
+
userHandling: {
|
|
39
|
+
newUserRoles: [] as string[],
|
|
40
|
+
CreateUserApplicationRecords: false,
|
|
41
|
+
UserApplications: [] as string[],
|
|
42
|
+
contextUserForNewUserCreation: '',
|
|
43
|
+
newUserTemplate: { email: 'demo@example.org', preset: 'template' } as { email: string; preset: string } | undefined,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
mockClone: vi.fn(),
|
|
47
|
+
mockRunView: vi.fn(),
|
|
48
|
+
mockLogError: vi.fn(),
|
|
49
|
+
mockProvider: {
|
|
50
|
+
GetEntityObject: vi.fn(async () => makeUser()),
|
|
51
|
+
BeginTransaction: vi.fn(),
|
|
52
|
+
CommitTransaction: vi.fn(),
|
|
53
|
+
RollbackTransaction: vi.fn(),
|
|
54
|
+
Roles: [],
|
|
55
|
+
Applications: [],
|
|
56
|
+
},
|
|
57
|
+
savedUsers,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
vi.mock('../config.js', () => ({ configInfo: mockConfig }));
|
|
62
|
+
vi.mock('../isolatedProvider.js', () => ({ CreateIsolatedProvider: vi.fn(async () => mockProvider) }));
|
|
63
|
+
vi.mock('../auth/principals.js', () => ({ ResolveConfiguredPrincipal: vi.fn(() => ({ ID: 'system-id', Type: 'Owner' })) }));
|
|
64
|
+
vi.mock('@memberjunction/record-cloning', () => ({
|
|
65
|
+
RecordCloneEngine: class {
|
|
66
|
+
Clone = mockClone;
|
|
67
|
+
},
|
|
68
|
+
}));
|
|
69
|
+
vi.mock('@memberjunction/core', async (importOriginal) => {
|
|
70
|
+
const actual = await importOriginal<typeof import('@memberjunction/core')>();
|
|
71
|
+
return {
|
|
72
|
+
...actual,
|
|
73
|
+
RunView: { FromMetadataProvider: () => ({ RunView: mockRunView }) },
|
|
74
|
+
LogError: mockLogError,
|
|
75
|
+
LogStatus: vi.fn(),
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
vi.mock('@memberjunction/core-entities', () => ({ UserInfoEngine: { GetDefaultApplicationsForNewUser: () => [] } }));
|
|
79
|
+
|
|
80
|
+
import { NewUserBase } from '../auth/newUsers.js';
|
|
81
|
+
|
|
82
|
+
const templateRow = (isTemplate: boolean) => ({ Success: true, Results: [{ ID: 'tpl-id', IsTemplate: isTemplate }] });
|
|
83
|
+
const cloned = { Success: true, ResultCode: 'SUCCESS', Warnings: [], RecordsCloned: 120, Roots: [{ EntityName: 'MJ: Users', SourceKey: { KeyValuePairs: [] }, TargetKey: { KeyValuePairs: [{ FieldName: 'ID', Value: 'new-user-id' }] } }] };
|
|
84
|
+
|
|
85
|
+
describe('NewUserBase.createNewUser with newUserTemplate', () => {
|
|
86
|
+
beforeEach(() => {
|
|
87
|
+
vi.clearAllMocks();
|
|
88
|
+
savedUsers.length = 0;
|
|
89
|
+
mockConfig.userHandling.newUserTemplate = { email: 'demo@example.org', preset: 'template' };
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('creates the user as an active copy of the template', async () => {
|
|
93
|
+
mockRunView.mockResolvedValue(templateRow(true));
|
|
94
|
+
mockClone.mockResolvedValue(cloned);
|
|
95
|
+
|
|
96
|
+
const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
|
|
97
|
+
|
|
98
|
+
expect(mockClone).toHaveBeenCalledWith(
|
|
99
|
+
expect.objectContaining({
|
|
100
|
+
EntityName: 'MJ: Users',
|
|
101
|
+
SourceRecordKey: expect.objectContaining({ KeyValuePairs: [expect.objectContaining({ FieldName: 'ID', Value: 'tpl-id' })] }),
|
|
102
|
+
Preset: 'template',
|
|
103
|
+
PromptedValues: { Email: 'jane@example.com', FirstName: 'Jane', LastName: 'Doe' },
|
|
104
|
+
}),
|
|
105
|
+
expect.objectContaining({ ID: 'system-id' })
|
|
106
|
+
);
|
|
107
|
+
expect(user?.ID).toBe('new-user-id');
|
|
108
|
+
expect(savedUsers).toEqual([expect.objectContaining({ ID: 'new-user-id', Created: 'template', IsActive: true, LinkedRecordType: 'None' })]);
|
|
109
|
+
expect(mockProvider.BeginTransaction).not.toHaveBeenCalled();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('falls back to a plain user when the template user is not flagged IsTemplate', async () => {
|
|
113
|
+
mockRunView.mockResolvedValue(templateRow(false));
|
|
114
|
+
|
|
115
|
+
const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
|
|
116
|
+
|
|
117
|
+
expect(mockClone).not.toHaveBeenCalled();
|
|
118
|
+
expect(mockLogError.mock.calls.flat().join(' ')).toContain('not marked as a template user');
|
|
119
|
+
expect(user?.ID).toBe('plain-user-id');
|
|
120
|
+
expect(savedUsers[0]).toEqual(expect.objectContaining({ Created: 'plain', Email: 'jane@example.com', IsActive: true }));
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('falls back to a plain user when no user has the template email', async () => {
|
|
124
|
+
mockRunView.mockResolvedValue({ Success: true, Results: [] });
|
|
125
|
+
|
|
126
|
+
const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
|
|
127
|
+
|
|
128
|
+
expect(mockClone).not.toHaveBeenCalled();
|
|
129
|
+
expect(user?.ID).toBe('plain-user-id');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('falls back to a plain user when the copy is blocked', async () => {
|
|
133
|
+
mockRunView.mockResolvedValue(templateRow(true));
|
|
134
|
+
mockClone.mockResolvedValue({ Success: false, ResultCode: 'BLOCKED', Warnings: [{ Severity: 'Error', Code: 'FORBIDDEN', Message: 'needs Clone Records' }] });
|
|
135
|
+
|
|
136
|
+
const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
|
|
137
|
+
|
|
138
|
+
expect(mockLogError.mock.calls.flat().join(' ')).toContain('needs Clone Records');
|
|
139
|
+
expect(user?.ID).toBe('plain-user-id');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('creates a plain user, as before, when no template is configured', async () => {
|
|
143
|
+
mockConfig.userHandling.newUserTemplate = undefined;
|
|
144
|
+
|
|
145
|
+
const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
|
|
146
|
+
|
|
147
|
+
expect(mockRunView).not.toHaveBeenCalled();
|
|
148
|
+
expect(mockClone).not.toHaveBeenCalled();
|
|
149
|
+
expect(user?.ID).toBe('plain-user-id');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -30,6 +30,8 @@ const {
|
|
|
30
30
|
mockBeginTransaction,
|
|
31
31
|
mockCommitTransaction,
|
|
32
32
|
mockRollbackTransaction,
|
|
33
|
+
mockGlobalProviderCalls,
|
|
34
|
+
mockCreateIsolatedProvider,
|
|
33
35
|
mockGetDefaultApplicationsForNewUser,
|
|
34
36
|
mockRolesArray,
|
|
35
37
|
mockApplicationsArray,
|
|
@@ -69,6 +71,10 @@ const {
|
|
|
69
71
|
mockBeginTransaction: vi.fn(),
|
|
70
72
|
mockCommitTransaction: vi.fn(),
|
|
71
73
|
mockRollbackTransaction: vi.fn(),
|
|
74
|
+
// Every call new-user creation makes on the SHARED Metadata.Provider. Must stay empty: a
|
|
75
|
+
// transaction there nests into every concurrent request's (see CreateIsolatedProvider).
|
|
76
|
+
mockGlobalProviderCalls: [] as string[],
|
|
77
|
+
mockCreateIsolatedProvider: vi.fn(),
|
|
72
78
|
mockGetDefaultApplicationsForNewUser: vi.fn(),
|
|
73
79
|
mockRolesArray: [] as Array<{ ID: string; Name: string }>,
|
|
74
80
|
mockApplicationsArray: [] as Array<{ ID: string; Name: string }>,
|
|
@@ -79,6 +85,9 @@ const {
|
|
|
79
85
|
|
|
80
86
|
vi.mock('../config.js', () => ({ configInfo: mockConfig }));
|
|
81
87
|
|
|
88
|
+
// The provider new-user creation must use: its own instance, so its transaction is its own.
|
|
89
|
+
vi.mock('../isolatedProvider.js', () => ({ CreateIsolatedProvider: mockCreateIsolatedProvider }));
|
|
90
|
+
|
|
82
91
|
vi.mock('@memberjunction/generic-database-provider', () => {
|
|
83
92
|
const instance = {
|
|
84
93
|
get Users() {
|
|
@@ -89,6 +98,18 @@ vi.mock('@memberjunction/generic-database-provider', () => {
|
|
|
89
98
|
},
|
|
90
99
|
Refresh: mockRefresh,
|
|
91
100
|
GetSystemUser: vi.fn(),
|
|
101
|
+
/**
|
|
102
|
+
* The real cache falls back to a database read on a miss. The stub answers from
|
|
103
|
+
* the same array `Users` exposes, so these tests still describe the cache's behaviour.
|
|
104
|
+
*/
|
|
105
|
+
FindUser: vi.fn(async ({ Email }: { Email?: string }) =>
|
|
106
|
+
mockCacheUsers.find((u: UserInfo) => {
|
|
107
|
+
if (!u.Email || u.Email.trim() === '') {
|
|
108
|
+
console.error(`SYSTEM METADATA ISSUE: User ${u.ID} has no email address`);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
return u.Email.toLowerCase().trim() === (Email ?? '').toLowerCase().trim();
|
|
112
|
+
})),
|
|
92
113
|
};
|
|
93
114
|
return {
|
|
94
115
|
UserCache: class MockUserCache {
|
|
@@ -104,10 +125,11 @@ vi.mock('@memberjunction/generic-database-provider', () => {
|
|
|
104
125
|
|
|
105
126
|
vi.mock('@memberjunction/core', async (importOriginal) => {
|
|
106
127
|
const actual = await importOriginal<typeof import('@memberjunction/core')>();
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
128
|
+
// The shared global provider: records any use instead of doing anything.
|
|
129
|
+
const globalProvider = {
|
|
130
|
+
BeginTransaction: async () => { mockGlobalProviderCalls.push('BeginTransaction'); },
|
|
131
|
+
CommitTransaction: async () => { mockGlobalProviderCalls.push('CommitTransaction'); },
|
|
132
|
+
RollbackTransaction: async () => { mockGlobalProviderCalls.push('RollbackTransaction'); },
|
|
111
133
|
Dialect: { BooleanLiteral: (value: boolean) => (value ? '1' : '0') },
|
|
112
134
|
};
|
|
113
135
|
class MockMetadata {
|
|
@@ -118,9 +140,10 @@ vi.mock('@memberjunction/core', async (importOriginal) => {
|
|
|
118
140
|
return mockApplicationsArray;
|
|
119
141
|
}
|
|
120
142
|
public GetEntityObject(entityName: string, contextUser?: UserInfo): Promise<unknown> {
|
|
143
|
+
mockGlobalProviderCalls.push(`GetEntityObject:${entityName}`);
|
|
121
144
|
return mockGetEntityObject(entityName, contextUser);
|
|
122
145
|
}
|
|
123
|
-
static Provider =
|
|
146
|
+
static Provider = globalProvider;
|
|
124
147
|
}
|
|
125
148
|
class MockUserInfo {
|
|
126
149
|
constructor(_provider: unknown, initData: Record<string, unknown>) {
|
|
@@ -128,6 +151,9 @@ vi.mock('@memberjunction/core', async (importOriginal) => {
|
|
|
128
151
|
}
|
|
129
152
|
}
|
|
130
153
|
class MockRunView {
|
|
154
|
+
public static FromMetadataProvider(_provider: unknown): MockRunView {
|
|
155
|
+
return new MockRunView();
|
|
156
|
+
}
|
|
131
157
|
public RunView(params: unknown, contextUser?: UserInfo): Promise<unknown> {
|
|
132
158
|
return mockRunViewFn(params, contextUser);
|
|
133
159
|
}
|
|
@@ -295,6 +321,21 @@ beforeEach(() => {
|
|
|
295
321
|
});
|
|
296
322
|
mockRunViewFn.mockResolvedValue({ Success: true, Results: [] });
|
|
297
323
|
mockGetDefaultApplicationsForNewUser.mockReturnValue([]);
|
|
324
|
+
|
|
325
|
+
mockGlobalProviderCalls.length = 0;
|
|
326
|
+
mockCreateIsolatedProvider.mockImplementation(async () => ({
|
|
327
|
+
BeginTransaction: mockBeginTransaction,
|
|
328
|
+
CommitTransaction: mockCommitTransaction,
|
|
329
|
+
RollbackTransaction: mockRollbackTransaction,
|
|
330
|
+
Dialect: { BooleanLiteral: (value: boolean) => (value ? '1' : '0') },
|
|
331
|
+
get Roles() {
|
|
332
|
+
return mockRolesArray;
|
|
333
|
+
},
|
|
334
|
+
get Applications() {
|
|
335
|
+
return mockApplicationsArray;
|
|
336
|
+
},
|
|
337
|
+
GetEntityObject: (entityName: string, contextUser?: UserInfo) => mockGetEntityObject(entityName, contextUser),
|
|
338
|
+
}));
|
|
298
339
|
});
|
|
299
340
|
|
|
300
341
|
// ─── NewUserBase.createNewUser ──────────────────────────────────────────────
|
|
@@ -303,6 +344,34 @@ describe('NewUserBase.createNewUser', () => {
|
|
|
303
344
|
const create = (first = 'Ada', last = 'Lovelace', email = 'ada@example.com') =>
|
|
304
345
|
new NewUserBase().createNewUser(first, last, email);
|
|
305
346
|
|
|
347
|
+
describe('provider isolation', () => {
|
|
348
|
+
it('runs the whole provisioning on its own provider, never the shared Metadata.Provider', async () => {
|
|
349
|
+
mockConfig.userHandling.CreateUserApplicationRecords = true;
|
|
350
|
+
mockConfig.userHandling.UserApplications = ['CRM'];
|
|
351
|
+
mockRunViewFn.mockResolvedValue({ Success: true, Results: [{ EntityID: 'ent-1', Entity: 'Accounts' }] });
|
|
352
|
+
|
|
353
|
+
const user = await create();
|
|
354
|
+
|
|
355
|
+
expect(user).toBeTruthy();
|
|
356
|
+
expect(mockCreateIsolatedProvider).toHaveBeenCalledTimes(1);
|
|
357
|
+
expect(mockBeginTransaction).toHaveBeenCalledTimes(1);
|
|
358
|
+
expect(mockCommitTransaction).toHaveBeenCalledTimes(1);
|
|
359
|
+
// User, role, application and application-entity records all came from the isolated one.
|
|
360
|
+
expect(getEntityObjectCalls.map((c) => c.entityName)).toEqual([
|
|
361
|
+
'MJ: Users', 'MJ: User Roles', 'MJ: User Applications', 'MJ: User Application Entities',
|
|
362
|
+
]);
|
|
363
|
+
expect(mockGlobalProviderCalls).toEqual([]);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('rolls back on the isolated provider too, never the shared one', async () => {
|
|
367
|
+
saveBehavior['MJ: Users'] = () => false;
|
|
368
|
+
|
|
369
|
+
expect(await create()).toBeNull();
|
|
370
|
+
expect(mockRollbackTransaction).toHaveBeenCalledTimes(1);
|
|
371
|
+
expect(mockGlobalProviderCalls).toEqual([]);
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
306
375
|
describe('context-user resolution', () => {
|
|
307
376
|
it('uses the configured contextUserForNewUserCreation for every entity it creates', async () => {
|
|
308
377
|
const user = await create();
|
|
@@ -20,8 +20,10 @@ const { mockGetEntityObject, writableStore } = vi.hoisted(() => ({
|
|
|
20
20
|
writableStore: { last: null as Record<string, unknown> | null },
|
|
21
21
|
}));
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
// The real UUID helpers: the reconciler indexes users with IndexByUUID, so a stub that only
|
|
24
|
+
// provides UUIDsEqual would make the owner lookup throw (and the pass silently do nothing).
|
|
25
|
+
vi.mock('@memberjunction/global', async (importOriginal) => ({
|
|
26
|
+
...(await importOriginal<typeof import('@memberjunction/global')>()),
|
|
25
27
|
}));
|
|
26
28
|
|
|
27
29
|
vi.mock('@memberjunction/sqlserver-dataprovider', () => ({
|
|
@@ -230,6 +232,19 @@ describe('ReconcileOrphanedConversationDetails', () => {
|
|
|
230
232
|
);
|
|
231
233
|
});
|
|
232
234
|
|
|
235
|
+
it('resolves the owner when the conversation spells the user ID in a different case', async () => {
|
|
236
|
+
// The cached user is 'OWNER-1'; SQL Server and PostgreSQL disagree on UUID case, so the
|
|
237
|
+
// conversation row may carry it lower-cased. The owner lookup must still find the user.
|
|
238
|
+
script([detail()], [{ ConversationDetailID: DETAIL_ID, Status: 'Failed', CompletedAt: longAgo() }], 'owner-1');
|
|
239
|
+
|
|
240
|
+
await ReconcileOrphanedConversationDetails(PROVIDER, USER);
|
|
241
|
+
|
|
242
|
+
expect(mockGetEntityObject).toHaveBeenCalledWith(
|
|
243
|
+
'MJ: Conversation Details',
|
|
244
|
+
expect.objectContaining({ ID: 'OWNER-1' })
|
|
245
|
+
);
|
|
246
|
+
});
|
|
247
|
+
|
|
233
248
|
it('leaves the detail alone when no owner can be resolved', async () => {
|
|
234
249
|
script([detail()], [{ ConversationDetailID: DETAIL_ID, Status: 'Failed', CompletedAt: longAgo() }], null);
|
|
235
250
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
3
|
+
import {
|
|
4
|
+
LoadOwnedRealtimeSession, RealtimeSessionNotFoundError, RealtimeSessionOwnershipError,
|
|
5
|
+
} from '../realtimeSessions/ownedRealtimeSession.js';
|
|
6
|
+
|
|
7
|
+
const SESSION = '11111111-1111-4111-8111-111111111111';
|
|
8
|
+
const OWNER = { ID: 'AAAAAAAA-0000-4000-8000-000000000001' } as unknown as UserInfo;
|
|
9
|
+
const STRANGER = { ID: 'BBBBBBBB-0000-4000-8000-000000000002' } as unknown as UserInfo;
|
|
10
|
+
|
|
11
|
+
function providerWith(row: { UserID: string } | null): IMetadataProvider {
|
|
12
|
+
return {
|
|
13
|
+
GetEntityObject: vi.fn(async () => ({
|
|
14
|
+
UserID: row?.UserID ?? '',
|
|
15
|
+
Load: vi.fn(async () => row !== null),
|
|
16
|
+
})),
|
|
17
|
+
} as unknown as IMetadataProvider;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('LoadOwnedRealtimeSession', () => {
|
|
21
|
+
it('returns the session when the caller owns it (UUIDs compared case-insensitively)', async () => {
|
|
22
|
+
const session = await LoadOwnedRealtimeSession(SESSION, OWNER, providerWith({ UserID: OWNER.ID.toLowerCase() }));
|
|
23
|
+
expect(session.UserID).toBe(OWNER.ID.toLowerCase());
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('throws RealtimeSessionNotFoundError when the row does not load', async () => {
|
|
27
|
+
await expect(LoadOwnedRealtimeSession(SESSION, OWNER, providerWith(null)))
|
|
28
|
+
.rejects.toBeInstanceOf(RealtimeSessionNotFoundError);
|
|
29
|
+
await expect(LoadOwnedRealtimeSession(SESSION, OWNER, providerWith(null)))
|
|
30
|
+
.rejects.toThrow(`Realtime session ${SESSION} not found`);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('throws RealtimeSessionOwnershipError for another user', async () => {
|
|
34
|
+
await expect(LoadOwnedRealtimeSession(SESSION, STRANGER, providerWith({ UserID: OWNER.ID })))
|
|
35
|
+
.rejects.toBeInstanceOf(RealtimeSessionOwnershipError);
|
|
36
|
+
await expect(LoadOwnedRealtimeSession(SESSION, STRANGER, providerWith({ UserID: OWNER.ID })))
|
|
37
|
+
.rejects.toThrow('Not authorized: you do not own this realtime session');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -53,4 +53,11 @@ describe('statusUpdatesFilter (B49 session-hijack gate)', () => {
|
|
|
53
53
|
it('matches identity case-insensitively (SQL Server upper vs PostgreSQL lower UUIDs)', () => {
|
|
54
54
|
expect(StatusUpdatesFilter({ payload: push({ ownerUserId: OWNER.toUpperCase() }), args: { sessionId: SESSION }, context: ctxFor(OWNER.toLowerCase()) })).toBe(true);
|
|
55
55
|
});
|
|
56
|
+
|
|
57
|
+
it('delivers server-originated wildcard sessionId ("*") pushes ONLY when ownerUserId matches the connection user', () => {
|
|
58
|
+
expect(StatusUpdatesFilter({ payload: push({ sessionId: '*', ownerUserId: OWNER }), args: { sessionId: SESSION }, context: ctxFor(OWNER) })).toBe(true);
|
|
59
|
+
expect(StatusUpdatesFilter({ payload: push({ sessionId: '*', ownerUserId: OWNER }), args: { sessionId: SESSION }, context: ctxFor(ATTACKER) })).toBe(false);
|
|
60
|
+
expect(StatusUpdatesFilter({ payload: push({ sessionId: '*', ownerUserId: '' }), args: { sessionId: SESSION }, context: ctxFor(OWNER) })).toBe(false);
|
|
61
|
+
});
|
|
56
62
|
});
|
|
63
|
+
|