@memberjunction/server 6.2.0-edge.5 → 6.2.0-edge.6
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 +27 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +3 -3
- package/dist/agentSessions/SessionJanitor.js +3 -3
- package/dist/agentSessions/SessionManager.d.ts +15 -5
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +34 -12
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.js +2 -0
- package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +51 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/initializeProviders.d.ts +10 -0
- package/dist/auth/initializeProviders.d.ts.map +1 -1
- package/dist/auth/initializeProviders.js +37 -1
- package/dist/auth/initializeProviders.js.map +1 -1
- package/dist/auth/localDevAuth.d.ts +79 -0
- package/dist/auth/localDevAuth.d.ts.map +1 -0
- package/dist/auth/localDevAuth.js +439 -0
- package/dist/auth/localDevAuth.js.map +1 -0
- package/dist/auth/magicLink/MagicLinkKeys.d.ts +2 -2
- package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
- package/dist/auth/magicLink/types.d.ts +12 -0
- package/dist/auth/magicLink/types.d.ts.map +1 -1
- package/dist/auth/sessionUserClone.d.ts +1 -1
- package/dist/auth/sessionUserClone.d.ts.map +1 -1
- package/dist/auth/sessionUserClone.js +3 -2
- package/dist/auth/sessionUserClone.js.map +1 -1
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts +292 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +442 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -0
- package/dist/broadcast/index.d.ts +6 -0
- package/dist/broadcast/index.d.ts.map +1 -0
- package/dist/broadcast/index.js +6 -0
- package/dist/broadcast/index.js.map +1 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts +43 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js +144 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts +46 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js +149 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts +49 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js +131 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts +24 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js +51 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js.map +1 -0
- package/dist/cache.d.ts +3 -2
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js.map +1 -1
- package/dist/config.d.ts +496 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +61 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +3 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +29 -8
- package/dist/context.js.map +1 -1
- package/dist/coreRoutes.d.ts.map +1 -1
- package/dist/coreRoutes.js +14 -0
- package/dist/coreRoutes.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +635 -14
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +3366 -418
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +50 -30
- package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
- package/dist/generic/CacheInvalidationResolver.js +154 -79
- package/dist/generic/CacheInvalidationResolver.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +2 -0
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/PubSubManager.d.ts +10 -0
- package/dist/generic/PubSubManager.d.ts.map +1 -1
- package/dist/generic/PubSubManager.js +44 -1
- package/dist/generic/PubSubManager.js.map +1 -1
- package/dist/generic/PushStatusResolver.d.ts +39 -6
- package/dist/generic/PushStatusResolver.d.ts.map +1 -1
- package/dist/generic/PushStatusResolver.js +93 -11
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +65 -25
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +201 -101
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +11 -3
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +79 -13
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/generic/actionOutputFieldSecurity.d.ts +26 -0
- package/dist/generic/actionOutputFieldSecurity.d.ts.map +1 -0
- package/dist/generic/actionOutputFieldSecurity.js +47 -0
- package/dist/generic/actionOutputFieldSecurity.js.map +1 -0
- package/dist/generic/fieldSecurityTransport.d.ts +30 -0
- package/dist/generic/fieldSecurityTransport.d.ts.map +1 -0
- package/dist/generic/fieldSecurityTransport.js +30 -0
- package/dist/generic/fieldSecurityTransport.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +122 -66
- package/dist/index.js.map +1 -1
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +6 -1
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.d.ts +12 -0
- package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.js +50 -1
- package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -1
- package/dist/realtimeSessions/SessionVerificationPortsBase.js +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts +8 -6
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.js +15 -6
- package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
- package/dist/realtimeSessions/sessionAccess.d.ts +88 -11
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
- package/dist/realtimeSessions/sessionAccess.js +125 -22
- package/dist/realtimeSessions/sessionAccess.js.map +1 -1
- package/dist/realtimeSessions/sessionConfigStamps.js +1 -1
- package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -1
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -1
- package/dist/realtimeSessions/subscriptionAuthorization.js +5 -2
- package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -1
- package/dist/realtimeSessions/verificationWorkflow.d.ts +2 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -1
- package/dist/realtimeSessions/verificationWorkflow.js +1 -1
- package/dist/realtimeSessions/verificationWorkflow.js.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.js +19 -3
- package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts +67 -7
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js +201 -19
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
- package/dist/realtimeWidget/host-identity.d.ts +1 -1
- package/dist/realtimeWidget/host-identity.js +1 -1
- package/dist/realtimeWidget/widgetCore.d.ts +20 -1
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetCore.js +17 -1
- package/dist/realtimeWidget/widgetCore.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +2 -1
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +19 -1
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +63 -12
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +1 -0
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.d.ts +18 -4
- package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.js +32 -8
- package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
- package/dist/resolvers/AgentSessionResolver.d.ts +10 -4
- package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/AgentSessionResolver.js +40 -17
- package/dist/resolvers/AgentSessionResolver.js.map +1 -1
- package/dist/resolvers/EntityCommunicationsResolver.d.ts +13 -1
- package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityCommunicationsResolver.js +50 -14
- package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts +28 -0
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +117 -21
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
- package/dist/resolvers/FileCategoryResolver.js +1 -0
- package/dist/resolvers/FileCategoryResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts +5 -0
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +13 -4
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +45 -7
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.d.ts +8 -1
- package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.js +31 -3
- package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
- package/dist/resolvers/PipelineProgressResolver.d.ts +6 -0
- package/dist/resolvers/PipelineProgressResolver.d.ts.map +1 -1
- package/dist/resolvers/PipelineProgressResolver.js +5 -3
- package/dist/resolvers/PipelineProgressResolver.js.map +1 -1
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +20 -6
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts +2 -0
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +15 -6
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RSUResolver.d.ts.map +1 -1
- package/dist/resolvers/RSUResolver.js +12 -0
- package/dist/resolvers/RSUResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +16 -2
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +82 -28
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +11 -9
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +56 -8
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +209 -64
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +4 -4
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTestResolver.js +3 -1
- package/dist/resolvers/RunTestResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +4 -0
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.js +1 -0
- package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts +17 -2
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +60 -15
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/UserViewResolver.d.ts +1 -1
- package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
- package/dist/resolvers/UserViewResolver.js +2 -1
- package/dist/resolvers/UserViewResolver.js.map +1 -1
- package/dist/rest/EntityCRUDHandler.d.ts +6 -0
- package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
- package/dist/rest/EntityCRUDHandler.js +19 -5
- package/dist/rest/EntityCRUDHandler.js.map +1 -1
- package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +8 -5
- package/dist/rest/RESTEndpointHandler.js.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
- package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.js +7 -7
- package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts +6 -0
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +22 -5
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/serverRoleConfigUnits.d.ts +29 -0
- package/dist/serverRoleConfigUnits.d.ts.map +1 -0
- package/dist/serverRoleConfigUnits.js +33 -0
- package/dist/serverRoleConfigUnits.js.map +1 -0
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +5 -0
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +42 -15
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/types.d.ts +23 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +98 -98
- package/src/__tests__/ActionResolver.fieldSecurity.test.ts +237 -0
- package/src/__tests__/AdhocQueryResolver.dialect.test.ts +137 -0
- package/src/__tests__/AgentRunStatusPublisher.test.ts +49 -0
- package/src/__tests__/AgentSessionResolver.ownership.test.ts +137 -0
- package/src/__tests__/CustomColumnPromoterOnePass.test.ts +15 -1
- package/src/__tests__/EntityCommunicationsResolver.actingUser.test.ts +151 -0
- package/src/__tests__/EvolutionActsOnWhatItDetects.test.ts +113 -0
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +206 -3
- package/src/__tests__/FileResolver.fieldSecurity.test.ts +82 -0
- package/src/__tests__/PipelineProgressResolver.filter.test.ts +58 -0
- package/src/__tests__/QuerySystemUserResolver.fieldSecurity.test.ts +49 -0
- package/src/__tests__/RESTEndpointHandler.test.ts +25 -0
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +23 -3
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +324 -46
- package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +98 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +37 -4
- package/src/__tests__/ResolverBase.deleteReturnProjection.test.ts +52 -0
- package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +176 -0
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +23 -1
- package/src/__tests__/ResolverBase.publishStatusUpdate.test.ts +68 -0
- package/src/__tests__/ResolverBase.userViewCacheAndDedup.test.ts +130 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +68 -13
- package/src/__tests__/RunAIAgentResolver.conversationWriter.test.ts +210 -0
- package/src/__tests__/RunAIAgentResolver.fireAndForgetCompletion.test.ts +209 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +17 -27
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +8 -2
- package/src/__tests__/RunAIAgentResolver.serverReply.test.ts +358 -0
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +4 -1
- package/src/__tests__/RunTemplateResolver.test.ts +35 -2
- package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +11 -0
- package/src/__tests__/SessionManager.test.ts +95 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +114 -3
- package/src/__tests__/TransactionGroupResolver.fieldSecurity.test.ts +199 -0
- package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +15 -0
- package/src/__tests__/ViewOperationsHandler.test.ts +7 -2
- package/src/__tests__/WidgetSessionService.test.ts +583 -4
- package/src/__tests__/apiRootBoot.test.ts +1 -1
- package/src/__tests__/buildMagicLinkSessionUser.test.ts +96 -0
- package/src/__tests__/cacheBroadcastProviders.test.ts +403 -0
- package/src/__tests__/cacheInvalidationRecordData.test.ts +45 -0
- package/src/__tests__/fieldSecurityFixtures.ts +91 -0
- package/src/__tests__/globalCors.test.ts +3 -3
- package/src/__tests__/localDevAuth.test.ts +196 -0
- package/src/__tests__/orphanedConversationDetailReconciler.test.ts +10 -0
- package/src/__tests__/pushStatusResolver.filter.test.ts +57 -1
- package/src/__tests__/realtimeRecordingHandler.test.ts +19 -2
- package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +29 -1
- package/src/__tests__/realtimeSessions/ownedRealtimeSession.anonymous.test.ts +31 -0
- package/src/__tests__/realtimeSessions/ownedRealtimeSession.namedScoped.test.ts +37 -0
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +146 -7
- package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +1 -1
- package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +25 -4
- package/src/__tests__/resolverBase.cloudEventChangedFields.test.ts +157 -0
- package/src/__tests__/resolverBase.fieldSecurityTransport.test.ts +222 -0
- package/src/__tests__/resolverBase.fls.test.ts +146 -97
- package/src/__tests__/restTransport.fieldSecurity.test.ts +148 -0
- package/src/__tests__/runParamsTimeZone.test.ts +179 -0
- package/src/__tests__/runStreams.fieldSecurity.test.ts +100 -0
- package/src/__tests__/runViewProperties.test.ts +114 -0
- package/src/__tests__/scalabilityBenchmarks.test.ts +765 -0
- package/src/__tests__/scalabilityDatabaseIntegration.test.ts +207 -0
- package/src/__tests__/sessionUserClone.test.ts +5 -1
- package/src/__tests__/startupSweepOrder.test.ts +40 -0
- package/src/__tests__/unifiedAuth.test.ts +28 -0
- package/src/__tests__/widgetGuestElevation.test.ts +2 -0
- package/src/agentSessions/SessionJanitor.ts +3 -3
- package/src/agentSessions/SessionManager.ts +35 -12
- package/src/auth/AuthProviderCatalogRouter.ts +3 -0
- package/src/auth/index.ts +56 -12
- package/src/auth/initializeProviders.ts +43 -1
- package/src/auth/localDevAuth.ts +528 -0
- package/src/auth/magicLink/MagicLinkKeys.ts +2 -2
- package/src/auth/magicLink/types.ts +10 -0
- package/src/auth/sessionUserClone.ts +3 -2
- package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +674 -0
- package/src/broadcast/index.ts +5 -0
- package/src/broadcast/providers/AWSIoTCoreBroadcastProvider.ts +180 -0
- package/src/broadcast/providers/AzureWebPubSubBroadcastProvider.ts +182 -0
- package/src/broadcast/providers/GCPPubSubBroadcastProvider.ts +169 -0
- package/src/broadcast/providers/SequencedRoleBucketBroadcastProvider.ts +57 -0
- package/src/cache.ts +2 -1
- package/src/config.ts +69 -0
- package/src/context.ts +32 -8
- package/src/coreRoutes.ts +15 -0
- package/src/generated/graphql-schemas/__mj.ts +2239 -102
- package/src/generic/CacheInvalidationResolver.ts +135 -78
- package/src/generic/OrphanedConversationDetailReconciler.ts +4 -2
- package/src/generic/PubSubManager.ts +54 -1
- package/src/generic/PushStatusResolver.ts +108 -11
- package/src/generic/ResolverBase.ts +237 -107
- package/src/generic/RunViewResolver.ts +80 -15
- package/src/generic/actionOutputFieldSecurity.ts +75 -0
- package/src/generic/fieldSecurityTransport.ts +62 -0
- package/src/index.ts +127 -68
- package/src/integration/CustomColumnPromoter.ts +6 -1
- package/src/logging/variablesLoggingMiddleware.ts +59 -3
- package/src/realtimeSessions/README.md +7 -3
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +1 -0
- package/src/realtimeSessions/ownedRealtimeSession.ts +20 -6
- package/src/realtimeSessions/sessionAccess.ts +181 -25
- package/src/realtimeSessions/sessionConfigStamps.ts +1 -1
- package/src/realtimeSessions/subscriptionAuthorization.ts +4 -2
- package/src/realtimeSessions/verificationWorkflow.ts +3 -1
- package/src/realtimeWidget/WidgetRouter.ts +22 -4
- package/src/realtimeWidget/WidgetSessionService.ts +252 -17
- package/src/realtimeWidget/host-identity.ts +1 -1
- package/src/realtimeWidget/widgetCore.ts +30 -1
- package/src/realtimeWidget/widgetGuestElevation.ts +2 -1
- package/src/resolvers/ActionResolver.ts +78 -15
- package/src/resolvers/AdhocQueryResolver.ts +1 -0
- package/src/resolvers/AgentRunStatusPublisher.ts +36 -8
- package/src/resolvers/AgentSessionResolver.ts +46 -18
- package/src/resolvers/EntityCommunicationsResolver.ts +56 -15
- package/src/resolvers/FetchEntityVectorsResolver.ts +132 -21
- package/src/resolvers/FileCategoryResolver.ts +1 -0
- package/src/resolvers/FileResolver.ts +14 -4
- package/src/resolvers/IntegrationDiscoveryResolver.ts +49 -7
- package/src/resolvers/MergeRecordsResolver.ts +23 -2
- package/src/resolvers/PipelineProgressResolver.ts +12 -3
- package/src/resolvers/QueryResolver.ts +20 -6
- package/src/resolvers/QuerySystemUserResolver.ts +15 -6
- package/src/resolvers/RSUResolver.ts +12 -0
- package/src/resolvers/RealtimeClientSessionResolver.ts +104 -33
- package/src/resolvers/RemoteBrowserActionResolver.ts +15 -10
- package/src/resolvers/RunAIAgentResolver.ts +258 -82
- package/src/resolvers/RunTemplateResolver.ts +6 -4
- package/src/resolvers/RunTestResolver.ts +3 -1
- package/src/resolvers/SyncDataResolver.ts +4 -0
- package/src/resolvers/TestQuerySQLResolver.ts +1 -0
- package/src/resolvers/TransactionGroupResolver.ts +66 -15
- package/src/resolvers/UserViewResolver.ts +2 -1
- package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +43 -0
- package/src/rest/EntityCRUDHandler.ts +22 -5
- package/src/rest/RESTEndpointHandler.ts +8 -5
- package/src/rest/RealtimeRecordingHandler.ts +13 -10
- package/src/rest/ViewOperationsHandler.ts +24 -6
- package/src/serverRoleConfigUnits.ts +48 -0
- package/src/services/TaskGraphContinuationDeliverer.ts +52 -20
- package/src/types.ts +23 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
2
|
import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
3
|
-
import { AuthorizeSessionAccess, LoadSessionForAccess } from '../../realtimeSessions/sessionAccess.js';
|
|
3
|
+
import { AuthorizeSessionAccess, AuthorizeConversationTurn, AuthorizeAnonymousRoomSessions, LoadSessionForAccess, LoadConversationScope } from '../../realtimeSessions/sessionAccess.js';
|
|
4
4
|
|
|
5
5
|
const OWNER = 'AAAAAAAA-0000-4000-8000-000000000001';
|
|
6
6
|
const OTHER = 'AAAAAAAA-0000-4000-8000-000000000002';
|
|
@@ -27,27 +27,57 @@ describe('AuthorizeSessionAccess', () => {
|
|
|
27
27
|
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: null }, { ID: '' })).toEqual({ Allowed: false, Reason: 'not_owner' });
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
describe('anonymous
|
|
31
|
-
const guest = (
|
|
30
|
+
describe('anonymous visitors (one shared Anonymous user, per-visitor AnonymousVisitorID)', () => {
|
|
31
|
+
const guest = (visitorId?: string, resourceScope?: string) => ({
|
|
32
|
+
ID: OWNER,
|
|
33
|
+
IsMagicLinkAnonymous: true,
|
|
34
|
+
AnonymousVisitorID: visitorId,
|
|
35
|
+
MagicLinkScope: resourceScope ? { ResourceID: resourceScope } : visitorId ? { ResourceID: visitorId } : undefined,
|
|
36
|
+
});
|
|
32
37
|
|
|
33
|
-
it('allows a
|
|
38
|
+
it('allows a visitor whose AnonymousVisitorID matches the conversation ExternalID', () => {
|
|
34
39
|
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: 'scope-1' }, guest('scope-1'))).toEqual({ Allowed: true });
|
|
35
40
|
});
|
|
36
41
|
|
|
37
|
-
it('refuses a different guest (same Anonymous user id, different
|
|
42
|
+
it('refuses a different widget guest (same Anonymous user id, different visitor id) — the cross-guest leak', () => {
|
|
38
43
|
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: 'scope-1' }, guest('scope-2'))).toEqual({
|
|
39
44
|
Allowed: false,
|
|
40
45
|
Reason: 'scope_mismatch',
|
|
41
46
|
});
|
|
42
47
|
});
|
|
43
48
|
|
|
44
|
-
it('refuses an anonymous principal with
|
|
45
|
-
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: 'scope-1' }, guest())).toEqual({
|
|
49
|
+
it('refuses an anonymous principal with neither a resource scope nor an AnonymousVisitorID', () => {
|
|
50
|
+
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: 'scope-1' }, guest(undefined))).toEqual({
|
|
46
51
|
Allowed: false,
|
|
47
52
|
Reason: 'scope_missing',
|
|
48
53
|
});
|
|
49
54
|
});
|
|
50
55
|
|
|
56
|
+
describe('identity per token kind: MagicLinkScope.ResourceID when the token carries one, else mj_sid', () => {
|
|
57
|
+
it('widget guest (ResourceID == mj_sid): matches its own sessionId', () => {
|
|
58
|
+
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: 'sid-w' }, guest('sid-w', 'sid-w'))).toEqual({ Allowed: true });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('anonymous invite WITH a ResourceID: identity is the ResourceID, not its per-redemption mj_sid', () => {
|
|
62
|
+
const visitor = guest('sid-redemption-1', 'invite-scope-R');
|
|
63
|
+
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: 'invite-scope-R' }, visitor)).toEqual({ Allowed: true });
|
|
64
|
+
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: 'sid-redemption-1' }, visitor)).toEqual({
|
|
65
|
+
Allowed: false,
|
|
66
|
+
Reason: 'scope_mismatch',
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('anonymous invite WITHOUT a ResourceID: identity is mj_sid, so two redemptions are told apart', () => {
|
|
71
|
+
const first = { ID: OWNER, IsMagicLinkAnonymous: true, AnonymousVisitorID: 'sid-r1' };
|
|
72
|
+
const second = { ID: OWNER, IsMagicLinkAnonymous: true, AnonymousVisitorID: 'sid-r2' };
|
|
73
|
+
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: 'sid-r1' }, first)).toEqual({ Allowed: true });
|
|
74
|
+
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: 'sid-r1' }, second)).toEqual({
|
|
75
|
+
Allowed: false,
|
|
76
|
+
Reason: 'scope_mismatch',
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
51
81
|
it('refuses when the conversation carries no scope id (unreadable or unscoped)', () => {
|
|
52
82
|
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: null }, guest('scope-1'))).toEqual({ Allowed: false, Reason: 'scope_mismatch' });
|
|
53
83
|
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: undefined }, guest('scope-1'))).toEqual({ Allowed: false, Reason: 'scope_mismatch' });
|
|
@@ -68,6 +98,28 @@ describe('AuthorizeSessionAccess', () => {
|
|
|
68
98
|
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: 'other' }, scoped)).toEqual({ Allowed: false, Reason: 'scope_mismatch' });
|
|
69
99
|
});
|
|
70
100
|
|
|
101
|
+
describe('a NAMED principal with a resource scope on a conversation with no scope stamp', () => {
|
|
102
|
+
const scoped = { ID: OWNER, MagicLinkScope: { ResourceID: 'share-1' } };
|
|
103
|
+
|
|
104
|
+
it('is allowed when it could read the conversation (e.g. one started from the Conversations app)', () => {
|
|
105
|
+
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: null, ConversationReadable: true }, scoped)).toEqual({ Allowed: true });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('is refused when it could not read the conversation, or readability is unknown (fail closed)', () => {
|
|
109
|
+
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: null, ConversationReadable: false }, scoped)).toEqual({ Allowed: false, Reason: 'scope_mismatch' });
|
|
110
|
+
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: null }, scoped)).toEqual({ Allowed: false, Reason: 'scope_mismatch' });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('still refuses a conversation stamped with another scope, readable or not', () => {
|
|
114
|
+
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: 'share-2', ConversationReadable: true }, scoped)).toEqual({ Allowed: false, Reason: 'scope_mismatch' });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('does not extend to an anonymous visitor, which an unstamped conversation cannot identify', () => {
|
|
118
|
+
const visitor = { ID: OWNER, IsMagicLinkAnonymous: true, AnonymousVisitorID: 'sid-1' };
|
|
119
|
+
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: null, ConversationReadable: true }, visitor)).toEqual({ Allowed: false, Reason: 'scope_mismatch' });
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
71
123
|
it('does not require a scope of a named principal without one, whatever the conversation says', () => {
|
|
72
124
|
expect(AuthorizeSessionAccess({ SessionUserID: OWNER, ConversationExternalID: 'scope-1' }, { ID: OWNER })).toEqual({ Allowed: true });
|
|
73
125
|
});
|
|
@@ -99,6 +151,7 @@ describe('LoadSessionForAccess', () => {
|
|
|
99
151
|
const loaded = await LoadSessionForAccess(SESSION_ID, USER, provider);
|
|
100
152
|
expect(loaded?.Session.ID).toBe(SESSION_ID);
|
|
101
153
|
expect(loaded?.ConversationExternalID).toBe('scope-1');
|
|
154
|
+
expect(loaded?.ConversationReadable).toBe(true);
|
|
102
155
|
});
|
|
103
156
|
|
|
104
157
|
it('returns null for a malformed id without touching the database', async () => {
|
|
@@ -114,16 +167,102 @@ describe('LoadSessionForAccess', () => {
|
|
|
114
167
|
it('reports a null scope id when the session has no conversation', async () => {
|
|
115
168
|
const loaded = await LoadSessionForAccess(SESSION_ID, USER, fakeProvider({ session: { ID: SESSION_ID, UserID: OWNER, ConversationID: null } }));
|
|
116
169
|
expect(loaded?.ConversationExternalID).toBeNull();
|
|
170
|
+
expect(loaded?.ConversationReadable).toBe(false);
|
|
117
171
|
});
|
|
118
172
|
|
|
119
173
|
it('reports a null scope id when the conversation is unreadable, so a scoped caller is refused downstream', async () => {
|
|
120
174
|
const unreadable = await LoadSessionForAccess(SESSION_ID, USER, fakeProvider({ session: { ID: SESSION_ID, UserID: OWNER, ConversationID: CONVERSATION_ID }, conversation: null }));
|
|
121
175
|
expect(unreadable?.ConversationExternalID).toBeNull();
|
|
176
|
+
expect(unreadable?.ConversationReadable).toBe(false);
|
|
122
177
|
const throwing = await LoadSessionForAccess(
|
|
123
178
|
SESSION_ID,
|
|
124
179
|
USER,
|
|
125
180
|
fakeProvider({ session: { ID: SESSION_ID, UserID: OWNER, ConversationID: CONVERSATION_ID }, conversation: {}, conversationThrows: true }),
|
|
126
181
|
);
|
|
127
182
|
expect(throwing?.ConversationExternalID).toBeNull();
|
|
183
|
+
expect(throwing?.ConversationReadable).toBe(false);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('reports a readable conversation that carries no scope stamp', async () => {
|
|
187
|
+
const loaded = await LoadSessionForAccess(SESSION_ID, USER, fakeProvider({ session: { ID: SESSION_ID, UserID: OWNER, ConversationID: CONVERSATION_ID }, conversation: { ExternalID: null } }));
|
|
188
|
+
expect(loaded?.ConversationExternalID).toBeNull();
|
|
189
|
+
expect(loaded?.ConversationReadable).toBe(true);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('LoadConversationScope (a conversation addressed directly, e.g. by a text agent turn)', () => {
|
|
194
|
+
it('reports the owner and scope id of a readable conversation', async () => {
|
|
195
|
+
const scope = await LoadConversationScope(CONVERSATION_ID, USER, fakeProvider({ conversation: { UserID: OWNER, ExternalID: 'scope-1' } }));
|
|
196
|
+
expect(scope).toEqual({ UserID: OWNER, ExternalID: 'scope-1', Readable: true });
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('reports an unreadable conversation (missing, refused by RLS, or a failed read) without an owner', async () => {
|
|
200
|
+
expect(await LoadConversationScope(CONVERSATION_ID, USER, fakeProvider({ conversation: null }))).toEqual({ UserID: null, ExternalID: null, Readable: false });
|
|
201
|
+
expect(await LoadConversationScope(CONVERSATION_ID, USER, fakeProvider({ conversation: {}, conversationThrows: true }))).toEqual({ UserID: null, ExternalID: null, Readable: false });
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('reports a null scope id for a readable conversation with no stamp', async () => {
|
|
205
|
+
const scope = await LoadConversationScope(CONVERSATION_ID, USER, fakeProvider({ conversation: { UserID: OWNER, ExternalID: null } }));
|
|
206
|
+
expect(scope).toEqual({ UserID: OWNER, ExternalID: null, Readable: true });
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('does not touch the database for an empty id', async () => {
|
|
210
|
+
const provider = fakeProvider({});
|
|
211
|
+
expect(await LoadConversationScope('', USER, provider)).toEqual({ UserID: null, ExternalID: null, Readable: false });
|
|
212
|
+
expect(provider.GetEntityObject).not.toHaveBeenCalled();
|
|
128
213
|
});
|
|
129
214
|
});
|
|
215
|
+
|
|
216
|
+
describe('AuthorizeConversationTurn (a text agent turn addressed by a message in the conversation, MJ#6032)', () => {
|
|
217
|
+
const ANON = 'AAAAAAAA-0000-4000-8000-00000000A0A0';
|
|
218
|
+
const visitor = (sid: string) => ({ ID: ANON, IsMagicLinkAnonymous: true, AnonymousVisitorID: sid, MagicLinkScope: { ResourceID: sid } }) as unknown as UserInfo;
|
|
219
|
+
|
|
220
|
+
it('allows a named principal without reading the conversation (its permissions already decided the turn)', async () => {
|
|
221
|
+
const provider = fakeProvider({});
|
|
222
|
+
expect(await AuthorizeConversationTurn(CONVERSATION_ID, { ID: OWNER } as UserInfo, provider)).toEqual({ Allowed: true });
|
|
223
|
+
expect(provider.GetEntityObject).not.toHaveBeenCalled();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('allows the anonymous visitor whose scope id the conversation carries', async () => {
|
|
227
|
+
const provider = fakeProvider({ conversation: { UserID: ANON, ExternalID: 'sid-1' } });
|
|
228
|
+
expect(await AuthorizeConversationTurn(CONVERSATION_ID, visitor('sid-1'), provider)).toEqual({ Allowed: true });
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('refuses a second anonymous visitor — same shared user, different scope id (the UI-guest-role leak)', async () => {
|
|
232
|
+
const provider = fakeProvider({ conversation: { UserID: ANON, ExternalID: 'sid-1' } });
|
|
233
|
+
expect(await AuthorizeConversationTurn(CONVERSATION_ID, visitor('sid-2'), provider)).toEqual({ Allowed: false, Reason: 'scope_mismatch' });
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('refuses an anonymous visitor when the conversation cannot be read or carries no scope id', async () => {
|
|
237
|
+
expect(await AuthorizeConversationTurn(CONVERSATION_ID, visitor('sid-1'), fakeProvider({ conversation: null }))).toEqual({ Allowed: false, Reason: 'not_owner' });
|
|
238
|
+
expect(await AuthorizeConversationTurn(CONVERSATION_ID, visitor('sid-1'), fakeProvider({ conversation: { UserID: ANON, ExternalID: null } }))).toEqual({ Allowed: false, Reason: 'scope_mismatch' });
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe('AuthorizeAnonymousRoomSessions (who of the anonymous visitors owns a LiveKit room\'s agent session, MJ#6044)', () => {
|
|
243
|
+
const ANON = 'AAAAAAAA-0000-4000-8000-00000000A0A0';
|
|
244
|
+
const visitor = (sid: string) => ({ ID: ANON, IsMagicLinkAnonymous: true, AnonymousVisitorID: sid, MagicLinkScope: { ResourceID: sid } }) as unknown as UserInfo;
|
|
245
|
+
const roomSession = { ID: SESSION_ID, UserID: ANON, ConversationID: CONVERSATION_ID };
|
|
246
|
+
|
|
247
|
+
it("admits the visitor whose scope id the session's conversation carries", async () => {
|
|
248
|
+
const provider = fakeProvider({ session: roomSession, conversation: { UserID: ANON, ExternalID: 'sid-1' } });
|
|
249
|
+
expect(await AuthorizeAnonymousRoomSessions([SESSION_ID], visitor('sid-1'), provider)).toBe(true);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('refuses another visitor — same shared user, different scope id', async () => {
|
|
253
|
+
const provider = fakeProvider({ session: roomSession, conversation: { UserID: ANON, ExternalID: 'sid-1' } });
|
|
254
|
+
expect(await AuthorizeAnonymousRoomSessions([SESSION_ID], visitor('sid-2'), provider)).toBe(false);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('refuses when the session cannot be read as the caller', async () => {
|
|
258
|
+
expect(await AuthorizeAnonymousRoomSessions([SESSION_ID], visitor('sid-1'), fakeProvider({ session: null }))).toBe(false);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('refuses without reading anything when there is no session or no provider', async () => {
|
|
262
|
+
const provider = fakeProvider({});
|
|
263
|
+
expect(await AuthorizeAnonymousRoomSessions([], visitor('sid-1'), provider)).toBe(false);
|
|
264
|
+
expect(await AuthorizeAnonymousRoomSessions([SESSION_ID], visitor('sid-1'), undefined)).toBe(false);
|
|
265
|
+
expect(provider.GetEntityObject).not.toHaveBeenCalled();
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
@@ -117,7 +117,7 @@ describe('LoadInheritedVerifiedIdentity', () => {
|
|
|
117
117
|
|
|
118
118
|
it('refuses another anonymous guest (same Anonymous user, different scope) and accepts the same guest', async () => {
|
|
119
119
|
const prov = provider({ externalId: 'scope-A' });
|
|
120
|
-
const guest = (scope: string) => user({ IsMagicLinkAnonymous: true, MagicLinkScope: { ResourceID: scope } });
|
|
120
|
+
const guest = (scope: string) => user({ IsMagicLinkAnonymous: true, AnonymousVisitorID: scope, MagicLinkScope: { ResourceID: scope } });
|
|
121
121
|
expect(await LoadInheritedVerifiedIdentity(PRIOR, guest('scope-B'), prov)).toBeUndefined();
|
|
122
122
|
expect(await LoadInheritedVerifiedIdentity(PRIOR, guest('scope-A'), prov)).toEqual(VERIFIED);
|
|
123
123
|
});
|
|
@@ -96,6 +96,7 @@ class FakePorts implements VerificationPorts {
|
|
|
96
96
|
AgentSessionID: SESSION_ID,
|
|
97
97
|
OwnerUserID: OWNER,
|
|
98
98
|
ConversationExternalID: null,
|
|
99
|
+
ConversationReadable: false,
|
|
99
100
|
Status: 'Active',
|
|
100
101
|
ConfigRaw: JSON.stringify({ targetAgentID: 'target-1' }),
|
|
101
102
|
StartedAtMs: T0,
|
|
@@ -219,11 +220,17 @@ describe('RealtimeSessionVerificationWorkflow', () => {
|
|
|
219
220
|
it('refuses another anonymous guest sharing the Anonymous user (scope mismatch) and a scope-less anonymous caller', async () => {
|
|
220
221
|
ports.AddSession({ ConversationExternalID: 'scope-A' });
|
|
221
222
|
expect(
|
|
222
|
-
await workflow.RequestVerification(
|
|
223
|
+
await workflow.RequestVerification(
|
|
224
|
+
REQUEST,
|
|
225
|
+
principal({ IsMagicLinkAnonymous: true, AnonymousVisitorID: 'scope-B', MagicLinkScope: { ResourceID: 'scope-B' } }),
|
|
226
|
+
),
|
|
223
227
|
).toMatchObject({ ErrorCode: 'session_not_found' });
|
|
224
228
|
expect(await workflow.RequestVerification(REQUEST, principal({ IsMagicLinkAnonymous: true }))).toMatchObject({ ErrorCode: 'session_not_found' });
|
|
225
229
|
expect(
|
|
226
|
-
await workflow.RequestVerification(
|
|
230
|
+
await workflow.RequestVerification(
|
|
231
|
+
REQUEST,
|
|
232
|
+
principal({ IsMagicLinkAnonymous: true, AnonymousVisitorID: 'scope-A', MagicLinkScope: { ResourceID: 'scope-A' } }),
|
|
233
|
+
),
|
|
227
234
|
).toMatchObject({ Success: true });
|
|
228
235
|
});
|
|
229
236
|
|
|
@@ -232,6 +239,17 @@ describe('RealtimeSessionVerificationWorkflow', () => {
|
|
|
232
239
|
expect(await workflow.RequestVerification(REQUEST, principal())).toMatchObject({ ErrorCode: 'session_closed' });
|
|
233
240
|
});
|
|
234
241
|
|
|
242
|
+
it("lets a named owner holding a link scope use an unstamped conversation only when it can read that conversation", async () => {
|
|
243
|
+
// A conversation started outside any link carries no ExternalID. The owner's own row-level security
|
|
244
|
+
// decides: readable is allowed, unreadable is refused like a missing session.
|
|
245
|
+
const linkHolder = principal({ MagicLinkScope: { ResourceID: 'invite-scope-1' } });
|
|
246
|
+
ports.AddSession({ ConversationExternalID: null, ConversationReadable: false });
|
|
247
|
+
expect(await workflow.RequestVerification(REQUEST, linkHolder)).toMatchObject({ ErrorCode: 'session_not_found' });
|
|
248
|
+
|
|
249
|
+
ports.AddSession({ ConversationExternalID: null, ConversationReadable: true });
|
|
250
|
+
expect(await workflow.RequestVerification(REQUEST, linkHolder)).toMatchObject({ Success: true });
|
|
251
|
+
});
|
|
252
|
+
|
|
235
253
|
it('treats an already-verified session as success without sending anything', async () => {
|
|
236
254
|
await workflow.RequestVerification(REQUEST, principal());
|
|
237
255
|
const { code } = secretsFrom(ports);
|
|
@@ -362,7 +380,7 @@ describe('RealtimeSessionVerificationWorkflow', () => {
|
|
|
362
380
|
|
|
363
381
|
it('routes the event with the session scope so an anonymous guest\'s event reaches only that guest', async () => {
|
|
364
382
|
ports.AddSession({ ConversationExternalID: 'scope-A' });
|
|
365
|
-
const guest = principal({ IsMagicLinkAnonymous: true, MagicLinkScope: { ResourceID: 'scope-A' } });
|
|
383
|
+
const guest = principal({ IsMagicLinkAnonymous: true, AnonymousVisitorID: 'scope-A', MagicLinkScope: { ResourceID: 'scope-A' } });
|
|
366
384
|
await workflow.RequestVerification(REQUEST, guest);
|
|
367
385
|
await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: secretsFrom(ports).code }, guest);
|
|
368
386
|
expect(ports.Published[0].routing.ScopeKey).toBe('scope-A');
|
|
@@ -445,7 +463,10 @@ describe('RealtimeSessionVerificationWorkflow', () => {
|
|
|
445
463
|
expect(await workflow.SubmitCode({ AgentSessionID: SESSION_ID, Code: code }, principal({ ID: STRANGER }))).toMatchObject({ ErrorCode: 'session_not_found' });
|
|
446
464
|
ports.AddSession({ ConversationExternalID: 'scope-A', ConfigRaw: ports.Sessions.get(SESSION_ID.toLowerCase())?.ConfigRaw ?? null });
|
|
447
465
|
expect(
|
|
448
|
-
await workflow.SubmitCode(
|
|
466
|
+
await workflow.SubmitCode(
|
|
467
|
+
{ AgentSessionID: SESSION_ID, Code: code },
|
|
468
|
+
principal({ IsMagicLinkAnonymous: true, AnonymousVisitorID: 'scope-B', MagicLinkScope: { ResourceID: 'scope-B' } }),
|
|
469
|
+
),
|
|
449
470
|
).toMatchObject({ ErrorCode: 'session_not_found' });
|
|
450
471
|
expect(ports.Published).toHaveLength(0);
|
|
451
472
|
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudEvents for an entity under field-level security carry the primary key and the names of the
|
|
3
|
+
* fields the save changed, never values.
|
|
4
|
+
*
|
|
5
|
+
* Driven by a REAL `BaseEntity` save rather than a stand-in object with `Dirty` set: a completed
|
|
6
|
+
* save rebases the object before it raises `save`, so nothing on it is dirty by the time a
|
|
7
|
+
* listener runs. The names must come from the event, which captures them before the rebase.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
10
|
+
|
|
11
|
+
const { cloudEventInits } = vi.hoisted(() => ({
|
|
12
|
+
cloudEventInits: [] as Array<Record<string, unknown>>,
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock('@memberjunction/sqlserver-dataprovider', () => ({ SQLServerDataProvider: class {} }));
|
|
16
|
+
vi.mock('@memberjunction/generic-database-provider', () => ({ UserCache: { Users: [] } }));
|
|
17
|
+
vi.mock('cloudevents', () => ({
|
|
18
|
+
CloudEvent: class {
|
|
19
|
+
constructor(init: Record<string, unknown>) {
|
|
20
|
+
cloudEventInits.push(init);
|
|
21
|
+
Object.assign(this, init);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
httpTransport: () => () => undefined,
|
|
25
|
+
emitterFor: () => () => undefined,
|
|
26
|
+
}));
|
|
27
|
+
vi.mock('type-graphql', () => {
|
|
28
|
+
const noop = () => () => undefined;
|
|
29
|
+
return {
|
|
30
|
+
Resolver: noop, Mutation: noop, Query: noop, Subscription: noop, Ctx: noop, Arg: noop, PubSub: noop,
|
|
31
|
+
Root: noop, ObjectType: noop, InputType: noop, Field: noop, FieldResolver: noop,
|
|
32
|
+
Int: undefined, Float: undefined, registerEnumType: () => undefined,
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
vi.mock('graphql', () => ({ GraphQLError: class extends Error {} }));
|
|
36
|
+
vi.mock('mssql', () => ({}));
|
|
37
|
+
vi.mock('@memberjunction/api-keys', () => ({ GetAPIKeyEngine: vi.fn() }));
|
|
38
|
+
vi.mock('@memberjunction/encryption', () => ({ EncryptionEngine: { Instance: {} } }));
|
|
39
|
+
vi.mock('@memberjunction/graphql-dataprovider', () => ({ FieldMapper: class { MapFieldName(n: string) { return n; } ReverseMapFieldName(n: string) { return n; } } }));
|
|
40
|
+
vi.mock('../generic/PubSubManager.js', () => ({ PubSubManager: { Instance: { Publish: () => undefined } } }));
|
|
41
|
+
vi.mock('../generic/PushStatusResolver.js', () => ({ PUSH_STATUS_UPDATES_TOPIC: 'test-push-topic', PublishStatusUpdate: vi.fn() }));
|
|
42
|
+
vi.mock('../generic/RunViewResolver.js', () => ({ RunViewByIDInput: class {}, RunViewByNameInput: class {}, RunDynamicViewInput: class {} }));
|
|
43
|
+
vi.mock('../generic/DeleteOptionsInput.js', () => ({ DeleteOptionsInput: class {} }));
|
|
44
|
+
vi.mock('../types.js', () => ({ RunViewGenericParams: class {} }));
|
|
45
|
+
vi.mock('@memberjunction/core-entities', () => ({}));
|
|
46
|
+
vi.mock('@memberjunction/core', async () => {
|
|
47
|
+
const actual = await vi.importActual<typeof import('@memberjunction/core')>('@memberjunction/core');
|
|
48
|
+
return { ...actual, LogError: vi.fn(), LogStatus: vi.fn(), LogDebug: vi.fn() };
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
import { BaseEntity, CompositeKey, EntityInfo, IEntityDataProvider, UserInfo } from '@memberjunction/core';
|
|
52
|
+
import type { PubSubEngine } from 'type-graphql';
|
|
53
|
+
import { ResolverBase } from '../generic/ResolverBase';
|
|
54
|
+
import type { UserPayload } from '../types.js';
|
|
55
|
+
import { EmployeeEntityInit, FullWidthEmployeeRow, HRUser, SECRET_SALARY } from './fieldSecurityFixtures.js';
|
|
56
|
+
|
|
57
|
+
/** `Employees` with every field updatable through the API, so a real save can dirty them. */
|
|
58
|
+
function updatableEmployees(withFls: boolean): EntityInfo {
|
|
59
|
+
const init = EmployeeEntityInit(withFls);
|
|
60
|
+
const fields = (init.Fields as Record<string, unknown>[]).map(f => ({ ...f, AllowUpdateAPI: true, AllowsNull: true }));
|
|
61
|
+
return new EntityInfo({ ...init, Fields: fields });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
class Employee extends BaseEntity {
|
|
65
|
+
public override CheckPermissions(): boolean {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** A provider whose database holds one full-width employee row and whose Save returns the stored row. */
|
|
71
|
+
function makeProvider() {
|
|
72
|
+
let stored = FullWidthEmployeeRow();
|
|
73
|
+
return {
|
|
74
|
+
CurrentUser: null as UserInfo | null,
|
|
75
|
+
async Load(): Promise<Record<string, unknown>> { return { ...stored }; },
|
|
76
|
+
async Save(entity: BaseEntity): Promise<Record<string, unknown>> {
|
|
77
|
+
stored = { ...stored, ...(entity.GetAll() as Record<string, unknown>) };
|
|
78
|
+
return { ...stored };
|
|
79
|
+
},
|
|
80
|
+
SetCachedRecordName(): void { /* no-op */ },
|
|
81
|
+
GetCachedRecordName(): string | undefined { return undefined; },
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
class TestResolver extends ResolverBase {
|
|
86
|
+
public Listen(entity: BaseEntity) {
|
|
87
|
+
this.ListenForEntityMessages(entity, {} as PubSubEngine, { sessionId: 's' } as UserPayload);
|
|
88
|
+
}
|
|
89
|
+
public Payload(entity: BaseEntity, changed?: string[]): Record<string, unknown> {
|
|
90
|
+
return this.EntityEventPayload(entity, changed);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function loadedEmployee(entityInfo: EntityInfo, user: UserInfo): Promise<Employee> {
|
|
95
|
+
const e = new Employee(entityInfo, makeProvider() as unknown as IEntityDataProvider);
|
|
96
|
+
e.ContextCurrentUser = user;
|
|
97
|
+
await e.InnerLoad(CompositeKey.FromID('emp-1'));
|
|
98
|
+
return e;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
describe('ResolverBase CloudEvents: changed fields from a real save', () => {
|
|
102
|
+
const emitterKey = '_emit';
|
|
103
|
+
let previousEmitter: unknown;
|
|
104
|
+
|
|
105
|
+
beforeEach(() => {
|
|
106
|
+
cloudEventInits.length = 0;
|
|
107
|
+
const statics = ResolverBase as unknown as Record<string, unknown>;
|
|
108
|
+
previousEmitter = statics[emitterKey];
|
|
109
|
+
statics[emitterKey] = async () => ({});
|
|
110
|
+
});
|
|
111
|
+
afterEach(() => {
|
|
112
|
+
(ResolverBase as unknown as Record<string, unknown>)[emitterKey] = previousEmitter;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/** The data of the CloudEvent emitted for the completed save. */
|
|
116
|
+
async function saveEventData(entity: Employee): Promise<Record<string, unknown>> {
|
|
117
|
+
new TestResolver().Listen(entity);
|
|
118
|
+
await expect(entity.Save()).resolves.toBe(true);
|
|
119
|
+
await vi.waitFor(() => expect(cloudEventInits.some(c => String(c.type).endsWith('.save'))).toBe(true));
|
|
120
|
+
return cloudEventInits.find(c => String(c.type).endsWith('.save'))!.data as Record<string, unknown>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
it('REGRESSION: names the fields the save changed, not an empty list', async () => {
|
|
124
|
+
const entity = await loadedEmployee(updatableEmployees(true), HRUser());
|
|
125
|
+
entity.Set('Notes', 'edited');
|
|
126
|
+
entity.Set('Salary', 1);
|
|
127
|
+
|
|
128
|
+
const data = await saveEventData(entity);
|
|
129
|
+
|
|
130
|
+
expect(entity.Dirty).toBe(false);
|
|
131
|
+
expect(data.PrimaryKey).toEqual([{ FieldName: 'ID', Value: 'emp-1' }]);
|
|
132
|
+
expect([...(data.ChangedFields as string[])].sort()).toEqual(['Notes', 'Salary']);
|
|
133
|
+
expect(JSON.stringify(data)).not.toContain(String(SECRET_SALARY));
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('an entity without field security still emits its row', async () => {
|
|
137
|
+
const entity = await loadedEmployee(updatableEmployees(false), HRUser());
|
|
138
|
+
entity.Set('Notes', 'edited');
|
|
139
|
+
|
|
140
|
+
const data = await saveEventData(entity);
|
|
141
|
+
|
|
142
|
+
expect(data).toMatchObject({ ID: 'emp-1', Notes: 'edited' });
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('a payload for a record still in raw mode does not build its field objects', async () => {
|
|
146
|
+
const entity = new Employee(updatableEmployees(true), makeProvider() as unknown as IEntityDataProvider);
|
|
147
|
+
entity.ContextCurrentUser = HRUser();
|
|
148
|
+
await entity.LoadFromData(FullWidthEmployeeRow()); // a load_complete listener sees exactly this object
|
|
149
|
+
const hydrated = () => (entity as unknown as { _fieldsHydrated: boolean })._fieldsHydrated;
|
|
150
|
+
expect(hydrated()).toBe(false);
|
|
151
|
+
|
|
152
|
+
const data = new TestResolver().Payload(entity);
|
|
153
|
+
|
|
154
|
+
expect(data.ChangedFields).toEqual([]);
|
|
155
|
+
expect(hydrated()).toBe(false);
|
|
156
|
+
});
|
|
157
|
+
});
|