@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
|
@@ -18,7 +18,7 @@ vi.mock('../config.js', () => ({
|
|
|
18
18
|
},
|
|
19
19
|
}));
|
|
20
20
|
|
|
21
|
-
import {
|
|
21
|
+
import { AuditResolverList } from '../logging/bootAudit.js';
|
|
22
22
|
import type { AuditArgParam, AuditResolver } from '../logging/bootAudit.js';
|
|
23
23
|
import { NoLog } from '../logging/NoLog.js';
|
|
24
24
|
|
|
@@ -37,12 +37,12 @@ function makeArgParam(opts: {
|
|
|
37
37
|
type: Function;
|
|
38
38
|
}): AuditArgParam {
|
|
39
39
|
return {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
Target: opts.target,
|
|
41
|
+
MethodName: opts.methodName,
|
|
42
|
+
Index: opts.index,
|
|
43
43
|
kind: 'arg',
|
|
44
44
|
name: opts.name,
|
|
45
|
-
|
|
45
|
+
GetType: () => opts.type,
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -52,8 +52,8 @@ function makeResolver(opts: {
|
|
|
52
52
|
params: AuditArgParam[];
|
|
53
53
|
}): AuditResolver {
|
|
54
54
|
return {
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
Target: opts.target,
|
|
56
|
+
MethodName: opts.methodName,
|
|
57
57
|
params: opts.params,
|
|
58
58
|
};
|
|
59
59
|
}
|
|
@@ -93,7 +93,7 @@ describe('auditResolverList', () => {
|
|
|
93
93
|
],
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
AuditResolverList([resolver]);
|
|
97
97
|
expect(warnSpy).not.toHaveBeenCalled();
|
|
98
98
|
});
|
|
99
99
|
|
|
@@ -117,7 +117,7 @@ describe('auditResolverList', () => {
|
|
|
117
117
|
],
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
AuditResolverList([resolver]);
|
|
121
121
|
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
122
122
|
const message = warnSpy.mock.calls[0][0];
|
|
123
123
|
expect(typeof message).toBe('string');
|
|
@@ -148,7 +148,7 @@ describe('auditResolverList', () => {
|
|
|
148
148
|
],
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
AuditResolverList([resolver]);
|
|
152
152
|
expect(warnSpy).not.toHaveBeenCalled();
|
|
153
153
|
});
|
|
154
154
|
|
|
@@ -179,7 +179,7 @@ describe('auditResolverList', () => {
|
|
|
179
179
|
],
|
|
180
180
|
});
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
AuditResolverList([resolver]);
|
|
183
183
|
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
184
184
|
const message = warnSpy.mock.calls[0][0];
|
|
185
185
|
expect(message).toContain("'custom'");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { BuildBoundaryLogPayload } from '../logging/boundaryLogPayload.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Guards the load-bearing #2638 fix: the always-on GraphQL boundary log line emits the
|
|
@@ -10,22 +10,22 @@ import { buildBoundaryLogPayload } from '../logging/boundaryLogPayload.js';
|
|
|
10
10
|
*/
|
|
11
11
|
describe('buildBoundaryLogPayload', () => {
|
|
12
12
|
it('returns operation name only', () => {
|
|
13
|
-
expect(
|
|
13
|
+
expect(BuildBoundaryLogPayload('CreateMJCredential')).toEqual({ operationName: 'CreateMJCredential' });
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
it('never includes a `variables` key', () => {
|
|
17
|
-
const payload =
|
|
17
|
+
const payload = BuildBoundaryLogPayload('RunViewsWithCacheCheck');
|
|
18
18
|
expect('variables' in payload).toBe(false);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
it('serialized form contains no value content — only the operation name', () => {
|
|
22
22
|
// Even if someone passes a secret-shaped operation name, nothing but the name is emitted.
|
|
23
|
-
const serialized = JSON.stringify(
|
|
23
|
+
const serialized = JSON.stringify(BuildBoundaryLogPayload('SomeOp'));
|
|
24
24
|
expect(serialized).toBe('{"operationName":"SomeOp"}');
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
it('handles undefined operation name (non-named operations)', () => {
|
|
28
|
-
expect(
|
|
29
|
-
expect('variables' in
|
|
28
|
+
expect(BuildBoundaryLogPayload(undefined)).toEqual({ operationName: undefined });
|
|
29
|
+
expect('variables' in BuildBoundaryLogPayload(undefined)).toBe(false);
|
|
30
30
|
});
|
|
31
31
|
});
|
|
@@ -66,11 +66,16 @@ vi.mock('mssql', () => {
|
|
|
66
66
|
// We must also mock the auth module and its transitive deps so that importing
|
|
67
67
|
// individual source files does not trigger type-graphql or reflect-metadata.
|
|
68
68
|
vi.mock('../auth/index', () => ({
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
GetSigningKeys: vi.fn(),
|
|
70
|
+
get getSigningKeys() { return this.GetSigningKeys; },
|
|
71
|
+
GetSystemUser: vi.fn(),
|
|
72
|
+
get getSystemUser() { return this.GetSystemUser; },
|
|
73
|
+
GetValidationOptions: vi.fn(),
|
|
74
|
+
get getValidationOptions() { return this.GetValidationOptions; },
|
|
75
|
+
VerifyUserRecord: vi.fn(),
|
|
76
|
+
get verifyUserRecord() { return this.VerifyUserRecord; },
|
|
77
|
+
ExtractUserInfoFromPayload: vi.fn(),
|
|
78
|
+
get extractUserInfoFromPayload() { return this.ExtractUserInfoFromPayload; },
|
|
74
79
|
TokenExpiredError: class TokenExpiredError extends Error {},
|
|
75
80
|
AuthProviderFactory: { getInstance: vi.fn(() => ({ getByIssuer: vi.fn() })) },
|
|
76
81
|
IAuthProvider: {},
|
|
@@ -82,7 +87,8 @@ vi.mock('../auth/AuthProviderFactory', () => ({
|
|
|
82
87
|
}));
|
|
83
88
|
|
|
84
89
|
vi.mock('../cache', () => ({
|
|
85
|
-
|
|
90
|
+
AuthCache: new Map(),
|
|
91
|
+
get authCache() { return this.AuthCache; },
|
|
86
92
|
}));
|
|
87
93
|
|
|
88
94
|
vi.mock('@memberjunction/core', () => ({
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import { describe, it, expect } from 'vitest';
|
|
3
3
|
import type { IMetadataProvider } from '@memberjunction/core';
|
|
4
|
-
import {
|
|
5
|
-
import { NoLog,
|
|
4
|
+
import { RedactArg } from '../logging/secretRedactor.js';
|
|
5
|
+
import { NoLog, GetNoLogFields } from '../logging/NoLog.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Test 4 (unit form): the `@NoLog`-decorated `Token` field on GetDataInputType is redacted
|
|
@@ -27,7 +27,7 @@ describe('Test 4: GetData Token redaction via field-level @NoLog', () => {
|
|
|
27
27
|
const provider = { Entities: [] } as unknown as IMetadataProvider;
|
|
28
28
|
|
|
29
29
|
it('getNoLogFields picks up the Token field mark', () => {
|
|
30
|
-
const fields =
|
|
30
|
+
const fields = GetNoLogFields(GetDataInputType);
|
|
31
31
|
expect(fields.has('Token')).toBe(true);
|
|
32
32
|
expect(fields.has('Queries')).toBe(false);
|
|
33
33
|
});
|
|
@@ -40,12 +40,12 @@ describe('Test 4: GetData Token redaction via field-level @NoLog', () => {
|
|
|
40
40
|
Entities: [{ ClassName: 'Something', EncryptedFields: [] }],
|
|
41
41
|
} as unknown as IMetadataProvider;
|
|
42
42
|
|
|
43
|
-
const result =
|
|
43
|
+
const result = RedactArg({
|
|
44
44
|
inputTypeName: 'GetDataInput', // not entity-bound — regex won't match
|
|
45
45
|
rawValue: { Token: 'FAKE_SYSTEM_TOKEN_DO_NOT_USE', Queries: ['SELECT 1', 'SELECT 2'] },
|
|
46
46
|
provider: providerWithEntities,
|
|
47
47
|
noLogParameter: false,
|
|
48
|
-
noLogFields:
|
|
48
|
+
noLogFields: GetNoLogFields(GetDataInputType),
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
expect(result).toEqual({ Token: '<redacted>', Queries: ['SELECT 1', 'SELECT 2'] });
|
|
@@ -58,7 +58,7 @@ describe('Test 4: GetData Token redaction via field-level @NoLog', () => {
|
|
|
58
58
|
Entities: [{ ClassName: 'GetDataThing', EncryptedFields: [] }],
|
|
59
59
|
} as unknown as IMetadataProvider;
|
|
60
60
|
|
|
61
|
-
const result =
|
|
61
|
+
const result = RedactArg({
|
|
62
62
|
inputTypeName: 'CreateGetDataThingInput',
|
|
63
63
|
rawValue: { Token: 'FAKE_SYSTEM_TOKEN_DO_NOT_USE', Queries: ['SELECT 1'] },
|
|
64
64
|
provider: providerWithEntity,
|
|
@@ -72,7 +72,7 @@ describe('Test 4: GetData Token redaction via field-level @NoLog', () => {
|
|
|
72
72
|
|
|
73
73
|
it('parameter-level @NoLog masks the entire arg regardless of shape', () => {
|
|
74
74
|
// If Token were a top-level @Arg with @NoLog (parameter form), noLogParameter=true masks it whole.
|
|
75
|
-
const result =
|
|
75
|
+
const result = RedactArg({
|
|
76
76
|
inputTypeName: 'String',
|
|
77
77
|
rawValue: 'FAKE_SYSTEM_TOKEN_DO_NOT_USE',
|
|
78
78
|
provider,
|
|
@@ -78,7 +78,7 @@ vi.mock('@memberjunction/core', () => {
|
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
import { AuthProviderEngine } from '../auth/AuthProviderEngine.js';
|
|
81
|
-
import {
|
|
81
|
+
import { RefreshAuthProviders } from '../auth/initializeProviders.js';
|
|
82
82
|
|
|
83
83
|
/** Minimal stand-in for a generated MJAuthenticationProviderEntity row. */
|
|
84
84
|
function metadataRow(name: string, driverClass: string) {
|
|
@@ -106,7 +106,7 @@ beforeEach(() => {
|
|
|
106
106
|
|
|
107
107
|
describe('refreshAuthProviders', () => {
|
|
108
108
|
it('re-registers the config-declared providers after clearing, so a refresh cannot lock config-path users out', async () => {
|
|
109
|
-
await
|
|
109
|
+
await RefreshAuthProviders();
|
|
110
110
|
|
|
111
111
|
expect(clearMock).toHaveBeenCalledTimes(1);
|
|
112
112
|
expect(createProviderMock).toHaveBeenCalledWith(expect.objectContaining({ name: 'config-okta' }));
|
|
@@ -124,7 +124,7 @@ describe('refreshAuthProviders', () => {
|
|
|
124
124
|
});
|
|
125
125
|
|
|
126
126
|
try {
|
|
127
|
-
await expect(
|
|
127
|
+
await expect(RefreshAuthProviders()).rejects.toThrow('PermissionConstrainedError');
|
|
128
128
|
expect(clearMock).not.toHaveBeenCalled();
|
|
129
129
|
expect(registerMock).not.toHaveBeenCalled();
|
|
130
130
|
} finally {
|
|
@@ -135,7 +135,7 @@ describe('refreshAuthProviders', () => {
|
|
|
135
135
|
it('layers the metadata rows on top of the config-declared providers, in that order', async () => {
|
|
136
136
|
(AuthProviderEngine.Instance as unknown as { _providers: unknown[] })._providers = [metadataRow('Catalog WorkOS', 'workos')];
|
|
137
137
|
|
|
138
|
-
const count = await
|
|
138
|
+
const count = await RefreshAuthProviders();
|
|
139
139
|
|
|
140
140
|
expect(count).toBe(1);
|
|
141
141
|
expect(registerMock).toHaveBeenCalledTimes(2);
|
|
@@ -2,30 +2,30 @@ import { describe, it, expect } from 'vitest';
|
|
|
2
2
|
import jwt from 'jsonwebtoken';
|
|
3
3
|
import { createPublicKey, type JsonWebKey } from 'node:crypto';
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
GenerateRawToken,
|
|
6
|
+
HashToken,
|
|
7
|
+
EvaluateInvite,
|
|
8
|
+
BuildSessionClaims,
|
|
9
|
+
BuildConsumeInvitePostgresSQL,
|
|
10
|
+
CanIssueInvites,
|
|
11
|
+
IsRoleGrantable,
|
|
12
|
+
UnionScopes,
|
|
13
13
|
MAGIC_LINK_TOKEN_PREFIX,
|
|
14
14
|
} from '../auth/magicLink/magicLinkCore.js';
|
|
15
|
-
import {
|
|
15
|
+
import { BuildRedeemLandingHtml, EscapeHtml } from '../auth/magicLink/redeemLanding.js';
|
|
16
16
|
import { MagicLinkKeyManager } from '../auth/magicLink/MagicLinkKeys.js';
|
|
17
17
|
|
|
18
18
|
describe('magic-link core', () => {
|
|
19
19
|
describe('generateRawToken', () => {
|
|
20
20
|
it('prefixes tokens and uses 32 bytes of hex entropy', () => {
|
|
21
|
-
const t =
|
|
21
|
+
const t = GenerateRawToken();
|
|
22
22
|
expect(t.startsWith(MAGIC_LINK_TOKEN_PREFIX)).toBe(true);
|
|
23
23
|
const body = t.slice(MAGIC_LINK_TOKEN_PREFIX.length);
|
|
24
24
|
expect(body).toMatch(/^[0-9a-f]{64}$/);
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
it('produces unique tokens', () => {
|
|
28
|
-
const set = new Set(Array.from({ length: 100 }, () =>
|
|
28
|
+
const set = new Set(Array.from({ length: 100 }, () => GenerateRawToken()));
|
|
29
29
|
expect(set.size).toBe(100);
|
|
30
30
|
});
|
|
31
31
|
});
|
|
@@ -33,15 +33,15 @@ describe('magic-link core', () => {
|
|
|
33
33
|
describe('hashToken', () => {
|
|
34
34
|
it('is deterministic and base64url-encoded sha256 (43 chars, URL-safe alphabet)', () => {
|
|
35
35
|
const raw = 'mj_ml_abc';
|
|
36
|
-
expect(
|
|
36
|
+
expect(HashToken(raw)).toBe(HashToken(raw));
|
|
37
37
|
// base64url of 32 bytes = 43 chars, no padding, only [A-Za-z0-9_-]
|
|
38
|
-
expect(
|
|
38
|
+
expect(HashToken(raw)).toMatch(/^[A-Za-z0-9_-]{43}$/);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
it('differs for different inputs and never equals the raw token', () => {
|
|
42
|
-
const raw =
|
|
43
|
-
expect(
|
|
44
|
-
expect(
|
|
42
|
+
const raw = GenerateRawToken();
|
|
43
|
+
expect(HashToken(raw)).not.toBe(HashToken(GenerateRawToken()));
|
|
44
|
+
expect(HashToken(raw)).not.toBe(raw);
|
|
45
45
|
});
|
|
46
46
|
});
|
|
47
47
|
|
|
@@ -51,37 +51,37 @@ describe('magic-link core', () => {
|
|
|
51
51
|
const past = new Date(now - 3600_000);
|
|
52
52
|
|
|
53
53
|
it('accepts an active, unexpired, unused invite', () => {
|
|
54
|
-
expect(
|
|
54
|
+
expect(EvaluateInvite({ Status: 'Active', ExpiresAt: future, MaxUses: 1, UseCount: 0 }, now)).toEqual({ ok: true });
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
it('rejects revoked invites first', () => {
|
|
58
|
-
expect(
|
|
58
|
+
expect(EvaluateInvite({ Status: 'Revoked', ExpiresAt: future, MaxUses: 1, UseCount: 0 }, now)).toEqual({ ok: false, errorCode: 'revoked' });
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
it('rejects expired invites', () => {
|
|
62
|
-
expect(
|
|
62
|
+
expect(EvaluateInvite({ Status: 'Active', ExpiresAt: past, MaxUses: 1, UseCount: 0 }, now)).toEqual({ ok: false, errorCode: 'expired' });
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
it('rejects consumed invites (by status)', () => {
|
|
66
|
-
expect(
|
|
66
|
+
expect(EvaluateInvite({ Status: 'Consumed', ExpiresAt: future, MaxUses: 1, UseCount: 1 }, now)).toEqual({ ok: false, errorCode: 'consumed' });
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
it('rejects when use count reaches max', () => {
|
|
70
|
-
expect(
|
|
70
|
+
expect(EvaluateInvite({ Status: 'Active', ExpiresAt: future, MaxUses: 3, UseCount: 3 }, now)).toEqual({ ok: false, errorCode: 'consumed' });
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
it('allows multi-use invites that still have uses left', () => {
|
|
74
|
-
expect(
|
|
74
|
+
expect(EvaluateInvite({ Status: 'Active', ExpiresAt: future, MaxUses: 3, UseCount: 2 }, now)).toEqual({ ok: true });
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
it('rejects unknown statuses as invalid', () => {
|
|
78
|
-
expect(
|
|
78
|
+
expect(EvaluateInvite({ Status: 'Pending', ExpiresAt: future, MaxUses: 1, UseCount: 0 }, now)).toEqual({ ok: false, errorCode: 'invalid' });
|
|
79
79
|
});
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
describe('buildSessionClaims', () => {
|
|
83
83
|
it('scopes the token to exactly the given app and role and marks it magic-link', () => {
|
|
84
|
-
const claims =
|
|
84
|
+
const claims = BuildSessionClaims({
|
|
85
85
|
issuer: 'http://localhost:4051',
|
|
86
86
|
audience: 'mj-magic-link',
|
|
87
87
|
inviteId: 'INVITE-1',
|
|
@@ -103,7 +103,7 @@ describe('magic-link core', () => {
|
|
|
103
103
|
});
|
|
104
104
|
|
|
105
105
|
it('carries mj_invited_by when an inviter is supplied (attribution claim)', () => {
|
|
106
|
-
const claims =
|
|
106
|
+
const claims = BuildSessionClaims({
|
|
107
107
|
issuer: 'i', audience: 'a', inviteId: 'INVITE-1', email: 'e@x.com',
|
|
108
108
|
applicationId: 'APP-1', roleName: 'Magic Link Baseline',
|
|
109
109
|
invitedByUserId: 'USER-42', nowSeconds: 1000, ttlSeconds: 3600,
|
|
@@ -112,7 +112,7 @@ describe('magic-link core', () => {
|
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
it('omits mj_invited_by when no inviter is supplied', () => {
|
|
115
|
-
const claims =
|
|
115
|
+
const claims = BuildSessionClaims({
|
|
116
116
|
issuer: 'i', audience: 'a', inviteId: 'INVITE-1', email: 'e@x.com',
|
|
117
117
|
applicationId: 'APP-1', roleName: 'Magic Link Baseline',
|
|
118
118
|
nowSeconds: 1000, ttlSeconds: 3600,
|
|
@@ -121,7 +121,7 @@ describe('magic-link core', () => {
|
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
it('always emits a single-entry mj_scopes for the current link', () => {
|
|
124
|
-
const claims =
|
|
124
|
+
const claims = BuildSessionClaims({
|
|
125
125
|
issuer: 'i', audience: 'a', inviteId: 'INVITE-1', email: 'e@x.com',
|
|
126
126
|
applicationId: 'APP-1', roleName: 'Magic Link Baseline', nowSeconds: 1000, ttlSeconds: 3600,
|
|
127
127
|
});
|
|
@@ -129,7 +129,7 @@ describe('magic-link core', () => {
|
|
|
129
129
|
});
|
|
130
130
|
|
|
131
131
|
it('marks anonymous sessions and carries the per-session id + prior-scope union', () => {
|
|
132
|
-
const claims =
|
|
132
|
+
const claims = BuildSessionClaims({
|
|
133
133
|
issuer: 'i', audience: 'a', inviteId: 'INVITE-2', email: 'anon@x',
|
|
134
134
|
applicationId: 'APP-2', roleName: 'Guest', anonymous: true, sessionId: 'SID-9',
|
|
135
135
|
priorScopes: [{ inviteId: 'INVITE-1', appId: 'APP-1', role: 'Guest' }],
|
|
@@ -142,7 +142,7 @@ describe('magic-link core', () => {
|
|
|
142
142
|
});
|
|
143
143
|
|
|
144
144
|
it('does NOT mark mj_anon for email sessions', () => {
|
|
145
|
-
const claims =
|
|
145
|
+
const claims = BuildSessionClaims({
|
|
146
146
|
issuer: 'i', audience: 'a', inviteId: 'INVITE-1', email: 'e@x.com',
|
|
147
147
|
applicationId: 'APP-1', roleName: 'Magic Link Baseline', nowSeconds: 1000, ttlSeconds: 3600,
|
|
148
148
|
});
|
|
@@ -155,84 +155,26 @@ describe('magic-link core', () => {
|
|
|
155
155
|
const b = { inviteId: 'I2', appId: 'A2', role: 'R' };
|
|
156
156
|
|
|
157
157
|
it('appends a new scope entry', () => {
|
|
158
|
-
expect(
|
|
158
|
+
expect(UnionScopes([a], b)).toEqual([a, b]);
|
|
159
159
|
});
|
|
160
160
|
|
|
161
161
|
it('is idempotent by inviteId — re-redeeming the same link never accretes a duplicate', () => {
|
|
162
|
-
expect(
|
|
162
|
+
expect(UnionScopes([a, b], { inviteId: 'I1', appId: 'A1', role: 'R' })).toEqual([a, b]);
|
|
163
163
|
});
|
|
164
164
|
|
|
165
165
|
it('handles an empty/undefined prior union', () => {
|
|
166
|
-
expect(
|
|
167
|
-
expect(
|
|
166
|
+
expect(UnionScopes(undefined, a)).toEqual([a]);
|
|
167
|
+
expect(UnionScopes([], a)).toEqual([a]);
|
|
168
168
|
});
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
-
describe('
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
expect(sql).toContain(`UPDATE ${table} `);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it('increments UseCount', () => {
|
|
180
|
-
expect(sql).toContain('UseCount = UseCount + 1');
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it('stamps ConsumedAt only the first time (COALESCE preserves an existing value)', () => {
|
|
184
|
-
expect(sql).toContain('ConsumedAt = COALESCE(ConsumedAt, SYSUTCDATETIME())');
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it('flips Status to Consumed exactly when the last use is taken', () => {
|
|
188
|
-
expect(sql).toContain("Status = CASE WHEN UseCount + 1 >= MaxUses THEN 'Consumed' ELSE Status END");
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it('returns the affected row via OUTPUT INTO a table var so the caller can detect a win (exactly one row)', () => {
|
|
192
|
-
// Must be OUTPUT ... INTO, not a bare OUTPUT: SQL Server forbids a bare
|
|
193
|
-
// OUTPUT clause on a table with enabled triggers, and CodeGen adds an
|
|
194
|
-
// __mj_UpdatedAt trigger to every MJ table. Regression guard for that bug.
|
|
195
|
-
expect(sql).toContain('OUTPUT INSERTED.ID INTO @consumed');
|
|
196
|
-
expect(sql).not.toMatch(/OUTPUT INSERTED\.ID(?!\s+INTO)/);
|
|
197
|
-
expect(sql).toContain('SELECT ID FROM @consumed');
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it('guards atomically on Active + not-exhausted + not-expired — this IS the single-use gate', () => {
|
|
201
|
-
const where = sql.slice(sql.indexOf('WHERE'));
|
|
202
|
-
expect(where).toContain("Status = 'Active'");
|
|
203
|
-
expect(where).toContain('UseCount < MaxUses');
|
|
204
|
-
expect(where).toContain('ExpiresAt > SYSUTCDATETIME()');
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('binds the invite ID as a parameter, never interpolated (injection-safe)', () => {
|
|
208
|
-
expect(sql).toContain('ID = @p0');
|
|
209
|
-
// The id must not be string-interpolated with quotes around a value.
|
|
210
|
-
expect(sql).not.toMatch(/ID = '/);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('is a self-contained batch: declare table var, update-with-output, select', () => {
|
|
214
|
-
// Intentionally a 3-statement batch (the OUTPUT-INTO requirement). The ID is
|
|
215
|
-
// still parameterized, so the batch carries no interpolated user input.
|
|
216
|
-
expect(sql).toContain('DECLARE @consumed TABLE (ID UNIQUEIDENTIFIER)');
|
|
217
|
-
expect(sql.match(/;/g)?.length).toBe(3);
|
|
218
|
-
expect(sql.trim().endsWith(';')).toBe(true);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it('rejects a non-whitelisted table identifier (defense-in-depth against injection)', () => {
|
|
222
|
-
// Only bracket-quoted [schema].[table] of word chars is accepted, even though
|
|
223
|
-
// the caller derives the table from EntityInfo and never from user input.
|
|
224
|
-
expect(() => buildConsumeInviteSQL('__mj.MagicLinkInvite')).toThrow();
|
|
225
|
-
expect(() => buildConsumeInviteSQL('[__mj].[MagicLinkInvite]; DROP TABLE x;--')).toThrow();
|
|
226
|
-
expect(() => buildConsumeInviteSQL('[__mj].[Magic Link]')).toThrow();
|
|
227
|
-
expect(() => buildConsumeInviteSQL(table)).not.toThrow();
|
|
228
|
-
});
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
describe('buildConsumeInviteSQL (postgresql dialect)', () => {
|
|
232
|
-
// PG uses `schema.table` (unquoted — PostgreSQLDataProvider.ExecuteSQL auto-quotes
|
|
233
|
-
// the PascalCase identifiers). SS uses the bracket-quoted `[schema].[table]` form.
|
|
171
|
+
describe('BuildConsumeInvitePostgresSQL', () => {
|
|
172
|
+
// PostgreSQL only: SQL Server consumes through spConsumeMagicLinkInvite (#4753), but PG has no
|
|
173
|
+
// such function and its runtime roles hold table DML, so PG keeps this direct guarded UPDATE.
|
|
174
|
+
// PG uses `schema.table` unquoted — PostgreSQLDataProvider.ExecuteSQL auto-quotes the
|
|
175
|
+
// PascalCase identifiers.
|
|
234
176
|
const pgTable = '__mj.MagicLinkInvite';
|
|
235
|
-
const pgSql =
|
|
177
|
+
const pgSql = BuildConsumeInvitePostgresSQL(pgTable);
|
|
236
178
|
|
|
237
179
|
it('targets the supplied unquoted schema.table', () => {
|
|
238
180
|
expect(pgSql).toContain(`UPDATE ${pgTable} `);
|
|
@@ -246,63 +188,60 @@ describe('magic-link core', () => {
|
|
|
246
188
|
|
|
247
189
|
it('binds the invite ID as PG positional param $1, not T-SQL @p0 (injection-safe)', () => {
|
|
248
190
|
expect(pgSql).toContain('ID = $1');
|
|
249
|
-
expect(pgSql).not.
|
|
250
|
-
expect(pgSql).not.toMatch(/@/); // no T-SQL @-anything survives on PG
|
|
191
|
+
expect(pgSql).not.toMatch(/@/);
|
|
251
192
|
expect(pgSql).not.toMatch(/ID = '/);
|
|
252
193
|
});
|
|
253
194
|
|
|
254
195
|
it('uses PG timestamp expressions, not T-SQL SYSUTCDATETIME()', () => {
|
|
255
196
|
expect(pgSql).not.toContain('SYSUTCDATETIME');
|
|
256
|
-
expect(pgSql).toContain("(now() AT TIME ZONE 'utc')");
|
|
257
197
|
expect(pgSql).toContain("ConsumedAt = COALESCE(ConsumedAt, (now() AT TIME ZONE 'utc'))");
|
|
258
198
|
});
|
|
259
199
|
|
|
260
|
-
it('guards atomically on Active + not-exhausted + not-expired (same predicate as
|
|
200
|
+
it('guards atomically on Active + not-exhausted + not-expired (same predicate as spConsumeMagicLinkInvite)', () => {
|
|
261
201
|
const where = pgSql.slice(pgSql.indexOf('WHERE'));
|
|
262
202
|
expect(where).toContain("Status = 'Active'");
|
|
263
203
|
expect(where).toContain('UseCount < MaxUses');
|
|
264
204
|
expect(where).toContain("ExpiresAt > (now() AT TIME ZONE 'utc')");
|
|
265
205
|
});
|
|
266
206
|
|
|
267
|
-
it('increments UseCount and flips Status on the last use (same semantics as
|
|
207
|
+
it('increments UseCount and flips Status on the last use (same semantics as spConsumeMagicLinkInvite)', () => {
|
|
268
208
|
expect(pgSql).toContain('UseCount = UseCount + 1');
|
|
269
209
|
expect(pgSql).toContain("Status = CASE WHEN UseCount + 1 >= MaxUses THEN 'Consumed' ELSE Status END");
|
|
270
210
|
});
|
|
271
211
|
|
|
272
|
-
it('rejects a non-whitelisted
|
|
273
|
-
|
|
274
|
-
expect(() =>
|
|
275
|
-
expect(() =>
|
|
276
|
-
expect(() =>
|
|
277
|
-
expect(() => buildConsumeInviteSQL(pgTable, 'postgresql')).not.toThrow();
|
|
212
|
+
it('rejects a non-whitelisted table identifier (defense-in-depth against injection)', () => {
|
|
213
|
+
expect(() => BuildConsumeInvitePostgresSQL('[__mj].[MagicLinkInvite]')).toThrow();
|
|
214
|
+
expect(() => BuildConsumeInvitePostgresSQL('__mj.MagicLinkInvite; DROP TABLE x;--')).toThrow();
|
|
215
|
+
expect(() => BuildConsumeInvitePostgresSQL('__mj.Magic Link')).toThrow();
|
|
216
|
+
expect(() => BuildConsumeInvitePostgresSQL(pgTable)).not.toThrow();
|
|
278
217
|
});
|
|
279
218
|
});
|
|
280
219
|
|
|
281
220
|
describe('canIssueInvites', () => {
|
|
282
221
|
it('always allows Owners, regardless of issuer-role config (case/space-insensitive)', () => {
|
|
283
|
-
expect(
|
|
284
|
-
expect(
|
|
222
|
+
expect(CanIssueInvites('Owner', [], [])).toBe(true);
|
|
223
|
+
expect(CanIssueInvites(' owner ', [], [])).toBe(true);
|
|
285
224
|
});
|
|
286
225
|
|
|
287
226
|
it('Owner-only by default: a non-Owner with no configured issuer roles is denied', () => {
|
|
288
|
-
expect(
|
|
227
|
+
expect(CanIssueInvites('User', ['Developer', 'Magic Link Baseline'], [])).toBe(false);
|
|
289
228
|
});
|
|
290
229
|
|
|
291
230
|
it('denies an external user holding the restricted role (the escalation we are blocking)', () => {
|
|
292
231
|
// restricted role is never an issuer role, so this stays false even if someone
|
|
293
232
|
// mistakenly leaves issuerRoleNames empty
|
|
294
|
-
expect(
|
|
233
|
+
expect(CanIssueInvites('User', ['Magic Link Baseline'], [])).toBe(false);
|
|
295
234
|
});
|
|
296
235
|
|
|
297
236
|
it('allows a non-Owner only when one of their roles is a configured issuer role', () => {
|
|
298
|
-
expect(
|
|
299
|
-
expect(
|
|
300
|
-
expect(
|
|
237
|
+
expect(CanIssueInvites('User', ['Sales Admin'], ['Sales Admin'])).toBe(true);
|
|
238
|
+
expect(CanIssueInvites('User', ['sales admin'], ['Sales Admin'])).toBe(true); // case-insensitive
|
|
239
|
+
expect(CanIssueInvites('User', ['Marketing'], ['Sales Admin'])).toBe(false);
|
|
301
240
|
});
|
|
302
241
|
|
|
303
242
|
it('handles null/undefined type and empty role lists safely', () => {
|
|
304
|
-
expect(
|
|
305
|
-
expect(
|
|
243
|
+
expect(CanIssueInvites(null, [], ['X'])).toBe(false);
|
|
244
|
+
expect(CanIssueInvites(undefined, ['X'], ['X'])).toBe(true);
|
|
306
245
|
});
|
|
307
246
|
});
|
|
308
247
|
|
|
@@ -310,41 +249,41 @@ describe('magic-link core', () => {
|
|
|
310
249
|
const restricted = 'Magic Link Baseline';
|
|
311
250
|
|
|
312
251
|
it('always allows the restricted role (case/space-insensitive)', () => {
|
|
313
|
-
expect(
|
|
314
|
-
expect(
|
|
252
|
+
expect(IsRoleGrantable('Magic Link Baseline', restricted, [])).toBe(true);
|
|
253
|
+
expect(IsRoleGrantable(' magic link baseline ', restricted, [])).toBe(true);
|
|
315
254
|
});
|
|
316
255
|
|
|
317
256
|
it('rejects a privileged role by default — blocks roleId=Owner escalation', () => {
|
|
318
|
-
expect(
|
|
319
|
-
expect(
|
|
257
|
+
expect(IsRoleGrantable('Owner', restricted, [])).toBe(false);
|
|
258
|
+
expect(IsRoleGrantable('Administrator', restricted, [])).toBe(false);
|
|
320
259
|
});
|
|
321
260
|
|
|
322
261
|
it('allows additional roles only when explicitly opted in', () => {
|
|
323
|
-
expect(
|
|
324
|
-
expect(
|
|
325
|
-
expect(
|
|
262
|
+
expect(IsRoleGrantable('Read Only Guest', restricted, ['Read Only Guest'])).toBe(true);
|
|
263
|
+
expect(IsRoleGrantable('read only guest', restricted, ['Read Only Guest'])).toBe(true);
|
|
264
|
+
expect(IsRoleGrantable('Owner', restricted, ['Read Only Guest'])).toBe(false);
|
|
326
265
|
});
|
|
327
266
|
|
|
328
267
|
it('rejects empty/null role names', () => {
|
|
329
|
-
expect(
|
|
330
|
-
expect(
|
|
268
|
+
expect(IsRoleGrantable('', restricted, [])).toBe(false);
|
|
269
|
+
expect(IsRoleGrantable(null, restricted, [])).toBe(false);
|
|
331
270
|
});
|
|
332
271
|
});
|
|
333
272
|
|
|
334
273
|
describe('escapeHtml', () => {
|
|
335
274
|
it('escapes the five significant HTML characters', () => {
|
|
336
|
-
expect(
|
|
275
|
+
expect(EscapeHtml(`<>&"'`)).toBe('<>&"'');
|
|
337
276
|
});
|
|
338
277
|
|
|
339
278
|
it('leaves a normal magic-link token untouched', () => {
|
|
340
|
-
const t =
|
|
341
|
-
expect(
|
|
279
|
+
const t = GenerateRawToken();
|
|
280
|
+
expect(EscapeHtml(t)).toBe(t);
|
|
342
281
|
});
|
|
343
282
|
});
|
|
344
283
|
|
|
345
284
|
describe('buildRedeemLandingHtml', () => {
|
|
346
|
-
const token =
|
|
347
|
-
const html =
|
|
285
|
+
const token = GenerateRawToken();
|
|
286
|
+
const html = BuildRedeemLandingHtml(token, '/magic-link/redeem');
|
|
348
287
|
|
|
349
288
|
it('renders a POST form to the redeem path (GET stays side-effect-free)', () => {
|
|
350
289
|
expect(html).toContain('method="POST"');
|
|
@@ -365,7 +304,7 @@ describe('magic-link core', () => {
|
|
|
365
304
|
});
|
|
366
305
|
|
|
367
306
|
it('escapes a token containing HTML metacharacters into the form value', () => {
|
|
368
|
-
const evil =
|
|
307
|
+
const evil = BuildRedeemLandingHtml('a"><script>x</script>', '/magic-link/redeem');
|
|
369
308
|
expect(evil).not.toContain('<script>x</script>');
|
|
370
309
|
expect(evil).toContain('<script>');
|
|
371
310
|
});
|
|
@@ -377,7 +316,7 @@ describe('MagicLinkKeyManager', () => {
|
|
|
377
316
|
const km = MagicLinkKeyManager.Instance;
|
|
378
317
|
km.Initialize(); // ephemeral keypair
|
|
379
318
|
|
|
380
|
-
const claims =
|
|
319
|
+
const claims = BuildSessionClaims({
|
|
381
320
|
issuer: 'http://localhost:4051',
|
|
382
321
|
audience: 'mj-magic-link',
|
|
383
322
|
inviteId: 'INVITE-1',
|
|
@@ -414,7 +353,7 @@ describe('MagicLinkKeyManager', () => {
|
|
|
414
353
|
it('rejects a tampered token', () => {
|
|
415
354
|
const km = MagicLinkKeyManager.Instance;
|
|
416
355
|
km.Initialize();
|
|
417
|
-
const claims =
|
|
356
|
+
const claims = BuildSessionClaims({
|
|
418
357
|
issuer: 'http://localhost:4051',
|
|
419
358
|
audience: 'mj-magic-link',
|
|
420
359
|
inviteId: 'INVITE-2',
|