@memberjunction/server 6.2.0-edge.6 → 6.2.0-edge.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/BridgeHostInstance.d.ts +8 -3
- package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -1
- package/dist/agentSessions/BridgeHostInstance.js +14 -4
- package/dist/agentSessions/BridgeHostInstance.js.map +1 -1
- package/dist/agentSessions/HandoffOfferExpiry.d.ts +10 -0
- package/dist/agentSessions/HandoffOfferExpiry.d.ts.map +1 -0
- package/dist/agentSessions/HandoffOfferExpiry.js +18 -0
- package/dist/agentSessions/HandoffOfferExpiry.js.map +1 -0
- package/dist/agentSessions/ReturningVisitorRecap.js +1 -0
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +31 -8
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +85 -41
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +14 -4
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +28 -4
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/conversationBrowsers.d.ts +61 -0
- package/dist/agentSessions/conversationBrowsers.d.ts.map +1 -0
- package/dist/agentSessions/conversationBrowsers.js +118 -0
- package/dist/agentSessions/conversationBrowsers.js.map +1 -0
- package/dist/agentSessions/sessionDeadline.d.ts +43 -0
- package/dist/agentSessions/sessionDeadline.d.ts.map +1 -0
- package/dist/agentSessions/sessionDeadline.js +68 -0
- package/dist/agentSessions/sessionDeadline.js.map +1 -0
- package/dist/auth/APIKeyScopeAuth.d.ts.map +1 -1
- package/dist/auth/APIKeyScopeAuth.js +5 -4
- package/dist/auth/APIKeyScopeAuth.js.map +1 -1
- package/dist/auth/loginEmailResolution.d.ts +41 -0
- package/dist/auth/loginEmailResolution.d.ts.map +1 -0
- package/dist/auth/loginEmailResolution.js +47 -0
- package/dist/auth/loginEmailResolution.js.map +1 -0
- package/dist/auth/magicLink/MagicLinkKeys.d.ts +50 -2
- package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js +129 -12
- package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts +3 -0
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +23 -5
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts +39 -5
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +221 -9
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/magicLink/index.d.ts +1 -1
- package/dist/auth/magicLink/index.d.ts.map +1 -1
- package/dist/auth/magicLink/index.js +1 -1
- package/dist/auth/magicLink/index.js.map +1 -1
- package/dist/auth/magicLink/redeemLanding.d.ts +2 -2
- package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -1
- package/dist/auth/magicLink/redeemLanding.js +7 -4
- package/dist/auth/magicLink/redeemLanding.js.map +1 -1
- package/dist/auth/magicLink/types.d.ts +27 -3
- package/dist/auth/magicLink/types.d.ts.map +1 -1
- package/dist/auth/magicLink/types.js +0 -4
- package/dist/auth/magicLink/types.js.map +1 -1
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +1 -0
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/backgroundSweepConfigUnits.d.ts +231 -0
- package/dist/backgroundSweepConfigUnits.d.ts.map +1 -0
- package/dist/backgroundSweepConfigUnits.js +180 -0
- package/dist/backgroundSweepConfigUnits.js.map +1 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -1
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +2 -1
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -1
- package/dist/config.d.ts +906 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +59 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +41 -12
- package/dist/context.js.map +1 -1
- package/dist/coreRoutes.d.ts +3 -1
- package/dist/coreRoutes.d.ts.map +1 -1
- package/dist/coreRoutes.js +6 -4
- package/dist/coreRoutes.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +1072 -5
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +10174 -4618
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts +11 -0
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +27 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +50 -5
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +329 -132
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +4 -0
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +47 -7
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/globalCors.d.ts.map +1 -1
- package/dist/globalCors.js +1 -0
- package/dist/globalCors.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +201 -43
- package/dist/index.js.map +1 -1
- package/dist/integration/ConnectionPause.d.ts +121 -0
- package/dist/integration/ConnectionPause.d.ts.map +1 -0
- package/dist/integration/ConnectionPause.js +183 -0
- package/dist/integration/ConnectionPause.js.map +1 -0
- package/dist/integration/CustomColumnPromoter.d.ts +72 -6
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +87 -46
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/integration/EntityMapLifecycle.d.ts +78 -4
- package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
- package/dist/integration/EntityMapLifecycle.js +199 -5
- package/dist/integration/EntityMapLifecycle.js.map +1 -1
- package/dist/integration/InactiveRowWarnings.d.ts +31 -0
- package/dist/integration/InactiveRowWarnings.d.ts.map +1 -1
- package/dist/integration/InactiveRowWarnings.js +29 -0
- package/dist/integration/InactiveRowWarnings.js.map +1 -1
- package/dist/integration/ObjectNamesByID.d.ts +28 -0
- package/dist/integration/ObjectNamesByID.d.ts.map +1 -0
- package/dist/integration/ObjectNamesByID.js +87 -0
- package/dist/integration/ObjectNamesByID.js.map +1 -0
- package/dist/integration/PostRestartIntrospect.d.ts +26 -0
- package/dist/integration/PostRestartIntrospect.d.ts.map +1 -0
- package/dist/integration/PostRestartIntrospect.js +27 -0
- package/dist/integration/PostRestartIntrospect.js.map +1 -0
- package/dist/integration/RSUPostRestartProgress.d.ts +176 -0
- package/dist/integration/RSUPostRestartProgress.d.ts.map +1 -0
- package/dist/integration/RSUPostRestartProgress.js +323 -0
- package/dist/integration/RSUPostRestartProgress.js.map +1 -0
- package/dist/integration/RSUProgressBridge.d.ts +41 -2
- package/dist/integration/RSUProgressBridge.d.ts.map +1 -1
- package/dist/integration/RSUProgressBridge.js +84 -1
- package/dist/integration/RSUProgressBridge.js.map +1 -1
- package/dist/integration/RunArtifactAuthorization.d.ts +39 -0
- package/dist/integration/RunArtifactAuthorization.d.ts.map +1 -0
- package/dist/integration/RunArtifactAuthorization.js +45 -0
- package/dist/integration/RunArtifactAuthorization.js.map +1 -0
- package/dist/integration/SchemaRefreshLaunch.d.ts +9 -0
- package/dist/integration/SchemaRefreshLaunch.d.ts.map +1 -1
- package/dist/integration/SchemaRefreshLaunch.js +3 -2
- package/dist/integration/SchemaRefreshLaunch.js.map +1 -1
- package/dist/integration/ScopedFieldsForBuild.d.ts +46 -0
- package/dist/integration/ScopedFieldsForBuild.d.ts.map +1 -0
- package/dist/integration/ScopedFieldsForBuild.js +91 -0
- package/dist/integration/ScopedFieldsForBuild.js.map +1 -0
- package/dist/integration/TargetConfigGuard.d.ts +40 -0
- package/dist/integration/TargetConfigGuard.d.ts.map +1 -0
- package/dist/integration/TargetConfigGuard.js +40 -0
- package/dist/integration/TargetConfigGuard.js.map +1 -0
- package/dist/logging/StartupLogger.d.ts +50 -0
- package/dist/logging/StartupLogger.d.ts.map +1 -1
- package/dist/logging/StartupLogger.js +15 -22
- package/dist/logging/StartupLogger.js.map +1 -1
- package/dist/middleware/BaseServerMiddleware.d.ts +21 -3
- package/dist/middleware/BaseServerMiddleware.d.ts.map +1 -1
- package/dist/middleware/BaseServerMiddleware.js +4 -2
- package/dist/middleware/BaseServerMiddleware.js.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.js +2 -3
- package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
- package/dist/realtimeSessions/sessionAccess.d.ts +15 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
- package/dist/realtimeSessions/sessionAccess.js +22 -0
- package/dist/realtimeSessions/sessionAccess.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js +2 -0
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.js +1 -0
- package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetCore.js +4 -7
- package/dist/realtimeWidget/widgetCore.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/requestOrigin.d.ts +22 -0
- package/dist/requestOrigin.d.ts.map +1 -0
- package/dist/requestOrigin.js +60 -0
- package/dist/requestOrigin.js.map +1 -0
- package/dist/resolvers/AgentRunStatusPublisher.d.ts +10 -0
- package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.js +18 -2
- package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
- package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/AgentSessionResolver.js +6 -2
- package/dist/resolvers/AgentSessionResolver.js.map +1 -1
- package/dist/resolvers/AutotagPipelineResolver.d.ts +1 -1
- package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
- package/dist/resolvers/AutotagPipelineResolver.js +61 -16
- package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.js +2 -0
- package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +6 -0
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.js +30 -7
- package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +34 -13
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/InfoResolver.js +1 -1
- package/dist/resolvers/InfoResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +401 -11
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +2690 -1305
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +13 -2
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts +38 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +181 -88
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +1 -0
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +6 -0
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +36 -5
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +62 -10
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +217 -34
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +24 -8
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +185 -67
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.d.ts +5 -3
- package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.js +19 -18
- package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +3 -1
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +7 -8
- package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
- package/dist/resolvers/TagGovernanceResolver.d.ts +12 -0
- package/dist/resolvers/TagGovernanceResolver.d.ts.map +1 -1
- package/dist/resolvers/TagGovernanceResolver.js +84 -0
- package/dist/resolvers/TagGovernanceResolver.js.map +1 -1
- package/dist/resolvers/TelemetryResolver.d.ts +13 -8
- package/dist/resolvers/TelemetryResolver.d.ts.map +1 -1
- package/dist/resolvers/TelemetryResolver.js +40 -9
- package/dist/resolvers/TelemetryResolver.js.map +1 -1
- package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
- package/dist/resolvers/meetingRecordingRegistration.js +2 -0
- package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.js +34 -4
- package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
- package/dist/rest/MediaAccessKeys.d.ts.map +1 -1
- package/dist/rest/MediaAccessKeys.js +6 -2
- package/dist/rest/MediaAccessKeys.js.map +1 -1
- package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
- package/dist/rest/OAuthCallbackHandler.js +11 -5
- package/dist/rest/OAuthCallbackHandler.js.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
- package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.js +2 -4
- package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +2 -1
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts +3 -1
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +11 -2
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/dist/services/DurableEntityActionTaskSubmitter.d.ts.map +1 -1
- package/dist/services/DurableEntityActionTaskSubmitter.js +6 -1
- package/dist/services/DurableEntityActionTaskSubmitter.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +2 -26
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphAgentRunner.d.ts +6 -0
- package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphAgentRunner.js +38 -0
- package/dist/services/TaskGraphAgentRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +1 -0
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/services/WorkQueueHostService.d.ts +2 -1
- package/dist/services/WorkQueueHostService.d.ts.map +1 -1
- package/dist/services/WorkQueueHostService.js +19 -4
- package/dist/services/WorkQueueHostService.js.map +1 -1
- package/dist/services/workQueueConfig.d.ts +2 -2
- package/dist/services/workQueueConfig.js +5 -5
- package/dist/services/workQueueConfig.js.map +1 -1
- package/dist/sharedCache.js +1 -1
- package/dist/sharedCache.js.map +1 -1
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +55 -2
- package/dist/util.js.map +1 -1
- package/package.json +107 -100
- package/src/__tests__/APIKeyScopeAuth.test.ts +4 -8
- package/src/__tests__/ActiveOperations.test.ts +68 -0
- package/src/__tests__/AgentSessionResolver.ownership.test.ts +13 -0
- package/src/__tests__/AssertTargetConfigs.test.ts +75 -0
- package/src/__tests__/BatchApplyParity.test.ts +109 -0
- package/src/__tests__/BridgeHostInstance.test.ts +42 -0
- package/src/__tests__/CollectInactiveRowWarnings.test.ts +115 -0
- package/src/__tests__/ConnectionPause.test.ts +192 -0
- package/src/__tests__/ConnectionPauseResolver.test.ts +166 -0
- package/src/__tests__/ConnectionPauseSave.test.ts +143 -0
- package/src/__tests__/CustomColumnPromoterCompletion.test.ts +142 -0
- package/src/__tests__/DeleteConnectionRemovesPerConnectionCatalog.test.ts +51 -0
- package/src/__tests__/DetachedDiscoveryStart.test.ts +65 -0
- package/src/__tests__/DurableEntityActionTaskSubmitter.test.ts +73 -0
- package/src/__tests__/EntityMapLifecycle.rekey.test.ts +252 -0
- package/src/__tests__/ExecuteRemoteOperationResolver.test.ts +213 -0
- package/src/__tests__/HandoffOfferExpiry.test.ts +40 -0
- package/src/__tests__/IntegrationRunArtifactAuthorization.test.ts +126 -0
- package/src/__tests__/MagicLinkKeys.test.ts +102 -0
- package/src/__tests__/MediaAccessKeys.test.ts +57 -10
- package/src/__tests__/NewArrivalsDefaultDisabled.test.ts +50 -0
- package/src/__tests__/ObjectNamesByID.test.ts +176 -0
- package/src/__tests__/PostRestartMapsInCatalogScope.test.ts +146 -0
- package/src/__tests__/QuerySystemUserResolver.childRows.test.ts +80 -0
- package/src/__tests__/QuerySystemUserResolver.collision.test.ts +200 -0
- package/src/__tests__/QuerySystemUserResolver.permissions.test.ts +132 -0
- package/src/__tests__/RSUPostRestartProgress.test.ts +343 -0
- package/src/__tests__/RSUProgressBridge.test.ts +188 -5
- package/src/__tests__/RSUStartupSweepWiring.test.ts +30 -0
- package/src/__tests__/ReactivateRefreshMessage.test.ts +10 -3
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +22 -0
- package/src/__tests__/{RefreshAdoptsNewColumns.test.ts → RefreshReportsNewColumns.test.ts} +25 -16
- package/src/__tests__/RemoteBrowserActionRecovery.test.ts +287 -0
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +3 -0
- package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +6 -4
- package/src/__tests__/RemoteBrowserAudioStream.test.ts +3 -0
- package/src/__tests__/RemoteBrowserSnapshot.test.ts +3 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +327 -26
- package/src/__tests__/ResolverBase.diagnosticSenderRegistrations.test.ts +26 -0
- package/src/__tests__/ResolverBase.encryptedApiZeroReencrypt.test.ts +307 -0
- package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +53 -2
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +60 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +4 -0
- package/src/__tests__/RunAIAgentResolver.consultPredecessor.test.ts +289 -0
- package/src/__tests__/RunAIAgentResolver.handoff.test.ts +649 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +2 -0
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +2 -0
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +87 -12
- package/src/__tests__/RunAIAgentResolver.streaming.test.ts +28 -4
- package/src/__tests__/SchemaEvolutionCustomColumns.test.ts +119 -0
- package/src/__tests__/SchemaRefreshLaunch.test.ts +24 -2
- package/src/__tests__/ScopedFieldsForBuild.test.ts +192 -0
- package/src/__tests__/SessionJanitor.test.ts +195 -4
- package/src/__tests__/SessionManager.test.ts +69 -0
- package/src/__tests__/TagGovernanceAndAutotagResolvers.test.ts +261 -0
- package/src/__tests__/TagGovernanceResolver.test.ts +60 -2
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +2 -2
- package/src/__tests__/TaskGraphAgentRunner.test.ts +272 -16
- package/src/__tests__/ViewOperationsHandler.test.ts +2 -5
- package/src/__tests__/WorkQueueHostService.test.ts +25 -7
- package/src/__tests__/backgroundSweepConfigUnits.test.ts +153 -0
- package/src/__tests__/contextRequestOrigin.test.ts +105 -0
- package/src/__tests__/conversationBrowsers.test.ts +62 -0
- package/src/__tests__/globalCors.test.ts +12 -0
- package/src/__tests__/magicLinkExtensionCapabilities.test.ts +413 -0
- package/src/__tests__/orphanedConversationDetailReconciler.start.test.ts +82 -0
- package/src/__tests__/ownedRealtimeSession.test.ts +14 -0
- package/src/__tests__/realtimeRecordingHandler.test.ts +7 -2
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +79 -1
- package/src/__tests__/requestOrigin.test.ts +104 -0
- package/src/__tests__/review-capture-valid-token-log.test.ts +98 -0
- package/src/__tests__/runStreams.fieldSecurity.test.ts +5 -5
- package/src/__tests__/securityHardeningPr6034.test.ts +120 -0
- package/src/__tests__/sessionDeadline.test.ts +51 -0
- package/src/__tests__/sharedCache.test.ts +11 -0
- package/src/__tests__/unifiedAuth.test.ts +73 -1
- package/src/agentSessions/BridgeHostInstance.ts +20 -5
- package/src/agentSessions/HandoffOfferExpiry.ts +18 -0
- package/src/agentSessions/ReturningVisitorRecap.ts +1 -0
- package/src/agentSessions/SessionJanitor.ts +92 -42
- package/src/agentSessions/SessionManager.ts +38 -8
- package/src/agentSessions/conversationBrowsers.ts +143 -0
- package/src/agentSessions/sessionDeadline.ts +81 -0
- package/src/auth/APIKeyScopeAuth.ts +5 -4
- package/src/auth/__tests__/loginEmailResolution.test.ts +67 -0
- package/src/auth/loginEmailResolution.ts +67 -0
- package/src/auth/magicLink/MagicLinkKeys.ts +146 -14
- package/src/auth/magicLink/MagicLinkRouter.ts +33 -6
- package/src/auth/magicLink/MagicLinkService.ts +250 -10
- package/src/auth/magicLink/index.ts +1 -1
- package/src/auth/magicLink/redeemLanding.ts +7 -4
- package/src/auth/magicLink/types.ts +28 -3
- package/src/auth/newUsers.ts +1 -0
- package/src/backgroundSweepConfigUnits.ts +213 -0
- package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +2 -1
- package/src/config.ts +69 -0
- package/src/context.ts +46 -15
- package/src/coreRoutes.ts +14 -7
- package/src/generated/graphql-schemas/__mj.ts +10360 -6479
- package/src/generic/OrphanedConversationDetailReconciler.ts +32 -1
- package/src/generic/ResolverBase.ts +422 -143
- package/src/generic/RunViewResolver.ts +96 -49
- package/src/globalCors.ts +1 -0
- package/src/index.ts +246 -44
- package/src/integration/ConnectionPause.ts +222 -0
- package/src/integration/CustomColumnPromoter.ts +123 -53
- package/src/integration/EntityMapLifecycle.ts +229 -6
- package/src/integration/InactiveRowWarnings.ts +50 -0
- package/src/integration/ObjectNamesByID.ts +97 -0
- package/src/integration/PostRestartIntrospect.ts +49 -0
- package/src/integration/RSUPostRestartProgress.ts +359 -0
- package/src/integration/RSUProgressBridge.ts +85 -2
- package/src/integration/RunArtifactAuthorization.ts +65 -0
- package/src/integration/SchemaRefreshLaunch.ts +12 -2
- package/src/integration/ScopedFieldsForBuild.ts +113 -0
- package/src/integration/TargetConfigGuard.ts +71 -0
- package/src/logging/StartupLogger.ts +24 -0
- package/src/middleware/BaseServerMiddleware.ts +20 -3
- package/src/realtimeSessions/ownedRealtimeSession.ts +2 -6
- package/src/realtimeSessions/sessionAccess.ts +30 -0
- package/src/realtimeWidget/WidgetSessionService.ts +2 -0
- package/src/realtimeWidget/visitorIdentity.ts +1 -0
- package/src/realtimeWidget/widgetCore.ts +4 -8
- package/src/realtimeWidget/widgetGuestElevation.ts +1 -0
- package/src/requestOrigin.ts +64 -0
- package/src/resolvers/AgentRunStatusPublisher.ts +27 -2
- package/src/resolvers/AgentSessionResolver.ts +6 -2
- package/src/resolvers/AutotagPipelineResolver.ts +59 -14
- package/src/resolvers/ConversationRunEventsResolver.ts +2 -0
- package/src/resolvers/ExecuteRemoteOperationResolver.ts +41 -9
- package/src/resolvers/GetDataResolver.ts +35 -13
- package/src/resolvers/InfoResolver.ts +1 -1
- package/src/resolvers/IntegrationDiscoveryResolver.ts +1522 -203
- package/src/resolvers/MCPResolver.ts +15 -2
- package/src/resolvers/QuerySystemUserResolver.ts +211 -100
- package/src/resolvers/RealtimeBridgeResolver.ts +1 -0
- package/src/resolvers/RealtimeClientSessionResolver.ts +40 -7
- package/src/resolvers/RemoteBrowserActionResolver.ts +242 -32
- package/src/resolvers/RunAIAgentResolver.ts +265 -86
- package/src/resolvers/RunAIPromptResolver.ts +18 -18
- package/src/resolvers/RunTemplateResolver.ts +3 -1
- package/src/resolvers/SqlLoggingConfigResolver.ts +7 -9
- package/src/resolvers/TagGovernanceResolver.ts +65 -0
- package/src/resolvers/TelemetryResolver.ts +44 -9
- package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +52 -0
- package/src/resolvers/meetingRecordingRegistration.ts +2 -0
- package/src/resolvers/meetings/meetingMutations.ts +37 -7
- package/src/rest/MediaAccessKeys.ts +6 -2
- package/src/rest/OAuthCallbackHandler.ts +13 -5
- package/src/rest/RealtimeRecordingHandler.ts +5 -9
- package/src/rest/ViewOperationsHandler.ts +2 -1
- package/src/rest/setupRESTEndpoints.ts +11 -2
- package/src/services/DurableEntityActionTaskSubmitter.ts +10 -2
- package/src/services/TaskGraphActionRunner.ts +2 -23
- package/src/services/TaskGraphAgentRunner.ts +43 -0
- package/src/services/TaskGraphContinuationDeliverer.ts +1 -0
- package/src/services/WorkQueueHostService.ts +26 -4
- package/src/services/workQueueConfig.ts +5 -5
- package/src/sharedCache.ts +1 -1
- package/src/types.ts +18 -0
- package/src/util.ts +55 -3
- package/src/services/TaskOrchestration-Integration.md +0 -188
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { DescribeUserEmailMap, EmailsMatch, ResolveUserLookupEmail } from '../loginEmailResolution.js';
|
|
3
|
+
|
|
4
|
+
describe('ResolveUserLookupEmail', () => {
|
|
5
|
+
it('looks a login up by its email claim when no map is set', () => {
|
|
6
|
+
expect(ResolveUserLookupEmail({ email: 'b@two.example', preferredUsername: 'b@two.example' }, undefined)).toEqual({
|
|
7
|
+
TokenEmail: 'b@two.example',
|
|
8
|
+
LookupEmail: 'b@two.example',
|
|
9
|
+
Remapped: false,
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('applies a USER_EMAIL_MAP entry and reports the rewrite', () => {
|
|
14
|
+
const result = ResolveUserLookupEmail({ email: 'b@two.example' }, { 'b@two.example': 'a@one.example' });
|
|
15
|
+
expect(result).toEqual({ TokenEmail: 'b@two.example', LookupEmail: 'a@one.example', Remapped: true });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('keeps the map key match exact (case-sensitive), as before', () => {
|
|
19
|
+
const result = ResolveUserLookupEmail({ email: 'B@two.example' }, { 'b@two.example': 'a@one.example' });
|
|
20
|
+
expect(result.LookupEmail).toBe('B@two.example');
|
|
21
|
+
expect(result.Remapped).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('does not count an entry that only changes case as a remap', () => {
|
|
25
|
+
const result = ResolveUserLookupEmail({ email: 'b@two.example' }, { 'b@two.example': 'B@Two.example' });
|
|
26
|
+
expect(result.LookupEmail).toBe('B@Two.example');
|
|
27
|
+
expect(result.Remapped).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('falls back to the preferred username and never remaps it', () => {
|
|
31
|
+
const result = ResolveUserLookupEmail({ preferredUsername: 'b@two.example' }, { 'b@two.example': 'a@one.example' });
|
|
32
|
+
expect(result).toEqual({ TokenEmail: 'b@two.example', LookupEmail: 'b@two.example', Remapped: false });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('returns no identity when the token has neither claim', () => {
|
|
36
|
+
expect(ResolveUserLookupEmail({}, undefined)).toEqual({ TokenEmail: undefined, LookupEmail: undefined, Remapped: false });
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('EmailsMatch', () => {
|
|
41
|
+
it('ignores case and surrounding whitespace, like the User lookup', () => {
|
|
42
|
+
expect(EmailsMatch(' A@One.example ', 'a@one.example')).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('treats a missing side as no match', () => {
|
|
46
|
+
expect(EmailsMatch(undefined, 'a@one.example')).toBe(false);
|
|
47
|
+
expect(EmailsMatch('a@one.example', '')).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('DescribeUserEmailMap', () => {
|
|
52
|
+
it('returns null when no map is configured', () => {
|
|
53
|
+
expect(DescribeUserEmailMap(undefined)).toBeNull();
|
|
54
|
+
expect(DescribeUserEmailMap({})).toBeNull();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('returns null when every entry maps an address to itself', () => {
|
|
58
|
+
expect(DescribeUserEmailMap({ 'a@one.example': 'A@one.example' })).toBeNull();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('lists every entry that sends a login to another record', () => {
|
|
62
|
+
const warning = DescribeUserEmailMap({ 'b@two.example': 'a@one.example', 'c@three.example': 'a@one.example' });
|
|
63
|
+
expect(warning).toContain('2 login email(s)');
|
|
64
|
+
expect(warning).toContain('b@two.example -> a@one.example');
|
|
65
|
+
expect(warning).toContain('c@three.example -> a@one.example');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which email a verified login is matched to a User record by, and whether that differs from the
|
|
3
|
+
* identity the token carries.
|
|
4
|
+
*
|
|
5
|
+
* The server links a login to a User row by email alone. `USER_EMAIL_MAP` can rewrite that email
|
|
6
|
+
* before the lookup, which makes a login run as a different person's record. That is a legitimate
|
|
7
|
+
* development aid, but it is silent, so a misconfigured map on a shared machine looks exactly like
|
|
8
|
+
* an authentication bug. These helpers keep the token's identity next to the one actually used, so
|
|
9
|
+
* the session audit and the server log can say when the two differ.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** The identity fields of a verified token that the lookup can use. */
|
|
13
|
+
export interface LoginIdentityClaims {
|
|
14
|
+
email?: string; // case-violation-ok-legacy-back-compat: mirrors the provider's extracted user info shape
|
|
15
|
+
preferredUsername?: string; // case-violation-ok-legacy-back-compat: mirrors the provider's extracted user info shape
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The email the token carries and the email the User lookup will use. */
|
|
19
|
+
export interface LoginEmailResolution {
|
|
20
|
+
/** The identity from the verified token: its email claim, or its preferred username when it has none. */
|
|
21
|
+
TokenEmail: string | undefined;
|
|
22
|
+
/** The email the User record is looked up by, after any `USER_EMAIL_MAP` rewrite. */
|
|
23
|
+
LookupEmail: string | undefined;
|
|
24
|
+
/** True when `USER_EMAIL_MAP` replaced the token's email with a different one. */
|
|
25
|
+
Remapped: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolves the lookup email for a verified token. The map applies only to the email claim, with an
|
|
30
|
+
* exact (case-sensitive) key match, as it always has; a preferred username is never remapped.
|
|
31
|
+
*
|
|
32
|
+
* Named without "login"/"auth"/"verify": CodeQL treats calls with those words as authentication
|
|
33
|
+
* actions and reports the `if (!token)` guard above this call in GetUserPayload as a user-controlled
|
|
34
|
+
* bypass. This is a plain lookup, not an authentication step.
|
|
35
|
+
*/
|
|
36
|
+
export function ResolveUserLookupEmail(claims: LoginIdentityClaims, emailMap: Record<string, string> | undefined): LoginEmailResolution {
|
|
37
|
+
if (!claims.email) {
|
|
38
|
+
return { TokenEmail: claims.preferredUsername, LookupEmail: claims.preferredUsername, Remapped: false };
|
|
39
|
+
}
|
|
40
|
+
const mapped = emailMap?.[claims.email];
|
|
41
|
+
const lookupEmail = mapped ?? claims.email;
|
|
42
|
+
return { TokenEmail: claims.email, LookupEmail: lookupEmail, Remapped: mapped !== undefined && !EmailsMatch(mapped, claims.email) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Case-insensitive, whitespace-trimmed comparison, matching how the User lookup compares emails. */
|
|
46
|
+
export function EmailsMatch(a: string | undefined, b: string | undefined): boolean {
|
|
47
|
+
if (!a || !b) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The startup warning for a configured `USER_EMAIL_MAP`, or null when the map is absent or rewrites
|
|
55
|
+
* nothing. Lists every entry so whoever starts the server can see whose record each login will use.
|
|
56
|
+
*/
|
|
57
|
+
export function DescribeUserEmailMap(emailMap: Record<string, string> | undefined): string | null {
|
|
58
|
+
const entries = Object.entries(emailMap ?? {}).filter(([from, to]) => !EmailsMatch(from, to));
|
|
59
|
+
if (entries.length === 0) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const lines = entries.map(([from, to]) => ` ${from} -> ${to}`).join('\n');
|
|
63
|
+
return (
|
|
64
|
+
`[Auth] USER_EMAIL_MAP is set: ${entries.length} login email(s) will be matched to a different user's record. ` +
|
|
65
|
+
`A login with the first address runs as the user with the second. Remove the setting unless this is intended.\n${lines}`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -10,11 +10,28 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { BaseSingleton } from '@memberjunction/global';
|
|
13
|
-
import { LogStatusEx } from '@memberjunction/core';
|
|
13
|
+
import { LogStatusEx, LogWarning } from '@memberjunction/core';
|
|
14
14
|
import { createPrivateKey, createPublicKey, generateKeyPairSync, createHash, type KeyObject } from 'node:crypto';
|
|
15
15
|
import jwt from 'jsonwebtoken';
|
|
16
16
|
import type { MagicLinkJWTClaims } from './types.js';
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* True when the process is NOT running with `NODE_ENV=production`. This is the same
|
|
20
|
+
* production/local-dev line MJServer already uses to forbid local dev auth in production.
|
|
21
|
+
*/
|
|
22
|
+
export function IsMagicLinkLocalDevEnvironment(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
23
|
+
return env.NODE_ENV?.trim().toLowerCase() !== 'production';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Warning logged when a non-local-dev server falls back to an ephemeral keypair. */
|
|
27
|
+
export const EPHEMERAL_MAGIC_LINK_KEY_WARNING =
|
|
28
|
+
'[MagicLink] No rsaPrivateKey configured and NODE_ENV=production — generated an EPHEMERAL RS256 keypair. ' +
|
|
29
|
+
'Magic-link session tokens signed with it will stop validating when this process restarts, and they will not ' +
|
|
30
|
+
'validate on any other MJAPI instance behind the same load balancer, because each process generates its own key. ' +
|
|
31
|
+
'To fix, generate one RSA private key (e.g. `openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048`) and set it ' +
|
|
32
|
+
'as magicLink.rsaPrivateKey in mj.config.cjs or as MJ_MAGIC_LINK_PRIVATE_KEY (raw or base64-encoded PEM), ' +
|
|
33
|
+
'using the same key on every instance.';
|
|
34
|
+
|
|
18
35
|
/** A single JSON Web Key plus the metadata jwks-rsa needs to match it. */
|
|
19
36
|
interface PublishedJWK extends JsonWebKey {
|
|
20
37
|
kid: string;
|
|
@@ -34,6 +51,7 @@ export class MagicLinkKeyManager extends BaseSingleton<MagicLinkKeyManager> {
|
|
|
34
51
|
private publicKey?: KeyObject;
|
|
35
52
|
private _kid = '';
|
|
36
53
|
private _initialized = false;
|
|
54
|
+
private _isEphemeral = false;
|
|
37
55
|
|
|
38
56
|
// Public to satisfy BaseSingleton.getInstance's `this: new () => T` constraint
|
|
39
57
|
// (matches the AuthProviderFactory singleton pattern).
|
|
@@ -49,6 +67,15 @@ export class MagicLinkKeyManager extends BaseSingleton<MagicLinkKeyManager> {
|
|
|
49
67
|
return this._initialized;
|
|
50
68
|
}
|
|
51
69
|
|
|
70
|
+
/**
|
|
71
|
+
* True when no private key was configured and the keypair was generated for this process
|
|
72
|
+
* only. Tokens signed with an ephemeral key do not survive a restart and do not validate on
|
|
73
|
+
* other instances. Downstream code should ask this rather than reading the raw config value.
|
|
74
|
+
*/
|
|
75
|
+
public get IsEphemeral(): boolean {
|
|
76
|
+
return this._isEphemeral;
|
|
77
|
+
}
|
|
78
|
+
|
|
52
79
|
public get Kid(): string {
|
|
53
80
|
return this._kid;
|
|
54
81
|
}
|
|
@@ -56,8 +83,11 @@ export class MagicLinkKeyManager extends BaseSingleton<MagicLinkKeyManager> {
|
|
|
56
83
|
/**
|
|
57
84
|
* Initializes the keypair from a provided PEM (raw or base64-encoded) or, if
|
|
58
85
|
* none is supplied, generates an ephemeral 2048-bit RSA keypair. Idempotent.
|
|
86
|
+
*
|
|
87
|
+
* @param env - process environment used to decide whether the ephemeral fallback is a local-dev
|
|
88
|
+
* default (verbose-only notice) or a production misconfiguration (warning). Defaults to `process.env`.
|
|
59
89
|
*/
|
|
60
|
-
public Initialize(privateKeyPem?: string): void {
|
|
90
|
+
public Initialize(privateKeyPem?: string, env: NodeJS.ProcessEnv = process.env): void {
|
|
61
91
|
if (this._initialized) {
|
|
62
92
|
return;
|
|
63
93
|
}
|
|
@@ -69,17 +99,8 @@ export class MagicLinkKeyManager extends BaseSingleton<MagicLinkKeyManager> {
|
|
|
69
99
|
} else {
|
|
70
100
|
const { privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
|
|
71
101
|
priv = privateKey;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// the default startup output. Deployments that care set the key; operators diagnosing
|
|
75
|
-
// magic-link session loss can re-run with verbose logging.
|
|
76
|
-
LogStatusEx({
|
|
77
|
-
message:
|
|
78
|
-
'[MagicLink] No rsaPrivateKey configured — generated an EPHEMERAL RS256 keypair. ' +
|
|
79
|
-
'Outstanding magic-link sessions will be invalidated on restart. Set magicLink.rsaPrivateKey ' +
|
|
80
|
-
'(or MJ_MAGIC_LINK_PRIVATE_KEY) for stable production keys.',
|
|
81
|
-
verboseOnly: true
|
|
82
|
-
});
|
|
102
|
+
this._isEphemeral = true;
|
|
103
|
+
this.reportEphemeralKey(env);
|
|
83
104
|
}
|
|
84
105
|
|
|
85
106
|
this.privateKeyPem = priv.export({ type: 'pkcs8', format: 'pem' }) as string;
|
|
@@ -92,11 +113,103 @@ export class MagicLinkKeyManager extends BaseSingleton<MagicLinkKeyManager> {
|
|
|
92
113
|
* Signs a set of magic-link (or local-dev) claims and returns the compact JWT.
|
|
93
114
|
* `iat`/`exp` are expected to already be set on the claims.
|
|
94
115
|
*/
|
|
95
|
-
public Sign(claims:
|
|
116
|
+
public Sign<T extends object>(claims: T): string {
|
|
96
117
|
this.ensureInitialized();
|
|
97
118
|
return jwt.sign(claims, this.privateKeyPem, { algorithm: 'RS256', keyid: this._kid });
|
|
98
119
|
}
|
|
99
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Verifies a magic-link session JWT signed by this key manager (`RS256`, `mj_magic_link === true`).
|
|
123
|
+
* Returns `null` if invalid, expired, or forged.
|
|
124
|
+
*/
|
|
125
|
+
public VerifySessionToken(
|
|
126
|
+
token: string,
|
|
127
|
+
options?: { issuer?: string; audience?: string },
|
|
128
|
+
): MagicLinkJWTClaims | null {
|
|
129
|
+
this.ensureInitialized();
|
|
130
|
+
try {
|
|
131
|
+
const publicPem = this.publicKey!.export({ type: 'spki', format: 'pem' }) as string;
|
|
132
|
+
const verifyOptions: jwt.VerifyOptions = { algorithms: ['RS256'] };
|
|
133
|
+
if (options?.issuer) {
|
|
134
|
+
verifyOptions.issuer = options.issuer;
|
|
135
|
+
}
|
|
136
|
+
if (options?.audience) {
|
|
137
|
+
verifyOptions.audience = options.audience;
|
|
138
|
+
}
|
|
139
|
+
const decoded = jwt.verify(token, publicPem, verifyOptions);
|
|
140
|
+
if (!decoded || typeof decoded !== 'object') {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const claims = decoded as MagicLinkJWTClaims;
|
|
144
|
+
if (claims.mj_magic_link !== true || typeof claims.sub !== 'string') {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return claims;
|
|
148
|
+
} catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Signs a redirect binding JWT (`mj_purpose = 'magic-link-redirect'`) that cryptographically
|
|
155
|
+
* binds a validated `redirectUrl` to a specific invite's `tokenHash`.
|
|
156
|
+
*
|
|
157
|
+
* Note: Expiration is intentionally omitted from the redirect binding JWT because the database
|
|
158
|
+
* invite row's `ExpiresAt` (enforced atomically during `RedeemInvite`) is the authoritative,
|
|
159
|
+
* mutable expiration — allowing `UpdateInviteExpiry()` to extend an invite without invalidating
|
|
160
|
+
* an already-delivered `&rt=` parameter.
|
|
161
|
+
*/
|
|
162
|
+
public SignRedirectBinding(tokenHash: string, redirectUrl: string, _expiresAtEpochSeconds?: number): string {
|
|
163
|
+
this.ensureInitialized();
|
|
164
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
165
|
+
return jwt.sign(
|
|
166
|
+
{
|
|
167
|
+
mj_purpose: 'magic-link-redirect',
|
|
168
|
+
mj_th: tokenHash,
|
|
169
|
+
mj_redirect: redirectUrl,
|
|
170
|
+
iat: nowSeconds,
|
|
171
|
+
},
|
|
172
|
+
this.privateKeyPem,
|
|
173
|
+
{ algorithm: 'RS256', keyid: this._kid },
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Verifies a redirect binding JWT produced by {@link SignRedirectBinding} and ensures it matches
|
|
179
|
+
* `expectedTokenHash` (preventing cross-invite substitution) and carries a valid `http(s)` URL.
|
|
180
|
+
* Ignores JWT `exp` so bindings minted before an `UpdateInviteExpiry()` extension remain valid
|
|
181
|
+
* while the underlying invite row is valid. Returns the bound URL or `null`.
|
|
182
|
+
*/
|
|
183
|
+
public VerifyRedirectBinding(bindingToken: string, expectedTokenHash: string): string | null {
|
|
184
|
+
this.ensureInitialized();
|
|
185
|
+
try {
|
|
186
|
+
const publicPem = this.publicKey!.export({ type: 'spki', format: 'pem' }) as string;
|
|
187
|
+
const decoded = jwt.verify(bindingToken, publicPem, {
|
|
188
|
+
algorithms: ['RS256'],
|
|
189
|
+
ignoreExpiration: true,
|
|
190
|
+
});
|
|
191
|
+
if (!decoded || typeof decoded !== 'object') {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
const record = decoded as { mj_purpose?: string; mj_th?: string; mj_redirect?: string };
|
|
195
|
+
if (
|
|
196
|
+
record.mj_purpose !== 'magic-link-redirect' ||
|
|
197
|
+
record.mj_th !== expectedTokenHash ||
|
|
198
|
+
typeof record.mj_redirect !== 'string' ||
|
|
199
|
+
!record.mj_redirect
|
|
200
|
+
) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
const parsed = new URL(record.mj_redirect);
|
|
204
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
return record.mj_redirect;
|
|
208
|
+
} catch {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
100
213
|
/**
|
|
101
214
|
* Returns the JWKS document (public key set) for the JWKS endpoint.
|
|
102
215
|
*/
|
|
@@ -106,6 +219,25 @@ export class MagicLinkKeyManager extends BaseSingleton<MagicLinkKeyManager> {
|
|
|
106
219
|
return { keys: [{ ...jwk, kid: this._kid, use: 'sig', alg: 'RS256' }] };
|
|
107
220
|
}
|
|
108
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Local dev (NODE_ENV not 'production'): the ephemeral fallback is the designed behavior and
|
|
224
|
+
* fires on every boot, so the notice stays verbose-only. Otherwise it is a data-loss-on-restart
|
|
225
|
+
* misconfiguration, so it is a warning in the default output.
|
|
226
|
+
*/
|
|
227
|
+
private reportEphemeralKey(env: NodeJS.ProcessEnv): void {
|
|
228
|
+
if (!IsMagicLinkLocalDevEnvironment(env)) {
|
|
229
|
+
LogWarning(EPHEMERAL_MAGIC_LINK_KEY_WARNING);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
LogStatusEx({
|
|
233
|
+
message:
|
|
234
|
+
'[MagicLink] No rsaPrivateKey configured — generated an EPHEMERAL RS256 keypair. ' +
|
|
235
|
+
'Outstanding magic-link sessions will be invalidated on restart. Set magicLink.rsaPrivateKey ' +
|
|
236
|
+
'(or MJ_MAGIC_LINK_PRIVATE_KEY) for stable production keys.',
|
|
237
|
+
verboseOnly: true
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
109
241
|
private ensureInitialized(): void {
|
|
110
242
|
if (!this._initialized) {
|
|
111
243
|
throw new Error('MagicLinkKeyManager not initialized — call Initialize() during server startup.');
|
|
@@ -32,6 +32,7 @@ export const MAGIC_LINK_JWKS_PATH = `${MAGIC_LINK_MOUNT_PATH}/jwks.json`;
|
|
|
32
32
|
export function CreateMagicLinkHandler(publicUrl: string, config: MagicLinkConfig): {
|
|
33
33
|
publicRouter: Router;
|
|
34
34
|
authenticatedRouter: Router;
|
|
35
|
+
service: MagicLinkService;
|
|
35
36
|
} {
|
|
36
37
|
// Initialize the signing keypair once.
|
|
37
38
|
MagicLinkKeyManager.Instance.Initialize(config.rsaPrivateKey);
|
|
@@ -64,10 +65,27 @@ export function CreateMagicLinkHandler(publicUrl: string, config: MagicLinkConfi
|
|
|
64
65
|
res.status(200).json(MagicLinkKeyManager.Instance.GetJWKS());
|
|
65
66
|
});
|
|
66
67
|
|
|
67
|
-
// Sends a completed redemption result: browser flow → 302 into
|
|
68
|
+
// Sends a completed redemption result: browser flow → 302 into a verified per-invite
|
|
69
|
+
// RedirectUrl (when cryptographically bound + origin-allowed) or Explorer with
|
|
68
70
|
// the session token in the URL fragment (never sent to a server, so it stays
|
|
69
71
|
// out of access/proxy logs); API flow → JSON with an appropriate status.
|
|
70
|
-
const sendRedeemResult = (
|
|
72
|
+
const sendRedeemResult = (
|
|
73
|
+
res: Response,
|
|
74
|
+
result: RedeemMagicLinkResult,
|
|
75
|
+
wantsJson: boolean,
|
|
76
|
+
verifiedRedirectUrl?: string,
|
|
77
|
+
): void => {
|
|
78
|
+
if (verifiedRedirectUrl && !wantsJson) {
|
|
79
|
+
const baseWithoutHash = verifiedRedirectUrl.split('#')[0];
|
|
80
|
+
if (result.success && result.token) {
|
|
81
|
+
res.redirect(302, `${baseWithoutHash}#token=${encodeURIComponent(result.token)}`);
|
|
82
|
+
} else {
|
|
83
|
+
const sep = baseWithoutHash.includes('?') ? '&' : '?';
|
|
84
|
+
res.redirect(302, `${baseWithoutHash}${sep}mlError=${encodeURIComponent(result.errorCode ?? 'invalid')}`);
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
71
89
|
const explorerUrl = config.explorerUrl?.replace(/\/$/, '');
|
|
72
90
|
if (explorerUrl && !wantsJson) {
|
|
73
91
|
if (result.success && result.token) {
|
|
@@ -102,6 +120,7 @@ export function CreateMagicLinkHandler(publicUrl: string, config: MagicLinkConfi
|
|
|
102
120
|
// the actual redemption happens only on POST (below), gated by a user click.
|
|
103
121
|
publicRouter.get('/redeem', redeemLimiter, (req: Request, res: Response) => {
|
|
104
122
|
const token = typeof req.query.token === 'string' ? req.query.token : '';
|
|
123
|
+
const rt = typeof req.query.rt === 'string' ? req.query.rt : undefined;
|
|
105
124
|
if (!token) {
|
|
106
125
|
res.status(400).json({ success: false, error: 'Missing token.' });
|
|
107
126
|
return;
|
|
@@ -114,16 +133,19 @@ export function CreateMagicLinkHandler(publicUrl: string, config: MagicLinkConfi
|
|
|
114
133
|
res
|
|
115
134
|
.status(200)
|
|
116
135
|
.type('html')
|
|
117
|
-
.send(BuildRedeemLandingHtml(token, ApiRoute(`${MAGIC_LINK_MOUNT_PATH}/redeem`)));
|
|
136
|
+
.send(BuildRedeemLandingHtml(token, ApiRoute(`${MAGIC_LINK_MOUNT_PATH}/redeem`), rt));
|
|
118
137
|
});
|
|
119
138
|
|
|
120
139
|
// POST /redeem performs the actual (side-effectful) redemption. Token arrives
|
|
121
140
|
// from the interstitial form body, an API client's JSON body, or the query.
|
|
122
141
|
publicRouter.post('/redeem', redeemLimiter, urlencoded({ extended: false }), json(), async (req: Request, res: Response) => {
|
|
123
|
-
const body = (req.body ?? {}) as { token?: string };
|
|
142
|
+
const body = (req.body ?? {}) as { token?: string; rt?: string };
|
|
124
143
|
const token =
|
|
125
144
|
(typeof body.token === 'string' && body.token) ||
|
|
126
145
|
(typeof req.query.token === 'string' ? req.query.token : '');
|
|
146
|
+
const rt =
|
|
147
|
+
(typeof body.rt === 'string' && body.rt) ||
|
|
148
|
+
(typeof req.query.rt === 'string' ? req.query.rt : undefined);
|
|
127
149
|
const wantsJson = req.query.format === 'json' || req.is('application/json') === 'application/json';
|
|
128
150
|
|
|
129
151
|
if (!token) {
|
|
@@ -131,6 +153,8 @@ export function CreateMagicLinkHandler(publicUrl: string, config: MagicLinkConfi
|
|
|
131
153
|
return;
|
|
132
154
|
}
|
|
133
155
|
|
|
156
|
+
const verifiedRedirectUrl = service.ResolveVerifiedRedirectUrl(token, rt);
|
|
157
|
+
|
|
134
158
|
// Forensic context for the redemption audit trail. `req.ip` honors the app's
|
|
135
159
|
// trust-proxy setting; headers are best-effort and may be absent for API clients.
|
|
136
160
|
const result = await service.RedeemInvite(token, {
|
|
@@ -138,7 +162,7 @@ export function CreateMagicLinkHandler(publicUrl: string, config: MagicLinkConfi
|
|
|
138
162
|
userAgent: req.get('user-agent') ?? undefined,
|
|
139
163
|
origin: req.get('origin') ?? undefined,
|
|
140
164
|
});
|
|
141
|
-
sendRedeemResult(res, result, wantsJson);
|
|
165
|
+
sendRedeemResult(res, result, wantsJson, verifiedRedirectUrl);
|
|
142
166
|
});
|
|
143
167
|
|
|
144
168
|
// ── Authenticated (invite creation) ───────────────────────────────────────
|
|
@@ -166,6 +190,8 @@ export function CreateMagicLinkHandler(publicUrl: string, config: MagicLinkConfi
|
|
|
166
190
|
MaxUses: body.MaxUses,
|
|
167
191
|
firstName: body.firstName,
|
|
168
192
|
lastName: body.lastName,
|
|
193
|
+
RedirectUrl: body.RedirectUrl,
|
|
194
|
+
SendEmail: body.SendEmail,
|
|
169
195
|
},
|
|
170
196
|
creatingUser,
|
|
171
197
|
);
|
|
@@ -175,13 +201,14 @@ export function CreateMagicLinkHandler(publicUrl: string, config: MagicLinkConfi
|
|
|
175
201
|
res.status(status).json(result);
|
|
176
202
|
});
|
|
177
203
|
|
|
178
|
-
return { publicRouter, authenticatedRouter };
|
|
204
|
+
return { publicRouter, authenticatedRouter, service };
|
|
179
205
|
}
|
|
180
206
|
|
|
181
207
|
/** @deprecated Use {@link CreateMagicLinkHandler}. */
|
|
182
208
|
export function createMagicLinkHandler(publicUrl: string, config: MagicLinkConfig): {
|
|
183
209
|
publicRouter: Router;
|
|
184
210
|
authenticatedRouter: Router;
|
|
211
|
+
service: MagicLinkService;
|
|
185
212
|
} {
|
|
186
213
|
return CreateMagicLinkHandler(publicUrl, config);
|
|
187
214
|
}
|