@memberjunction/server 6.1.4 → 6.2.0-edge.1
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/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.d.ts +2 -0
- package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.js +5 -1
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionManager.js +2 -2
- package/dist/agentSessions/remoteBrowserGoalEngine.d.ts +8 -0
- package/dist/agentSessions/remoteBrowserGoalEngine.d.ts.map +1 -1
- package/dist/agentSessions/remoteBrowserGoalEngine.js +21 -5
- package/dist/agentSessions/remoteBrowserGoalEngine.js.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.d.ts +2 -0
- package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.js +5 -1
- package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
- package/dist/auth/exampleNewUserSubClass.js +2 -1
- package/dist/auth/exampleNewUserSubClass.js.map +1 -1
- package/dist/auth/index.d.ts +20 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +20 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/initializeProviders.d.ts +8 -0
- package/dist/auth/initializeProviders.d.ts.map +1 -1
- package/dist/auth/initializeProviders.js +21 -5
- package/dist/auth/initializeProviders.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts +9 -0
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +22 -10
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts +17 -15
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +65 -51
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/magicLink/index.d.ts +2 -2
- package/dist/auth/magicLink/index.d.ts.map +1 -1
- package/dist/auth/magicLink/index.js +2 -2
- package/dist/auth/magicLink/index.js.map +1 -1
- package/dist/auth/magicLink/magicLinkCore.d.ts +54 -33
- package/dist/auth/magicLink/magicLinkCore.d.ts.map +1 -1
- package/dist/auth/magicLink/magicLinkCore.js +61 -61
- package/dist/auth/magicLink/magicLinkCore.js.map +1 -1
- package/dist/auth/magicLink/redeemLanding.d.ts +4 -0
- package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -1
- package/dist/auth/magicLink/redeemLanding.js +12 -4
- package/dist/auth/magicLink/redeemLanding.js.map +1 -1
- package/dist/auth/magicLink/types.d.ts +4 -4
- package/dist/auth/principals.d.ts +2 -0
- package/dist/auth/principals.d.ts.map +1 -1
- package/dist/auth/principals.js +6 -2
- package/dist/auth/principals.js.map +1 -1
- package/dist/cache.d.ts +2 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +3 -1
- package/dist/cache.js.map +1 -1
- package/dist/config.d.ts +293 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +11 -4
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +20 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +27 -19
- package/dist/context.js.map +1 -1
- package/dist/directives/Public.d.ts +2 -0
- package/dist/directives/Public.d.ts.map +1 -1
- package/dist/directives/Public.js +3 -1
- package/dist/directives/Public.js.map +1 -1
- package/dist/directives/RequireSystemUser.d.ts +2 -0
- package/dist/directives/RequireSystemUser.d.ts.map +1 -1
- package/dist/directives/RequireSystemUser.js +3 -1
- package/dist/directives/RequireSystemUser.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +266 -4
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +1304 -0
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +8 -2
- package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
- package/dist/generic/CacheInvalidationResolver.js +7 -3
- package/dist/generic/CacheInvalidationResolver.js.map +1 -1
- package/dist/generic/FireAndForgetHeartbeat.d.ts +3 -1
- package/dist/generic/FireAndForgetHeartbeat.d.ts.map +1 -1
- package/dist/generic/FireAndForgetHeartbeat.js +8 -4
- package/dist/generic/FireAndForgetHeartbeat.js.map +1 -1
- package/dist/generic/PushStatusResolver.d.ts +10 -1
- package/dist/generic/PushStatusResolver.d.ts.map +1 -1
- package/dist/generic/PushStatusResolver.js +11 -3
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +27 -1
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +64 -6
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -6
- package/dist/index.js.map +1 -1
- package/dist/integration/CustomColumnPromoter.d.ts +2 -0
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +5 -1
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/integration/EntityMapLifecycle.d.ts +4 -0
- package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
- package/dist/integration/EntityMapLifecycle.js +10 -2
- package/dist/integration/EntityMapLifecycle.js.map +1 -1
- package/dist/logging/NoLog.d.ts +4 -0
- package/dist/logging/NoLog.d.ts.map +1 -1
- package/dist/logging/NoLog.js +10 -2
- package/dist/logging/NoLog.js.map +1 -1
- package/dist/logging/StartupLogger.d.ts +2 -0
- package/dist/logging/StartupLogger.d.ts.map +1 -1
- package/dist/logging/StartupLogger.js +5 -1
- package/dist/logging/StartupLogger.js.map +1 -1
- package/dist/logging/bootAudit.d.ts +11 -7
- package/dist/logging/bootAudit.d.ts.map +1 -1
- package/dist/logging/bootAudit.js +15 -7
- package/dist/logging/bootAudit.js.map +1 -1
- package/dist/logging/boundaryLogPayload.d.ts +4 -0
- package/dist/logging/boundaryLogPayload.d.ts.map +1 -1
- package/dist/logging/boundaryLogPayload.js +5 -1
- package/dist/logging/boundaryLogPayload.js.map +1 -1
- package/dist/logging/secretRedactor.d.ts +2 -0
- package/dist/logging/secretRedactor.d.ts.map +1 -1
- package/dist/logging/secretRedactor.js +8 -4
- package/dist/logging/secretRedactor.js.map +1 -1
- package/dist/logging/shortenForLog.d.ts +2 -0
- package/dist/logging/shortenForLog.d.ts.map +1 -1
- package/dist/logging/shortenForLog.js +7 -3
- package/dist/logging/shortenForLog.js.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.d.ts +2 -0
- package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.js +8 -6
- package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
- package/dist/metadataRefreshSignal.d.ts +19 -0
- package/dist/metadataRefreshSignal.d.ts.map +1 -0
- package/dist/metadataRefreshSignal.js +45 -0
- package/dist/metadataRefreshSignal.js.map +1 -0
- package/dist/middleware/MJTenantFilterMiddleware.js +4 -4
- package/dist/multiTenancy/index.d.ts +8 -0
- package/dist/multiTenancy/index.d.ts.map +1 -1
- package/dist/multiTenancy/index.js +21 -5
- package/dist/multiTenancy/index.js.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.d.ts +5 -0
- package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.js +14 -10
- package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts +6 -6
- package/dist/realtimeWidget/WidgetSessionService.js +27 -27
- package/dist/realtimeWidget/host-identity.d.ts +5 -1
- package/dist/realtimeWidget/host-identity.d.ts.map +1 -1
- package/dist/realtimeWidget/host-identity.js +11 -3
- package/dist/realtimeWidget/host-identity.js.map +1 -1
- package/dist/realtimeWidget/index.d.ts +2 -2
- package/dist/realtimeWidget/index.d.ts.map +1 -1
- package/dist/realtimeWidget/index.js +2 -2
- package/dist/realtimeWidget/index.js.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.d.ts +21 -1
- package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.js +16 -4
- package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
- package/dist/realtimeWidget/widgetCore.d.ts +48 -0
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetCore.js +38 -10
- package/dist/realtimeWidget/widgetCore.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +10 -6
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +14 -6
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +3 -3
- package/dist/resolvers/FetchEntityVectorsResolver.js +2 -2
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.js +3 -3
- package/dist/resolvers/GeoResolver.d.ts +2 -2
- package/dist/resolvers/GeoResolver.js +4 -4
- package/dist/resolvers/GetDataResolver.d.ts +14 -0
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +43 -15
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +2 -2
- package/dist/resolvers/ListOperationsResolver.d.ts +20 -0
- package/dist/resolvers/ListOperationsResolver.d.ts.map +1 -1
- package/dist/resolvers/ListOperationsResolver.js +68 -28
- package/dist/resolvers/ListOperationsResolver.js.map +1 -1
- package/dist/resolvers/MCPResolver.d.ts +11 -1
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +7 -3
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +2 -1
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +3 -3
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +37 -0
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +101 -11
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +5 -5
- package/dist/resolvers/RunAIAgentResolver.d.ts +10 -4
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +74 -42
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.d.ts +1 -1
- package/dist/resolvers/RunAIPromptResolver.js +2 -2
- package/dist/resolvers/RunTestResolver.js +5 -5
- package/dist/resolvers/SearchEntitiesResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchEntitiesResolver.js +3 -1
- package/dist/resolvers/SearchEntitiesResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.d.ts +1 -0
- package/dist/resolvers/SearchKnowledgeResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.js +5 -0
- package/dist/resolvers/SearchKnowledgeResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.js +1 -0
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts +8 -4
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +5 -5
- package/dist/resolvers/TaskGraphFrameResolver.d.ts +8 -1
- package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.js +6 -2
- package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +53 -4
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/adhoc-query-helpers.d.ts +6 -0
- package/dist/resolvers/adhoc-query-helpers.d.ts.map +1 -1
- package/dist/resolvers/adhoc-query-helpers.js +10 -2
- package/dist/resolvers/adhoc-query-helpers.js.map +1 -1
- package/dist/resolvers/artifact-routing.d.ts +8 -8
- package/dist/resolvers/artifact-routing.js +2 -2
- package/dist/resolvers/artifact-routing.js.map +1 -1
- package/dist/resolvers/meetingRecordingRegistration.d.ts +10 -2
- package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
- package/dist/resolvers/meetingRecordingRegistration.js +24 -8
- package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
- package/dist/resolvers/peaksSidecar.d.ts +4 -0
- package/dist/resolvers/peaksSidecar.d.ts.map +1 -1
- package/dist/resolvers/peaksSidecar.js +10 -2
- package/dist/resolvers/peaksSidecar.js.map +1 -1
- package/dist/rest/EntityCRUDHandler.d.ts +28 -0
- package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
- package/dist/rest/EntityCRUDHandler.js +20 -4
- package/dist/rest/EntityCRUDHandler.js.map +1 -1
- package/dist/rest/MediaStreamHandler.d.ts +2 -0
- package/dist/rest/MediaStreamHandler.d.ts.map +1 -1
- package/dist/rest/MediaStreamHandler.js +10 -6
- package/dist/rest/MediaStreamHandler.js.map +1 -1
- package/dist/rest/OAuthCallbackHandler.d.ts +9 -0
- package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
- package/dist/rest/OAuthCallbackHandler.js +15 -3
- package/dist/rest/OAuthCallbackHandler.js.map +1 -1
- package/dist/rest/RESTEndpointHandler.d.ts +6 -4
- package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +15 -11
- package/dist/rest/RESTEndpointHandler.js.map +1 -1
- package/dist/rest/RealtimeSdpBrokerHandler.d.ts +5 -1
- package/dist/rest/RealtimeSdpBrokerHandler.d.ts.map +1 -1
- package/dist/rest/RealtimeSdpBrokerHandler.js +12 -4
- package/dist/rest/RealtimeSdpBrokerHandler.js.map +1 -1
- package/dist/rest/SignatureWebhookHandler.d.ts +2 -0
- package/dist/rest/SignatureWebhookHandler.d.ts.map +1 -1
- package/dist/rest/SignatureWebhookHandler.js +5 -1
- package/dist/rest/SignatureWebhookHandler.js.map +1 -1
- package/dist/rest/UploadTokenManager.d.ts +24 -8
- package/dist/rest/UploadTokenManager.d.ts.map +1 -1
- package/dist/rest/UploadTokenManager.js +53 -21
- package/dist/rest/UploadTokenManager.js.map +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts +20 -0
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +20 -4
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/rest/mediaRange.d.ts +4 -0
- package/dist/rest/mediaRange.d.ts.map +1 -1
- package/dist/rest/mediaRange.js +10 -2
- package/dist/rest/mediaRange.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts +4 -0
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +13 -7
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/dist/serverExtensionReservedRoots.d.ts +2 -0
- package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
- package/dist/serverExtensionReservedRoots.js +5 -1
- package/dist/serverExtensionReservedRoots.js.map +1 -1
- package/dist/services/MJStorageBlobStore.d.ts +41 -0
- package/dist/services/MJStorageBlobStore.d.ts.map +1 -0
- package/dist/services/MJStorageBlobStore.js +160 -0
- package/dist/services/MJStorageBlobStore.js.map +1 -0
- package/dist/services/TaskGraphActionRunner.d.ts +13 -8
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +16 -15
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -3
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts +12 -3
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +19 -8
- package/dist/util.js.map +1 -1
- package/package.json +92 -92
- package/src/__tests__/FileResolverPeaks.test.ts +13 -13
- package/src/__tests__/ListOperationsResolver.test.ts +17 -17
- package/src/__tests__/MediaStreamHandler.test.ts +28 -28
- package/src/__tests__/NoLog.test.ts +11 -11
- package/src/__tests__/PrimaryKeyAlwaysMapped.test.ts +10 -10
- package/src/__tests__/RESTEndpointHandler.test.ts +14 -14
- package/src/__tests__/RealtimeBridgeResolver.test.ts +4 -2
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +229 -0
- package/src/__tests__/RealtimeSdpBrokerHandler.test.ts +6 -6
- package/src/__tests__/RefreshAdoptsNewColumns.test.ts +8 -8
- package/src/__tests__/RemoteBrowserGoalEngine.test.ts +18 -18
- package/src/__tests__/ResolverBase.isaPromotionCreate.test.ts +412 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +228 -0
- package/src/__tests__/SearchEntitiesResolver.readOnlyFallback.test.ts +78 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +129 -0
- package/src/__tests__/TaskGraphFrameResolver.test.ts +2 -2
- package/src/__tests__/TransactionGroupResolver.refusals.test.ts +290 -0
- package/src/__tests__/WidgetSessionService.test.ts +8 -4
- package/src/__tests__/adhoc-query-helpers.test.ts +21 -21
- package/src/__tests__/bootAudit.test.ts +11 -11
- package/src/__tests__/boundaryLogPayload.test.ts +6 -6
- package/src/__tests__/databaseAbstraction.test.ts +12 -6
- package/src/__tests__/getDataTokenRedaction.test.ts +7 -7
- package/src/__tests__/initializeProviders.refresh.test.ts +4 -4
- package/src/__tests__/magicLink.test.ts +73 -134
- package/src/__tests__/magicLinkService.consume.test.ts +224 -0
- package/src/__tests__/meetingRecordingRegistration.test.ts +12 -12
- package/src/__tests__/metadataRefreshSignal.test.ts +78 -0
- package/src/__tests__/multiTenancy.security.test.ts +40 -40
- package/src/__tests__/multiTenancy.test.ts +18 -18
- package/src/__tests__/newUsers.test.ts +24 -23
- package/src/__tests__/principals.test.ts +22 -22
- package/src/__tests__/pushStatusResolver.filter.test.ts +8 -8
- package/src/__tests__/secretRedactor.failOpen.test.ts +12 -12
- package/src/__tests__/secretRedactor.test.ts +12 -12
- package/src/__tests__/serverExtensionReservedRoots.test.ts +7 -7
- package/src/__tests__/subscriptionRedaction.test.ts +5 -5
- package/src/__tests__/unifiedAuth.test.ts +15 -9
- package/src/__tests__/visitorIdentity.resolve.test.ts +6 -6
- package/src/__tests__/widget.test.ts +45 -45
- package/src/__tests__/widgetGuestElevation.test.ts +7 -7
- package/src/__tests__/widgetHostIdentity.test.ts +14 -14
- package/src/agentSessions/ReturningVisitorRecap.ts +12 -1
- package/src/agentSessions/SessionManager.ts +2 -2
- package/src/agentSessions/remoteBrowserGoalEngine.ts +30 -5
- package/src/auth/AuthProviderCatalogRouter.ts +6 -1
- package/src/auth/__tests__/backward-compatibility.test.ts +3 -3
- package/src/auth/exampleNewUserSubClass.ts +2 -2
- package/src/auth/index.ts +43 -28
- package/src/auth/initializeProviders.ts +25 -5
- package/src/auth/magicLink/MagicLinkRouter.ts +28 -10
- package/src/auth/magicLink/MagicLinkService.ts +71 -52
- package/src/auth/magicLink/index.ts +7 -7
- package/src/auth/magicLink/magicLinkCore.ts +96 -64
- package/src/auth/magicLink/redeemLanding.ts +14 -4
- package/src/auth/magicLink/types.ts +54 -54
- package/src/auth/newUsers.ts +1 -1
- package/src/auth/principals.ts +11 -2
- package/src/cache.ts +4 -1
- package/src/config.ts +14 -4
- package/src/context.ts +37 -24
- package/src/directives/Public.ts +4 -1
- package/src/directives/RequireSystemUser.ts +4 -1
- package/src/generated/graphql-schemas/__mj.ts +933 -5
- package/src/generic/CacheInvalidationResolver.ts +12 -4
- package/src/generic/FireAndForgetHeartbeat.ts +19 -14
- package/src/generic/PushStatusResolver.ts +21 -7
- package/src/generic/ResolverBase.ts +66 -5
- package/src/generic/RunViewResolver.ts +22 -22
- package/src/generic/__tests__/FireAndForgetHeartbeat.test.ts +7 -7
- package/src/index.ts +34 -7
- package/src/integration/CustomColumnPromoter.ts +6 -1
- package/src/integration/EntityMapLifecycle.ts +20 -2
- package/src/logging/NoLog.ts +16 -2
- package/src/logging/StartupLogger.ts +6 -1
- package/src/logging/bootAudit.ts +26 -16
- package/src/logging/boundaryLogPayload.ts +8 -1
- package/src/logging/secretRedactor.ts +14 -9
- package/src/logging/shortenForLog.ts +8 -3
- package/src/logging/variablesLoggingMiddleware.ts +9 -6
- package/src/metadataRefreshSignal.ts +45 -0
- package/src/middleware/MJTenantFilterMiddleware.ts +4 -4
- package/src/multiTenancy/index.ts +29 -5
- package/src/realtimeWidget/WidgetRouter.ts +15 -10
- package/src/realtimeWidget/WidgetSessionService.ts +76 -76
- package/src/realtimeWidget/host-identity.ts +24 -10
- package/src/realtimeWidget/index.ts +3 -3
- package/src/realtimeWidget/visitorIdentity.ts +39 -8
- package/src/realtimeWidget/widgetCore.ts +75 -10
- package/src/realtimeWidget/widgetGuestElevation.ts +25 -12
- package/src/resolvers/AdhocQueryResolver.ts +3 -3
- package/src/resolvers/AgentSessionResolver.ts +3 -3
- package/src/resolvers/ColorResolver.ts +6 -6
- package/src/resolvers/ComponentRegistryResolver.ts +36 -36
- package/src/resolvers/EntityCommunicationsResolver.ts +4 -4
- package/src/resolvers/ExecuteRemoteOperationResolver.ts +9 -9
- package/src/resolvers/FetchEntityVectorsResolver.ts +2 -2
- package/src/resolvers/FileResolver.ts +42 -42
- package/src/resolvers/GeoResolver.ts +4 -4
- package/src/resolvers/GetDataResolver.ts +50 -15
- package/src/resolvers/IntegrationDiscoveryResolver.ts +2 -2
- package/src/resolvers/ListOperationsResolver.ts +78 -28
- package/src/resolvers/MCPResolver.ts +30 -15
- package/src/resolvers/QueryResolver.ts +13 -13
- package/src/resolvers/QuerySystemUserResolver.ts +2 -1
- package/src/resolvers/RealtimeBridgeResolver.ts +3 -3
- package/src/resolvers/RealtimeClientSessionResolver.ts +126 -13
- package/src/resolvers/RemoteBrowserActionResolver.ts +5 -5
- package/src/resolvers/RunAIAgentResolver.ts +118 -76
- package/src/resolvers/RunAIPromptResolver.ts +22 -22
- package/src/resolvers/RunTemplateResolver.ts +4 -4
- package/src/resolvers/RunTestResolver.ts +25 -25
- package/src/resolvers/SearchEntitiesResolver.ts +3 -1
- package/src/resolvers/SearchKnowledgeResolver.ts +4 -0
- package/src/resolvers/SearchKnowledgeSystemUserResolver.ts +1 -0
- package/src/resolvers/SqlLoggingConfigResolver.ts +56 -56
- package/src/resolvers/TaskGraphFrameResolver.ts +37 -28
- package/src/resolvers/TelemetryResolver.ts +79 -79
- package/src/resolvers/TransactionGroupResolver.ts +57 -4
- package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +40 -38
- package/src/resolvers/__tests__/artifact-routing.test.ts +23 -23
- package/src/resolvers/adhoc-query-helpers.ts +19 -2
- package/src/resolvers/artifact-routing.ts +19 -19
- package/src/resolvers/meetingRecordingRegistration.ts +44 -10
- package/src/resolvers/peaksSidecar.ts +12 -2
- package/src/rest/EntityCRUDHandler.ts +24 -4
- package/src/rest/MediaStreamHandler.ts +11 -6
- package/src/rest/OAuthCallbackHandler.ts +25 -7
- package/src/rest/RESTEndpointHandler.ts +20 -15
- package/src/rest/RealtimeSdpBrokerHandler.ts +14 -4
- package/src/rest/SignatureWebhookHandler.ts +6 -1
- package/src/rest/UploadTokenManager.ts +66 -30
- package/src/rest/ViewOperationsHandler.ts +24 -4
- package/src/rest/mediaRange.ts +16 -6
- package/src/rest/setupRESTEndpoints.ts +24 -12
- package/src/serverExtensionReservedRoots.ts +6 -1
- package/src/services/MJStorageBlobStore.ts +209 -0
- package/src/services/TaskGraphActionRunner.ts +16 -15
- package/src/types.ts +21 -16
- package/src/util.ts +22 -9
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { describe, it, expect, vi } from 'vitest';
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
CreateTenantPreRunViewHook,
|
|
11
|
+
CreateTenantPreSaveHook,
|
|
12
|
+
CreateTenantMiddleware,
|
|
13
13
|
IsValidTenantId,
|
|
14
|
-
|
|
14
|
+
AttachTenantContext,
|
|
15
15
|
} from '../multiTenancy/index.js';
|
|
16
16
|
import type { MultiTenancyConfig } from '../config.js';
|
|
17
17
|
import { Metadata } from '@memberjunction/core';
|
|
@@ -76,7 +76,7 @@ function makeConfig(overrides: Partial<MultiTenancyConfig> = {}): MultiTenancyCo
|
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
type MockUser = Parameters<ReturnType<typeof
|
|
79
|
+
type MockUser = Parameters<ReturnType<typeof CreateTenantPreRunViewHook>>[1];
|
|
80
80
|
|
|
81
81
|
function makeUser(tenantId?: string, roles: string[] = []): MockUser {
|
|
82
82
|
return {
|
|
@@ -91,7 +91,7 @@ function makeUser(tenantId?: string, roles: string[] = []): MockUser {
|
|
|
91
91
|
describe('Multi-Tenancy Security Edge Cases', () => {
|
|
92
92
|
describe('SQL injection in tenant ID', () => {
|
|
93
93
|
it('should escape quotes so an injection attempt stays INSIDE the string literal', () => {
|
|
94
|
-
const hook =
|
|
94
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
95
95
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
96
96
|
const user = makeUser("' OR 1=1 --");
|
|
97
97
|
|
|
@@ -105,7 +105,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
105
105
|
});
|
|
106
106
|
|
|
107
107
|
it('should escape quotes in a UNION attack payload', () => {
|
|
108
|
-
const hook =
|
|
108
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
109
109
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
110
110
|
const user = makeUser("' UNION SELECT * FROM Users --");
|
|
111
111
|
|
|
@@ -115,7 +115,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
115
115
|
});
|
|
116
116
|
|
|
117
117
|
it('should escape quotes in a stacked-statement payload', () => {
|
|
118
|
-
const hook =
|
|
118
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
119
119
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
120
120
|
const user = makeUser("abc'; DROP TABLE Customers; --");
|
|
121
121
|
|
|
@@ -177,19 +177,19 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
177
177
|
|
|
178
178
|
it('throws on an invalid tenant id instead of attaching it', () => {
|
|
179
179
|
const user = makePlainUser();
|
|
180
|
-
expect(() =>
|
|
180
|
+
expect(() => AttachTenantContext(user, "x' OR '1'='1", 'header')).toThrow(/Invalid tenant identifier/);
|
|
181
181
|
expect(user.TenantContext).toBeUndefined();
|
|
182
182
|
});
|
|
183
183
|
|
|
184
184
|
it('throws on empty and oversized tenant ids', () => {
|
|
185
185
|
const user = makePlainUser();
|
|
186
|
-
expect(() =>
|
|
187
|
-
expect(() =>
|
|
186
|
+
expect(() => AttachTenantContext(user, '', 'custom')).toThrow(/Invalid tenant identifier/);
|
|
187
|
+
expect(() => AttachTenantContext(user, 'a'.repeat(129), 'custom')).toThrow(/Invalid tenant identifier/);
|
|
188
188
|
});
|
|
189
189
|
|
|
190
190
|
it('attaches a valid tenant id with the given source', () => {
|
|
191
191
|
const user = makePlainUser();
|
|
192
|
-
|
|
192
|
+
AttachTenantContext(user, 'tenant_1.a-b', 'linkedEntity');
|
|
193
193
|
expect(user.TenantContext).toEqual<TenantContext>({ TenantID: 'tenant_1.a-b', Source: 'linkedEntity' });
|
|
194
194
|
});
|
|
195
195
|
});
|
|
@@ -198,7 +198,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
198
198
|
|
|
199
199
|
describe('tenant column must resolve to a stored field', () => {
|
|
200
200
|
it('produces an escaped-value predicate with a quoted identifier for a valid setup', () => {
|
|
201
|
-
const hook =
|
|
201
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
202
202
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
203
203
|
const user = makeUser("t'1"); // legal only via non-header sources; must still be escaped
|
|
204
204
|
|
|
@@ -207,7 +207,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
207
207
|
});
|
|
208
208
|
|
|
209
209
|
it('throws when the configured tenant column does not exist on the entity', () => {
|
|
210
|
-
const hook =
|
|
210
|
+
const hook = CreateTenantPreRunViewHook(makeConfig({ defaultTenantColumn: 'NoSuchColumn' }));
|
|
211
211
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
212
212
|
const user = makeUser('tenant-1');
|
|
213
213
|
|
|
@@ -215,7 +215,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
215
215
|
});
|
|
216
216
|
|
|
217
217
|
it('throws when the tenant column resolves only to a VIRTUAL field', () => {
|
|
218
|
-
const hook =
|
|
218
|
+
const hook = CreateTenantPreRunViewHook(makeConfig({ defaultTenantColumn: 'OrganizationName' }));
|
|
219
219
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
220
220
|
const user = makeUser('tenant-1');
|
|
221
221
|
|
|
@@ -223,7 +223,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
223
223
|
});
|
|
224
224
|
|
|
225
225
|
it('throws when an entityColumnMappings override points at a nonexistent column', () => {
|
|
226
|
-
const hook =
|
|
226
|
+
const hook = CreateTenantPreRunViewHook(makeConfig({
|
|
227
227
|
entityColumnMappings: { 'Customers': "Bad]Column" },
|
|
228
228
|
}));
|
|
229
229
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
@@ -237,7 +237,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
237
237
|
// @ts-expect-error — simulating the no-provider-available case
|
|
238
238
|
Metadata.Provider = undefined;
|
|
239
239
|
try {
|
|
240
|
-
const hook =
|
|
240
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
241
241
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
242
242
|
const user = makeUser('tenant-1');
|
|
243
243
|
|
|
@@ -255,7 +255,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
255
255
|
|
|
256
256
|
describe('Tenant ID boundary values', () => {
|
|
257
257
|
it('should not filter when tenant ID is empty string (falsy)', () => {
|
|
258
|
-
const hook =
|
|
258
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
259
259
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
260
260
|
// Empty string is falsy → makeUser sets TenantContext = undefined
|
|
261
261
|
// This matches createTenantMiddleware behavior: `if (tenantId)` skips empty strings
|
|
@@ -267,7 +267,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
267
267
|
|
|
268
268
|
it('should handle very long tenant ID', () => {
|
|
269
269
|
const longId = 'a'.repeat(1000);
|
|
270
|
-
const hook =
|
|
270
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
271
271
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
272
272
|
const user = makeUser(longId);
|
|
273
273
|
|
|
@@ -277,7 +277,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
277
277
|
|
|
278
278
|
it('should handle UUID tenant ID (standard format)', () => {
|
|
279
279
|
const uuid = '550e8400-e29b-41d4-a716-446655440000';
|
|
280
|
-
const hook =
|
|
280
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
281
281
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
282
282
|
const user = makeUser(uuid);
|
|
283
283
|
|
|
@@ -286,7 +286,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
286
286
|
});
|
|
287
287
|
|
|
288
288
|
it('should handle tenant ID with special characters', () => {
|
|
289
|
-
const hook =
|
|
289
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
290
290
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
291
291
|
const user = makeUser('tenant<script>alert(1)</script>');
|
|
292
292
|
|
|
@@ -299,7 +299,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
299
299
|
|
|
300
300
|
describe('Admin role matching', () => {
|
|
301
301
|
it('should be case-insensitive for admin role matching', () => {
|
|
302
|
-
const hook =
|
|
302
|
+
const hook = CreateTenantPreRunViewHook(makeConfig({ adminRoles: ['Admin'] }));
|
|
303
303
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
304
304
|
|
|
305
305
|
// User role is 'admin' (lowercase), config has 'Admin'
|
|
@@ -309,7 +309,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
309
309
|
});
|
|
310
310
|
|
|
311
311
|
it('should handle whitespace in role names', () => {
|
|
312
|
-
const hook =
|
|
312
|
+
const hook = CreateTenantPreRunViewHook(makeConfig({ adminRoles: [' Admin '] }));
|
|
313
313
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
314
314
|
|
|
315
315
|
const user = makeUser('tenant-1', ['Admin']);
|
|
@@ -318,7 +318,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
318
318
|
});
|
|
319
319
|
|
|
320
320
|
it('should not bypass for non-admin roles', () => {
|
|
321
|
-
const hook =
|
|
321
|
+
const hook = CreateTenantPreRunViewHook(makeConfig({ adminRoles: ['Admin'] }));
|
|
322
322
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
323
323
|
|
|
324
324
|
const user = makeUser('tenant-1', ['User', 'Editor']);
|
|
@@ -327,7 +327,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
327
327
|
});
|
|
328
328
|
|
|
329
329
|
it('should handle user with empty roles array', () => {
|
|
330
|
-
const hook =
|
|
330
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
331
331
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
332
332
|
|
|
333
333
|
const user = makeUser('tenant-1', []);
|
|
@@ -340,7 +340,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
340
340
|
|
|
341
341
|
describe('Entity name matching', () => {
|
|
342
342
|
it('should be case-insensitive for entity names in allowlist', () => {
|
|
343
|
-
const hook =
|
|
343
|
+
const hook = CreateTenantPreRunViewHook(makeConfig({
|
|
344
344
|
scopingStrategy: 'allowlist',
|
|
345
345
|
scopedEntities: ['customers'],
|
|
346
346
|
}));
|
|
@@ -352,7 +352,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
352
352
|
});
|
|
353
353
|
|
|
354
354
|
it('should handle whitespace in entity names', () => {
|
|
355
|
-
const hook =
|
|
355
|
+
const hook = CreateTenantPreRunViewHook(makeConfig({
|
|
356
356
|
scopingStrategy: 'allowlist',
|
|
357
357
|
scopedEntities: [' Customers '],
|
|
358
358
|
}));
|
|
@@ -364,7 +364,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
364
364
|
});
|
|
365
365
|
|
|
366
366
|
it('should not filter when EntityName is missing from params', () => {
|
|
367
|
-
const hook =
|
|
367
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
368
368
|
const params = { ExtraFilter: '' } as RunViewParams;
|
|
369
369
|
const user = makeUser('tenant-1');
|
|
370
370
|
|
|
@@ -377,7 +377,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
377
377
|
|
|
378
378
|
describe('createTenantMiddleware', () => {
|
|
379
379
|
it('should skip tenant resolution when no userPayload on request', () => {
|
|
380
|
-
const middleware =
|
|
380
|
+
const middleware = CreateTenantMiddleware(makeConfig());
|
|
381
381
|
const req = { headers: { 'x-tenant-id': 'tenant-1' } } as unknown as Parameters<typeof middleware>[0];
|
|
382
382
|
const res = {} as Parameters<typeof middleware>[1];
|
|
383
383
|
const next = vi.fn();
|
|
@@ -388,7 +388,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
388
388
|
});
|
|
389
389
|
|
|
390
390
|
it('should attach TenantContext to a CLONED userRecord when userPayload and header are present', () => {
|
|
391
|
-
const middleware =
|
|
391
|
+
const middleware = CreateTenantMiddleware(makeConfig());
|
|
392
392
|
const userRecord = { ID: 'u1' } as Record<string, unknown>;
|
|
393
393
|
const userPayload = { userRecord, email: 'test@test.com', sessionId: 's1' };
|
|
394
394
|
const req = {
|
|
@@ -414,7 +414,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
414
414
|
});
|
|
415
415
|
|
|
416
416
|
it('should reject an invalid tenant header with 400 and never attach or continue', () => {
|
|
417
|
-
const middleware =
|
|
417
|
+
const middleware = CreateTenantMiddleware(makeConfig());
|
|
418
418
|
const userRecord = { ID: 'u1' } as Record<string, unknown>;
|
|
419
419
|
const userPayload = { userRecord, email: 'test@test.com', sessionId: 's1' };
|
|
420
420
|
const req = {
|
|
@@ -437,7 +437,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
437
437
|
});
|
|
438
438
|
|
|
439
439
|
it('should handle case-insensitive header name', () => {
|
|
440
|
-
const middleware =
|
|
440
|
+
const middleware = CreateTenantMiddleware(makeConfig({ tenantHeader: 'X-Tenant-ID' }));
|
|
441
441
|
const userRecord = { ID: 'u1' } as Record<string, unknown>;
|
|
442
442
|
const userPayload = { userRecord, email: 'test@test.com', sessionId: 's1' };
|
|
443
443
|
const req = {
|
|
@@ -457,7 +457,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
457
457
|
});
|
|
458
458
|
|
|
459
459
|
it('should not set TenantContext when header is missing', () => {
|
|
460
|
-
const middleware =
|
|
460
|
+
const middleware = CreateTenantMiddleware(makeConfig());
|
|
461
461
|
const userRecord = { ID: 'u1' } as Record<string, unknown>;
|
|
462
462
|
const req = {
|
|
463
463
|
headers: {},
|
|
@@ -472,7 +472,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
472
472
|
});
|
|
473
473
|
|
|
474
474
|
it('should reject a repeated header (array value) with 400 rather than guessing which value applies', () => {
|
|
475
|
-
const middleware =
|
|
475
|
+
const middleware = CreateTenantMiddleware(makeConfig());
|
|
476
476
|
const userRecord = { ID: 'u1' } as Record<string, unknown>;
|
|
477
477
|
const userPayload = { userRecord, email: 'test@test.com', sessionId: 's1' };
|
|
478
478
|
const req = {
|
|
@@ -494,7 +494,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
494
494
|
});
|
|
495
495
|
|
|
496
496
|
it('two concurrent requests for the same shared user with different headers do not cross-contaminate', () => {
|
|
497
|
-
const middleware =
|
|
497
|
+
const middleware = CreateTenantMiddleware(makeConfig());
|
|
498
498
|
const sharedUserRecord = { ID: 'u1', UserRoles: [] } as unknown as Record<string, unknown>;
|
|
499
499
|
|
|
500
500
|
const reqA = {
|
|
@@ -527,11 +527,11 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
527
527
|
IsSaved: isSaved,
|
|
528
528
|
Get: vi.fn((col: string) => col === 'OrganizationID' ? tenantValue : null),
|
|
529
529
|
Set: vi.fn(),
|
|
530
|
-
} as unknown as Parameters<ReturnType<typeof
|
|
530
|
+
} as unknown as Parameters<ReturnType<typeof CreateTenantPreSaveHook>>[0];
|
|
531
531
|
}
|
|
532
532
|
|
|
533
533
|
it('should reject save with SQL injection in entity tenant column', () => {
|
|
534
|
-
const hook =
|
|
534
|
+
const hook = CreateTenantPreSaveHook(makeConfig());
|
|
535
535
|
const entity = makeEntity('Customers', "' OR 1=1 --", true);
|
|
536
536
|
const user = makeUser('tenant-abc');
|
|
537
537
|
|
|
@@ -541,13 +541,13 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
541
541
|
});
|
|
542
542
|
|
|
543
543
|
it('should auto-assign tenant for new record even when entity has no EntityInfo', () => {
|
|
544
|
-
const hook =
|
|
544
|
+
const hook = CreateTenantPreSaveHook(makeConfig());
|
|
545
545
|
const entity = {
|
|
546
546
|
EntityInfo: undefined,
|
|
547
547
|
IsSaved: false,
|
|
548
548
|
Get: vi.fn(() => null),
|
|
549
549
|
Set: vi.fn(),
|
|
550
|
-
} as unknown as Parameters<ReturnType<typeof
|
|
550
|
+
} as unknown as Parameters<ReturnType<typeof CreateTenantPreSaveHook>>[0];
|
|
551
551
|
const user = makeUser('tenant-abc');
|
|
552
552
|
|
|
553
553
|
// No EntityInfo → no entity name → not scoped → allow
|
|
@@ -556,7 +556,7 @@ describe('Multi-Tenancy Security Edge Cases', () => {
|
|
|
556
556
|
});
|
|
557
557
|
|
|
558
558
|
it('should handle null contextUser gracefully', () => {
|
|
559
|
-
const hook =
|
|
559
|
+
const hook = CreateTenantPreSaveHook(makeConfig());
|
|
560
560
|
const entity = makeEntity('Customers', 'tenant-abc', true);
|
|
561
561
|
|
|
562
562
|
const result = hook(entity, undefined);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { CreateTenantPreRunViewHook, CreateTenantPreSaveHook } from '../multiTenancy/index.js';
|
|
3
3
|
import type { MultiTenancyConfig } from '../config.js';
|
|
4
4
|
import type { RunViewParams } from '@memberjunction/core';
|
|
5
5
|
|
|
@@ -66,13 +66,13 @@ function makeUser(tenantId?: string, roles: string[] = []) {
|
|
|
66
66
|
ID: 'user-1',
|
|
67
67
|
TenantContext: tenantId ? { TenantID: tenantId, Source: 'header' as const } : undefined,
|
|
68
68
|
UserRoles: roles.map(r => ({ Role: r, RoleID: `role-${r}`, UserID: 'user-1' })),
|
|
69
|
-
} as Parameters<ReturnType<typeof
|
|
69
|
+
} as Parameters<ReturnType<typeof CreateTenantPreRunViewHook>>[1];
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
describe('Multi-Tenancy Hooks', () => {
|
|
73
73
|
describe('createTenantPreRunViewHook', () => {
|
|
74
74
|
it('should inject tenant filter for scoped entity', () => {
|
|
75
|
-
const hook =
|
|
75
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
76
76
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
77
77
|
const user = makeUser('tenant-abc');
|
|
78
78
|
|
|
@@ -81,7 +81,7 @@ describe('Multi-Tenancy Hooks', () => {
|
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
it('should AND with existing filter', () => {
|
|
84
|
-
const hook =
|
|
84
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
85
85
|
const params = { EntityName: 'Customers', ExtraFilter: "Status = 'Active'" } as RunViewParams;
|
|
86
86
|
const user = makeUser('tenant-abc');
|
|
87
87
|
|
|
@@ -92,7 +92,7 @@ describe('Multi-Tenancy Hooks', () => {
|
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
it('should skip core __mj entities when autoExcludeCoreEntities is true', () => {
|
|
95
|
-
const hook =
|
|
95
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
96
96
|
const params = { EntityName: 'AI Models', ExtraFilter: '' } as RunViewParams;
|
|
97
97
|
const user = makeUser('tenant-abc');
|
|
98
98
|
|
|
@@ -101,7 +101,7 @@ describe('Multi-Tenancy Hooks', () => {
|
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
it('should not filter when user has no TenantContext', () => {
|
|
104
|
-
const hook =
|
|
104
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
105
105
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
106
106
|
const user = makeUser(undefined);
|
|
107
107
|
|
|
@@ -110,7 +110,7 @@ describe('Multi-Tenancy Hooks', () => {
|
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
it('should bypass filtering for admin users', () => {
|
|
113
|
-
const hook =
|
|
113
|
+
const hook = CreateTenantPreRunViewHook(makeConfig());
|
|
114
114
|
const params = { EntityName: 'Customers', ExtraFilter: '' } as RunViewParams;
|
|
115
115
|
const user = makeUser('tenant-abc', ['Admin']);
|
|
116
116
|
|
|
@@ -119,7 +119,7 @@ describe('Multi-Tenancy Hooks', () => {
|
|
|
119
119
|
});
|
|
120
120
|
|
|
121
121
|
it('should use entity column mapping override', () => {
|
|
122
|
-
const hook =
|
|
122
|
+
const hook = CreateTenantPreRunViewHook(makeConfig({
|
|
123
123
|
entityColumnMappings: { 'Orders': 'TenantID' },
|
|
124
124
|
}));
|
|
125
125
|
const params = { EntityName: 'Orders', ExtraFilter: '' } as RunViewParams;
|
|
@@ -130,7 +130,7 @@ describe('Multi-Tenancy Hooks', () => {
|
|
|
130
130
|
});
|
|
131
131
|
|
|
132
132
|
it('should respect allowlist scoping strategy', () => {
|
|
133
|
-
const hook =
|
|
133
|
+
const hook = CreateTenantPreRunViewHook(makeConfig({
|
|
134
134
|
scopingStrategy: 'allowlist',
|
|
135
135
|
scopedEntities: ['Customers'],
|
|
136
136
|
}));
|
|
@@ -148,7 +148,7 @@ describe('Multi-Tenancy Hooks', () => {
|
|
|
148
148
|
});
|
|
149
149
|
|
|
150
150
|
it('should respect denylist scoping strategy', () => {
|
|
151
|
-
const hook =
|
|
151
|
+
const hook = CreateTenantPreRunViewHook(makeConfig({
|
|
152
152
|
scopingStrategy: 'denylist',
|
|
153
153
|
scopedEntities: ['Orders'],
|
|
154
154
|
}));
|
|
@@ -173,11 +173,11 @@ describe('Multi-Tenancy Hooks', () => {
|
|
|
173
173
|
IsSaved: isSaved,
|
|
174
174
|
Get: vi.fn((col: string) => col === 'OrganizationID' ? tenantValue : null),
|
|
175
175
|
Set: vi.fn(),
|
|
176
|
-
} as unknown as Parameters<ReturnType<typeof
|
|
176
|
+
} as unknown as Parameters<ReturnType<typeof CreateTenantPreSaveHook>>[0];
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
it('should allow save when tenant matches', () => {
|
|
180
|
-
const hook =
|
|
180
|
+
const hook = CreateTenantPreSaveHook(makeConfig());
|
|
181
181
|
const entity = makeEntity('Customers', 'tenant-abc', true);
|
|
182
182
|
const user = makeUser('tenant-abc');
|
|
183
183
|
|
|
@@ -186,7 +186,7 @@ describe('Multi-Tenancy Hooks', () => {
|
|
|
186
186
|
});
|
|
187
187
|
|
|
188
188
|
it('should reject save in strict mode when tenant mismatches', () => {
|
|
189
|
-
const hook =
|
|
189
|
+
const hook = CreateTenantPreSaveHook(makeConfig());
|
|
190
190
|
const entity = makeEntity('Customers', 'tenant-other', true);
|
|
191
191
|
const user = makeUser('tenant-abc');
|
|
192
192
|
|
|
@@ -197,7 +197,7 @@ describe('Multi-Tenancy Hooks', () => {
|
|
|
197
197
|
|
|
198
198
|
it('should warn but allow in log mode when tenant mismatches', () => {
|
|
199
199
|
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
200
|
-
const hook =
|
|
200
|
+
const hook = CreateTenantPreSaveHook(makeConfig({ writeProtection: 'log' }));
|
|
201
201
|
const entity = makeEntity('Customers', 'tenant-other', true);
|
|
202
202
|
const user = makeUser('tenant-abc');
|
|
203
203
|
|
|
@@ -208,7 +208,7 @@ describe('Multi-Tenancy Hooks', () => {
|
|
|
208
208
|
});
|
|
209
209
|
|
|
210
210
|
it('should allow when writeProtection is off', () => {
|
|
211
|
-
const hook =
|
|
211
|
+
const hook = CreateTenantPreSaveHook(makeConfig({ writeProtection: 'off' }));
|
|
212
212
|
const entity = makeEntity('Customers', 'tenant-other', true);
|
|
213
213
|
const user = makeUser('tenant-abc');
|
|
214
214
|
|
|
@@ -217,7 +217,7 @@ describe('Multi-Tenancy Hooks', () => {
|
|
|
217
217
|
});
|
|
218
218
|
|
|
219
219
|
it('should auto-assign tenant for new records without tenant value', () => {
|
|
220
|
-
const hook =
|
|
220
|
+
const hook = CreateTenantPreSaveHook(makeConfig());
|
|
221
221
|
const entity = makeEntity('Customers', null, false);
|
|
222
222
|
const user = makeUser('tenant-abc');
|
|
223
223
|
|
|
@@ -227,7 +227,7 @@ describe('Multi-Tenancy Hooks', () => {
|
|
|
227
227
|
});
|
|
228
228
|
|
|
229
229
|
it('should bypass validation for admin users', () => {
|
|
230
|
-
const hook =
|
|
230
|
+
const hook = CreateTenantPreSaveHook(makeConfig());
|
|
231
231
|
const entity = makeEntity('Customers', 'tenant-other', true);
|
|
232
232
|
const user = makeUser('tenant-abc', ['Admin']);
|
|
233
233
|
|
|
@@ -236,7 +236,7 @@ describe('Multi-Tenancy Hooks', () => {
|
|
|
236
236
|
});
|
|
237
237
|
|
|
238
238
|
it('should skip core __mj entities', () => {
|
|
239
|
-
const hook =
|
|
239
|
+
const hook = CreateTenantPreSaveHook(makeConfig());
|
|
240
240
|
const entity = makeEntity('AI Models', 'any-tenant', true);
|
|
241
241
|
const user = makeUser('tenant-abc');
|
|
242
242
|
|
|
@@ -168,7 +168,8 @@ vi.mock('@memberjunction/global', async (importOriginal) => {
|
|
|
168
168
|
};
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
-
vi.mock('../auth/initializeProviders.js', () => ({
|
|
171
|
+
vi.mock('../auth/initializeProviders.js', () => ({ InitializeAuthProviders: vi.fn(),
|
|
172
|
+
get initializeAuthProviders() { return this.InitializeAuthProviders; } }));
|
|
172
173
|
|
|
173
174
|
vi.mock('@memberjunction/auth-providers', () => ({
|
|
174
175
|
AuthProviderFactory: {
|
|
@@ -195,7 +196,7 @@ vi.mock('@memberjunction/api-keys', () => ({
|
|
|
195
196
|
|
|
196
197
|
// ─── Import after mocks ─────────────────────────────────────────────────────
|
|
197
198
|
import { NewUserBase } from '../auth/newUsers.js';
|
|
198
|
-
import {
|
|
199
|
+
import { VerifyUserRecord } from '../auth/index.js';
|
|
199
200
|
|
|
200
201
|
// ─── Mock-entity plumbing ───────────────────────────────────────────────────
|
|
201
202
|
|
|
@@ -557,7 +558,7 @@ describe('verifyUserRecord', () => {
|
|
|
557
558
|
}
|
|
558
559
|
|
|
559
560
|
it('returns undefined when no email is supplied', async () => {
|
|
560
|
-
const user = await
|
|
561
|
+
const user = await VerifyUserRecord(undefined, 'A', 'B');
|
|
561
562
|
|
|
562
563
|
expect(user).toBeUndefined();
|
|
563
564
|
expect(mockGetEntityObject).not.toHaveBeenCalled();
|
|
@@ -566,7 +567,7 @@ describe('verifyUserRecord', () => {
|
|
|
566
567
|
it('returns the cached user, matching email case-insensitively and trimmed', async () => {
|
|
567
568
|
addCacheUser('someone@example.com');
|
|
568
569
|
|
|
569
|
-
const user = await
|
|
570
|
+
const user = await VerifyUserRecord(' SomeOne@Example.COM ', 'A', 'B');
|
|
570
571
|
|
|
571
572
|
expect(user).toBeDefined();
|
|
572
573
|
expect((user as unknown as { Email: string }).Email).toBe('someone@example.com');
|
|
@@ -577,7 +578,7 @@ describe('verifyUserRecord', () => {
|
|
|
577
578
|
mockCacheUsers.push({ ID: 'broken-1', Name: 'Broken', Email: ' ', Type: 'User' });
|
|
578
579
|
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
|
|
579
580
|
|
|
580
|
-
const user = await
|
|
581
|
+
const user = await VerifyUserRecord('missing@example.com', undefined, undefined);
|
|
581
582
|
|
|
582
583
|
expect(user).toBeUndefined();
|
|
583
584
|
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('broken-1'));
|
|
@@ -587,15 +588,15 @@ describe('verifyUserRecord', () => {
|
|
|
587
588
|
it('does NOT create a user when autoCreateNewUsers is false', async () => {
|
|
588
589
|
mockConfig.userHandling.autoCreateNewUsers = false;
|
|
589
590
|
|
|
590
|
-
const user = await
|
|
591
|
+
const user = await VerifyUserRecord('new@example.com', 'New', 'User');
|
|
591
592
|
|
|
592
593
|
expect(user).toBeUndefined();
|
|
593
594
|
expect(mockGetEntityObject).not.toHaveBeenCalled();
|
|
594
595
|
});
|
|
595
596
|
|
|
596
597
|
it('does NOT create a user when firstName or lastName is missing', async () => {
|
|
597
|
-
const missingFirst = await
|
|
598
|
-
const missingLast = await
|
|
598
|
+
const missingFirst = await VerifyUserRecord('new@example.com', undefined, 'User');
|
|
599
|
+
const missingLast = await VerifyUserRecord('new@example.com', 'New', undefined);
|
|
599
600
|
|
|
600
601
|
expect(missingFirst).toBeUndefined();
|
|
601
602
|
expect(missingLast).toBeUndefined();
|
|
@@ -605,7 +606,7 @@ describe('verifyUserRecord', () => {
|
|
|
605
606
|
it('creates the user when enabled and domains are unrestricted, stamping config roles onto the returned UserInfo', async () => {
|
|
606
607
|
mockConfig.userHandling.newUserRoles = ['UI', 'Developer'];
|
|
607
608
|
|
|
608
|
-
const user = await
|
|
609
|
+
const user = await VerifyUserRecord('new@example.com', 'New', 'User');
|
|
609
610
|
|
|
610
611
|
expect(user).toBeDefined();
|
|
611
612
|
const stamped = user as unknown as { Email: string; UserRoles: Array<{ Role: string; RoleID: string; UserID: string }> };
|
|
@@ -621,7 +622,7 @@ describe('verifyUserRecord', () => {
|
|
|
621
622
|
it('returns undefined when the underlying createNewUser fails', async () => {
|
|
622
623
|
saveBehavior['MJ: Users'] = () => false;
|
|
623
624
|
|
|
624
|
-
const user = await
|
|
625
|
+
const user = await VerifyUserRecord('new@example.com', 'New', 'User');
|
|
625
626
|
|
|
626
627
|
expect(user).toBeUndefined();
|
|
627
628
|
// The cache is no longer empty at rest (it holds the configured context user), so
|
|
@@ -637,35 +638,35 @@ describe('verifyUserRecord', () => {
|
|
|
637
638
|
});
|
|
638
639
|
|
|
639
640
|
it('creates the user when the email domain is authorized', async () => {
|
|
640
|
-
const user = await
|
|
641
|
+
const user = await VerifyUserRecord('new@example.com', 'New', 'User');
|
|
641
642
|
|
|
642
643
|
expect(user).toBeDefined();
|
|
643
644
|
expect(entitiesOf('MJ: Users')).toHaveLength(1);
|
|
644
645
|
});
|
|
645
646
|
|
|
646
647
|
it('does NOT create when the email domain is not authorized', async () => {
|
|
647
|
-
const user = await
|
|
648
|
+
const user = await VerifyUserRecord('new@evil.com', 'New', 'User');
|
|
648
649
|
|
|
649
650
|
expect(user).toBeUndefined();
|
|
650
651
|
expect(mockGetEntityObject).not.toHaveBeenCalled();
|
|
651
652
|
});
|
|
652
653
|
|
|
653
654
|
it('creates when restricted and no request Origin is supplied, if the email domain is authorized', async () => {
|
|
654
|
-
const user = await
|
|
655
|
+
const user = await VerifyUserRecord('new@example.com', 'New', 'User', undefined);
|
|
655
656
|
|
|
656
657
|
expect(user).toBeDefined();
|
|
657
658
|
expect(entitiesOf('MJ: Users')).toHaveLength(1);
|
|
658
659
|
});
|
|
659
660
|
|
|
660
661
|
it('does NOT create when a forged Origin is authorized but the email domain is not', async () => {
|
|
661
|
-
const user = await
|
|
662
|
+
const user = await VerifyUserRecord('new@evil.com', 'New', 'User', 'example.com');
|
|
662
663
|
|
|
663
664
|
expect(user).toBeUndefined();
|
|
664
665
|
expect(mockGetEntityObject).not.toHaveBeenCalled();
|
|
665
666
|
});
|
|
666
667
|
|
|
667
668
|
it('matches email domains case-insensitively', async () => {
|
|
668
|
-
const user = await
|
|
669
|
+
const user = await VerifyUserRecord('new@EXAMPLE.COM', 'New', 'User');
|
|
669
670
|
|
|
670
671
|
expect(user).toBeDefined();
|
|
671
672
|
});
|
|
@@ -673,7 +674,7 @@ describe('verifyUserRecord', () => {
|
|
|
673
674
|
it('supports wildcard patterns for email subdomains', async () => {
|
|
674
675
|
mockConfig.userHandling.newUserAuthorizedDomains = ['*.example.com'];
|
|
675
676
|
|
|
676
|
-
const user = await
|
|
677
|
+
const user = await VerifyUserRecord('new@mail.example.com', 'New', 'User');
|
|
677
678
|
|
|
678
679
|
expect(user).toBeDefined();
|
|
679
680
|
});
|
|
@@ -681,7 +682,7 @@ describe('verifyUserRecord', () => {
|
|
|
681
682
|
it('does NOT match the apex against "*.example.com" (pattern is matched in full)', async () => {
|
|
682
683
|
mockConfig.userHandling.newUserAuthorizedDomains = ['*.example.com'];
|
|
683
684
|
|
|
684
|
-
const user = await
|
|
685
|
+
const user = await VerifyUserRecord('new@example.com', 'New', 'User');
|
|
685
686
|
|
|
686
687
|
expect(user).toBeUndefined();
|
|
687
688
|
expect(mockGetEntityObject).not.toHaveBeenCalled();
|
|
@@ -690,27 +691,27 @@ describe('verifyUserRecord', () => {
|
|
|
690
691
|
it('rejects prefix confusion: "*.example.com" does not match "evilexample.com" (dot is escaped)', async () => {
|
|
691
692
|
mockConfig.userHandling.newUserAuthorizedDomains = ['*.example.com'];
|
|
692
693
|
|
|
693
|
-
const user = await
|
|
694
|
+
const user = await VerifyUserRecord('new@evilexample.com', 'New', 'User');
|
|
694
695
|
|
|
695
696
|
expect(user).toBeUndefined();
|
|
696
697
|
expect(mockGetEntityObject).not.toHaveBeenCalled();
|
|
697
698
|
});
|
|
698
699
|
|
|
699
700
|
it('rejects suffix confusion: the pattern is anchored, so "example.com.evil.com" is not authorized', async () => {
|
|
700
|
-
const user = await
|
|
701
|
+
const user = await VerifyUserRecord('new@example.com.evil.com', 'New', 'User');
|
|
701
702
|
|
|
702
703
|
expect(user).toBeUndefined();
|
|
703
704
|
expect(mockGetEntityObject).not.toHaveBeenCalled();
|
|
704
705
|
});
|
|
705
706
|
|
|
706
707
|
it('rejects "evilexample.com" against the plain "example.com" pattern (anchored at both ends)', async () => {
|
|
707
|
-
const user = await
|
|
708
|
+
const user = await VerifyUserRecord('x@evilexample.com', 'New', 'User');
|
|
708
709
|
|
|
709
710
|
expect(user).toBeUndefined();
|
|
710
711
|
});
|
|
711
712
|
|
|
712
713
|
it('does NOT create when the identity has no email domain (username-only IdP)', async () => {
|
|
713
|
-
const user = await
|
|
714
|
+
const user = await VerifyUserRecord('bare-username', 'New', 'User');
|
|
714
715
|
|
|
715
716
|
expect(user).toBeUndefined();
|
|
716
717
|
expect(mockGetEntityObject).not.toHaveBeenCalled();
|
|
@@ -727,7 +728,7 @@ describe('verifyUserRecord', () => {
|
|
|
727
728
|
});
|
|
728
729
|
const dataSource = {} as unknown as import('mssql').ConnectionPool;
|
|
729
730
|
|
|
730
|
-
const user = await
|
|
731
|
+
const user = await VerifyUserRecord('late@example.com', undefined, undefined, undefined, dataSource);
|
|
731
732
|
|
|
732
733
|
expect(user).toBeDefined();
|
|
733
734
|
expect((user as unknown as { Email: string }).Email).toBe('late@example.com');
|
|
@@ -739,7 +740,7 @@ describe('verifyUserRecord', () => {
|
|
|
739
740
|
mockConfig.userHandling.updateCacheWhenNotFound = true;
|
|
740
741
|
const dataSource = {} as unknown as import('mssql').ConnectionPool;
|
|
741
742
|
|
|
742
|
-
const user = await
|
|
743
|
+
const user = await VerifyUserRecord('never@example.com', undefined, undefined, undefined, dataSource);
|
|
743
744
|
|
|
744
745
|
expect(user).toBeUndefined();
|
|
745
746
|
expect(mockRefresh).toHaveBeenCalledTimes(1);
|