@memberjunction/server 6.2.0-edge.2 → 6.2.0-edge.4
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 +30 -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 +173 -25
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +452 -54
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +43 -2
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +99 -8
- 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/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.map +1 -1
- package/dist/auth/index.js +3 -10
- package/dist/auth/index.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 +4567 -71
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +246 -3
- 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 +25 -34
- package/dist/context.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +1073 -10
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +9188 -3649
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +62 -6
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +179 -35
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +4 -0
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +37 -1
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +214 -159
- 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/postgresqlCompat.d.ts +8 -0
- package/dist/postgresqlCompat.d.ts.map +1 -0
- package/dist/postgresqlCompat.js +19 -0
- package/dist/postgresqlCompat.js.map +1 -0
- package/dist/postgresqlPoolSettings.d.ts +108 -0
- package/dist/postgresqlPoolSettings.d.ts.map +1 -0
- package/dist/postgresqlPoolSettings.js +134 -0
- package/dist/postgresqlPoolSettings.js.map +1 -0
- 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 +138 -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 +98 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.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 +17 -0
- package/dist/realtimeSessions/index.d.ts.map +1 -0
- package/dist/realtimeSessions/index.js +17 -0
- package/dist/realtimeSessions/index.js.map +1 -0
- package/dist/realtimeSessions/sessionAccess.d.ts +86 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionAccess.js +90 -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 +35 -17
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +92 -166
- package/dist/resolvers/AdhocQueryResolver.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/EntityRecordNameResolver.d.ts +7 -23
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +9 -47
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts +12 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +83 -2
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +9 -0
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.d.ts +107 -0
- package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -0
- package/dist/resolvers/HumanHandoffResolver.js +330 -0
- package/dist/resolvers/HumanHandoffResolver.js.map +1 -0
- 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/MeetingResolver.d.ts +69 -0
- package/dist/resolvers/MeetingResolver.d.ts.map +1 -0
- package/dist/resolvers/MeetingResolver.js +381 -0
- package/dist/resolvers/MeetingResolver.js.map +1 -0
- package/dist/resolvers/RealtimeBridgeResolver.d.ts +88 -3
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +352 -12
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +156 -108
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +395 -293
- 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 +68 -11
- 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/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/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/adhocTimeout.d.ts +7 -0
- package/dist/resolvers/adhocTimeout.d.ts.map +1 -0
- package/dist/resolvers/adhocTimeout.js +12 -0
- package/dist/resolvers/adhocTimeout.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/index.d.ts +5 -0
- package/dist/resolvers/meetings/index.d.ts.map +1 -0
- package/dist/resolvers/meetings/index.js +5 -0
- package/dist/resolvers/meetings/index.js.map +1 -0
- package/dist/resolvers/meetings/meetingHelpers.d.ts +17 -0
- package/dist/resolvers/meetings/meetingHelpers.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingHelpers.js +212 -0
- package/dist/resolvers/meetings/meetingHelpers.js.map +1 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts +18 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingMutations.js +439 -0
- package/dist/resolvers/meetings/meetingMutations.js.map +1 -0
- package/dist/resolvers/meetings/meetingQueries.d.ts +7 -0
- package/dist/resolvers/meetings/meetingQueries.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingQueries.js +113 -0
- package/dist/resolvers/meetings/meetingQueries.js.map +1 -0
- package/dist/resolvers/meetings/meetingTypes.d.ts +89 -0
- package/dist/resolvers/meetings/meetingTypes.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingTypes.js +345 -0
- package/dist/resolvers/meetings/meetingTypes.js.map +1 -0
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +8 -5
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +8 -0
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +27 -1
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +4 -3
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +13 -7
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/services/WorkQueueHostService.d.ts +36 -0
- package/dist/services/WorkQueueHostService.d.ts.map +1 -0
- package/dist/services/WorkQueueHostService.js +97 -0
- package/dist/services/WorkQueueHostService.js.map +1 -0
- package/dist/services/workQueueConfig.d.ts +103 -0
- package/dist/services/workQueueConfig.d.ts.map +1 -0
- package/dist/services/workQueueConfig.js +48 -0
- package/dist/services/workQueueConfig.js.map +1 -0
- package/dist/sharedCache.d.ts +105 -0
- package/dist/sharedCache.d.ts.map +1 -0
- package/dist/sharedCache.js +276 -0
- package/dist/sharedCache.js.map +1 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +97 -94
- package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
- package/src/__tests__/AdhocQueryResolver.bugs.test.ts +14 -35
- package/src/__tests__/AdhocQueryResolver.pagination.test.ts +6 -2
- package/src/__tests__/AdhocQueryResolver.scopeGuard.test.ts +3 -3
- 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__/EntityRecordNameResolver.fieldSecurity.test.ts +27 -72
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +285 -0
- package/src/__tests__/HostInstance.test.ts +84 -0
- package/src/__tests__/HumanHandoffResolver.test.ts +250 -0
- package/src/__tests__/MeetingResolver.test.ts +448 -0
- package/src/__tests__/RealtimeBridgeResolver.test.ts +651 -4
- 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__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +96 -0
- package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +6 -3
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +21 -2
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +291 -0
- package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +169 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +4 -4
- 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__/SessionJanitor.test.ts +845 -18
- package/src/__tests__/SessionManager.test.ts +213 -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 +41 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +50 -3
- package/src/__tests__/UserAvatarResolver.test.ts +255 -0
- package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
- package/src/__tests__/WorkQueueHostService.test.ts +133 -0
- package/src/__tests__/actionAuthorization.test.ts +130 -0
- package/src/__tests__/adhocTimeoutClamp.test.ts +26 -0
- package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
- package/src/__tests__/avatarInputValidation.test.ts +160 -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__/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__/postgresqlPoolSettings.test.ts +131 -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__/serverExtensionReservedRoots.test.ts +2 -0
- package/src/__tests__/sharedCache.test.ts +310 -0
- package/src/__tests__/translateBracketsToPG.test.ts +20 -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 +543 -57
- package/src/agentSessions/SessionManager.ts +125 -25
- package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
- package/src/agentSessions/index.ts +10 -3
- package/src/auth/actionAuthorization.ts +116 -0
- package/src/auth/index.ts +3 -8
- package/src/auth/magicLink/MagicLinkService.ts +6 -3
- package/src/auth/newUsers.ts +99 -11
- package/src/config.ts +255 -3
- package/src/context.ts +26 -34
- package/src/generated/graphql-schemas/__mj.ts +22701 -18838
- package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
- package/src/generic/ResolverBase.ts +210 -36
- package/src/generic/RunViewResolver.ts +37 -2
- package/src/index.ts +236 -170
- package/src/isolatedProvider.ts +22 -0
- package/src/postgresqlCompat.ts +21 -0
- package/src/postgresqlPoolSettings.ts +213 -0
- 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 +167 -0
- package/src/realtimeSessions/RealtimeVerifyRouter.ts +108 -0
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
- package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
- package/src/realtimeSessions/index.ts +16 -0
- package/src/realtimeSessions/sessionAccess.ts +141 -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 +105 -187
- package/src/resolvers/CacheStatsResolver.ts +89 -2
- package/src/resolvers/ClientToolRequestResolver.ts +25 -7
- package/src/resolvers/EntityRecordNameResolver.ts +9 -51
- package/src/resolvers/FetchEntityVectorsResolver.ts +97 -2
- package/src/resolvers/GetDataResolver.ts +10 -0
- package/src/resolvers/HumanHandoffResolver.ts +312 -0
- package/src/resolvers/MCPResolver.ts +8 -5
- package/src/resolvers/MeetingResolver.ts +438 -0
- package/src/resolvers/RealtimeBridgeResolver.ts +385 -19
- package/src/resolvers/RealtimeClientSessionResolver.ts +503 -344
- 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 +78 -11
- package/src/resolvers/RunTemplateResolver.ts +13 -12
- package/src/resolvers/SyncDataResolver.ts +6 -2
- package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
- package/src/resolvers/UserAvatarResolver.ts +111 -0
- package/src/resolvers/UserResolver.ts +2 -16
- package/src/resolvers/adhocTimeout.ts +12 -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/index.ts +4 -0
- package/src/resolvers/meetings/meetingHelpers.ts +307 -0
- package/src/resolvers/meetings/meetingMutations.ts +556 -0
- package/src/resolvers/meetings/meetingQueries.ts +172 -0
- package/src/resolvers/meetings/meetingTypes.ts +238 -0
- package/src/rest/ViewOperationsHandler.ts +8 -5
- package/src/services/TaskGraphActionRunner.ts +29 -1
- package/src/services/TaskGraphContinuationDeliverer.ts +13 -7
- package/src/services/WorkQueueHostService.ts +129 -0
- package/src/services/workQueueConfig.ts +52 -0
- package/src/sharedCache.ts +302 -0
- package/src/types.ts +12 -0
- package/dist/resolvers/adhoc-query-helpers.d.ts +0 -33
- package/dist/resolvers/adhoc-query-helpers.d.ts.map +0 -1
- package/dist/resolvers/adhoc-query-helpers.js +0 -50
- package/dist/resolvers/adhoc-query-helpers.js.map +0 -1
- package/src/__tests__/adhoc-query-helpers.test.ts +0 -50
- package/src/resolvers/adhoc-query-helpers.ts +0 -67
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LogError, LogStatus, RunView } from '@memberjunction/core';
|
|
2
|
-
import {
|
|
2
|
+
import { IndexByUUID } from '@memberjunction/global';
|
|
3
3
|
import { UserCache } from '@memberjunction/sqlserver-dataprovider';
|
|
4
4
|
import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
5
5
|
import type { MJAIAgentRunEntity, MJConversationDetailEntity } from '@memberjunction/core-entities';
|
|
@@ -253,12 +253,12 @@ async function loadConversationOwners(
|
|
|
253
253
|
if (!result.Success) {
|
|
254
254
|
return owners;
|
|
255
255
|
}
|
|
256
|
-
const
|
|
256
|
+
const usersByID = IndexByUUID(UserCache.Instance.Users);
|
|
257
257
|
for (const row of result.Results ?? []) {
|
|
258
258
|
if (!row.UserID) {
|
|
259
259
|
continue;
|
|
260
260
|
}
|
|
261
|
-
const user =
|
|
261
|
+
const user = usersByID.Get(row.UserID);
|
|
262
262
|
if (user) {
|
|
263
263
|
owners.set(row.ID, user);
|
|
264
264
|
}
|
|
@@ -37,10 +37,10 @@ import { httpTransport, CloudEvent, emitterFor } from 'cloudevents';
|
|
|
37
37
|
import { RunViewGenericParams, UserPayload } from '../types.js';
|
|
38
38
|
import { RunDynamicViewInput, RunViewByIDInput, RunViewByNameInput } from './RunViewResolver.js';
|
|
39
39
|
import { DeleteOptionsInput } from './DeleteOptionsInput.js';
|
|
40
|
-
import { MJEvent, MJEventType, MJGlobal, ENCRYPTED_SENTINEL, EscapeSQLString, IsValueEncrypted, IsOnlyTimezoneShift } from '@memberjunction/global';
|
|
40
|
+
import { MJEvent, MJEventType, MJGlobal, ENCRYPTED_SENTINEL, ReplaceByteArraysWithBase64, EscapeSQLString, IsValueEncrypted, IsOnlyTimezoneShift } from '@memberjunction/global';
|
|
41
41
|
import { SQLParser } from '@memberjunction/sql-parser';
|
|
42
42
|
import { PostgreSQLDialect, SQLServerDialect, type SQLParserDialect } from '@memberjunction/sql-dialect';
|
|
43
|
-
import { EncryptionEngine } from '@memberjunction/encryption';
|
|
43
|
+
import { EncryptionEngine, IsEncryptionKeyUnavailable, ReportEncryptionKeyUnavailable } from '@memberjunction/encryption';
|
|
44
44
|
import { PUSH_STATUS_UPDATES_TOPIC, PublishStatusUpdate } from './PushStatusResolver.js';
|
|
45
45
|
import { CACHE_INVALIDATION_TOPIC, MayBroadcastRecordData } from './CacheInvalidationResolver.js';
|
|
46
46
|
import { PubSubManager } from './PubSubManager.js';
|
|
@@ -99,7 +99,10 @@ export class ResolverBase {
|
|
|
99
99
|
return null;
|
|
100
100
|
}
|
|
101
101
|
// Shallow copy up front so every write below lands on our object, never the caller's.
|
|
102
|
-
|
|
102
|
+
// Binary values are base64 strings everywhere above the providers; any byte array that still
|
|
103
|
+
// reaches a resolver (custom code, an external driver) is converted here, because GraphQL's
|
|
104
|
+
// String scalar cannot serialize a Buffer and would fail the whole response.
|
|
105
|
+
dataObject = ReplaceByteArraysWithBase64({ ...dataObject });
|
|
103
106
|
|
|
104
107
|
// for the given entity name provided, check to see if there are any fields
|
|
105
108
|
// where the code name is different from the field name, and for just those
|
|
@@ -196,7 +199,11 @@ export class ResolverBase {
|
|
|
196
199
|
const decryptedValue = await engine.Decrypt(value, contextUser);
|
|
197
200
|
dataObject[fieldName] = decryptedValue;
|
|
198
201
|
} catch (err) {
|
|
199
|
-
|
|
202
|
+
if (IsEncryptionKeyUnavailable(err)) {
|
|
203
|
+
ReportEncryptionKeyUnavailable(err);
|
|
204
|
+
} else {
|
|
205
|
+
LogError(`Failed to decrypt field ${fieldName} for API response: ${err}`);
|
|
206
|
+
}
|
|
200
207
|
// On decryption failure, use sentinel for safety
|
|
201
208
|
dataObject[fieldName] = ENCRYPTED_SENTINEL;
|
|
202
209
|
}
|
|
@@ -566,7 +573,8 @@ export class ResolverBase {
|
|
|
566
573
|
? CompositeKey.FromKeyValuePairs((viewInput.AfterKey as { KeyValuePairs: { FieldName: string; Value: string }[] }).KeyValuePairs)
|
|
567
574
|
: undefined,
|
|
568
575
|
viewInput.BypassCache,
|
|
569
|
-
viewInput.DataSource
|
|
576
|
+
viewInput.DataSource,
|
|
577
|
+
viewInput.IncludeBinaryFields
|
|
570
578
|
);
|
|
571
579
|
}
|
|
572
580
|
else {
|
|
@@ -610,7 +618,8 @@ export class ResolverBase {
|
|
|
610
618
|
viewInput.Aggregates,
|
|
611
619
|
undefined,
|
|
612
620
|
viewInput.BypassCache,
|
|
613
|
-
viewInput.DataSource
|
|
621
|
+
viewInput.DataSource,
|
|
622
|
+
viewInput.IncludeBinaryFields
|
|
614
623
|
);
|
|
615
624
|
} catch (err) {
|
|
616
625
|
console.log(err);
|
|
@@ -657,7 +666,8 @@ export class ResolverBase {
|
|
|
657
666
|
viewInput.Aggregates,
|
|
658
667
|
undefined,
|
|
659
668
|
viewInput.BypassCache,
|
|
660
|
-
viewInput.DataSource
|
|
669
|
+
viewInput.DataSource,
|
|
670
|
+
viewInput.IncludeBinaryFields
|
|
661
671
|
);
|
|
662
672
|
} catch (err) {
|
|
663
673
|
console.log(err);
|
|
@@ -732,6 +742,7 @@ export class ResolverBase {
|
|
|
732
742
|
aggregates: viewInput.Aggregates,
|
|
733
743
|
bypassCache: viewInput.BypassCache,
|
|
734
744
|
dataSource: viewInput.DataSource,
|
|
745
|
+
includeBinaryFields: viewInput.IncludeBinaryFields,
|
|
735
746
|
});
|
|
736
747
|
} catch (err) {
|
|
737
748
|
LogError(err);
|
|
@@ -924,6 +935,8 @@ export class ResolverBase {
|
|
|
924
935
|
clause: string | undefined | null,
|
|
925
936
|
label: string,
|
|
926
937
|
provider?: IMetadataProvider,
|
|
938
|
+
user?: UserInfo,
|
|
939
|
+
options?: { LeaveUnparseableToProvider?: boolean },
|
|
927
940
|
): void {
|
|
928
941
|
if (!clause?.trim()) return;
|
|
929
942
|
|
|
@@ -937,6 +950,9 @@ export class ResolverBase {
|
|
|
937
950
|
? `SELECT 1 FROM __mj_clause_screen ORDER BY ${clause}`
|
|
938
951
|
: `SELECT 1 FROM __mj_clause_screen WHERE (${clause})`;
|
|
939
952
|
const parser = new SQLParser(wrapped, dialect);
|
|
953
|
+
// A clause the provider checks again on its own (an aggregate expression) leaves text the parser
|
|
954
|
+
// cannot read to that per-item check instead of failing the whole request.
|
|
955
|
+
if (!parser.IsValid && options?.LeaveUnparseableToProvider) return;
|
|
940
956
|
if (!parser.IsValid || parser.HasWriteStatement || parser.StatementKind !== 'select') {
|
|
941
957
|
throw new Error(
|
|
942
958
|
`Invalid ${label}: not a safe read-only filter fragment — refusing under uncertainty`,
|
|
@@ -952,15 +968,87 @@ export class ResolverBase {
|
|
|
952
968
|
const table = this.stripSqlIdent(t.TableName);
|
|
953
969
|
const schema = this.stripSqlIdent(t.SchemaName);
|
|
954
970
|
if (table.toLowerCase() === '__mj_clause_screen') continue;
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
971
|
+
this.assertTableRefReadable(allowed, schema, table, label, user);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* SECURITY — screen for a FULL client-supplied SQL statement (ad-hoc query path). In addition
|
|
977
|
+
* to the SELECT-only validation the caller performs, every table reference must resolve to an
|
|
978
|
+
* entity BaseView the acting user can read UNSCOPED. CTE names defined by the statement itself
|
|
979
|
+
* are excluded from the check. Fails closed: no user, or an unresolvable reference, refuses.
|
|
980
|
+
*
|
|
981
|
+
* Entity CanRead is not unscoped base-view read authority: RunView narrows a granted read with
|
|
982
|
+
* row-level security and denied-field projection, and raw SQL applies neither. Rather than try
|
|
983
|
+
* to rewrite arbitrary SQL (joins, CTEs) to compose that policy, this refuses any entity that
|
|
984
|
+
* carries a row filter or a denied field for the caller — those reads belong on RunView. For
|
|
985
|
+
* the remaining entities CanRead genuinely means "every row and field of the view".
|
|
986
|
+
*/
|
|
987
|
+
protected assertFullQueryUsesReadableEntityViews(
|
|
988
|
+
sqlText: string,
|
|
989
|
+
provider: IMetadataProvider | undefined,
|
|
990
|
+
user: UserInfo | undefined,
|
|
991
|
+
label = 'SQL',
|
|
992
|
+
): void {
|
|
993
|
+
if (!user) {
|
|
994
|
+
throw new Error(`Invalid ${label}: no acting user resolved for ad-hoc SQL — refusing`);
|
|
995
|
+
}
|
|
996
|
+
const dialect = this.dialectForProvider(provider);
|
|
997
|
+
const parser = new SQLParser(sqlText, dialect);
|
|
998
|
+
if (!parser.IsValid || parser.HasWriteStatement || parser.StatementKind !== 'select') {
|
|
999
|
+
throw new Error(`Invalid ${label}: not a safe read-only statement — refusing under uncertainty`);
|
|
1000
|
+
}
|
|
1001
|
+
if (this.astContainsWriteNode(parser.AST)) {
|
|
1002
|
+
throw new Error(`Invalid ${label}: write/DDL nested in a subquery is not permitted`);
|
|
1003
|
+
}
|
|
1004
|
+
const cteNames = new Set<string>();
|
|
1005
|
+
this.collectCTENames(parser.AST, cteNames);
|
|
1006
|
+
const allowed = this.entityBaseViewAllowList(provider);
|
|
1007
|
+
const tables = SQLParser.ExtractTableRefs(sqlText, dialect);
|
|
1008
|
+
for (const t of tables) {
|
|
1009
|
+
const table = this.stripSqlIdent(t.TableName);
|
|
1010
|
+
const schema = this.stripSqlIdent(t.SchemaName);
|
|
1011
|
+
if (this.isUnqualifiedSchema(schema) && cteNames.has(table.toLowerCase())) continue;
|
|
1012
|
+
const entity = this.assertTableRefReadable(allowed, schema, table, label, user);
|
|
1013
|
+
this.assertUnscopedRead(entity, user, label);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/** Refuses an entity whose read is narrowed for this user by row-level security or field-level denials. */
|
|
1018
|
+
private assertUnscopedRead(entity: EntityInfo, user: UserInfo, label: string): void {
|
|
1019
|
+
if (entity.GetEffectiveRowFilterWhereClause(user, EntityPermissionType.Read, '').length > 0) {
|
|
958
1020
|
throw new Error(
|
|
959
|
-
`Invalid ${label}:
|
|
1021
|
+
`Invalid ${label}: entity '${entity.Name}' is row-level-security filtered for you — ad-hoc SQL cannot apply that filter; use RunView`,
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
if (entity.GetDeniedReadFields(user).size > 0) {
|
|
1025
|
+
throw new Error(
|
|
1026
|
+
`Invalid ${label}: entity '${entity.Name}' has fields you are not permitted to read — ad-hoc SQL cannot project them away; use RunView`,
|
|
960
1027
|
);
|
|
961
1028
|
}
|
|
962
1029
|
}
|
|
963
1030
|
|
|
1031
|
+
/** Walks a parsed AST collecting the names of CTEs the statement itself defines. */
|
|
1032
|
+
private collectCTENames(node: unknown, names: Set<string>): void {
|
|
1033
|
+
if (!node || typeof node !== 'object') return;
|
|
1034
|
+
if (Array.isArray(node)) {
|
|
1035
|
+
for (const n of node) this.collectCTENames(n, names);
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
const obj = node as Record<string, unknown>;
|
|
1039
|
+
const withClause = obj.with;
|
|
1040
|
+
if (Array.isArray(withClause)) {
|
|
1041
|
+
for (const cte of withClause) {
|
|
1042
|
+
const name = (cte as Record<string, unknown>)?.name;
|
|
1043
|
+
if (typeof name === 'string') names.add(name.toLowerCase());
|
|
1044
|
+
else if (name && typeof name === 'object' && typeof (name as Record<string, unknown>).value === 'string') {
|
|
1045
|
+
names.add(((name as Record<string, unknown>).value as string).toLowerCase());
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
for (const v of Object.values(obj)) this.collectCTENames(v, names);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
964
1052
|
/**
|
|
965
1053
|
* Applies {@link assertClientClauseUsesEntityBaseViews} to every client-supplied clause
|
|
966
1054
|
* a view request can carry that is actually a SQL fragment. GraphQL entry points
|
|
@@ -982,6 +1070,11 @@ export class ResolverBase {
|
|
|
982
1070
|
* The single place that is not true is a field carrying `UserSearchParamFormatAPI`, whose
|
|
983
1071
|
* admin-authored format may splice the term in unquoted; `createViewUserSearchSQL` re-applies
|
|
984
1072
|
* `ValidateUserProvidedSQLClause` for exactly those entities.
|
|
1073
|
+
*
|
|
1074
|
+
* Each `Aggregates[].expression` gets the same screen, except that text the parser cannot read
|
|
1075
|
+
* goes on to the provider, which refuses it per aggregate. The provider never runs the caller's
|
|
1076
|
+
* aggregate text: it runs the SQL that `CheckAggregateExpression` rebuilds from a single
|
|
1077
|
+
* aggregate call over the entity's columns, and refuses everything else per aggregate.
|
|
985
1078
|
*/
|
|
986
1079
|
protected screenClientViewClauses(
|
|
987
1080
|
clauses: {
|
|
@@ -989,12 +1082,17 @@ export class ResolverBase {
|
|
|
989
1082
|
orderBy?: string | null;
|
|
990
1083
|
userSearchString?: string | null;
|
|
991
1084
|
overrideExcludeFilter?: string | null;
|
|
1085
|
+
aggregates?: AggregateExpression[] | null;
|
|
992
1086
|
},
|
|
993
1087
|
provider?: IMetadataProvider,
|
|
1088
|
+
user?: UserInfo,
|
|
994
1089
|
): void {
|
|
995
|
-
this.assertClientClauseUsesEntityBaseViews(clauses.extraFilter, 'ExtraFilter', provider);
|
|
996
|
-
this.assertClientClauseUsesEntityBaseViews(clauses.orderBy, 'OrderBy', provider);
|
|
997
|
-
this.assertClientClauseUsesEntityBaseViews(clauses.overrideExcludeFilter, 'OverrideExcludeFilter', provider);
|
|
1090
|
+
this.assertClientClauseUsesEntityBaseViews(clauses.extraFilter, 'ExtraFilter', provider, user);
|
|
1091
|
+
this.assertClientClauseUsesEntityBaseViews(clauses.orderBy, 'OrderBy', provider, user);
|
|
1092
|
+
this.assertClientClauseUsesEntityBaseViews(clauses.overrideExcludeFilter, 'OverrideExcludeFilter', provider, user);
|
|
1093
|
+
for (const aggregate of clauses.aggregates ?? []) {
|
|
1094
|
+
this.assertClientClauseUsesEntityBaseViews(aggregate?.expression, 'Aggregate', provider, user, { LeaveUnparseableToProvider: true });
|
|
1095
|
+
}
|
|
998
1096
|
}
|
|
999
1097
|
|
|
1000
1098
|
/** Same write-node walk as EDS `sqlReadOnlyScreen.astContainsWriteNode`. */
|
|
@@ -1020,28 +1118,84 @@ export class ResolverBase {
|
|
|
1020
1118
|
return new SQLServerDialect();
|
|
1021
1119
|
}
|
|
1022
1120
|
|
|
1121
|
+
/** True for a reference with no schema — which `SQLParser.ExtractTableRefs` reports as `dbo`. */
|
|
1122
|
+
private isUnqualifiedSchema(schema: string): boolean {
|
|
1123
|
+
return !schema || schema.toLowerCase() === 'dbo';
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1023
1126
|
private stripSqlIdent(name: string | null | undefined): string {
|
|
1024
1127
|
if (!name) return '';
|
|
1025
1128
|
return name.replace(/^\[|\]$/g, '').replace(/^"|"$/g, '').replace(/^`|`$/g, '');
|
|
1026
1129
|
}
|
|
1027
1130
|
|
|
1131
|
+
/**
|
|
1132
|
+
* `bare` maps an unqualified view name to its entity, or to `null` when the name exists in
|
|
1133
|
+
* more than one schema — the database, not this screen, would decide which one runs.
|
|
1134
|
+
*/
|
|
1028
1135
|
private entityBaseViewAllowList(provider?: IMetadataProvider): {
|
|
1029
|
-
qualified:
|
|
1030
|
-
bare:
|
|
1136
|
+
qualified: Map<string, EntityInfo>;
|
|
1137
|
+
bare: Map<string, EntityInfo | null>;
|
|
1031
1138
|
} {
|
|
1032
|
-
const qualified = new
|
|
1033
|
-
const bare = new
|
|
1139
|
+
const qualified = new Map<string, EntityInfo>();
|
|
1140
|
+
const bare = new Map<string, EntityInfo | null>();
|
|
1034
1141
|
const entities = provider?.Entities ?? [];
|
|
1035
1142
|
for (const e of entities) {
|
|
1036
1143
|
const view = this.stripSqlIdent(e.BaseView);
|
|
1037
1144
|
if (!view) continue;
|
|
1038
1145
|
const schema = this.stripSqlIdent(e.SchemaName);
|
|
1039
|
-
|
|
1040
|
-
|
|
1146
|
+
const bareKey = view.toLowerCase();
|
|
1147
|
+
bare.set(bareKey, bare.has(bareKey) ? null : e);
|
|
1148
|
+
if (schema) qualified.set(`${schema}.${view}`.toLowerCase(), e);
|
|
1041
1149
|
}
|
|
1042
1150
|
return { qualified, bare };
|
|
1043
1151
|
}
|
|
1044
1152
|
|
|
1153
|
+
/**
|
|
1154
|
+
* SECURITY — resolves a table reference against the entity BaseView allow-list and, when an
|
|
1155
|
+
* acting user is supplied, additionally requires that user to hold CanRead on the referenced
|
|
1156
|
+
* entity. Base views do not embed RLS and entity permissions are otherwise checked only on
|
|
1157
|
+
* the TOP entity of a request, so without this check a subquery (or ad-hoc query) could read
|
|
1158
|
+
* entities the caller has no read grant on.
|
|
1159
|
+
*
|
|
1160
|
+
* A schema-qualified reference must match that exact schema — `secret.vwFoo` never resolves
|
|
1161
|
+
* through another schema's `vwFoo`. An unqualified reference resolves only when the view name
|
|
1162
|
+
* is unique across schemas; an ambiguous one must be qualified, because the database's
|
|
1163
|
+
* default-schema resolution, not this screen, would pick which view actually runs.
|
|
1164
|
+
*
|
|
1165
|
+
* `SQLParser.ExtractTableRefs` reports an unqualified reference as schema `dbo`, so `dbo` is
|
|
1166
|
+
* indistinguishable from "no schema" here and both take the unqualified path (an exact
|
|
1167
|
+
* `dbo.<view>` entity wins first).
|
|
1168
|
+
*/
|
|
1169
|
+
private assertTableRefReadable(
|
|
1170
|
+
allowed: { qualified: Map<string, EntityInfo>; bare: Map<string, EntityInfo | null> },
|
|
1171
|
+
schema: string,
|
|
1172
|
+
table: string,
|
|
1173
|
+
label: string,
|
|
1174
|
+
user?: UserInfo,
|
|
1175
|
+
): EntityInfo {
|
|
1176
|
+
const qualified = allowed.qualified.get(`${schema}.${table}`.toLowerCase());
|
|
1177
|
+
const entity = this.isUnqualifiedSchema(schema) ? qualified ?? allowed.bare.get(table.toLowerCase()) : qualified;
|
|
1178
|
+
if (entity === null) {
|
|
1179
|
+
throw new Error(
|
|
1180
|
+
`Invalid ${label}: '${table}' is a base view in more than one schema — qualify it with its schema`,
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
if (!entity) {
|
|
1184
|
+
throw new Error(
|
|
1185
|
+
`Invalid ${label}: subquery must use an entity base view, not '${schema ? schema + '.' : ''}${table}'`,
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
if (user) {
|
|
1189
|
+
const perms = entity.GetUserPermisions(user);
|
|
1190
|
+
if (!perms.CanRead) {
|
|
1191
|
+
throw new Error(
|
|
1192
|
+
`Invalid ${label}: you do not have read permission on entity '${entity.Name}' referenced by '${schema ? schema + '.' : ''}${table}'`,
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
return entity;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1045
1199
|
/**
|
|
1046
1200
|
* Optimized RunViewGenericInternal implementation with:
|
|
1047
1201
|
* - Field filtering at source (Fix #7)
|
|
@@ -1068,29 +1222,33 @@ export class ResolverBase {
|
|
|
1068
1222
|
aggregates?: AggregateExpression[],
|
|
1069
1223
|
afterKey?: CompositeKey,
|
|
1070
1224
|
bypassCache?: boolean,
|
|
1071
|
-
dataSource?: 'Live' | 'Materialized'
|
|
1225
|
+
dataSource?: 'Live' | 'Materialized',
|
|
1226
|
+
includeBinaryFields?: boolean
|
|
1072
1227
|
) {
|
|
1073
1228
|
try {
|
|
1074
1229
|
if (!viewInfo || !userPayload) return null;
|
|
1075
1230
|
|
|
1231
|
+
// Prefer the authenticated session's payload user — it is the authoritative per-request
|
|
1232
|
+
// identity and (for magic-link sessions) carries the per-session resource scope / synthesized
|
|
1233
|
+
// roles that drive RLS. The cached lookup is a fallback for paths where the payload user
|
|
1234
|
+
// isn't populated. For normal users the two are the same instance, so this is a no-op.
|
|
1235
|
+
const user = this.GetUserFromPayload(userPayload)
|
|
1236
|
+
?? UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
|
|
1237
|
+
if (!user) throw new Error(`User ${userPayload?.email} not found in metadata`);
|
|
1238
|
+
|
|
1076
1239
|
// SECURITY: GraphQL ExtraFilter may contain IN (SELECT … FROM <base view>).
|
|
1077
|
-
// Screen at this boundary: parse, reject writes, allow only entity BaseViews
|
|
1240
|
+
// Screen at this boundary: parse, reject writes, allow only entity BaseViews the
|
|
1241
|
+
// acting user can read (subqueries do not inherit the top entity's permission check).
|
|
1078
1242
|
this.screenClientViewClauses(
|
|
1079
|
-
{ extraFilter, orderBy, userSearchString, overrideExcludeFilter },
|
|
1243
|
+
{ extraFilter, orderBy, userSearchString, overrideExcludeFilter, aggregates },
|
|
1080
1244
|
provider as unknown as IMetadataProvider,
|
|
1245
|
+
user,
|
|
1081
1246
|
);
|
|
1082
1247
|
|
|
1083
1248
|
// Check API key scope authorization for view operations
|
|
1084
1249
|
await this.CheckAPIKeyScopeAuthorization('view:run', viewInfo.Entity, userPayload);
|
|
1085
1250
|
|
|
1086
1251
|
const md = provider
|
|
1087
|
-
// Prefer the authenticated session's payload user — it is the authoritative per-request
|
|
1088
|
-
// identity and (for magic-link sessions) carries the per-session resource scope / synthesized
|
|
1089
|
-
// roles that drive RLS. The cached lookup is a fallback for paths where the payload user
|
|
1090
|
-
// isn't populated. For normal users the two are the same instance, so this is a no-op.
|
|
1091
|
-
const user = this.GetUserFromPayload(userPayload)
|
|
1092
|
-
?? UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
|
|
1093
|
-
if (!user) throw new Error(`User ${userPayload?.email} not found in metadata`);
|
|
1094
1252
|
|
|
1095
1253
|
const entityInfo = md.Entities.find((e) => e.Name === viewInfo.Entity);
|
|
1096
1254
|
if (!entityInfo) throw new Error(`Entity ${viewInfo.Entity} not found in metadata`);
|
|
@@ -1146,6 +1304,7 @@ export class ResolverBase {
|
|
|
1146
1304
|
Aggregates: aggregates,
|
|
1147
1305
|
BypassCache: bypassCache,
|
|
1148
1306
|
DataSource: dataSource,
|
|
1307
|
+
IncludeBinaryFields: includeBinaryFields,
|
|
1149
1308
|
},
|
|
1150
1309
|
user
|
|
1151
1310
|
);
|
|
@@ -1248,8 +1407,10 @@ export class ResolverBase {
|
|
|
1248
1407
|
orderBy: param.orderBy,
|
|
1249
1408
|
userSearchString: param.userSearchString,
|
|
1250
1409
|
overrideExcludeFilter: param.overrideExcludeFilter,
|
|
1410
|
+
aggregates: param.aggregates,
|
|
1251
1411
|
},
|
|
1252
1412
|
md,
|
|
1413
|
+
contextUser ?? undefined,
|
|
1253
1414
|
);
|
|
1254
1415
|
|
|
1255
1416
|
if (param.viewInfo) {
|
|
@@ -1293,6 +1454,7 @@ export class ResolverBase {
|
|
|
1293
1454
|
Aggregates: param.aggregates,
|
|
1294
1455
|
BypassCache: param.bypassCache,
|
|
1295
1456
|
DataSource: param.dataSource,
|
|
1457
|
+
IncludeBinaryFields: param.includeBinaryFields,
|
|
1296
1458
|
});
|
|
1297
1459
|
}
|
|
1298
1460
|
|
|
@@ -1674,9 +1836,10 @@ export class ResolverBase {
|
|
|
1674
1836
|
// that hydrates from those values — never loading what the database actually holds.
|
|
1675
1837
|
const hasNarrowedAuditPayload = this.StripRecordChangePayloadFromClientInput(entityInfo, userInfo, input, clientNewValues);
|
|
1676
1838
|
|
|
1677
|
-
if (this.MustLoadTruthFromDatabase(entityInfo, input, hasDeniedReadFields, hasNarrowedAuditPayload)) {
|
|
1839
|
+
if (this.MustLoadTruthFromDatabase(entityInfo, input, hasDeniedReadFields, hasNarrowedAuditPayload, userInfo)) {
|
|
1678
1840
|
// We get here because the entity tracks record changes, OR the client did not provide OldValues,
|
|
1679
|
-
// OR field-level security is in play — in every case we need the true
|
|
1841
|
+
// OR field-level security or an Update row filter is in play — in every case we need the true
|
|
1842
|
+
// old values from the DB
|
|
1680
1843
|
const cKey = new CompositeKey(
|
|
1681
1844
|
entityInfo.PrimaryKeys.map((pk) => {
|
|
1682
1845
|
return {
|
|
@@ -1774,19 +1937,28 @@ export class ResolverBase {
|
|
|
1774
1937
|
* it never applies them to the entity. An update-denied field can then only become dirty by being
|
|
1775
1938
|
* named in the mutation input itself, which is precisely the case that check does catch.
|
|
1776
1939
|
*
|
|
1777
|
-
*
|
|
1778
|
-
*
|
|
1940
|
+
* The flag comes after the cheap terms, so the extra load lands only on entities that have the
|
|
1941
|
+
* feature switched on, which is almost none of them.
|
|
1779
1942
|
*
|
|
1780
1943
|
* `MJ: Record Changes` always loads from the database: its server class allows an update only when
|
|
1781
1944
|
* Comments is the one dirty field, and "dirty" compared against client-supplied OldValues lets a
|
|
1782
1945
|
* caller pin forged audit columns as both old and new values. It doesn't track its own changes, so
|
|
1783
1946
|
* nothing else forces the load.
|
|
1947
|
+
*
|
|
1948
|
+
* A caller under an Update row filter (a role's Update RLS filter or an API-key row filter) always
|
|
1949
|
+
* loads from the database too (#4919). The save's post-image check skips its query when no column
|
|
1950
|
+
* the filter reads is dirty, and dirtiness measured against client OldValues is the caller's to
|
|
1951
|
+
* choose: claiming the new value of a filter column as its old value made it clean, so the update
|
|
1952
|
+
* moved the row outside the caller's scope without the check ever running. Evaluated last: it
|
|
1953
|
+
* builds the filter clause, and only callers with such a filter pay for the load. A missing user
|
|
1954
|
+
* (a user-cache miss) also loads: with no user the filter can't be ruled out.
|
|
1784
1955
|
*/
|
|
1785
1956
|
protected MustLoadTruthFromDatabase(
|
|
1786
1957
|
entityInfo: EntityInfo,
|
|
1787
1958
|
input: { OldValues___?: Array<{ Key: string; Value: unknown }> },
|
|
1788
1959
|
hasDeniedReadFields: boolean,
|
|
1789
|
-
hasNarrowedAuditPayload: boolean
|
|
1960
|
+
hasNarrowedAuditPayload: boolean,
|
|
1961
|
+
user: UserInfo | undefined
|
|
1790
1962
|
): boolean {
|
|
1791
1963
|
return (
|
|
1792
1964
|
entityInfo.TrackRecordChanges ||
|
|
@@ -1794,7 +1966,9 @@ export class ResolverBase {
|
|
|
1794
1966
|
!input.OldValues___ ||
|
|
1795
1967
|
hasDeniedReadFields ||
|
|
1796
1968
|
hasNarrowedAuditPayload ||
|
|
1797
|
-
entityInfo.EnableFieldLevelSecurity
|
|
1969
|
+
entityInfo.EnableFieldLevelSecurity ||
|
|
1970
|
+
!user ||
|
|
1971
|
+
entityInfo.GetEffectiveRowFilterWhereClause(user, EntityPermissionType.Update, '').length > 0
|
|
1798
1972
|
);
|
|
1799
1973
|
}
|
|
1800
1974
|
|
|
@@ -3,7 +3,7 @@ import { AppContext } from '../types.js';
|
|
|
3
3
|
import { ResolverBase } from './ResolverBase.js';
|
|
4
4
|
import { LogError, LogStatus, EntityInfo, FieldSecurityError, RunViewWithCacheCheckResult, RunViewsWithCacheCheckResponse, RunViewWithCacheCheckParams, AggregateResult, CompositeKey } from '@memberjunction/core';
|
|
5
5
|
|
|
6
|
-
import { UUIDsEqual } from '@memberjunction/global';
|
|
6
|
+
import { ReplaceByteArraysWithBase64, UUIDsEqual } from '@memberjunction/global';
|
|
7
7
|
import { RequireSystemUser } from '../directives/RequireSystemUser.js';
|
|
8
8
|
import { GetReadOnlyProvider } from '../util.js';
|
|
9
9
|
import { MJUserViewEntityExtended } from '@memberjunction/core-entities';
|
|
@@ -200,6 +200,13 @@ export class RunViewByIDInput {
|
|
|
200
200
|
"Optional source-of-truth selector for a base-view materialization: 'Live' (default) reads the entity's live base view; 'Materialized' reads its materialized snapshot wrapper view (materialized_vw<CodeName>). Only request 'Materialized' for an entity that actually has a base-view materialization: it resolves to the wrapper view by naming convention with NO existence check or fallback, so requesting it for a non-materialized entity targets a view that does not exist and the query errors.",
|
|
201
201
|
})
|
|
202
202
|
DataSource?: 'Live' | 'Materialized';
|
|
203
|
+
|
|
204
|
+
@Field(() => Boolean, {
|
|
205
|
+
nullable: true,
|
|
206
|
+
description:
|
|
207
|
+
'Optional, when true the binary fields of the entity (varbinary / bytea columns) are returned, as base64 strings. They are omitted by default because they are usually large and lists rarely read them. A binary field named explicitly in Fields is always returned.',
|
|
208
|
+
})
|
|
209
|
+
IncludeBinaryFields?: boolean;
|
|
203
210
|
}
|
|
204
211
|
|
|
205
212
|
@InputType()
|
|
@@ -321,6 +328,13 @@ export class RunViewByNameInput {
|
|
|
321
328
|
"Optional source-of-truth selector for a base-view materialization: 'Live' (default) reads the entity's live base view; 'Materialized' reads its materialized snapshot wrapper view (materialized_vw<CodeName>). Only request 'Materialized' for an entity that actually has a base-view materialization: it resolves to the wrapper view by naming convention with NO existence check or fallback, so requesting it for a non-materialized entity targets a view that does not exist and the query errors.",
|
|
322
329
|
})
|
|
323
330
|
DataSource?: 'Live' | 'Materialized';
|
|
331
|
+
|
|
332
|
+
@Field(() => Boolean, {
|
|
333
|
+
nullable: true,
|
|
334
|
+
description:
|
|
335
|
+
'Optional, when true the binary fields of the entity (varbinary / bytea columns) are returned, as base64 strings. They are omitted by default because they are usually large and lists rarely read them. A binary field named explicitly in Fields is always returned.',
|
|
336
|
+
})
|
|
337
|
+
IncludeBinaryFields?: boolean;
|
|
324
338
|
}
|
|
325
339
|
|
|
326
340
|
@InputType()
|
|
@@ -428,6 +442,13 @@ export class RunDynamicViewInput {
|
|
|
428
442
|
"Optional source-of-truth selector for a base-view materialization: 'Live' (default) reads the entity's live base view; 'Materialized' reads its materialized snapshot wrapper view (materialized_vw<CodeName>). Only request 'Materialized' for an entity that actually has a base-view materialization: it resolves to the wrapper view by naming convention with NO existence check or fallback, so requesting it for a non-materialized entity targets a view that does not exist and the query errors.",
|
|
429
443
|
})
|
|
430
444
|
DataSource?: 'Live' | 'Materialized';
|
|
445
|
+
|
|
446
|
+
@Field(() => Boolean, {
|
|
447
|
+
nullable: true,
|
|
448
|
+
description:
|
|
449
|
+
'Optional, when true the binary fields of the entity (varbinary / bytea columns) are returned, as base64 strings. They are omitted by default because they are usually large and lists rarely read them. A binary field named explicitly in Fields is always returned.',
|
|
450
|
+
})
|
|
451
|
+
IncludeBinaryFields?: boolean;
|
|
431
452
|
}
|
|
432
453
|
|
|
433
454
|
@InputType()
|
|
@@ -564,6 +585,13 @@ export class RunViewGenericInput {
|
|
|
564
585
|
"Optional source-of-truth selector for a base-view materialization: 'Live' (default) reads the entity's live base view; 'Materialized' reads its materialized snapshot wrapper view (materialized_vw<CodeName>). Only request 'Materialized' for an entity that actually has a base-view materialization: it resolves to the wrapper view by naming convention with NO existence check or fallback, so requesting it for a non-materialized entity targets a view that does not exist and the query errors.",
|
|
565
586
|
})
|
|
566
587
|
DataSource?: 'Live' | 'Materialized';
|
|
588
|
+
|
|
589
|
+
@Field(() => Boolean, {
|
|
590
|
+
nullable: true,
|
|
591
|
+
description:
|
|
592
|
+
'Optional, when true the binary fields of the entity (varbinary / bytea columns) are returned, as base64 strings. They are omitted by default because they are usually large and lists rarely read them. A binary field named explicitly in Fields is always returned.',
|
|
593
|
+
})
|
|
594
|
+
IncludeBinaryFields?: boolean;
|
|
567
595
|
}
|
|
568
596
|
|
|
569
597
|
//****************************************************************************
|
|
@@ -1219,6 +1247,11 @@ export class RunViewResolver extends ResolverBase {
|
|
|
1219
1247
|
// DataSource must be forwarded on this transport too (same as the InternalRunView/RunViews maps):
|
|
1220
1248
|
// otherwise a CacheLocal batch's DataSource:'Materialized' request silently reads the live view.
|
|
1221
1249
|
DataSource: item.params.DataSource,
|
|
1250
|
+
IncludeBinaryFields: item.params.IncludeBinaryFields,
|
|
1251
|
+
// BypassCache likewise — the input type (RunDynamicViewInput) has always declared it, and its
|
|
1252
|
+
// own description promises "the pre-check cache lookup is skipped". Dropping it here left the
|
|
1253
|
+
// provider free to answer a deliberate database read from the server cache.
|
|
1254
|
+
BypassCache: item.params.BypassCache,
|
|
1222
1255
|
AfterKey: item.params.AfterKey
|
|
1223
1256
|
? CompositeKey.FromKeyValuePairs(item.params.AfterKey.KeyValuePairs)
|
|
1224
1257
|
: undefined,
|
|
@@ -1322,7 +1355,9 @@ export class RunViewResolver extends ResolverBase {
|
|
|
1322
1355
|
returnResult.push({
|
|
1323
1356
|
PrimaryKey: primaryKey,
|
|
1324
1357
|
EntityID: entityId,
|
|
1325
|
-
|
|
1358
|
+
// Binary columns arrive as base64 from the providers; this is the safety net for any
|
|
1359
|
+
// row that still holds a byte array, which JSON.stringify would emit as {type,data}.
|
|
1360
|
+
Data: JSON.stringify(ReplaceByteArraysWithBase64(row)),
|
|
1326
1361
|
});
|
|
1327
1362
|
}
|
|
1328
1363
|
return returnResult;
|