@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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { UserInfo } from '@memberjunction/core';
|
|
3
|
+
import type jwt from 'jsonwebtoken';
|
|
4
|
+
|
|
5
|
+
vi.mock('../config.js', () => ({
|
|
6
|
+
userEmailMap: {},
|
|
7
|
+
apiKey: 'test-api-key',
|
|
8
|
+
mj_core_schema: '__mj',
|
|
9
|
+
configInfo: {},
|
|
10
|
+
}));
|
|
11
|
+
vi.mock('../auth/index.js', () => ({
|
|
12
|
+
GetSigningKeys: vi.fn(),
|
|
13
|
+
GetSystemUser: vi.fn(),
|
|
14
|
+
GetValidationOptions: vi.fn(),
|
|
15
|
+
VerifyUserRecord: vi.fn(),
|
|
16
|
+
ExtractUserInfoFromPayload: vi.fn(),
|
|
17
|
+
}));
|
|
18
|
+
vi.mock('../util.js', () => ({
|
|
19
|
+
GetReadOnlyDataSource: vi.fn(),
|
|
20
|
+
GetReadWriteDataSource: vi.fn(),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
import { BuildMagicLinkSessionUser } from '../context.js';
|
|
24
|
+
|
|
25
|
+
const uuid = (a: string, b: string) =>
|
|
26
|
+
[a.repeat(8), b.repeat(4), '4' + b.repeat(3), '8' + b.repeat(3), b.repeat(12)].join('-');
|
|
27
|
+
const ANON_USER_ID = uuid('A', '0');
|
|
28
|
+
const NAMED_USER_ID = uuid('B', '1');
|
|
29
|
+
|
|
30
|
+
function makeUserRecord(id: string, email: string): UserInfo {
|
|
31
|
+
return new UserInfo(null, {
|
|
32
|
+
ID: id,
|
|
33
|
+
Name: 'Session Test User',
|
|
34
|
+
Email: email,
|
|
35
|
+
IsActive: true,
|
|
36
|
+
Type: 'User',
|
|
37
|
+
UserRoles: [],
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe('BuildMagicLinkSessionUser', () => {
|
|
42
|
+
it('sets AnonymousVisitorID from mj_sid for an anonymous magic-link token', () => {
|
|
43
|
+
const baseUser = makeUserRecord(ANON_USER_ID, 'anonymous@magic-link.local');
|
|
44
|
+
const payload: jwt.JwtPayload = {
|
|
45
|
+
mj_magic_link: true,
|
|
46
|
+
mj_anon: true,
|
|
47
|
+
mj_sid: 'sid-visitor-123',
|
|
48
|
+
mj_scopes: [{ inviteId: 'inv-1', appId: 'app-1', role: 'Widget Guest', resourceId: 'res-shared' }],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const result = BuildMagicLinkSessionUser(baseUser, payload);
|
|
52
|
+
|
|
53
|
+
expect(result).not.toBe(baseUser);
|
|
54
|
+
expect(result.IsMagicLinkAnonymous).toBe(true);
|
|
55
|
+
expect(result.AnonymousVisitorID).toBe('sid-visitor-123');
|
|
56
|
+
expect(result.MagicLinkScope?.ResourceID).toBe('res-shared');
|
|
57
|
+
expect(baseUser.AnonymousVisitorID).toBeUndefined();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('rejects an anonymous magic-link token without mj_sid', () => {
|
|
61
|
+
const baseUser = makeUserRecord(ANON_USER_ID, 'anonymous@magic-link.local');
|
|
62
|
+
const missingSid: jwt.JwtPayload = {
|
|
63
|
+
mj_magic_link: true,
|
|
64
|
+
mj_anon: true,
|
|
65
|
+
mj_scopes: [{ inviteId: 'inv-1', appId: 'app-1', role: 'Widget Guest', resourceId: 'res-shared' }],
|
|
66
|
+
};
|
|
67
|
+
const blankSid: jwt.JwtPayload = {
|
|
68
|
+
mj_magic_link: true,
|
|
69
|
+
mj_anon: true,
|
|
70
|
+
mj_sid: ' ',
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
expect(() => BuildMagicLinkSessionUser(baseUser, missingSid)).toThrow(/mj_sid/);
|
|
74
|
+
expect(() => BuildMagicLinkSessionUser(baseUser, blankSid)).toThrow(/mj_sid/);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('leaves AnonymousVisitorID undefined for a named magic-link token (with or without resource scope)', () => {
|
|
78
|
+
const baseUser = makeUserRecord(NAMED_USER_ID, 'named@example.com');
|
|
79
|
+
const unscopedNamed: jwt.JwtPayload = {
|
|
80
|
+
mj_magic_link: true,
|
|
81
|
+
mj_scopes: [{ inviteId: 'inv-named', appId: 'app-1', role: 'UI' }],
|
|
82
|
+
};
|
|
83
|
+
const scopedNamed: jwt.JwtPayload = {
|
|
84
|
+
mj_magic_link: true,
|
|
85
|
+
mj_scopes: [{ inviteId: 'inv-named-2', appId: 'app-1', role: 'UI', resourceId: 'res-named-1', resourceType: 'Report' }],
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const unscopedResult = BuildMagicLinkSessionUser(baseUser, unscopedNamed);
|
|
89
|
+
expect(unscopedResult.AnonymousVisitorID).toBeUndefined();
|
|
90
|
+
|
|
91
|
+
const scopedResult = BuildMagicLinkSessionUser(baseUser, scopedNamed);
|
|
92
|
+
expect(scopedResult.IsMagicLinkAnonymous).toBe(false);
|
|
93
|
+
expect(scopedResult.MagicLinkScope?.ResourceID).toBe('res-named-1');
|
|
94
|
+
expect(scopedResult.AnonymousVisitorID).toBeUndefined();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
3
|
+
import type { EntityInfo, UserInfo } from '@memberjunction/core';
|
|
4
|
+
import type { PubSubEngine } from 'type-graphql';
|
|
5
|
+
import {
|
|
6
|
+
AzureWebPubSubBroadcastProvider,
|
|
7
|
+
AWSIoTCoreBroadcastProvider,
|
|
8
|
+
CacheInvalidationBroadcastManager,
|
|
9
|
+
GCPPubSubBroadcastProvider,
|
|
10
|
+
SequencedRoleBucketBroadcastProvider,
|
|
11
|
+
type AzureWebPubSubGroupMessage,
|
|
12
|
+
type AWSIoTCoreTopicMessage,
|
|
13
|
+
type GCPPubSubTopicMessage,
|
|
14
|
+
} from '../broadcast/index.js';
|
|
15
|
+
import type { CacheInvalidationPayload } from '../generic/CacheInvalidationResolver.js';
|
|
16
|
+
import {
|
|
17
|
+
GetSessionStatusTopic,
|
|
18
|
+
PUSH_STATUS_UPDATES_TOPIC,
|
|
19
|
+
ParseStatusUpdateMessage,
|
|
20
|
+
PublishStatusUpdate,
|
|
21
|
+
ShouldReplicateStatusUpdate,
|
|
22
|
+
type PushStatusNotificationPayload,
|
|
23
|
+
} from '../generic/PushStatusResolver.js';
|
|
24
|
+
import { CreateSessionRoutingPubSub, PubSubManager } from '../generic/PubSubManager.js';
|
|
25
|
+
import {
|
|
26
|
+
ResolveServerRole,
|
|
27
|
+
ShouldRunBackgroundWorkers,
|
|
28
|
+
ShouldServeInteractiveTraffic,
|
|
29
|
+
} from '../serverRoleConfigUnits.js';
|
|
30
|
+
|
|
31
|
+
function makeUser(id: string, roleIds: string[]): UserInfo {
|
|
32
|
+
return {
|
|
33
|
+
ID: id,
|
|
34
|
+
Name: `User ${id}`,
|
|
35
|
+
Email: `${id}@example.com`,
|
|
36
|
+
UserRoles: roleIds.map((r) => ({ UserID: id, RoleID: r, RoleName: r })),
|
|
37
|
+
} as unknown as UserInfo;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function makePayload(overrides: Partial<CacheInvalidationPayload> = {}): CacheInvalidationPayload {
|
|
41
|
+
return {
|
|
42
|
+
entityName: 'Invoices',
|
|
43
|
+
primaryKeyValues: '[{"FieldName":"ID","Value":"inv-1"}]',
|
|
44
|
+
action: 'save',
|
|
45
|
+
sourceServerId: 'srv-1',
|
|
46
|
+
timestamp: new Date('2026-10-20T12:00:00Z'),
|
|
47
|
+
recordData: '{"ID":"inv-1","Total":500}',
|
|
48
|
+
...overrides,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('CacheInvalidationBroadcastManager & SequencedRoleBucketBroadcastProvider (Topologies 1 & 2)', () => {
|
|
53
|
+
let provider: SequencedRoleBucketBroadcastProvider;
|
|
54
|
+
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
provider = CacheInvalidationBroadcastManager.Instance.Configure({
|
|
57
|
+
provider: 'SequencedRoleBucket',
|
|
58
|
+
ringBufferSize: 5,
|
|
59
|
+
excludedEntities: ['MJ: Audit Logs'],
|
|
60
|
+
}) as SequencedRoleBucketBroadcastProvider;
|
|
61
|
+
provider.ResetStateForTesting();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('defaults to SequencedRoleBucket and stamps monotonically increasing sequences', () => {
|
|
65
|
+
expect(provider.Topology).toBe('SequencedRoleBucket');
|
|
66
|
+
expect(provider.CurrentSequence).toBe(0);
|
|
67
|
+
|
|
68
|
+
const p1 = provider.RecordInvalidation(makePayload({ primaryKeyValues: 'pk-1' }));
|
|
69
|
+
const p2 = provider.RecordInvalidation(makePayload({ primaryKeyValues: 'pk-2' }));
|
|
70
|
+
|
|
71
|
+
expect(p1.sequence).toBe(1);
|
|
72
|
+
expect(p2.sequence).toBe(2);
|
|
73
|
+
expect(provider.CurrentSequence).toBe(2);
|
|
74
|
+
expect(provider.BufferedEventCount).toBe(2);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('suppresses excluded high-churn entities from sequencing and broadcast', () => {
|
|
78
|
+
expect(provider.ShouldBroadcastEntity('MJ: Audit Logs')).toBe(false);
|
|
79
|
+
expect(provider.ShouldBroadcastEntity('Invoices')).toBe(true);
|
|
80
|
+
|
|
81
|
+
const ignored = provider.RecordInvalidation(makePayload({ entityName: 'MJ: Audit Logs' }));
|
|
82
|
+
expect(ignored.sequence).toBeUndefined();
|
|
83
|
+
expect(provider.CurrentSequence).toBe(0);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('computes canonical, order-independent role fingerprints and memoizes per user object', () => {
|
|
87
|
+
const u1 = makeUser('u1', ['Role-B', 'role-a']);
|
|
88
|
+
const u2 = makeUser('u2', ['ROLE-A', ' role-b ']);
|
|
89
|
+
|
|
90
|
+
const fp1 = provider.GetRoleFingerprint(u1);
|
|
91
|
+
const fp2 = provider.GetRoleFingerprint(u2);
|
|
92
|
+
|
|
93
|
+
expect(fp1).toBe('roles:role-a|role-b');
|
|
94
|
+
expect(fp2).toBe('roles:role-a|role-b');
|
|
95
|
+
// Second call hits WeakMap cache
|
|
96
|
+
expect(provider.GetRoleFingerprint(u1)).toBe(fp1);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('coalesces permission & RLS evaluation to O(Roles) instead of O(Subscribers)', () => {
|
|
100
|
+
const getUserPermisionsSpy = vi.fn((user: UserInfo) => {
|
|
101
|
+
const hasDeniedOnly = user.UserRoles.every((r) => r.RoleID === 'role-none');
|
|
102
|
+
return { CanRead: !hasDeniedOnly };
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const fakeEntity = {
|
|
106
|
+
Name: 'Invoices',
|
|
107
|
+
GetUserPermisions: getUserPermisionsSpy,
|
|
108
|
+
Permissions: [
|
|
109
|
+
{ RoleID: 'role-open', CanRead: true, Type: 'Allow', ReadRLSFilterID: null },
|
|
110
|
+
{ RoleID: 'role-rls', CanRead: true, Type: 'Allow', ReadRLSFilterID: 'rls-filter-1' },
|
|
111
|
+
{ RoleID: 'role-none', CanRead: false, Type: 'Allow', ReadRLSFilterID: null },
|
|
112
|
+
],
|
|
113
|
+
} as unknown as EntityInfo;
|
|
114
|
+
|
|
115
|
+
vi.spyOn(provider, 'ResolveEntityForPayload').mockReturnValue(fakeEntity);
|
|
116
|
+
const rlsSpy = vi.spyOn(provider, 'ReadIsRowFiltered');
|
|
117
|
+
|
|
118
|
+
const payload = makePayload();
|
|
119
|
+
|
|
120
|
+
// Create 300 subscribers across only 3 distinct role buckets (100 per bucket)
|
|
121
|
+
const openUsers = Array.from({ length: 100 }, (_, i) => makeUser(`open-${i}`, ['role-open']));
|
|
122
|
+
const rlsUsers = Array.from({ length: 100 }, (_, i) => makeUser(`rls-${i}`, ['role-rls']));
|
|
123
|
+
const deniedUsers = Array.from({ length: 100 }, (_, i) => makeUser(`denied-${i}`, ['role-none']));
|
|
124
|
+
|
|
125
|
+
for (const u of openUsers) {
|
|
126
|
+
expect(provider.EvaluateEntityReadAccess(payload, u)).toBe(true);
|
|
127
|
+
const shaped = provider.ShapeEventForSubscriber(payload, u, fakeEntity);
|
|
128
|
+
expect(shaped.PrimaryKeyValues).toBe(payload.primaryKeyValues);
|
|
129
|
+
expect(shaped.RecordData).toBe(payload.recordData);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (const u of rlsUsers) {
|
|
133
|
+
expect(provider.EvaluateEntityReadAccess(payload, u)).toBe(true);
|
|
134
|
+
const shaped = provider.ShapeEventForSubscriber(payload, u, fakeEntity);
|
|
135
|
+
expect(shaped.PrimaryKeyValues).toBeUndefined();
|
|
136
|
+
expect(shaped.RecordData).toBeUndefined();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (const u of deniedUsers) {
|
|
140
|
+
expect(provider.EvaluateEntityReadAccess(payload, u)).toBe(false);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Despite 300 subscribers, GetUserPermisions ran only 3 times (once per unique role bucket)
|
|
144
|
+
// and ReadIsRowFiltered ran only 2 times (for the 2 allowed role buckets)!
|
|
145
|
+
expect(getUserPermisionsSpy).toHaveBeenCalledTimes(3);
|
|
146
|
+
expect(rlsSpy).toHaveBeenCalledTimes(2);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('serves delta catch-up from the circular ring buffer and coalesces duplicate PK updates', () => {
|
|
150
|
+
const fakeEntity = {
|
|
151
|
+
Name: 'Invoices',
|
|
152
|
+
GetUserPermisions: () => ({ CanRead: true }),
|
|
153
|
+
Permissions: [{ RoleID: 'role-open', CanRead: true, Type: 'Allow', ReadRLSFilterID: null }],
|
|
154
|
+
} as unknown as EntityInfo;
|
|
155
|
+
vi.spyOn(provider, 'ResolveEntityForPayload').mockReturnValue(fakeEntity);
|
|
156
|
+
|
|
157
|
+
const user = makeUser('u1', ['role-open']);
|
|
158
|
+
|
|
159
|
+
// Record 4 events (two on inv-1, one on inv-2, one on inv-1 again)
|
|
160
|
+
provider.RecordInvalidation(makePayload({ primaryKeyValues: 'inv-1', recordData: '{"v":1}' })); // seq 1
|
|
161
|
+
provider.RecordInvalidation(makePayload({ primaryKeyValues: 'inv-2', recordData: '{"v":2}' })); // seq 2
|
|
162
|
+
provider.RecordInvalidation(makePayload({ primaryKeyValues: 'inv-1', recordData: '{"v":3}' })); // seq 3
|
|
163
|
+
|
|
164
|
+
const catchUp = provider.GetChangesSince(0, user);
|
|
165
|
+
expect(catchUp.RequiresFullRefresh).toBe(false);
|
|
166
|
+
expect(catchUp.CurrentSequence).toBe(3);
|
|
167
|
+
// inv-1 (seq 1 and seq 3) coalesced to latest (seq 3), so 2 events total
|
|
168
|
+
expect(catchUp.Events).toHaveLength(2);
|
|
169
|
+
const inv1Event = catchUp.Events.find((e) => e.PrimaryKeyValues === 'inv-1');
|
|
170
|
+
expect(inv1Event?.RecordData).toBe('{"v":3}');
|
|
171
|
+
expect(inv1Event?.Sequence).toBe(3);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('signals RequiresFullRefresh when sinceSequence has been evicted from the circular ring buffer', () => {
|
|
175
|
+
const fakeEntity = {
|
|
176
|
+
Name: 'Invoices',
|
|
177
|
+
GetUserPermisions: () => ({ CanRead: true }),
|
|
178
|
+
Permissions: [{ RoleID: 'role-open', CanRead: true, Type: 'Allow', ReadRLSFilterID: null }],
|
|
179
|
+
} as unknown as EntityInfo;
|
|
180
|
+
vi.spyOn(provider, 'ResolveEntityForPayload').mockReturnValue(fakeEntity);
|
|
181
|
+
|
|
182
|
+
const user = makeUser('u1', ['role-open']);
|
|
183
|
+
|
|
184
|
+
// Ring buffer size is 5; push 7 events so sequences 1 and 2 are overwritten (oldest in buffer is 3)
|
|
185
|
+
for (let i = 1; i <= 7; i++) {
|
|
186
|
+
provider.RecordInvalidation(makePayload({ primaryKeyValues: `inv-${i}` }));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Requesting sinceSequence = 1 (missed seq 2, which was evicted) -> RequiresFullRefresh: true
|
|
190
|
+
const evictedCatchUp = provider.GetChangesSince(1, user);
|
|
191
|
+
expect(evictedCatchUp.RequiresFullRefresh).toBe(true);
|
|
192
|
+
expect(evictedCatchUp.Events).toHaveLength(0);
|
|
193
|
+
|
|
194
|
+
// Requesting sinceSequence = 2 (oldest needed is seq 3, still in buffer) -> RequiresFullRefresh: false
|
|
195
|
+
const validCatchUp = provider.GetChangesSince(2, user);
|
|
196
|
+
expect(validCatchUp.RequiresFullRefresh).toBe(false);
|
|
197
|
+
expect(validCatchUp.Events).toHaveLength(5);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('Topology 3 Cloud Multiplexer Providers (AzureWebPubSub, AWSIoTCore, GCPPubSub)', () => {
|
|
202
|
+
afterEach(() => {
|
|
203
|
+
CacheInvalidationBroadcastManager.Instance.Configure({ provider: 'SequencedRoleBucket' });
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('AzureWebPubSubBroadcastProvider negotiates signed JWT URLs and publishes role-shaped group messages', async () => {
|
|
207
|
+
const azureProvider = CacheInvalidationBroadcastManager.Instance.Configure({
|
|
208
|
+
provider: 'AzureWebPubSub',
|
|
209
|
+
azureWebPubSub: {
|
|
210
|
+
endpoint: 'https://mj-test.webpubsub.azure.com',
|
|
211
|
+
hubName: 'mj_hub',
|
|
212
|
+
accessKey: 'test-secret-access-key-123456',
|
|
213
|
+
tokenTtlMinutes: 30,
|
|
214
|
+
},
|
|
215
|
+
}) as AzureWebPubSubBroadcastProvider;
|
|
216
|
+
|
|
217
|
+
expect(azureProvider).toBeInstanceOf(AzureWebPubSubBroadcastProvider);
|
|
218
|
+
expect(azureProvider.Topology).toBe('AzureWebPubSub');
|
|
219
|
+
|
|
220
|
+
const user = makeUser('u-azure', ['Role-Admin', 'Role-Finance']);
|
|
221
|
+
const negotiated = await azureProvider.NegotiateClientConnection(user);
|
|
222
|
+
expect(negotiated.Provider).toBe('AzureWebPubSub');
|
|
223
|
+
expect(negotiated.Url).toContain('wss://mj-test.webpubsub.azure.com/client/hubs/mj_hub?access_token=');
|
|
224
|
+
expect(negotiated.Token).toBeDefined();
|
|
225
|
+
expect(negotiated.Groups).toEqual(['mj-role-role-admin', 'mj-role-role-finance']);
|
|
226
|
+
|
|
227
|
+
const published: AzureWebPubSubGroupMessage[] = [];
|
|
228
|
+
azureProvider.SetGroupPublisher(async (msg) => {
|
|
229
|
+
published.push(msg);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const fakeEntity = {
|
|
233
|
+
Name: 'Invoices',
|
|
234
|
+
GetUserPermisions: () => ({ CanRead: true }),
|
|
235
|
+
Permissions: [
|
|
236
|
+
{ RoleID: 'role-admin', CanRead: true, Type: 'Allow', ReadRLSFilterID: null },
|
|
237
|
+
{ RoleID: 'role-rls', CanRead: true, Type: 'Allow', ReadRLSFilterID: 'rls-1' },
|
|
238
|
+
],
|
|
239
|
+
} as unknown as EntityInfo;
|
|
240
|
+
vi.spyOn(azureProvider, 'ResolveEntityForPayload').mockReturnValue(fakeEntity);
|
|
241
|
+
|
|
242
|
+
azureProvider.RecordInvalidation(makePayload());
|
|
243
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
244
|
+
|
|
245
|
+
expect(published).toHaveLength(2);
|
|
246
|
+
const adminMsg = published.find((m) => m.GroupName === 'mj-role-role-admin');
|
|
247
|
+
const rlsMsg = published.find((m) => m.GroupName === 'mj-role-role-rls');
|
|
248
|
+
expect(adminMsg?.Event.PrimaryKeyValues).toBeDefined();
|
|
249
|
+
expect(rlsMsg?.Event.PrimaryKeyValues).toBeUndefined();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('AWSIoTCoreBroadcastProvider negotiates SigV4 presigned MQTT URLs and publishes role topics', async () => {
|
|
253
|
+
const awsProvider = CacheInvalidationBroadcastManager.Instance.Configure({
|
|
254
|
+
provider: 'AWSIoTCore',
|
|
255
|
+
awsIoTCore: {
|
|
256
|
+
endpoint: 'a1b2c3-ats.iot.us-east-1.amazonaws.com',
|
|
257
|
+
region: 'us-east-1',
|
|
258
|
+
topicPrefix: 'mj/prod/cache',
|
|
259
|
+
accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
|
|
260
|
+
secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
|
261
|
+
},
|
|
262
|
+
}) as AWSIoTCoreBroadcastProvider;
|
|
263
|
+
|
|
264
|
+
expect(awsProvider).toBeInstanceOf(AWSIoTCoreBroadcastProvider);
|
|
265
|
+
const user = makeUser('u-aws', ['role-ops']);
|
|
266
|
+
const negotiated = await awsProvider.NegotiateClientConnection(user);
|
|
267
|
+
expect(negotiated.Provider).toBe('AWSIoTCore');
|
|
268
|
+
expect(negotiated.Url).toContain('wss://a1b2c3-ats.iot.us-east-1.amazonaws.com/mqtt?X-Amz-Algorithm=AWS4-HMAC-SHA256');
|
|
269
|
+
expect(negotiated.Groups).toEqual(['mj/prod/cache/roles/role-ops']);
|
|
270
|
+
|
|
271
|
+
const published: AWSIoTCoreTopicMessage[] = [];
|
|
272
|
+
awsProvider.SetTopicPublisher(async (msg) => {
|
|
273
|
+
published.push(msg);
|
|
274
|
+
});
|
|
275
|
+
const fakeEntity = {
|
|
276
|
+
Name: 'Invoices',
|
|
277
|
+
GetUserPermisions: () => ({ CanRead: true }),
|
|
278
|
+
Permissions: [{ RoleID: 'role-ops', CanRead: true, Type: 'Allow', ReadRLSFilterID: null }],
|
|
279
|
+
} as unknown as EntityInfo;
|
|
280
|
+
vi.spyOn(awsProvider, 'ResolveEntityForPayload').mockReturnValue(fakeEntity);
|
|
281
|
+
|
|
282
|
+
awsProvider.RecordInvalidation(makePayload());
|
|
283
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
284
|
+
expect(published).toHaveLength(1);
|
|
285
|
+
expect(published[0].Topic).toBe('mj/prod/cache/roles/role-ops');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('GCPPubSubBroadcastProvider negotiates gateway tokens and publishes Pub/Sub topic messages', async () => {
|
|
289
|
+
const gcpProvider = CacheInvalidationBroadcastManager.Instance.Configure({
|
|
290
|
+
provider: 'GCPPubSub',
|
|
291
|
+
gcpPubSub: {
|
|
292
|
+
projectId: 'mj-cloud-prod',
|
|
293
|
+
topicPrefix: 'mj-invalidation',
|
|
294
|
+
gatewayUrl: 'https://cache-gw.run.app/ws',
|
|
295
|
+
signingSecret: 'gcp-gateway-secret-key',
|
|
296
|
+
},
|
|
297
|
+
}) as GCPPubSubBroadcastProvider;
|
|
298
|
+
|
|
299
|
+
expect(gcpProvider).toBeInstanceOf(GCPPubSubBroadcastProvider);
|
|
300
|
+
const user = makeUser('u-gcp', ['role-analyst']);
|
|
301
|
+
const negotiated = await gcpProvider.NegotiateClientConnection(user);
|
|
302
|
+
expect(negotiated.Provider).toBe('GCPPubSub');
|
|
303
|
+
expect(negotiated.Url).toContain('wss://cache-gw.run.app/ws?token=');
|
|
304
|
+
expect(negotiated.Groups).toEqual(['projects/mj-cloud-prod/topics/mj-invalidation-role-role-analyst']);
|
|
305
|
+
|
|
306
|
+
const published: GCPPubSubTopicMessage[] = [];
|
|
307
|
+
gcpProvider.SetTopicPublisher(async (msg) => {
|
|
308
|
+
published.push(msg);
|
|
309
|
+
});
|
|
310
|
+
const fakeEntity = {
|
|
311
|
+
Name: 'Invoices',
|
|
312
|
+
GetUserPermisions: () => ({ CanRead: true }),
|
|
313
|
+
Permissions: [{ RoleID: 'role-analyst', CanRead: true, Type: 'Allow', ReadRLSFilterID: null }],
|
|
314
|
+
} as unknown as EntityInfo;
|
|
315
|
+
vi.spyOn(gcpProvider, 'ResolveEntityForPayload').mockReturnValue(fakeEntity);
|
|
316
|
+
|
|
317
|
+
gcpProvider.RecordInvalidation(makePayload());
|
|
318
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
319
|
+
expect(published).toHaveLength(1);
|
|
320
|
+
expect(published[0].TopicName).toBe('projects/mj-cloud-prod/topics/mj-invalidation-role-role-analyst');
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
describe('PushStatusResolver O(1) session routing and WeakMap JSON memoization', () => {
|
|
325
|
+
it('routes session-specific updates to PUSH_STATUS_UPDATES:session:<id> and wildcard updates to PUSH_STATUS_UPDATES', async () => {
|
|
326
|
+
const rawPublish = vi.fn(async () => undefined);
|
|
327
|
+
const rawPubSub: PubSubEngine = {
|
|
328
|
+
publish: rawPublish,
|
|
329
|
+
subscribe: vi.fn(),
|
|
330
|
+
unsubscribe: vi.fn(),
|
|
331
|
+
asyncIterator: vi.fn(),
|
|
332
|
+
} as unknown as PubSubEngine;
|
|
333
|
+
|
|
334
|
+
PubSubManager.Instance.SetPubSubEngine(rawPubSub);
|
|
335
|
+
const routedPubSub = PubSubManager.Instance.PubSubEngine!;
|
|
336
|
+
|
|
337
|
+
PublishStatusUpdate(routedPubSub, {
|
|
338
|
+
sessionId: 'tab-abc',
|
|
339
|
+
ownerUserId: 'user-1',
|
|
340
|
+
message: '{"type":"Progress"}',
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
PublishStatusUpdate(routedPubSub, {
|
|
344
|
+
sessionId: '*',
|
|
345
|
+
ownerUserId: 'user-1',
|
|
346
|
+
message: '{"type":"Broadcast"}',
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
expect(rawPublish).toHaveBeenCalledTimes(2);
|
|
350
|
+
expect(rawPublish).toHaveBeenNthCalledWith(
|
|
351
|
+
1,
|
|
352
|
+
GetSessionStatusTopic('tab-abc'),
|
|
353
|
+
expect.objectContaining({ sessionId: 'tab-abc' })
|
|
354
|
+
);
|
|
355
|
+
expect(rawPublish).toHaveBeenNthCalledWith(
|
|
356
|
+
2,
|
|
357
|
+
PUSH_STATUS_UPDATES_TOPIC,
|
|
358
|
+
expect.objectContaining({ sessionId: '*' })
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('memoizes JSON.parse per PushStatusNotificationPayload object via WeakMap', () => {
|
|
363
|
+
const payload: PushStatusNotificationPayload = {
|
|
364
|
+
sessionId: 'tab-1',
|
|
365
|
+
ownerUserId: 'u-1',
|
|
366
|
+
message: JSON.stringify({ type: 'StreamingContent', step: 1 }),
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const parseSpy = vi.spyOn(JSON, 'parse');
|
|
370
|
+
try {
|
|
371
|
+
const first = ParseStatusUpdateMessage(payload);
|
|
372
|
+
const second = ParseStatusUpdateMessage(payload);
|
|
373
|
+
const shouldReplicate = ShouldReplicateStatusUpdate(payload.message, payload);
|
|
374
|
+
|
|
375
|
+
expect(first).toBe(second);
|
|
376
|
+
expect(shouldReplicate).toBe(false);
|
|
377
|
+
expect(parseSpy).toHaveBeenCalledTimes(1);
|
|
378
|
+
} finally {
|
|
379
|
+
parseSpy.mockRestore();
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
describe('ServerRole configuration units (Pillar 4)', () => {
|
|
385
|
+
it('resolves all, api, and worker roles case-insensitively and defaults to all', () => {
|
|
386
|
+
expect(ResolveServerRole(undefined)).toBe('all');
|
|
387
|
+
expect(ResolveServerRole('')).toBe('all');
|
|
388
|
+
expect(ResolveServerRole(' API ')).toBe('api');
|
|
389
|
+
expect(ResolveServerRole('WORKER')).toBe('worker');
|
|
390
|
+
expect(ResolveServerRole('all')).toBe('all');
|
|
391
|
+
expect(() => ResolveServerRole('invalid-role')).toThrow(/Invalid MJ_SERVER_ROLE/);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('correctly gates background workers and interactive traffic by role', () => {
|
|
395
|
+
expect(ShouldRunBackgroundWorkers('all')).toBe(true);
|
|
396
|
+
expect(ShouldRunBackgroundWorkers('worker')).toBe(true);
|
|
397
|
+
expect(ShouldRunBackgroundWorkers('api')).toBe(false);
|
|
398
|
+
|
|
399
|
+
expect(ShouldServeInteractiveTraffic('all')).toBe(true);
|
|
400
|
+
expect(ShouldServeInteractiveTraffic('api')).toBe(true);
|
|
401
|
+
expect(ShouldServeInteractiveTraffic('worker')).toBe(false);
|
|
402
|
+
});
|
|
403
|
+
});
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
ReadIsRowFiltered,
|
|
9
9
|
ShapeCacheInvalidationEvent,
|
|
10
10
|
} from '../generic/CacheInvalidationResolver.js';
|
|
11
|
+
import { EntityInfo } from '@memberjunction/core';
|
|
12
|
+
import { EmployeeEntity } from './fieldSecurityFixtures.js';
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* The cache-invalidation subscription is delivered to every connected client with no per-user
|
|
@@ -62,6 +64,49 @@ describe('MayBroadcastRecordData', () => {
|
|
|
62
64
|
});
|
|
63
65
|
});
|
|
64
66
|
|
|
67
|
+
/**
|
|
68
|
+
* An entity under field-level security never broadcasts its row, whatever the allowlist says.
|
|
69
|
+
*
|
|
70
|
+
* The event reaches every signed-in session that may read the entity, with no per-user
|
|
71
|
+
* projection, so a row of an entity whose fields are restricted per role would hand every
|
|
72
|
+
* session the columns it may not read. The check is the IS-A-aware one: a child whose parent has
|
|
73
|
+
* field security carries the parent's restricted fields as its own.
|
|
74
|
+
*/
|
|
75
|
+
describe('MayBroadcastRecordData: entities under field-level security', () => {
|
|
76
|
+
afterEach(() => ConfigureRecordDataBroadcast([]));
|
|
77
|
+
|
|
78
|
+
it("REGRESSION: refuses an FLS entity even under an explicit '*'", () => {
|
|
79
|
+
ConfigureRecordDataBroadcast(['*']);
|
|
80
|
+
expect(MayBroadcastRecordData('Employees', EmployeeEntity(true))).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('REGRESSION: refuses an FLS entity that is named in the allowlist', () => {
|
|
84
|
+
ConfigureRecordDataBroadcast(['Employees']);
|
|
85
|
+
expect(MayBroadcastRecordData('Employees', EmployeeEntity(true))).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('still allows the same entity with field security off', () => {
|
|
89
|
+
ConfigureRecordDataBroadcast(['Employees']);
|
|
90
|
+
expect(MayBroadcastRecordData('Employees', EmployeeEntity(false))).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("REGRESSION: refuses MJ: Record Changes under '*', with or without its metadata", () => {
|
|
94
|
+
// A Record Change row carries other entities' values in ChangesJSON / FullRecordJSON, and its
|
|
95
|
+
// own FieldLevelSecurityApplies is false; the per-row projection never runs on this push.
|
|
96
|
+
ConfigureRecordDataBroadcast(['*']);
|
|
97
|
+
const recordChanges = new EntityInfo({ ID: 'rc', Name: 'MJ: Record Changes', SchemaName: '__mj', BaseTable: 'RecordChange', BaseView: 'vwRecordChanges', Fields: [] });
|
|
98
|
+
expect(recordChanges.FieldLevelSecurityApplies).toBe(false);
|
|
99
|
+
expect(MayBroadcastRecordData('MJ: Record Changes', recordChanges)).toBe(false);
|
|
100
|
+
expect(MayBroadcastRecordData('MJ: Record Changes')).toBe(false);
|
|
101
|
+
expect(MayBroadcastRecordData(' mj: record changes ')).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('REGRESSION: refuses MJ: Record Changes even when named in the allowlist', () => {
|
|
105
|
+
ConfigureRecordDataBroadcast(['MJ: Record Changes']);
|
|
106
|
+
expect(MayBroadcastRecordData('MJ: Record Changes')).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
65
110
|
/**
|
|
66
111
|
* Importing this module must not drag server config loading in with it.
|
|
67
112
|
*
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared field-security fixtures for the transport tests: an `Employees` entity whose `Salary`
|
|
3
|
+
* and `Base Salary` only the HR role may read, plus users holding either role.
|
|
4
|
+
*
|
|
5
|
+
* Built from real `EntityInfo` / `UserInfo` objects so the denied sets come from the same
|
|
6
|
+
* aggregation production uses, not from a stub.
|
|
7
|
+
*/
|
|
8
|
+
import { EntityInfo, FieldSecurityClientInputMessage, UserInfo, UserRoleInfo } from '@memberjunction/core';
|
|
9
|
+
|
|
10
|
+
export const HR_ROLE_ID = 'A0000000-0000-0000-0000-000000000001';
|
|
11
|
+
export const INTERN_ROLE_ID = 'A0000000-0000-0000-0000-000000000003';
|
|
12
|
+
export const EMPLOYEES_ENTITY_ID = 'entity-employees';
|
|
13
|
+
export const EMPLOYEES_ENTITY_NAME = 'Employees';
|
|
14
|
+
|
|
15
|
+
/** The salary an HR user may read and an intern may not. Distinctive so a leak is easy to spot. */
|
|
16
|
+
export const SECRET_SALARY = 987654.32;
|
|
17
|
+
|
|
18
|
+
function openTo(fieldId: string, roles: string[] = [HR_ROLE_ID, INTERN_ROLE_ID]): Record<string, unknown>[] {
|
|
19
|
+
return roles.map((roleId, i) => ({
|
|
20
|
+
ID: `${fieldId}-open-${i}`,
|
|
21
|
+
EntityFieldID: fieldId,
|
|
22
|
+
RoleID: roleId,
|
|
23
|
+
ReadAccess: 'Allow',
|
|
24
|
+
UpdateAccess: 'Allow',
|
|
25
|
+
CreateAccess: 'Allow',
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Init data for the `Employees` entity. `withFls: false` switches field security off entirely. */
|
|
30
|
+
export function EmployeeEntityInit(withFls = true): Record<string, unknown> {
|
|
31
|
+
const field = (id: string, sequence: number, name: string, type: string, extra: Record<string, unknown> = {}) => ({
|
|
32
|
+
ID: id, EntityID: EMPLOYEES_ENTITY_ID, Sequence: sequence, Name: name, Entity: EMPLOYEES_ENTITY_NAME, Type: type, ...extra,
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
ID: EMPLOYEES_ENTITY_ID,
|
|
36
|
+
Name: EMPLOYEES_ENTITY_NAME,
|
|
37
|
+
SchemaName: 'dbo',
|
|
38
|
+
BaseTable: 'Employee',
|
|
39
|
+
BaseView: 'vwEmployees',
|
|
40
|
+
IncludeInAPI: true,
|
|
41
|
+
AllowCreateAPI: true,
|
|
42
|
+
AllowUpdateAPI: true,
|
|
43
|
+
AllowDeleteAPI: true,
|
|
44
|
+
EnableFieldLevelSecurity: withFls,
|
|
45
|
+
Permissions: [
|
|
46
|
+
{ EntityID: EMPLOYEES_ENTITY_ID, RoleID: HR_ROLE_ID, CanCreate: true, CanRead: true, CanUpdate: true, CanDelete: true },
|
|
47
|
+
{ EntityID: EMPLOYEES_ENTITY_ID, RoleID: INTERN_ROLE_ID, CanCreate: true, CanRead: true, CanUpdate: true, CanDelete: true },
|
|
48
|
+
],
|
|
49
|
+
Fields: [
|
|
50
|
+
field('f-id', 1, 'ID', 'uniqueidentifier', { IsPrimaryKey: true }),
|
|
51
|
+
field('f-name', 2, 'Name', 'nvarchar', { EntityFieldPermissions: openTo('f-name') }),
|
|
52
|
+
field('f-salary', 3, 'Salary', 'money', { EntityFieldPermissions: openTo('f-salary', [HR_ROLE_ID]) }),
|
|
53
|
+
field('f-base-salary', 4, 'Base Salary', 'money', { EntityFieldPermissions: openTo('f-base-salary', [HR_ROLE_ID]) }),
|
|
54
|
+
field('f-notes', 5, 'Notes', 'nvarchar', { EntityFieldPermissions: openTo('f-notes') }),
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function EmployeeEntity(withFls = true): EntityInfo {
|
|
60
|
+
return new EntityInfo(EmployeeEntityInit(withFls));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function BuildUser(roleIds: string[], id = 'user-1'): UserInfo {
|
|
64
|
+
const u = new UserInfo();
|
|
65
|
+
u.ID = id;
|
|
66
|
+
u.Name = 'Test User';
|
|
67
|
+
u.Email = `${id}@test.com`;
|
|
68
|
+
u.IsActive = true;
|
|
69
|
+
(u as unknown as Record<string, unknown>)['_UserRoles'] =
|
|
70
|
+
roleIds.map(rid => new UserRoleInfo({ UserID: id, RoleID: rid, Role: `Role-${rid}` }));
|
|
71
|
+
return u;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const Intern = (): UserInfo => BuildUser([INTERN_ROLE_ID], 'intern-1');
|
|
75
|
+
export const HRUser = (): UserInfo => BuildUser([HR_ROLE_ID], 'hr-1');
|
|
76
|
+
|
|
77
|
+
/** A full-width employee row: what a system-identity load, or a save's re-read, returns. */
|
|
78
|
+
export function FullWidthEmployeeRow(): Record<string, unknown> {
|
|
79
|
+
return { ID: 'emp-1', Name: 'Ada', Salary: SECRET_SALARY, 'Base Salary': SECRET_SALARY, Notes: 'n' };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The refusal text for a key, identical whether the key is denied or names no field. */
|
|
83
|
+
export function RefusalFor(key: string): string {
|
|
84
|
+
return FieldSecurityClientInputMessage(key, EMPLOYEES_ENTITY_NAME);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** A metadata source the projection accepts; Record Changes resolution is never reached for Employees. */
|
|
88
|
+
export const NoRecordChangesMetadata = {
|
|
89
|
+
Entities: [] as EntityInfo[],
|
|
90
|
+
EntityByID: (): EntityInfo | undefined => undefined,
|
|
91
|
+
};
|
|
@@ -58,7 +58,7 @@ async function startApp(extensions: ServerExtensionConfig[]): Promise<string> {
|
|
|
58
58
|
app.use(CreateGlobalCorsMiddleware({ allowedOrigins: ['*'] }, GetCorsOwningServerExtensionRoots(extensions)));
|
|
59
59
|
mountBallotExtension(app);
|
|
60
60
|
app.get('/core', (_req, res) => { res.json({ ok: true }); });
|
|
61
|
-
server = app.listen(0);
|
|
61
|
+
server = app.listen(0, '127.0.0.1');
|
|
62
62
|
await new Promise<void>((resolve) => server!.once('listening', () => resolve()));
|
|
63
63
|
return `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
|
|
64
64
|
}
|
|
@@ -138,7 +138,7 @@ describe('CreateGlobalCorsMiddleware', () => {
|
|
|
138
138
|
const app = express();
|
|
139
139
|
app.use(CreateGlobalCorsMiddleware({ allowedOrigins: ['*'], allowCredentials: true }));
|
|
140
140
|
app.get('/core', (_req, res) => { res.json({ ok: true }); });
|
|
141
|
-
server = app.listen(0);
|
|
141
|
+
server = app.listen(0, '127.0.0.1');
|
|
142
142
|
await new Promise<void>((resolve) => server!.once('listening', () => resolve()));
|
|
143
143
|
const base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
|
|
144
144
|
const res = await fetch(`${base}/core`, { headers: { Origin: OTHER_ORIGIN } });
|
|
@@ -164,7 +164,7 @@ async function startAppWithGraphQLMount(withFallback: boolean): Promise<string>
|
|
|
164
164
|
app.use(CreateExtensionOwnedPreflightFallback(ownedRoots));
|
|
165
165
|
}
|
|
166
166
|
app.use('/', cors(), (_req, res) => { res.json({ graphql: true }); });
|
|
167
|
-
server = app.listen(0);
|
|
167
|
+
server = app.listen(0, '127.0.0.1');
|
|
168
168
|
await new Promise<void>((resolve) => server!.once('listening', () => resolve()));
|
|
169
169
|
return `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
|
|
170
170
|
}
|