@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
|
@@ -18,8 +18,14 @@
|
|
|
18
18
|
* Permission enforcement is identical to SearchKnowledge: each requested
|
|
19
19
|
* scopeID is run through SearchScopePermissionResolver before the engine
|
|
20
20
|
* is invoked. A denial publishes a single 'error' event and ends.
|
|
21
|
+
*
|
|
22
|
+
* 'provider' events are progress only — ProviderName, DurationMs and
|
|
23
|
+
* ResultCount. They arrive before the engine's permission pass, so they never
|
|
24
|
+
* carry a result: rows the caller's row filters would drop and unverified
|
|
25
|
+
* external-index hits must not reach the client. Results arrive in 'fused'
|
|
26
|
+
* and 'final'.
|
|
21
27
|
*/
|
|
22
|
-
import { Resolver, Mutation, Arg, Ctx, ObjectType, Field, Float, ID, Subscription, Root, PubSub, PubSubEngine } from 'type-graphql';
|
|
28
|
+
import { Resolver, Mutation, Arg, Ctx, ObjectType, Field, Float, Int, ID, Subscription, Root, PubSub, PubSubEngine } from 'type-graphql';
|
|
23
29
|
import { v4 as uuidv4 } from 'uuid';
|
|
24
30
|
import { GraphQLJSON } from 'graphql-type-json';
|
|
25
31
|
import { AppContext } from '../types.js';
|
|
@@ -59,10 +65,17 @@ export class SearchStreamNotification {
|
|
|
59
65
|
@Field(() => Float, { nullable: true })
|
|
60
66
|
DurationMs?: number;
|
|
61
67
|
|
|
62
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* Set on `fused`, `reranked` and `final` events. Always empty on a `provider` event: a provider's
|
|
70
|
+
* hits arrive before the permission pass, so the stream reports only their count (`ResultCount`).
|
|
71
|
+
*/
|
|
63
72
|
@Field(() => [SearchKnowledgeResultItem], { nullable: true })
|
|
64
73
|
Results?: SearchKnowledgeResultItem[];
|
|
65
74
|
|
|
75
|
+
/** Set on `provider` events: how many hits the provider returned, capped at `maxResults`. */
|
|
76
|
+
@Field(() => Int, { nullable: true })
|
|
77
|
+
ResultCount?: number;
|
|
78
|
+
|
|
66
79
|
/** Set on `final` events. */
|
|
67
80
|
@Field(() => SearchSourceCounts, { nullable: true })
|
|
68
81
|
SourceCounts?: SearchSourceCounts;
|
|
@@ -253,12 +266,15 @@ export class SearchKnowledgeStreamResolver extends ResolverBase {
|
|
|
253
266
|
|
|
254
267
|
switch (ev.phase) {
|
|
255
268
|
case 'provider':
|
|
269
|
+
// Progress only. `ev.results` is never forwarded, whatever it holds: these hits have
|
|
270
|
+
// not been through the permission pass.
|
|
256
271
|
return {
|
|
257
272
|
StreamID: streamID,
|
|
258
273
|
Phase: 'provider',
|
|
259
274
|
ProviderName: ev.providerName,
|
|
260
275
|
DurationMs: ev.durationMs,
|
|
261
|
-
|
|
276
|
+
ResultCount: ev.resultCount,
|
|
277
|
+
Results: [],
|
|
262
278
|
};
|
|
263
279
|
case 'fused':
|
|
264
280
|
return {
|
|
@@ -670,10 +670,8 @@ export class SqlLoggingConfigResolver extends ResolverBase {
|
|
|
670
670
|
}
|
|
671
671
|
|
|
672
672
|
try {
|
|
673
|
-
//
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
// Read file content
|
|
673
|
+
// Read directly: a missing file surfaces as ENOENT below, so a separate existence check
|
|
674
|
+
// would only open a window for the file to change between the check and the read.
|
|
677
675
|
const content = await fs.readFile(session.filePath, 'utf-8');
|
|
678
676
|
|
|
679
677
|
// Apply line limit if specified
|
|
@@ -3,6 +3,7 @@ import { AppContext, UserPayload } from '../types.js';
|
|
|
3
3
|
import { BaseEntity, CompositeKey, DatabaseProviderBase, EntityDeleteOptions, EntitySaveOptions, IMetadataProvider, LogError, Metadata, RunView, UserInfo } from '@memberjunction/core';
|
|
4
4
|
import { RequireSystemUser } from '../directives/RequireSystemUser.js';
|
|
5
5
|
import { GetReadWriteProvider } from '../util.js';
|
|
6
|
+
import { CreateIsolatedProvider } from '../isolatedProvider.js';
|
|
6
7
|
import { CompositeKeyInputType, CompositeKeyOutputType } from '../generic/KeyInputOutputTypes.js';
|
|
7
8
|
import { MJDatasetItemEntity } from '@memberjunction/core-entities';
|
|
8
9
|
|
|
@@ -216,7 +217,11 @@ export class SyncDataResolver {
|
|
|
216
217
|
try {
|
|
217
218
|
// Run the view to find matching records, then delete them all atomically —
|
|
218
219
|
// any single failure rolls back the entire batch so the dataset stays consistent.
|
|
219
|
-
|
|
220
|
+
// The rows are loaded FROM the transaction's provider so each Delete() runs inside it;
|
|
221
|
+
// with no request provider, an isolated one, never the shared Metadata.Provider, whose
|
|
222
|
+
// transaction would swallow every concurrent caller's writes (see CreateIsolatedProvider).
|
|
223
|
+
const provider = (providerOverride as unknown as DatabaseProviderBase) ?? await CreateIsolatedProvider();
|
|
224
|
+
const rv = RunView.FromMetadataProvider(provider);
|
|
220
225
|
const data = await rv.RunView<BaseEntity>({
|
|
221
226
|
EntityName: entityName,
|
|
222
227
|
ExtraFilter: filter,
|
|
@@ -234,7 +239,6 @@ export class SyncDataResolver {
|
|
|
234
239
|
return;
|
|
235
240
|
}
|
|
236
241
|
|
|
237
|
-
const provider = (providerOverride ?? Metadata.Provider) as unknown as DatabaseProviderBase;
|
|
238
242
|
await provider.BeginTransaction();
|
|
239
243
|
try {
|
|
240
244
|
for (const entityObject of data.Results) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Arg, Ctx, Field, InputType, Mutation, ObjectType, registerEnumType } from 'type-graphql';
|
|
2
2
|
import { AppContext, UserPayload } from '../types.js';
|
|
3
3
|
import { DatabaseProviderBase, EntityDeleteOptions, EntitySaveOptions, IMetadataProvider, LogError, Metadata, RunView, UserInfo } from '@memberjunction/core';
|
|
4
|
-
import { UUIDsEqual } from '@memberjunction/global';
|
|
4
|
+
import { GroupByUUID, IndexByUUID, UUIDsEqual } from '@memberjunction/global';
|
|
5
5
|
import { RequireSystemUser } from '../directives/RequireSystemUser.js';
|
|
6
6
|
import { MJRoleEntity, MJUserEntity, MJUserRoleEntity } from '@memberjunction/core-entities';
|
|
7
7
|
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
@@ -430,14 +430,23 @@ export class SyncRolesAndUsersResolver {
|
|
|
430
430
|
const dbRoles = rResult.Results;
|
|
431
431
|
const dbUserRoles = urResult.Results;
|
|
432
432
|
|
|
433
|
+
// Index everything once: looking each user, role and assignment up by scanning the full lists
|
|
434
|
+
// made this O(users x user roles).
|
|
435
|
+
const dbUsersByEmail = this.indexByTrimmedLowerCase(dbUsers, u => u.Email);
|
|
436
|
+
const dbRolesByName = this.indexByTrimmedLowerCase(dbRoles, r => r.Name);
|
|
437
|
+
const dbRolesByID = IndexByUUID(dbRoles);
|
|
438
|
+
const dbUserRolesByUser = GroupByUUID(dbUserRoles, ur => ur.UserID);
|
|
439
|
+
|
|
433
440
|
// Saves/Deletes below run inside the outer SyncUsers/SyncRolesAndUsers transaction.
|
|
434
441
|
for (const user of users) {
|
|
435
|
-
const dbUser =
|
|
442
|
+
const dbUser = dbUsersByEmail.get(user.Email.trim().toLowerCase());
|
|
436
443
|
if (!dbUser) continue;
|
|
437
444
|
|
|
445
|
+
// Read before any save below; the saves never add to dbUserRoles, so this matches the full-list scans it replaces.
|
|
446
|
+
const thisUserDBRoles = dbUserRolesByUser.Get(dbUser.ID) ?? [];
|
|
438
447
|
for (const role of user.Roles) {
|
|
439
|
-
const dbRole =
|
|
440
|
-
if (dbRole && !
|
|
448
|
+
const dbRole = dbRolesByName.get(role.Name.trim().toLowerCase());
|
|
449
|
+
if (dbRole && !thisUserDBRoles.some(ur => UUIDsEqual(ur.RoleID, dbRole.ID))) {
|
|
441
450
|
const ur = await md.GetEntityObject<MJUserRoleEntity>("MJ: User Roles", u);
|
|
442
451
|
ur.UserID = dbUser.ID;
|
|
443
452
|
ur.RoleID = dbRole.ID;
|
|
@@ -448,9 +457,8 @@ export class SyncRolesAndUsersResolver {
|
|
|
448
457
|
}
|
|
449
458
|
|
|
450
459
|
// Check for DB user roles that are NOT in the user.Roles input — those need to be removed
|
|
451
|
-
const thisUserDBRoles = dbUserRoles.filter(ur => UUIDsEqual(ur.UserID, dbUser.ID));
|
|
452
460
|
for (const dbUserRole of thisUserDBRoles) {
|
|
453
|
-
const role = user.Roles.find(r => r.Name.trim().toLowerCase() ===
|
|
461
|
+
const role = user.Roles.find(r => r.Name.trim().toLowerCase() === dbRolesByID.Get(dbUserRole.RoleID)?.Name.trim().toLowerCase());
|
|
454
462
|
if (!role && !this.IsStandardRole(dbUserRole.Role)) {
|
|
455
463
|
if (!await dbUserRole.Delete()) {
|
|
456
464
|
throw new Error(`Failed to remove role from user '${dbUser.Email}': ${dbUserRole.LatestResult?.CompleteMessage ?? 'unknown error'}`);
|
|
@@ -459,5 +467,15 @@ export class SyncRolesAndUsersResolver {
|
|
|
459
467
|
}
|
|
460
468
|
}
|
|
461
469
|
}
|
|
470
|
+
|
|
471
|
+
/** Keys rows by a trimmed, lower-cased name; the first row wins, as `find` would return. */
|
|
472
|
+
private indexByTrimmedLowerCase<T>(rows: T[], keyOf: (row: T) => string | null | undefined): Map<string, T> {
|
|
473
|
+
const index = new Map<string, T>();
|
|
474
|
+
for (const row of rows) {
|
|
475
|
+
const key = keyOf(row)?.trim().toLowerCase();
|
|
476
|
+
if (key != null && !index.has(key)) index.set(key, row);
|
|
477
|
+
}
|
|
478
|
+
return index;
|
|
479
|
+
}
|
|
462
480
|
}
|
|
463
481
|
|
|
@@ -19,6 +19,7 @@ import { Arg, Field, ID, ObjectType, PubSubEngine, Resolver, ResolverFilterData,
|
|
|
19
19
|
import { LogError } from '@memberjunction/core';
|
|
20
20
|
import { UUIDsEqual } from '@memberjunction/global';
|
|
21
21
|
import type { TaskGraphFrame, TaskGraphObserver } from '@memberjunction/task-graph';
|
|
22
|
+
import { PublishStatusUpdate } from '../generic/PushStatusResolver.js';
|
|
22
23
|
|
|
23
24
|
export const TASK_GRAPH_FRAMES_TOPIC = 'TASK_GRAPH_FRAMES';
|
|
24
25
|
|
|
@@ -173,7 +174,10 @@ export function taskGraphFrameFilter(data: {
|
|
|
173
174
|
* the same reason.
|
|
174
175
|
*/
|
|
175
176
|
export class TaskGraphFrameBroadcaster implements TaskGraphObserver {
|
|
176
|
-
constructor(
|
|
177
|
+
constructor(
|
|
178
|
+
private readonly pubSub: PubSubEngine,
|
|
179
|
+
private readonly bridgeToPushStatus: boolean = false,
|
|
180
|
+
) {}
|
|
177
181
|
|
|
178
182
|
public OnFrame(frame: TaskGraphFrame): void {
|
|
179
183
|
try {
|
|
@@ -187,6 +191,34 @@ export class TaskGraphFrameBroadcaster implements TaskGraphObserver {
|
|
|
187
191
|
TASK_GRAPH_FRAMES_TOPIC,
|
|
188
192
|
{ ...frame, ownerUserId: frame.OwnerUserID } satisfies TaskGraphFramePayload,
|
|
189
193
|
);
|
|
194
|
+
|
|
195
|
+
if (
|
|
196
|
+
this.bridgeToPushStatus &&
|
|
197
|
+
(frame.Kind === 'TaskStarted' ||
|
|
198
|
+
frame.Kind === 'TaskCompleted' ||
|
|
199
|
+
frame.Kind === 'TaskFailed' ||
|
|
200
|
+
frame.Kind === 'NodeProgress' ||
|
|
201
|
+
frame.Kind === 'GraphSettled')
|
|
202
|
+
) {
|
|
203
|
+
const defaultMsg = frame.TaskName
|
|
204
|
+
? `${frame.TaskName}: ${frame.Status ?? frame.Kind}`
|
|
205
|
+
: `Workflow: ${frame.Status ?? frame.Kind}`;
|
|
206
|
+
PublishStatusUpdate(this.pubSub, {
|
|
207
|
+
sessionId: '*',
|
|
208
|
+
ownerUserId: frame.OwnerUserID,
|
|
209
|
+
message: JSON.stringify({
|
|
210
|
+
resolver: 'TaskOrchestrator',
|
|
211
|
+
type: 'TaskGraphFrame',
|
|
212
|
+
status: 'ok',
|
|
213
|
+
data: {
|
|
214
|
+
...frame,
|
|
215
|
+
taskName: frame.TaskName,
|
|
216
|
+
message: frame.ProgressMessage || defaultMsg,
|
|
217
|
+
percentComplete: frame.ProgressPercent,
|
|
218
|
+
},
|
|
219
|
+
}),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
190
222
|
} catch (e) {
|
|
191
223
|
LogError(`[TaskGraphFrameBroadcaster] Could not publish ${frame.Kind} (ignored): ${e instanceof Error ? e.message : String(e)}`);
|
|
192
224
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { Arg, Ctx, Field, InputType, Int, ObjectType, Query, Resolver } from 'type-graphql';
|
|
2
2
|
import { GraphQLJSONObject } from 'graphql-type-json';
|
|
3
3
|
import { RunQuery, IRunQueryProvider, LogError, LogStatus, QueryExecutionSpec } from '@memberjunction/core';
|
|
4
|
+
import type { DatabaseProviderBase, UserInfo } from '@memberjunction/core';
|
|
5
|
+
import { RenderPipeline } from '@memberjunction/generic-database-provider';
|
|
4
6
|
import { AppContext } from '../types.js';
|
|
5
7
|
import { GetReadOnlyProvider } from '../util.js';
|
|
6
8
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
9
|
+
import { IsScopeLimitedPrincipal } from '../auth/scopeLimitedPrincipal.js';
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
12
|
* GraphQL input type for inline dependency queries.
|
|
@@ -88,6 +91,14 @@ export class TestQuerySQLResult {
|
|
|
88
91
|
*
|
|
89
92
|
* Security:
|
|
90
93
|
* - Requires `query:test` scope for API key users (no-op for JWT auth)
|
|
94
|
+
* - Refuses scope-limited principals (see {@link IsScopeLimitedPrincipal}). Raw SQL bypasses
|
|
95
|
+
* RunView, entity permissions and row-level security, so the RLS scope tokens that confine a
|
|
96
|
+
* magic-link session are never applied on this path — there is no narrower filter to fall back
|
|
97
|
+
* to, only refusal.
|
|
98
|
+
* - Authorizes the RENDERED SQL: every table reference must be an entity base view the caller
|
|
99
|
+
* reads unscoped (CanRead, no row-level filter, no denied fields). Entities with row/field
|
|
100
|
+
* policy for this user are refused — raw SQL cannot apply that policy, so those reads belong
|
|
101
|
+
* on RunView. Same screen as {@link AdhocQueryResolver.ExecuteAdhocQuery}.
|
|
91
102
|
* - Uses read-only database connection (no mutation possible)
|
|
92
103
|
* - Enforces MaxRows limit (default 100) to prevent unbounded queries
|
|
93
104
|
*
|
|
@@ -104,15 +115,24 @@ export class TestQuerySQLResolver extends ResolverBase {
|
|
|
104
115
|
): Promise<TestQuerySQLResult> {
|
|
105
116
|
try {
|
|
106
117
|
await this.CheckAPIKeyScopeAuthorization('query:test', '*', context.userPayload);
|
|
118
|
+
// SECURITY: refuse principals whose read authority is narrower than their role grant.
|
|
119
|
+
// Transient test SQL does not go through RunView, entity permissions or row-level
|
|
120
|
+
// security, so the RLS scope tokens that confine a magic-link session are never
|
|
121
|
+
// substituted. There is no filter to narrow, so the only correct answer for such a
|
|
122
|
+
// principal is no — the same refusal ExecuteAdhocQuery applies.
|
|
123
|
+
if (IsScopeLimitedPrincipal(context.userPayload?.userRecord)) {
|
|
124
|
+
return this.buildErrorResult('Ad-hoc SQL execution is not permitted for scope-limited sessions.');
|
|
125
|
+
}
|
|
107
126
|
// Use read-only provider for security — no mutation possible
|
|
108
127
|
const provider = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: false });
|
|
109
128
|
if (!provider) {
|
|
110
|
-
return
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
129
|
+
return this.buildErrorResult('Read-only data source is not available. TestQuerySQL requires a read-only connection for security.');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const contextUser = context.userPayload?.userRecord;
|
|
133
|
+
const refusal = this.AuthorizeRenderedTestSQL(input, provider, contextUser);
|
|
134
|
+
if (refusal) {
|
|
135
|
+
return this.buildErrorResult(refusal);
|
|
116
136
|
}
|
|
117
137
|
|
|
118
138
|
const rq = new RunQuery(provider as unknown as IRunQueryProvider);
|
|
@@ -127,7 +147,7 @@ export class TestQuerySQLResolver extends ResolverBase {
|
|
|
127
147
|
|
|
128
148
|
LogStatus(`TestQuerySQL: Executing transient query (MaxRows=${spec.MaxRows}, UsesTemplate=${spec.UsesTemplate ?? false}, Dependencies=${spec.Dependencies?.length ?? 0})`);
|
|
129
149
|
|
|
130
|
-
const result = await rq.ExecuteFromSpec(spec,
|
|
150
|
+
const result = await rq.ExecuteFromSpec(spec, contextUser);
|
|
131
151
|
|
|
132
152
|
return {
|
|
133
153
|
Success: result.Success,
|
|
@@ -141,12 +161,47 @@ export class TestQuerySQLResolver extends ResolverBase {
|
|
|
141
161
|
} catch (err) {
|
|
142
162
|
LogError(err);
|
|
143
163
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
144
|
-
return {
|
|
145
|
-
Success: false,
|
|
146
|
-
RowCount: 0,
|
|
147
|
-
ExecutionTime: 0,
|
|
148
|
-
ErrorMessage: `TestQuerySQL failed: ${errorMessage}`,
|
|
149
|
-
};
|
|
164
|
+
return this.buildErrorResult(`TestQuerySQL failed: ${errorMessage}`);
|
|
150
165
|
}
|
|
151
166
|
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* SECURITY: authorizes the READ itself, against the SQL that will actually run — the same
|
|
170
|
+
* screen `AdhocQueryResolver.AuthorizeRenderedSQL` applies to ad-hoc SQL. Rendering comes
|
|
171
|
+
* first because composition (`{{query:"..."}}`), inline dependencies and Nunjucks templates
|
|
172
|
+
* can introduce table references the raw input never named, so the render here uses the same
|
|
173
|
+
* Parameters / UsesTemplate / Dependencies the execution spec will. Every table reference in
|
|
174
|
+
* the rendered SQL must be an entity base view the caller can read UNSCOPED — CanRead with no
|
|
175
|
+
* row-level filter and no denied fields — because raw SQL applies neither (see
|
|
176
|
+
* `assertFullQueryUsesReadableEntityViews`). The provider renders the same input the same way
|
|
177
|
+
* and only wraps it in server-built MaxRows capping, so the rendered SQL is the full set of
|
|
178
|
+
* reads. Returns the refusal message, or null.
|
|
179
|
+
*/
|
|
180
|
+
public AuthorizeRenderedTestSQL(input: TestQuerySQLInput, provider: DatabaseProviderBase, contextUser: UserInfo | undefined): string | null {
|
|
181
|
+
try {
|
|
182
|
+
const rendered = RenderPipeline.Run(input.SQL, {
|
|
183
|
+
Platform: provider.PlatformKey,
|
|
184
|
+
ContextUser: contextUser,
|
|
185
|
+
Parameters: input.Parameters,
|
|
186
|
+
UsesTemplate: input.UsesTemplate,
|
|
187
|
+
Dependencies: input.Dependencies,
|
|
188
|
+
OriginalSQL: input.SQL,
|
|
189
|
+
RequireReadStatement: true,
|
|
190
|
+
});
|
|
191
|
+
this.assertFullQueryUsesReadableEntityViews(rendered.Trace.AfterTemplates, provider, contextUser, 'transient test SQL');
|
|
192
|
+
return null;
|
|
193
|
+
} catch (err) {
|
|
194
|
+
return err instanceof Error ? err.message : String(err);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Builds a failed TestQuerySQLResult carrying the given message. */
|
|
199
|
+
private buildErrorResult(errorMessage: string): TestQuerySQLResult {
|
|
200
|
+
return {
|
|
201
|
+
Success: false,
|
|
202
|
+
RowCount: 0,
|
|
203
|
+
ExecutionTime: 0,
|
|
204
|
+
ErrorMessage: errorMessage,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
152
207
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Arg, Ctx, Field, InputType, Int, Mutation, ObjectType, registerEnumType } from 'type-graphql';
|
|
2
2
|
import { AppContext } from '../types.js';
|
|
3
|
-
import { CompositeKey, IMetadataProvider, KeyValuePair, LogError, Metadata, TransactionVariable, BaseEntity, EntityDeleteOptions, EntitySaveOptions } from '@memberjunction/core';
|
|
4
|
-
import { SafeJSONParse } from '@memberjunction/global';
|
|
3
|
+
import { CompositeKey, EntityInfo, IMetadataProvider, KeyValuePair, LogError, Metadata, TransactionVariable, TransactionGroupBase, BaseEntity, EntityDeleteOptions, EntitySaveOptions } from '@memberjunction/core';
|
|
4
|
+
import { SafeJSONParse, ValuesEqual } from '@memberjunction/global';
|
|
5
5
|
import { GetReadWriteProvider } from '../util.js';
|
|
6
6
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
7
7
|
|
|
@@ -142,11 +142,19 @@ export class TransactionResolver extends ResolverBase {
|
|
|
142
142
|
// rows were all refused arrive at Submit() empty, take its legitimate "nothing to do"
|
|
143
143
|
// branch, and report success for writes that never happened.
|
|
144
144
|
const refusals: string[] = [];
|
|
145
|
+
const preUpdateValues = new Map<number, Record<string, unknown>>();
|
|
146
|
+
|
|
147
|
+
// instantiate an entity object for every item first, so the group's variables are known
|
|
148
|
+
// BEFORE any Save()/Delete() runs below: a 'Use' variable sets its field at Submit(), after
|
|
149
|
+
// every server-side write check has passed, so an entity whose check depends on a field's
|
|
150
|
+
// final value must be able to see that a variable will set it.
|
|
151
|
+
for (const item of group.Items) {
|
|
152
|
+
entityObjects.push(await md.GetEntityObject(item.EntityName, context.userPayload.userRecord));
|
|
153
|
+
}
|
|
154
|
+
this.AddNetworkVariables(group, entityObjects, tg);
|
|
145
155
|
|
|
146
156
|
for (const [index, item] of group.Items.entries()) {
|
|
147
|
-
|
|
148
|
-
const entity = await md.GetEntityObject(item.EntityName, context.userPayload.userRecord);
|
|
149
|
-
entityObjects.push(entity); // save for later for mapping variables if needed
|
|
157
|
+
const entity = entityObjects[index];
|
|
150
158
|
|
|
151
159
|
// get the values from the payload
|
|
152
160
|
const itemValues = SafeJSONParse(item.EntityObjectJSON);
|
|
@@ -162,6 +170,11 @@ export class TransactionResolver extends ResolverBase {
|
|
|
162
170
|
case "Create":
|
|
163
171
|
if (item.OperationType === "Update") {
|
|
164
172
|
await entity.InnerLoad(pkey);
|
|
173
|
+
if (typeof entity.GetDataObject === 'function') {
|
|
174
|
+
preUpdateValues.set(index, await entity.GetDataObject());
|
|
175
|
+
} else if (typeof entity.GetAll === 'function') {
|
|
176
|
+
preUpdateValues.set(index, { ...(entity.GetAll() as Record<string, unknown>) });
|
|
177
|
+
}
|
|
165
178
|
}
|
|
166
179
|
objectValues.push(itemValues);
|
|
167
180
|
entity.SetMany(itemValues, true);
|
|
@@ -208,32 +221,17 @@ export class TransactionResolver extends ResolverBase {
|
|
|
208
221
|
`TransactionResolver::ExecuteTransactionGroup --- ${refusals.length} of ${group.Items.length} ` +
|
|
209
222
|
`item(s) were refused, so the group was not submitted:\n${refusals.join('\n')}`
|
|
210
223
|
);
|
|
211
|
-
return await this.PrepareReturnValue(false, entityObjects, objectValues, group);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// now, we need to set the variables
|
|
215
|
-
if (group.Variables && group.Variables.length > 0) {
|
|
216
|
-
for (const networkVar of group.Variables) {
|
|
217
|
-
// for each variable, add it to the transaction group and map the index from the network payload to the specific entity object loaded up above
|
|
218
|
-
if (networkVar.ItemIndex >= 0 && networkVar.ItemIndex < entityObjects.length) {
|
|
219
|
-
const entityObject = entityObjects[networkVar.ItemIndex];
|
|
220
|
-
const newVar = new TransactionVariable(networkVar.Name, entityObject, networkVar.FieldName, networkVar.Type);
|
|
221
|
-
tg.AddVariable(newVar);
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
throw new Error(`TransactionResolver::ExecuteTransactionGroup --- Error\n\n' + 'Invalid ItemIndex ${networkVar.ItemIndex} in TransactionVariable "${JSON.stringify(networkVar)}"`);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
224
|
+
return await this.PrepareReturnValue(false, entityObjects, objectValues, group, preUpdateValues);
|
|
227
225
|
}
|
|
228
226
|
|
|
229
227
|
// after all that, we are ready to roll, so let's run the TG
|
|
230
228
|
if (await tg.Submit()) {
|
|
231
229
|
// success!
|
|
232
|
-
return await this.PrepareReturnValue(true, entityObjects, objectValues, group);
|
|
230
|
+
return await this.PrepareReturnValue(true, entityObjects, objectValues, group, preUpdateValues);
|
|
233
231
|
}
|
|
234
232
|
else {
|
|
235
233
|
// failure, send back the results
|
|
236
|
-
return await this.PrepareReturnValue(false, entityObjects, objectValues, group);
|
|
234
|
+
return await this.PrepareReturnValue(false, entityObjects, objectValues, group, preUpdateValues);
|
|
237
235
|
}
|
|
238
236
|
}
|
|
239
237
|
catch (err) {
|
|
@@ -242,15 +240,48 @@ export class TransactionResolver extends ResolverBase {
|
|
|
242
240
|
}
|
|
243
241
|
}
|
|
244
242
|
|
|
245
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Adds the network payload's variables to the group, mapping each one's item index to the entity
|
|
245
|
+
* object built for that item.
|
|
246
|
+
*/
|
|
247
|
+
protected AddNetworkVariables(group: TransactionInputType, entityObjects: BaseEntity[], tg: TransactionGroupBase): void {
|
|
248
|
+
for (const networkVar of group.Variables ?? []) {
|
|
249
|
+
if (networkVar.ItemIndex >= 0 && networkVar.ItemIndex < entityObjects.length) {
|
|
250
|
+
const entityObject = entityObjects[networkVar.ItemIndex];
|
|
251
|
+
tg.AddVariable(new TransactionVariable(networkVar.Name, entityObject, networkVar.FieldName, networkVar.Type));
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
throw new Error(`TransactionResolver::ExecuteTransactionGroup --- Error\n\n' + 'Invalid ItemIndex ${networkVar.ItemIndex} in TransactionVariable "${JSON.stringify(networkVar)}"`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
protected async PrepareReturnValue(
|
|
260
|
+
success: boolean,
|
|
261
|
+
entityObjects: BaseEntity[],
|
|
262
|
+
objectValues: unknown[],
|
|
263
|
+
group: TransactionInputType,
|
|
264
|
+
preUpdateValues?: Map<number, Record<string, unknown>>
|
|
265
|
+
): Promise<TransactionOutputType> {
|
|
246
266
|
const jsonResults = [];
|
|
247
267
|
for (let i = 0; i < group.Items.length; i++) {
|
|
248
268
|
const item = group.Items[i];
|
|
249
|
-
if (item.OperationType==='Delete') {
|
|
269
|
+
if (item.OperationType === 'Delete') {
|
|
250
270
|
jsonResults.push(JSON.stringify(objectValues[i]));
|
|
251
271
|
}
|
|
272
|
+
else if (item.OperationType === 'Update') {
|
|
273
|
+
jsonResults.push(
|
|
274
|
+
await this.serializeUpdateReturnValue(
|
|
275
|
+
entityObjects[i],
|
|
276
|
+
objectValues[i],
|
|
277
|
+
preUpdateValues?.get(i),
|
|
278
|
+
group,
|
|
279
|
+
i
|
|
280
|
+
)
|
|
281
|
+
);
|
|
282
|
+
}
|
|
252
283
|
else {
|
|
253
|
-
// create
|
|
284
|
+
// create, return what is in the database
|
|
254
285
|
jsonResults.push(await entityObjects[i].GetDataObjectJSON());
|
|
255
286
|
}
|
|
256
287
|
}
|
|
@@ -261,6 +292,79 @@ export class TransactionResolver extends ResolverBase {
|
|
|
261
292
|
ResultsJSON: jsonResults
|
|
262
293
|
}
|
|
263
294
|
}
|
|
264
|
-
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Serializes a delta return payload for an `Update` transaction group item containing:
|
|
298
|
+
* - Primary key fields
|
|
299
|
+
* - All keys sent by the client in `sentValues` (`objectValues[i]`)
|
|
300
|
+
* - Any `TransactionVariable` fields targeting this item
|
|
301
|
+
* - `__mj_CreatedAt` and `__mj_UpdatedAt`
|
|
302
|
+
* - Virtual fields when any foreign key field was in `sentValues`
|
|
303
|
+
* - Any field whose post-save value differs from its pre-update DB snapshot (`preSaveValues`),
|
|
304
|
+
* capturing virtual fields, triggers, or server-side `BeforeSave()` mutations automatically
|
|
305
|
+
* while omitting unchanged columns.
|
|
306
|
+
*/
|
|
307
|
+
private async serializeUpdateReturnValue(
|
|
308
|
+
entity: BaseEntity,
|
|
309
|
+
sentValues: unknown,
|
|
310
|
+
preSaveValues: Record<string, unknown> | undefined,
|
|
311
|
+
group: TransactionInputType,
|
|
312
|
+
itemIndex: number
|
|
313
|
+
): Promise<string> {
|
|
314
|
+
const fullDataObj: Record<string, unknown> | null = typeof entity.GetDataObject === 'function'
|
|
315
|
+
? (await entity.GetDataObject() as Record<string, unknown>)
|
|
316
|
+
: SafeJSONParse<Record<string, unknown>>(await entity.GetDataObjectJSON());
|
|
317
|
+
if (!fullDataObj || typeof fullDataObj !== 'object' || !sentValues || typeof sentValues !== 'object') {
|
|
318
|
+
return await entity.GetDataObjectJSON();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const sentRecord = sentValues as Record<string, unknown>;
|
|
322
|
+
const includeKeys = new Set<string>([
|
|
323
|
+
EntityInfo.CreatedAtFieldName,
|
|
324
|
+
EntityInfo.UpdatedAtFieldName,
|
|
325
|
+
]);
|
|
326
|
+
|
|
327
|
+
for (const pk of entity.PrimaryKeys ?? []) {
|
|
328
|
+
if (pk?.Name) {
|
|
329
|
+
includeKeys.add(pk.Name);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
for (const key of Object.keys(sentRecord)) {
|
|
333
|
+
includeKeys.add(key);
|
|
334
|
+
}
|
|
335
|
+
for (const v of group.Variables ?? []) {
|
|
336
|
+
if (v.ItemIndex === itemIndex && v.FieldName) {
|
|
337
|
+
includeKeys.add(v.FieldName);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (Array.isArray(entity.Fields)) {
|
|
341
|
+
for (const f of entity.Fields) {
|
|
342
|
+
if (
|
|
343
|
+
f.EntityFieldInfo?.IsSpecialDateField ||
|
|
344
|
+
Boolean(f.EntityFieldInfo?.IsVirtual) ||
|
|
345
|
+
Boolean(f.EntityFieldInfo?.IsComputed) ||
|
|
346
|
+
f.ReadOnly
|
|
347
|
+
) {
|
|
348
|
+
includeKeys.add(f.Name);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (preSaveValues) {
|
|
353
|
+
for (const key of Object.keys(fullDataObj)) {
|
|
354
|
+
if (!ValuesEqual(fullDataObj[key], preSaveValues[key])) {
|
|
355
|
+
includeKeys.add(key);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const deltaObj: Record<string, unknown> = {};
|
|
361
|
+
for (const key of Object.keys(fullDataObj)) {
|
|
362
|
+
if (includeKeys.has(key)) {
|
|
363
|
+
deltaObj[key] = fullDataObj[key];
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return JSON.stringify(deltaObj);
|
|
367
|
+
}
|
|
368
|
+
|
|
265
369
|
}
|
|
266
370
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Self-service avatar mutation: `UpdateMyAvatar(ImageURL, IconClass)`.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS. Explorer's My Profile photo used to be a plain entity save of the caller's own
|
|
5
|
+
* `MJ: Users` row, which needs Update permission on `MJ: Users`. A deployment that locks its
|
|
6
|
+
* security model down cannot give ordinary users that permission: a row-level "own row only"
|
|
7
|
+
* filter restricts ROWS, not COLUMNS, so a user who may update their own row may also rewrite
|
|
8
|
+
* their own `Email` — and many read filters key on `{{UserEmail}}`, so the new value would match
|
|
9
|
+
* another person's memberships. MJ has no per-role field permissions (`MJUserEntityServer` in
|
|
10
|
+
* core-entities-server documents the same limit for `Type`). On such deployments every avatar save
|
|
11
|
+
* failed, and the login avatar sync failed silently.
|
|
12
|
+
*
|
|
13
|
+
* WHAT IT DOES. The caller comes ONLY from the request context — there is no user-ID argument. The
|
|
14
|
+
* input is validated at this trust boundary (`ValidateAvatarInput`). The caller's own row is then
|
|
15
|
+
* loaded AS THE SYSTEM USER, any state other than the two avatar columns is discarded, and only
|
|
16
|
+
* `UserImageURL` and `UserImageIconClass` are set before saving. Because the write is made as the
|
|
17
|
+
* system user, the caller's ID is logged. It works the same whether or not the caller holds Update
|
|
18
|
+
* on `MJ: Users`.
|
|
19
|
+
*
|
|
20
|
+
* Scope-limited sessions are refused (`IsScopeLimitedPrincipal`). An anonymous magic-link guest IS
|
|
21
|
+
* the shared Anonymous principal, so "their own row" belongs to every guest, and a system-user save
|
|
22
|
+
* goes around the entity permissions and RLS that confine any magic-link session.
|
|
23
|
+
*
|
|
24
|
+
* `MJUserEntityServer` accepts this save: the system user is the seeded `Type='Owner'` user, which
|
|
25
|
+
* is exempt from its invariants, and the save changes neither `Type` nor `Name` in any case.
|
|
26
|
+
*
|
|
27
|
+
* @module @memberjunction/server/resolvers/UserAvatarResolver
|
|
28
|
+
*/
|
|
29
|
+
import { Arg, Ctx, Field, Mutation, ObjectType, Resolver } from 'type-graphql';
|
|
30
|
+
import { LogError, LogStatus, UserInfo } from '@memberjunction/core';
|
|
31
|
+
import type { MJUserEntity } from '@memberjunction/core-entities';
|
|
32
|
+
import { AppContext } from '../types.js';
|
|
33
|
+
import { GetReadWriteProvider } from '../util.js';
|
|
34
|
+
import { GetSystemUser } from '../auth/index.js';
|
|
35
|
+
import { IsScopeLimitedPrincipal } from '../auth/scopeLimitedPrincipal.js';
|
|
36
|
+
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
37
|
+
import { ValidateAvatarInput } from './avatarInputValidation.js';
|
|
38
|
+
|
|
39
|
+
/** What the browser sees when the save fails. The server's detail goes to the log only. */
|
|
40
|
+
const SAVE_FAILED_MESSAGE = 'Could not save your avatar. Please try again or contact your administrator.';
|
|
41
|
+
|
|
42
|
+
@ObjectType()
|
|
43
|
+
export class UpdateMyAvatarResult {
|
|
44
|
+
@Field()
|
|
45
|
+
Success: boolean;
|
|
46
|
+
|
|
47
|
+
@Field({ nullable: true })
|
|
48
|
+
ErrorMessage?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@Resolver()
|
|
52
|
+
export class UserAvatarResolver extends ResolverBase {
|
|
53
|
+
/**
|
|
54
|
+
* Sets the CALLER's avatar. Null or empty clears a column; both null reverts to the default.
|
|
55
|
+
* Validation failures and save failures are returned as `Success: false` with a message.
|
|
56
|
+
*/
|
|
57
|
+
@Mutation(() => UpdateMyAvatarResult)
|
|
58
|
+
async UpdateMyAvatar(
|
|
59
|
+
@Ctx() ctx: AppContext,
|
|
60
|
+
@Arg('ImageURL', () => String, { nullable: true }) imageURL?: string | null,
|
|
61
|
+
@Arg('IconClass', () => String, { nullable: true }) iconClass?: string | null,
|
|
62
|
+
): Promise<UpdateMyAvatarResult> {
|
|
63
|
+
const caller = ctx.userPayload?.userRecord as UserInfo | undefined;
|
|
64
|
+
if (!caller?.ID) {
|
|
65
|
+
throw new Error('User is not authenticated');
|
|
66
|
+
}
|
|
67
|
+
if (IsScopeLimitedPrincipal(caller)) {
|
|
68
|
+
return { Success: false, ErrorMessage: 'Changing your avatar is not permitted for scope-limited sessions.' };
|
|
69
|
+
}
|
|
70
|
+
await this.CheckAPIKeyScopeAuthorization('entity:update', 'MJ: Users', ctx.userPayload);
|
|
71
|
+
|
|
72
|
+
const input = ValidateAvatarInput(imageURL, iconClass);
|
|
73
|
+
if (!input.Valid) {
|
|
74
|
+
return { Success: false, ErrorMessage: input.ErrorMessage };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
return await this.saveAvatar(ctx, caller, input.ImageURL, input.IconClass);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
LogError(`UpdateMyAvatar: failed for user ${caller.ID}: ${e instanceof Error ? e.message : String(e)}`);
|
|
81
|
+
return { Success: false, ErrorMessage: SAVE_FAILED_MESSAGE };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Loads the caller's own row as the system user and writes ONLY the two avatar columns. */
|
|
86
|
+
private async saveAvatar(
|
|
87
|
+
ctx: AppContext,
|
|
88
|
+
caller: UserInfo,
|
|
89
|
+
imageURL: string | null,
|
|
90
|
+
iconClass: string | null,
|
|
91
|
+
): Promise<UpdateMyAvatarResult> {
|
|
92
|
+
const systemUser = await GetSystemUser();
|
|
93
|
+
const provider = GetReadWriteProvider(ctx.providers);
|
|
94
|
+
const user = await provider.GetEntityObject<MJUserEntity>('MJ: Users', systemUser);
|
|
95
|
+
if (!(await user.Load(caller.ID))) {
|
|
96
|
+
LogError(`UpdateMyAvatar: could not load the MJ: Users row for user ${caller.ID}`);
|
|
97
|
+
return { Success: false, ErrorMessage: 'Could not load your user record.' };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
user.Revert(); // nothing but the two avatar columns may reach the write
|
|
101
|
+
user.UserImageURL = imageURL;
|
|
102
|
+
user.UserImageIconClass = iconClass;
|
|
103
|
+
LogStatus(`UpdateMyAvatar: user ${caller.ID} requested an avatar change; saving as the system user`);
|
|
104
|
+
if (!(await user.Save())) {
|
|
105
|
+
const detail = user.LatestResult?.CompleteMessage ?? 'unknown error';
|
|
106
|
+
LogError(`UpdateMyAvatar: save failed for user ${caller.ID}: ${detail}`);
|
|
107
|
+
return { Success: false, ErrorMessage: SAVE_FAILED_MESSAGE };
|
|
108
|
+
}
|
|
109
|
+
return { Success: true };
|
|
110
|
+
}
|
|
111
|
+
}
|