@memberjunction/server 6.2.0-edge.0 → 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/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 +346 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +34 -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 +11 -0
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +48 -0
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +94 -4
- package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
- package/dist/generic/CacheInvalidationResolver.js +122 -3
- package/dist/generic/CacheInvalidationResolver.js.map +1 -1
- package/dist/generic/DeleteOptionsInput.d.ts +8 -1
- package/dist/generic/DeleteOptionsInput.d.ts.map +1 -1
- package/dist/generic/DeleteOptionsInput.js +1 -1
- package/dist/generic/DeleteOptionsInput.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 +5 -1
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +15 -8
- 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 +30 -9
- 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/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/RealtimeBridgeResolver.js +3 -3
- package/dist/resolvers/RealtimeClientSessionResolver.js +3 -3
- 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 +69 -38
- 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/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/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/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 +6 -0
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +13 -5
- package/dist/util.js.map +1 -1
- package/package.json +92 -92
- package/src/__tests__/DeleteOptionsInput.wireShape.test.ts +62 -0
- 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__/RealtimeSdpBrokerHandler.test.ts +6 -6
- package/src/__tests__/RefreshAdoptsNewColumns.test.ts +8 -8
- package/src/__tests__/RemoteBrowserGoalEngine.test.ts +18 -18
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +228 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +129 -0
- package/src/__tests__/TaskGraphFrameResolver.test.ts +2 -2
- 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__/cacheInvalidationRecordData.test.ts +177 -0
- 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/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 +37 -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 +37 -0
- package/src/generic/CacheInvalidationResolver.ts +148 -4
- package/src/generic/DeleteOptionsInput.ts +9 -2
- package/src/generic/FireAndForgetHeartbeat.ts +19 -14
- package/src/generic/PushStatusResolver.ts +21 -7
- package/src/generic/ResolverBase.ts +16 -8
- package/src/generic/RunViewResolver.ts +22 -22
- package/src/generic/__tests__/FireAndForgetHeartbeat.test.ts +7 -7
- package/src/index.ts +35 -9
- 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/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/RealtimeBridgeResolver.ts +3 -3
- package/src/resolvers/RealtimeClientSessionResolver.ts +3 -3
- package/src/resolvers/RemoteBrowserActionResolver.ts +5 -5
- package/src/resolvers/RunAIAgentResolver.ts +113 -72
- package/src/resolvers/RunAIPromptResolver.ts +22 -22
- package/src/resolvers/RunTemplateResolver.ts +4 -4
- package/src/resolvers/RunTestResolver.ts +25 -25
- package/src/resolvers/SqlLoggingConfigResolver.ts +56 -56
- package/src/resolvers/TaskGraphFrameResolver.ts +37 -28
- package/src/resolvers/TelemetryResolver.ts +79 -79
- 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/TaskGraphActionRunner.ts +16 -15
- package/src/types.ts +21 -16
- package/src/util.ts +16 -6
|
@@ -35,7 +35,7 @@ import { CommunicationEngine } from '@memberjunction/communication-engine';
|
|
|
35
35
|
import { Message } from '@memberjunction/communication-types';
|
|
36
36
|
import { configInfo, type MagicLinkConfig } from '../../config.js';
|
|
37
37
|
import { MagicLinkKeyManager } from './MagicLinkKeys.js';
|
|
38
|
-
import {
|
|
38
|
+
import { GenerateRawToken, GenerateSessionId, HashToken, EvaluateInvite, BuildSessionClaims, BuildConsumeInvitePostgresSQL, CanIssueInvites, IsRoleGrantable, MAGIC_LINK_TOKEN_PREFIX } from './magicLinkCore.js';
|
|
39
39
|
import type {
|
|
40
40
|
CreateMagicLinkInviteParams,
|
|
41
41
|
CreateMagicLinkInviteResult,
|
|
@@ -48,6 +48,21 @@ const REDEMPTION_ENTITY = 'MJ: Magic Link Redemptions';
|
|
|
48
48
|
/** Shared Anonymous principal — seeded by the Phase-4 migration; must match that UUID. */
|
|
49
49
|
const ANONYMOUS_USER_ID = '273910DF-28F1-45C1-A8F8-6E9AD8E5F008';
|
|
50
50
|
|
|
51
|
+
/** Outcome of the atomic single-use consume — see {@link MagicLinkService.consumeInvite}. */
|
|
52
|
+
type ConsumeOutcome = 'won' | 'lost' | 'failed';
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The SQL Server guarded single-use procedure (migration V202609291627__v6.2.x__MagicLink_Consume_Invite_Sproc).
|
|
56
|
+
* A procedure, not raw DML, because on SQL Server MJ grants its runtime roles EXECUTE on procedures
|
|
57
|
+
* and SELECT on views — never DML on base tables — so on any host whose MJAPI login is not db_owner
|
|
58
|
+
* a raw UPDATE is refused (#4753). Ownership chaining lets the procedure update the table on the
|
|
59
|
+
* caller's behalf. PostgreSQL has no counterpart function; see {@link BuildConsumeInvitePostgresSQL}.
|
|
60
|
+
*/
|
|
61
|
+
const CONSUME_INVITE_PROC = 'spConsumeMagicLinkInvite';
|
|
62
|
+
|
|
63
|
+
/** MagicLinkInvite base table (core schema) — the PostgreSQL consume updates it directly. */
|
|
64
|
+
const INVITE_TABLE = 'MagicLinkInvite';
|
|
65
|
+
|
|
51
66
|
/** Outcome of provisioning a redeeming user. */
|
|
52
67
|
interface ProvisionResult {
|
|
53
68
|
success: boolean;
|
|
@@ -90,11 +105,11 @@ export class MagicLinkService {
|
|
|
90
105
|
// external user already holding a restricted magic-link session — could
|
|
91
106
|
// issue invites and escalate.
|
|
92
107
|
const callerRoleNames = (creatingUser.UserRoles ?? []).map((r) => r.Role).filter((n): n is string => !!n);
|
|
93
|
-
if (!
|
|
108
|
+
if (!CanIssueInvites(creatingUser.Type, callerRoleNames, this.config.inviteIssuerRoleNames)) {
|
|
94
109
|
return { success: false, errorCode: 'forbidden', error: 'Not authorized to issue magic-link invites.' };
|
|
95
110
|
}
|
|
96
111
|
|
|
97
|
-
const roleId = params.
|
|
112
|
+
const roleId = params.RoleId ?? this.resolveRestrictedRoleId(md);
|
|
98
113
|
if (!roleId) {
|
|
99
114
|
return { success: false, error: `Restricted role '${this.config.restrictedRoleName}' not found and no roleId supplied.` };
|
|
100
115
|
}
|
|
@@ -106,7 +121,7 @@ export class MagicLinkService {
|
|
|
106
121
|
if (!roleToGrant) {
|
|
107
122
|
return { success: false, errorCode: 'invalid_role', error: `Role '${roleId}' not found.` };
|
|
108
123
|
}
|
|
109
|
-
if (!
|
|
124
|
+
if (!IsRoleGrantable(roleToGrant.Name, this.config.restrictedRoleName, this.config.grantableRoleNames)) {
|
|
110
125
|
return {
|
|
111
126
|
success: false,
|
|
112
127
|
errorCode: 'invalid_role',
|
|
@@ -114,23 +129,23 @@ export class MagicLinkService {
|
|
|
114
129
|
};
|
|
115
130
|
}
|
|
116
131
|
|
|
117
|
-
const app = md.Applications.find((a) => UUIDsEqual(a.ID, params.
|
|
132
|
+
const app = md.Applications.find((a) => UUIDsEqual(a.ID, params.ApplicationId));
|
|
118
133
|
if (!app) {
|
|
119
|
-
return { success: false, error: `Application '${params.
|
|
134
|
+
return { success: false, error: `Application '${params.ApplicationId}' not found.` };
|
|
120
135
|
}
|
|
121
136
|
|
|
122
|
-
const rawToken =
|
|
123
|
-
const expiresInHours = params.
|
|
137
|
+
const rawToken = GenerateRawToken();
|
|
138
|
+
const expiresInHours = params.ExpiresInHours ?? this.config.defaultExpiresInHours;
|
|
124
139
|
const expiresAt = new Date(Date.now() + expiresInHours * 3600 * 1000);
|
|
125
140
|
|
|
126
141
|
const invite = await md.GetEntityObject<MJMagicLinkInviteEntity>(INVITE_ENTITY, creatingUser);
|
|
127
142
|
invite.NewRecord();
|
|
128
|
-
invite.TokenHash =
|
|
143
|
+
invite.TokenHash = HashToken(rawToken);
|
|
129
144
|
invite.Email = params.email;
|
|
130
|
-
invite.ApplicationID = params.
|
|
145
|
+
invite.ApplicationID = params.ApplicationId;
|
|
131
146
|
invite.RoleID = roleId;
|
|
132
147
|
invite.ExpiresAt = expiresAt;
|
|
133
|
-
invite.MaxUses = params.
|
|
148
|
+
invite.MaxUses = params.MaxUses ?? 1;
|
|
134
149
|
invite.UseCount = 0;
|
|
135
150
|
invite.CreatedByUserID = creatingUser.ID;
|
|
136
151
|
invite.Status = 'Active';
|
|
@@ -143,7 +158,7 @@ export class MagicLinkService {
|
|
|
143
158
|
// the single ApplicationID/RoleID. Additive — the columns stay authoritative for
|
|
144
159
|
// redemption today; this populates the child tables for the eventual switch to
|
|
145
160
|
// multi-scope reads. Best-effort so a child-row hiccup never fails issuance.
|
|
146
|
-
await this.writeInviteScopeChildRows(invite.ID, params.
|
|
161
|
+
await this.writeInviteScopeChildRows(invite.ID, params.ApplicationId, roleId, creatingUser, md);
|
|
147
162
|
|
|
148
163
|
const redemptionUrl = this.buildRedemptionUrl(rawToken);
|
|
149
164
|
|
|
@@ -173,9 +188,9 @@ export class MagicLinkService {
|
|
|
173
188
|
* session JWT. Runs in the public (pre-auth) path, so it uses the global
|
|
174
189
|
* provider and a configured provisioning context user.
|
|
175
190
|
*
|
|
176
|
-
* Single-use is enforced by
|
|
177
|
-
* BEFORE the token is minted — two concurrent
|
|
178
|
-
* link race on the same row and exactly one wins. The consume is the gate:
|
|
191
|
+
* Single-use is enforced by the guarded UPDATE inside the `spConsumeMagicLinkInvite`
|
|
192
|
+
* procedure ({@link consumeInvite}) BEFORE the token is minted — two concurrent
|
|
193
|
+
* redemptions of a single-use link race on the same row and exactly one wins. The consume is the gate:
|
|
179
194
|
* if a later step fails, the use is already burned (fail-closed). That is the
|
|
180
195
|
* correct trade-off for a single-use credential — burning a token on a server
|
|
181
196
|
* error is recoverable (re-issue); allowing replay is not.
|
|
@@ -208,7 +223,7 @@ export class MagicLinkService {
|
|
|
208
223
|
const md = Metadata.Provider; // global-provider-ok: server-side magic-link service; runs under the server's single default provider
|
|
209
224
|
const provider = md as DatabaseProviderBase;
|
|
210
225
|
|
|
211
|
-
const tokenHash =
|
|
226
|
+
const tokenHash = HashToken(rawToken);
|
|
212
227
|
const rv = new RunView();
|
|
213
228
|
const found = await rv.RunView<MJMagicLinkInviteEntity>(
|
|
214
229
|
{
|
|
@@ -231,7 +246,7 @@ export class MagicLinkService {
|
|
|
231
246
|
|
|
232
247
|
// Fast, friendly pre-check (returns a precise reason). NOT the authority —
|
|
233
248
|
// the atomic consume below is what actually enforces single-use.
|
|
234
|
-
const eligibility =
|
|
249
|
+
const eligibility = EvaluateInvite(invite, Date.now());
|
|
235
250
|
if (!eligibility.ok) {
|
|
236
251
|
return done({ success: false, errorCode: eligibility.errorCode, error: `Invite is ${eligibility.errorCode}.` });
|
|
237
252
|
}
|
|
@@ -252,14 +267,18 @@ export class MagicLinkService {
|
|
|
252
267
|
return done({ success: false, errorCode: 'server_error', error: 'Invite role not found.' });
|
|
253
268
|
}
|
|
254
269
|
|
|
255
|
-
// Atomic consume BEFORE minting.
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
|
|
270
|
+
// Atomic consume BEFORE minting. A statement that never ran (permissions,
|
|
271
|
+
// missing procedure, connection) is a server fault, not a lost race — report
|
|
272
|
+
// it as such so operators aren't sent hunting a double-redeem (#4753).
|
|
273
|
+
const consume = await this.consumeInvite(invite, provider, contextUser);
|
|
274
|
+
if (consume === 'failed') {
|
|
275
|
+
return done({ success: false, errorCode: 'server_error', error: 'Could not redeem the invite due to a server error.' });
|
|
276
|
+
}
|
|
277
|
+
if (consume === 'lost') {
|
|
259
278
|
// Lost the race or the invite expired between the pre-check and the
|
|
260
279
|
// consume. The in-memory copy still looks eligible if only the DB-side
|
|
261
280
|
// use count changed, so default that case to 'consumed'.
|
|
262
|
-
const recheck =
|
|
281
|
+
const recheck = EvaluateInvite(invite, Date.now());
|
|
263
282
|
return done({ success: false, errorCode: recheck.ok ? 'consumed' : recheck.errorCode, error: 'Invite already redeemed or expired.' });
|
|
264
283
|
}
|
|
265
284
|
|
|
@@ -273,7 +292,7 @@ export class MagicLinkService {
|
|
|
273
292
|
|
|
274
293
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
275
294
|
const isAnon = invite.IdentityMode === 'anonymous';
|
|
276
|
-
const claims =
|
|
295
|
+
const claims = BuildSessionClaims({
|
|
277
296
|
issuer: this.publicUrl,
|
|
278
297
|
audience: this.config.audience,
|
|
279
298
|
inviteId: invite.ID,
|
|
@@ -286,7 +305,7 @@ export class MagicLinkService {
|
|
|
286
305
|
anonymous: isAnon,
|
|
287
306
|
// Per-session id for anon forensics (correlates one session's activity since all
|
|
288
307
|
// anon redemptions share the Anonymous principal). Carried into the redemption audit row.
|
|
289
|
-
sessionId: isAnon ?
|
|
308
|
+
sessionId: isAnon ? GenerateSessionId() : undefined,
|
|
290
309
|
// Resource-share scope (Phase 5): the single shared resource this link grants.
|
|
291
310
|
resourceId: invite.ResourceID ?? undefined,
|
|
292
311
|
nowSeconds,
|
|
@@ -639,39 +658,39 @@ export class MagicLinkService {
|
|
|
639
658
|
}
|
|
640
659
|
|
|
641
660
|
/**
|
|
642
|
-
* Atomically consumes one use of the invite
|
|
643
|
-
*
|
|
644
|
-
*
|
|
645
|
-
*
|
|
646
|
-
* (won the race); concurrent redemptions of a single-use link see 0 rows.
|
|
661
|
+
* Atomically consumes one use of the invite with a single guarded UPDATE that re-checks every
|
|
662
|
+
* eligibility condition (Active, not exhausted, not expired) at the DB level, so the increment and
|
|
663
|
+
* the guard are one atomic operation. Concurrent redemptions of a single-use link race on the row
|
|
664
|
+
* and exactly one gets the row back.
|
|
647
665
|
*
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
*
|
|
651
|
-
*
|
|
652
|
-
*
|
|
653
|
-
*
|
|
666
|
+
* The statement differs per platform because the grants do:
|
|
667
|
+
* - **SQL Server** calls {@link CONSUME_INVITE_PROC} (`EXEC … @ID=@p0`). Runtime roles have
|
|
668
|
+
* EXECUTE on procedures but no DML on base tables (#4753).
|
|
669
|
+
* - **PostgreSQL** runs {@link BuildConsumeInvitePostgresSQL}'s `UPDATE … RETURNING` directly.
|
|
670
|
+
* Runtime roles hold table DML there, and no PG counterpart of the procedure exists.
|
|
671
|
+
* The ID is always a bound parameter.
|
|
654
672
|
*
|
|
655
|
-
*
|
|
673
|
+
* Returns `'won'` (this call consumed a use), `'lost'` (the guard matched nothing: consumed,
|
|
674
|
+
* revoked or expired concurrently) or `'failed'` (the statement never ran — permissions, a missing
|
|
675
|
+
* procedure, a connection error). `'failed'` is kept distinct from `'lost'` on purpose: collapsing
|
|
676
|
+
* them is what made #4753's permission error surface to users as "already redeemed".
|
|
656
677
|
*/
|
|
657
|
-
private async consumeInvite(invite: MJMagicLinkInviteEntity, provider: DatabaseProviderBase, contextUser: UserInfo): Promise<
|
|
678
|
+
private async consumeInvite(invite: MJMagicLinkInviteEntity, provider: DatabaseProviderBase, contextUser: UserInfo): Promise<ConsumeOutcome> {
|
|
679
|
+
const isPg = provider.PlatformKey === 'postgresql';
|
|
680
|
+
const statement = isPg ? `PostgreSQL guarded UPDATE on ${INVITE_TABLE}` : CONSUME_INVITE_PROC;
|
|
658
681
|
try {
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
// OUTPUT/RETURNING yields one row iff the WHERE matched — the atomic single-use gate.
|
|
669
|
-
const sql = buildConsumeInviteSQL(table, isPg ? 'postgresql' : 'sqlserver');
|
|
670
|
-
const rows = await provider.ExecuteSQL<{ ID: string }>(sql, [invite.ID], { isMutation: true }, contextUser);
|
|
671
|
-
return Array.isArray(rows) && rows.length === 1;
|
|
682
|
+
const sql = isPg
|
|
683
|
+
? BuildConsumeInvitePostgresSQL(`${provider.MJCoreSchemaName}.${INVITE_TABLE}`)
|
|
684
|
+
: provider.Dialect.ProcedureCallSyntax(provider.MJCoreSchemaName, CONSUME_INVITE_PROC, [`@ID=${provider.BuildParameterPlaceholder(0)}`]);
|
|
685
|
+
const rows = await provider.ExecuteSQL<{ ID: string }>(sql, [invite.ID], { isMutation: true, description: `MagicLink ${statement}` }, contextUser);
|
|
686
|
+
if (!Array.isArray(rows)) {
|
|
687
|
+
LogError(`[MagicLink] ${statement} returned no result set for invite ${invite.ID}; treating the consume as failed.`);
|
|
688
|
+
return 'failed';
|
|
689
|
+
}
|
|
690
|
+
return rows.length === 1 ? 'won' : 'lost';
|
|
672
691
|
} catch (e) {
|
|
673
|
-
LogError(`[MagicLink]
|
|
674
|
-
return
|
|
692
|
+
LogError(`[MagicLink] ${statement} FAILED for invite ${invite.ID} — the statement never ran, so this is NOT a lost race: ${e instanceof Error ? e.message : String(e)}`);
|
|
693
|
+
return 'failed';
|
|
675
694
|
}
|
|
676
695
|
}
|
|
677
696
|
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
2
|
export {
|
|
3
|
-
generateRawToken,
|
|
4
|
-
hashToken,
|
|
5
|
-
evaluateInvite,
|
|
6
|
-
buildSessionClaims,
|
|
3
|
+
GenerateRawToken, generateRawToken,
|
|
4
|
+
HashToken, hashToken,
|
|
5
|
+
EvaluateInvite, evaluateInvite,
|
|
6
|
+
BuildSessionClaims, buildSessionClaims,
|
|
7
7
|
MAGIC_LINK_TOKEN_PREFIX,
|
|
8
8
|
type InviteEvaluationInput,
|
|
9
9
|
} from './magicLinkCore.js';
|
|
10
10
|
export { MagicLinkKeyManager } from './MagicLinkKeys.js';
|
|
11
11
|
export { MagicLinkService } from './MagicLinkService.js';
|
|
12
12
|
export {
|
|
13
|
-
createMagicLinkHandler,
|
|
14
|
-
createMagicLinkJwksRouter,
|
|
15
|
-
registerMagicLinkAuthProvider,
|
|
13
|
+
CreateMagicLinkHandler, createMagicLinkHandler,
|
|
14
|
+
CreateMagicLinkJwksRouter, createMagicLinkJwksRouter,
|
|
15
|
+
RegisterMagicLinkAuthProvider, registerMagicLinkAuthProvider,
|
|
16
16
|
MAGIC_LINK_MOUNT_PATH,
|
|
17
17
|
MAGIC_LINK_JWKS_PATH,
|
|
18
18
|
} from './MagicLinkRouter.js';
|
|
@@ -13,25 +13,40 @@ import type { MagicLinkJWTClaims, MagicLinkScopeEntry, RedeemErrorCode } from '.
|
|
|
13
13
|
export const MAGIC_LINK_TOKEN_PREFIX = 'mj_ml_';
|
|
14
14
|
|
|
15
15
|
/** Generates a cryptographically random raw magic-link token. */
|
|
16
|
-
export function
|
|
16
|
+
export function GenerateRawToken(): string {
|
|
17
17
|
return MAGIC_LINK_TOKEN_PREFIX + randomBytes(32).toString('hex');
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** @deprecated Use {@link GenerateRawToken}. */
|
|
21
|
+
export function generateRawToken(): string {
|
|
22
|
+
return GenerateRawToken();
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
/** Generates an opaque per-session id (anonymous-session forensics correlation). */
|
|
21
|
-
export function
|
|
26
|
+
export function GenerateSessionId(): string {
|
|
22
27
|
return randomBytes(16).toString('base64url');
|
|
23
28
|
}
|
|
24
29
|
|
|
30
|
+
/** @deprecated Use {@link GenerateSessionId}. */
|
|
31
|
+
export function generateSessionId(): string {
|
|
32
|
+
return GenerateSessionId();
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
/**
|
|
26
36
|
* SHA-256 hash of a raw token, base64url-encoded — only the hash is ever
|
|
27
37
|
* persisted. base64url (43 chars) is shorter than hex (64 chars), URL-safe, and
|
|
28
38
|
* carries the full 256 bits. Both write (CreateInvite) and read (RedeemInvite)
|
|
29
39
|
* paths call this, so the encoding stays internally consistent.
|
|
30
40
|
*/
|
|
31
|
-
export function
|
|
41
|
+
export function HashToken(rawToken: string): string {
|
|
32
42
|
return createHash('sha256').update(rawToken).digest('base64url');
|
|
33
43
|
}
|
|
34
44
|
|
|
45
|
+
/** @deprecated Use {@link HashToken}. */
|
|
46
|
+
export function hashToken(rawToken: string): string {
|
|
47
|
+
return HashToken(rawToken);
|
|
48
|
+
}
|
|
49
|
+
|
|
35
50
|
/** Minimal shape of an invite needed for redemption eligibility. */
|
|
36
51
|
export interface InviteEvaluationInput {
|
|
37
52
|
Status: string;
|
|
@@ -54,7 +69,7 @@ function normalizeName(name: string): string {
|
|
|
54
69
|
* (including an external user already holding a restricted magic-link session)
|
|
55
70
|
* from minting invites.
|
|
56
71
|
*/
|
|
57
|
-
export function
|
|
72
|
+
export function CanIssueInvites(
|
|
58
73
|
userType: string | null | undefined,
|
|
59
74
|
userRoleNames: readonly string[],
|
|
60
75
|
issuerRoleNames: readonly string[],
|
|
@@ -69,13 +84,22 @@ export function canIssueInvites(
|
|
|
69
84
|
return userRoleNames.some((r) => allowed.has(normalizeName(r)));
|
|
70
85
|
}
|
|
71
86
|
|
|
87
|
+
/** @deprecated Use {@link CanIssueInvites}. */
|
|
88
|
+
export function canIssueInvites(
|
|
89
|
+
userType: string | null | undefined,
|
|
90
|
+
userRoleNames: readonly string[],
|
|
91
|
+
issuerRoleNames: readonly string[],
|
|
92
|
+
): boolean {
|
|
93
|
+
return CanIssueInvites(userType, userRoleNames, issuerRoleNames);
|
|
94
|
+
}
|
|
95
|
+
|
|
72
96
|
/**
|
|
73
97
|
* Pure check for WHAT role an invite may grant. The restricted role is always
|
|
74
98
|
* grantable; any other role must be explicitly listed in `grantableRoleNames`.
|
|
75
99
|
* Applied to every caller (Owners included) so a privileged role can never be
|
|
76
100
|
* attached to an external magic-link user unless the deployment opts in.
|
|
77
101
|
*/
|
|
78
|
-
export function
|
|
102
|
+
export function IsRoleGrantable(
|
|
79
103
|
roleName: string | null | undefined,
|
|
80
104
|
restrictedRoleName: string,
|
|
81
105
|
grantableRoleNames: readonly string[],
|
|
@@ -88,8 +112,17 @@ export function isRoleGrantable(
|
|
|
88
112
|
return allowed.has(target);
|
|
89
113
|
}
|
|
90
114
|
|
|
115
|
+
/** @deprecated Use {@link IsRoleGrantable}. */
|
|
116
|
+
export function isRoleGrantable(
|
|
117
|
+
roleName: string | null | undefined,
|
|
118
|
+
restrictedRoleName: string,
|
|
119
|
+
grantableRoleNames: readonly string[],
|
|
120
|
+
): boolean {
|
|
121
|
+
return IsRoleGrantable(roleName, restrictedRoleName, grantableRoleNames);
|
|
122
|
+
}
|
|
123
|
+
|
|
91
124
|
/** Pure redemption-eligibility check. Returns ok + an error code when not. */
|
|
92
|
-
export function
|
|
125
|
+
export function EvaluateInvite(invite: InviteEvaluationInput, nowMs: number): { ok: boolean; errorCode?: RedeemErrorCode } {
|
|
93
126
|
if (invite.Status === 'Revoked') {
|
|
94
127
|
return { ok: false, errorCode: 'revoked' };
|
|
95
128
|
}
|
|
@@ -106,75 +139,43 @@ export function evaluateInvite(invite: InviteEvaluationInput, nowMs: number): {
|
|
|
106
139
|
return { ok: true };
|
|
107
140
|
}
|
|
108
141
|
|
|
109
|
-
/**
|
|
110
|
-
export
|
|
142
|
+
/** @deprecated Use {@link EvaluateInvite}. */
|
|
143
|
+
export function evaluateInvite(invite: InviteEvaluationInput, nowMs: number): { ok: boolean; errorCode?: RedeemErrorCode } {
|
|
144
|
+
return EvaluateInvite(invite, nowMs);
|
|
145
|
+
}
|
|
111
146
|
|
|
112
|
-
/** SQL Server table identifier — bracket-quoted `[schema].[table]` (word chars only). */
|
|
113
|
-
const QUALIFIED_TABLE_PATTERN = /^\[\w+\]\.\[\w+\]$/;
|
|
114
147
|
/** PostgreSQL table identifier — `schema.table` (word chars only); auto-quoted downstream. */
|
|
115
148
|
const QUALIFIED_TABLE_PATTERN_PG = /^\w+\.\w+$/;
|
|
116
149
|
|
|
117
150
|
/**
|
|
118
|
-
* Builds the atomic compare-and-swap UPDATE that consumes one use of an invite
|
|
119
|
-
* in the given SQL dialect.
|
|
120
|
-
*
|
|
121
|
-
* The WHERE clause re-checks every eligibility condition at the DB level, so the
|
|
122
|
-
* increment and the guard are a single atomic operation: concurrent redemptions
|
|
123
|
-
* of a single-use link race on the row and exactly one matches. This is what
|
|
124
|
-
* actually enforces single-use — the JS-side `evaluateInvite` is only a friendly
|
|
125
|
-
* pre-check. The matched row's ID is returned to the caller (via `OUTPUT` on SQL
|
|
126
|
-
* Server, `RETURNING` on PostgreSQL) so it can detect a win (exactly one row).
|
|
151
|
+
* Builds the PostgreSQL atomic compare-and-swap UPDATE that consumes one use of an invite.
|
|
127
152
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
153
|
+
* PostgreSQL only. SQL Server consumes through the granted `spConsumeMagicLinkInvite` procedure,
|
|
154
|
+
* because its runtime roles are never granted table DML (#4753). PostgreSQL needs neither: its
|
|
155
|
+
* runtime roles hold INSERT/UPDATE/DELETE on every `__mj` table
|
|
156
|
+
* (V202605040300__v5.33.x__Unblock_PostgreSQL_End_To_End.pg-only.sql), and no PG counterpart of
|
|
157
|
+
* the procedure exists — the release-time converter leaves `CREATE PROCEDURE` unhandled.
|
|
131
158
|
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
* and
|
|
135
|
-
*
|
|
159
|
+
* The WHERE re-checks every eligibility condition (the same predicate as the procedure), so the
|
|
160
|
+
* increment and the guard are one atomic statement: concurrent redemptions of a single-use link
|
|
161
|
+
* serialize on the row lock and exactly one matches. `RETURNING ID` yields the row iff the guard
|
|
162
|
+
* matched. The invite ID MUST be bound by the caller as `$1` — it is never interpolated.
|
|
136
163
|
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* wins; `RETURNING` yields the row iff the guard matched (equivalent to the SS
|
|
141
|
-
* `OUTPUT`-into-table win-detect). PascalCase identifiers are auto-quoted by
|
|
142
|
-
* `PostgreSQLDataProvider.ExecuteSQL`, so the table is passed unquoted (`schema.table`).
|
|
143
|
-
*
|
|
144
|
-
* `qualifiedTable` is asserted to match the dialect's whitelist pattern before
|
|
145
|
-
* interpolation. The caller derives it from `EntityInfo` (never user input), so
|
|
146
|
-
* this is defense-in-depth: even a future careless caller cannot turn this into
|
|
147
|
-
* an injection vector — a non-conforming table throws.
|
|
148
|
-
*
|
|
149
|
-
* @param qualifiedTable `[schema].[table]` (sqlserver) or `schema.table` (postgresql) for MagicLinkInvite
|
|
150
|
-
* @param dialect target SQL dialect (defaults to `'sqlserver'`)
|
|
164
|
+
* `qualifiedTable` is `schema.table` unquoted (PostgreSQLDataProvider.ExecuteSQL auto-quotes the
|
|
165
|
+
* PascalCase identifiers) and is asserted against a whitelist before interpolation — the caller
|
|
166
|
+
* never passes user input, so this is defense-in-depth.
|
|
151
167
|
*/
|
|
152
|
-
export function
|
|
153
|
-
if (
|
|
154
|
-
|
|
155
|
-
throw new Error(`buildConsumeInviteSQL: refusing to build SQL for non-whitelisted table identifier '${qualifiedTable}'.`);
|
|
156
|
-
}
|
|
157
|
-
return (
|
|
158
|
-
`UPDATE ${qualifiedTable} ` +
|
|
159
|
-
`SET UseCount = UseCount + 1, ` +
|
|
160
|
-
`ConsumedAt = COALESCE(ConsumedAt, (now() AT TIME ZONE 'utc')), ` +
|
|
161
|
-
`Status = CASE WHEN UseCount + 1 >= MaxUses THEN 'Consumed' ELSE Status END ` +
|
|
162
|
-
`WHERE ID = $1 AND Status = 'Active' AND UseCount < MaxUses AND ExpiresAt > (now() AT TIME ZONE 'utc') ` +
|
|
163
|
-
`RETURNING ID;`
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
if (!QUALIFIED_TABLE_PATTERN.test(qualifiedTable)) {
|
|
167
|
-
throw new Error(`buildConsumeInviteSQL: refusing to build SQL for non-whitelisted table identifier '${qualifiedTable}'.`);
|
|
168
|
+
export function BuildConsumeInvitePostgresSQL(qualifiedTable: string): string {
|
|
169
|
+
if (!QUALIFIED_TABLE_PATTERN_PG.test(qualifiedTable)) {
|
|
170
|
+
throw new Error(`BuildConsumeInvitePostgresSQL: refusing to build SQL for non-whitelisted table identifier '${qualifiedTable}'.`);
|
|
168
171
|
}
|
|
169
172
|
return (
|
|
170
|
-
`DECLARE @consumed TABLE (ID UNIQUEIDENTIFIER); ` +
|
|
171
173
|
`UPDATE ${qualifiedTable} ` +
|
|
172
174
|
`SET UseCount = UseCount + 1, ` +
|
|
173
|
-
`ConsumedAt = COALESCE(ConsumedAt,
|
|
175
|
+
`ConsumedAt = COALESCE(ConsumedAt, (now() AT TIME ZONE 'utc')), ` +
|
|
174
176
|
`Status = CASE WHEN UseCount + 1 >= MaxUses THEN 'Consumed' ELSE Status END ` +
|
|
175
|
-
`
|
|
176
|
-
`
|
|
177
|
-
`SELECT ID FROM @consumed;`
|
|
177
|
+
`WHERE ID = $1 AND Status = 'Active' AND UseCount < MaxUses AND ExpiresAt > (now() AT TIME ZONE 'utc') ` +
|
|
178
|
+
`RETURNING ID;`
|
|
178
179
|
);
|
|
179
180
|
}
|
|
180
181
|
|
|
@@ -184,7 +185,7 @@ export function buildConsumeInviteSQL(qualifiedTable: string, dialect: ConsumeIn
|
|
|
184
185
|
* of scopes across multiple redeemed links — carried in the re-minted JWT, never as
|
|
185
186
|
* roles on a shared user, so anonymous sessions can't accrete into a superuser.
|
|
186
187
|
*/
|
|
187
|
-
export function
|
|
188
|
+
export function UnionScopes(prior: readonly MagicLinkScopeEntry[] | undefined, next: MagicLinkScopeEntry): MagicLinkScopeEntry[] {
|
|
188
189
|
const result = [...(prior ?? [])];
|
|
189
190
|
if (!result.some((s) => s.inviteId === next.inviteId)) {
|
|
190
191
|
result.push(next);
|
|
@@ -192,8 +193,13 @@ export function unionScopes(prior: readonly MagicLinkScopeEntry[] | undefined, n
|
|
|
192
193
|
return result;
|
|
193
194
|
}
|
|
194
195
|
|
|
196
|
+
/** @deprecated Use {@link UnionScopes}. */
|
|
197
|
+
export function unionScopes(prior: readonly MagicLinkScopeEntry[] | undefined, next: MagicLinkScopeEntry): MagicLinkScopeEntry[] {
|
|
198
|
+
return UnionScopes(prior, next);
|
|
199
|
+
}
|
|
200
|
+
|
|
195
201
|
/** Builds the session-token claims (pure). */
|
|
196
|
-
export function
|
|
202
|
+
export function BuildSessionClaims(args: {
|
|
197
203
|
issuer: string;
|
|
198
204
|
audience: string;
|
|
199
205
|
inviteId: string;
|
|
@@ -236,9 +242,35 @@ export function buildSessionClaims(args: {
|
|
|
236
242
|
mj_app_id: args.applicationId,
|
|
237
243
|
mj_role: args.roleName,
|
|
238
244
|
mj_invited_by: args.invitedByUserId,
|
|
239
|
-
mj_scopes:
|
|
245
|
+
mj_scopes: UnionScopes(args.priorScopes, scopeEntry),
|
|
240
246
|
mj_anon: args.anonymous ? true : undefined,
|
|
241
247
|
mj_sid: args.sessionId,
|
|
242
248
|
mj_magic_link: true,
|
|
243
249
|
};
|
|
244
250
|
}
|
|
251
|
+
|
|
252
|
+
/** @deprecated Use {@link BuildSessionClaims}. */
|
|
253
|
+
export function buildSessionClaims(args: {
|
|
254
|
+
issuer: string;
|
|
255
|
+
audience: string;
|
|
256
|
+
inviteId: string;
|
|
257
|
+
email: string;
|
|
258
|
+
firstName?: string;
|
|
259
|
+
lastName?: string;
|
|
260
|
+
applicationId: string;
|
|
261
|
+
roleName: string;
|
|
262
|
+
invitedByUserId?: string;
|
|
263
|
+
/** True for anonymous sessions — the server enforces scope from mj_scopes, not roles. */
|
|
264
|
+
anonymous?: boolean;
|
|
265
|
+
/** Opaque per-session id (anonymous forensics correlation). */
|
|
266
|
+
sessionId?: string;
|
|
267
|
+
/** Prior session's scope union to carry forward (multi-link anonymous sessions). */
|
|
268
|
+
priorScopes?: readonly MagicLinkScopeEntry[];
|
|
269
|
+
/** Resource-share/embed scope for this link's entry. */
|
|
270
|
+
resourceType?: string;
|
|
271
|
+
resourceId?: string;
|
|
272
|
+
nowSeconds: number;
|
|
273
|
+
ttlSeconds: number;
|
|
274
|
+
}): MagicLinkJWTClaims {
|
|
275
|
+
return BuildSessionClaims(args);
|
|
276
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/** Minimal HTML-escape for safe interpolation into attribute/text content. */
|
|
8
|
-
export function
|
|
8
|
+
export function EscapeHtml(s: string): string {
|
|
9
9
|
return s
|
|
10
10
|
.replace(/&/g, '&')
|
|
11
11
|
.replace(/</g, '<')
|
|
@@ -14,6 +14,11 @@ export function escapeHtml(s: string): string {
|
|
|
14
14
|
.replace(/'/g, ''');
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/** @deprecated Use {@link EscapeHtml}. */
|
|
18
|
+
export function escapeHtml(s: string): string {
|
|
19
|
+
return EscapeHtml(s);
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
/**
|
|
18
23
|
* Builds the redemption landing page served by `GET /magic-link/redeem`.
|
|
19
24
|
*
|
|
@@ -27,9 +32,9 @@ export function escapeHtml(s: string): string {
|
|
|
27
32
|
* @param token the raw magic-link token (embedded in a hidden field)
|
|
28
33
|
* @param actionPath the same-origin path the form POSTs to (e.g. `/magic-link/redeem`)
|
|
29
34
|
*/
|
|
30
|
-
export function
|
|
31
|
-
const t =
|
|
32
|
-
const action =
|
|
35
|
+
export function BuildRedeemLandingHtml(token: string, actionPath: string): string {
|
|
36
|
+
const t = EscapeHtml(token);
|
|
37
|
+
const action = EscapeHtml(actionPath);
|
|
33
38
|
// Hardcoded colors are intentional here — this is a standalone server-rendered
|
|
34
39
|
// HTML response, not Angular component CSS, so the design-token rule doesn't apply.
|
|
35
40
|
return `<!doctype html>
|
|
@@ -60,3 +65,8 @@ export function buildRedeemLandingHtml(token: string, actionPath: string): strin
|
|
|
60
65
|
</body>
|
|
61
66
|
</html>`;
|
|
62
67
|
}
|
|
68
|
+
|
|
69
|
+
/** @deprecated Use {@link BuildRedeemLandingHtml}. */
|
|
70
|
+
export function buildRedeemLandingHtml(token: string, actionPath: string): string {
|
|
71
|
+
return BuildRedeemLandingHtml(token, actionPath);
|
|
72
|
+
}
|