@memberjunction/server 6.2.0-edge.3 → 6.2.0-edge.5
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 +20 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
- package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
- package/dist/agentSessions/BridgeHostInstance.js +30 -0
- package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
- package/dist/agentSessions/HostInstance.d.ts +75 -11
- package/dist/agentSessions/HostInstance.d.ts.map +1 -1
- package/dist/agentSessions/HostInstance.js +99 -28
- package/dist/agentSessions/HostInstance.js.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +236 -27
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +682 -54
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +48 -3
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +112 -9
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
- package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
- package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
- package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
- package/dist/agentSessions/index.d.ts +2 -0
- package/dist/agentSessions/index.d.ts.map +1 -1
- package/dist/agentSessions/index.js +10 -3
- package/dist/agentSessions/index.js.map +1 -1
- package/dist/apiRoot.d.ts +32 -0
- package/dist/apiRoot.d.ts.map +1 -0
- package/dist/apiRoot.js +67 -0
- package/dist/apiRoot.js.map +1 -0
- package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.js +25 -0
- package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
- package/dist/auth/LoginScreenEngine.d.ts +22 -0
- package/dist/auth/LoginScreenEngine.d.ts.map +1 -0
- package/dist/auth/LoginScreenEngine.js +57 -0
- package/dist/auth/LoginScreenEngine.js.map +1 -0
- package/dist/auth/actionAuthorization.d.ts +40 -0
- package/dist/auth/actionAuthorization.d.ts.map +1 -0
- package/dist/auth/actionAuthorization.js +94 -0
- package/dist/auth/actionAuthorization.js.map +1 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +4 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +3 -2
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +6 -3
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/newUsers.d.ts +10 -0
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +80 -11
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/config.d.ts +2514 -22
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +166 -4
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +8 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +14 -11
- package/dist/context.js.map +1 -1
- package/dist/coreRoutes.d.ts +29 -0
- package/dist/coreRoutes.d.ts.map +1 -0
- package/dist/coreRoutes.js +142 -0
- package/dist/coreRoutes.js.map +1 -0
- package/dist/generated/graphql-schemas/__mj.d.ts +504 -3
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +2456 -7
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +43 -8
- package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
- package/dist/generic/CacheInvalidationResolver.js +59 -19
- package/dist/generic/CacheInvalidationResolver.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/PushStatusResolver.js +1 -1
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +123 -48
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +293 -305
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +16 -0
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/globalCors.d.ts +33 -0
- package/dist/globalCors.d.ts.map +1 -0
- package/dist/globalCors.js +58 -0
- package/dist/globalCors.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +219 -278
- package/dist/index.js.map +1 -1
- package/dist/isolatedProvider.d.ts +20 -0
- package/dist/isolatedProvider.d.ts.map +1 -0
- package/dist/isolatedProvider.js +22 -0
- package/dist/isolatedProvider.js.map +1 -0
- package/dist/multiTenancy/index.d.ts.map +1 -1
- package/dist/multiTenancy/index.js +22 -2
- package/dist/multiTenancy/index.js.map +1 -1
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
- package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
- package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js +139 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js +99 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts +100 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts.map +1 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.js +173 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.js.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
- package/dist/realtimeSessions/index.d.ts +19 -0
- package/dist/realtimeSessions/index.d.ts.map +1 -0
- package/dist/realtimeSessions/index.js +19 -0
- package/dist/realtimeSessions/index.js.map +1 -0
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts +30 -0
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -0
- package/dist/realtimeSessions/ownedRealtimeSession.js +47 -0
- package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -0
- package/dist/realtimeSessions/sessionAccess.d.ts +92 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionAccess.js +94 -0
- package/dist/realtimeSessions/sessionAccess.js.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
- package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
- package/dist/realtimeSessions/verificationCore.d.ts +243 -0
- package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationCore.js +356 -0
- package/dist/realtimeSessions/verificationCore.js.map +1 -0
- package/dist/realtimeSessions/verificationPages.d.ts +66 -0
- package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPages.js +110 -0
- package/dist/realtimeSessions/verificationPages.js.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.js +249 -0
- package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.js +435 -0
- package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +31 -5
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +75 -14
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts +14 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +21 -5
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/ArtifactFileResolver.d.ts +5 -1
- package/dist/resolvers/ArtifactFileResolver.d.ts.map +1 -1
- package/dist/resolvers/ArtifactFileResolver.js +9 -7
- package/dist/resolvers/ArtifactFileResolver.js.map +1 -1
- package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
- package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
- package/dist/resolvers/CacheStatsResolver.js +113 -2
- package/dist/resolvers/CacheStatsResolver.js.map +1 -1
- package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
- package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
- package/dist/resolvers/ClientToolRequestResolver.js +28 -10
- package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.d.ts +8 -4
- package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.js +6 -6
- package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
- package/dist/resolvers/DatasetResolver.d.ts +24 -3
- package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
- package/dist/resolvers/DatasetResolver.js +57 -8
- package/dist/resolvers/DatasetResolver.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +2 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +3 -2
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts +27 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +148 -2
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts +82 -11
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +279 -101
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.d.ts +33 -7
- package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.js +117 -38
- package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +6 -3
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.js +7 -1
- package/dist/resolvers/HumanHandoffResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +4 -2
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +9 -4
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/QueryResolver.d.ts +2 -0
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +13 -0
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts +54 -2
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +155 -10
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +157 -33
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +404 -116
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +100 -14
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +12 -11
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts +12 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.js +15 -2
- package/dist/resolvers/SearchKnowledgeStreamResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +2 -3
- package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +6 -2
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.d.ts +2 -1
- package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.js +28 -1
- package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.d.ts +24 -0
- package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.js +65 -13
- package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts +19 -2
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +102 -24
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
- package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
- package/dist/resolvers/UserAvatarResolver.js +124 -0
- package/dist/resolvers/UserAvatarResolver.js.map +1 -0
- package/dist/resolvers/UserResolver.d.ts.map +1 -1
- package/dist/resolvers/UserResolver.js +2 -17
- package/dist/resolvers/UserResolver.js.map +1 -1
- package/dist/resolvers/agent-run-data-guard.d.ts +31 -0
- package/dist/resolvers/agent-run-data-guard.d.ts.map +1 -0
- package/dist/resolvers/agent-run-data-guard.js +72 -0
- package/dist/resolvers/agent-run-data-guard.js.map +1 -0
- package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
- package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
- package/dist/resolvers/agentVisionConsentAudit.js +63 -0
- package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
- package/dist/resolvers/avatarInputValidation.d.ts +40 -0
- package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
- package/dist/resolvers/avatarInputValidation.js +97 -0
- package/dist/resolvers/avatarInputValidation.js.map +1 -0
- package/dist/resolvers/currentUserRoles.d.ts +29 -0
- package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
- package/dist/resolvers/currentUserRoles.js +30 -0
- package/dist/resolvers/currentUserRoles.js.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.d.ts +6 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.js +21 -0
- package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.d.ts +49 -0
- package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -0
- package/dist/rest/RealtimeRecordingHandler.js +383 -0
- package/dist/rest/RealtimeRecordingHandler.js.map +1 -0
- package/dist/rest/ViewOperationsHandler.d.ts +3 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +21 -12
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts +6 -0
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +6 -0
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/dist/serverExtensionReservedRoots.d.ts +12 -10
- package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
- package/dist/serverExtensionReservedRoots.js +14 -17
- package/dist/serverExtensionReservedRoots.js.map +1 -1
- package/dist/services/MJStorageBlobStore.d.ts +8 -1
- package/dist/services/MJStorageBlobStore.d.ts.map +1 -1
- package/dist/services/MJStorageBlobStore.js +25 -19
- package/dist/services/MJStorageBlobStore.js.map +1 -1
- package/dist/services/StartTaskGraphDispatcher.js +2 -2
- package/dist/services/StartTaskGraphDispatcher.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +5 -3
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +27 -5
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphAgentRunner.d.ts +14 -2
- package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphAgentRunner.js +157 -9
- package/dist/services/TaskGraphAgentRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +57 -4
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +607 -18
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/sharedCache.d.ts +105 -0
- package/dist/sharedCache.d.ts.map +1 -0
- package/dist/sharedCache.js +279 -0
- package/dist/sharedCache.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +99 -96
- package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
- package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
- package/src/__tests__/BridgeHostInstance.test.ts +232 -0
- package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
- package/src/__tests__/DatasetResolver.itemFilters.test.ts +201 -0
- package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +433 -0
- package/src/__tests__/FileResolver.storageAccess.test.ts +562 -0
- package/src/__tests__/GetDataContextDataResolver.sqlItems.test.ts +416 -0
- package/src/__tests__/HostInstance.test.ts +84 -0
- package/src/__tests__/HumanHandoffResolver.test.ts +17 -0
- package/src/__tests__/ListOperationsResolver.adhocScreen.test.ts +137 -0
- package/src/__tests__/LoginScreenEngine.test.ts +86 -0
- package/src/__tests__/MJStorageBlobStore.storageGate.test.ts +63 -0
- package/src/__tests__/MeetingResolver.test.ts +75 -1
- package/src/__tests__/RealtimeBridgeResolver.test.ts +415 -6
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
- package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
- package/src/__tests__/RealtimeRelay.test.ts +876 -0
- package/src/__tests__/RecordingAssemblyScheduler.test.ts +178 -0
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +133 -2
- package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
- package/src/__tests__/ResolverBase.balancedClause.test.ts +155 -0
- package/src/__tests__/ResolverBase.clauseUnscopedRead.test.ts +127 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +212 -6
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +5 -2
- package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +170 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +6 -4
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +258 -0
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
- package/src/__tests__/RunTemplateResolver.test.ts +132 -0
- package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
- package/src/__tests__/RunViewResolver.cacheCheckScreen.test.ts +165 -0
- package/src/__tests__/SearchKnowledgeStreamResolver.test.ts +161 -0
- package/src/__tests__/SessionJanitor.test.ts +1203 -21
- package/src/__tests__/SessionManager.test.ts +240 -17
- package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
- package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +25 -0
- package/src/__tests__/TaskGraphAgentRunner.test.ts +266 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +403 -22
- package/src/__tests__/TaskGraphFrameResolver.test.ts +22 -0
- package/src/__tests__/TestQuerySQLResolver.test.ts +274 -0
- package/src/__tests__/TransactionGroupResolver.refusals.test.ts +98 -1
- package/src/__tests__/UpdateMJDataContextItem.oldValues.test.ts +220 -0
- package/src/__tests__/UserAvatarResolver.test.ts +255 -0
- package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
- package/src/__tests__/ViewOperationsHandler.clauseScreen.test.ts +157 -0
- package/src/__tests__/actionAuthorization.test.ts +130 -0
- package/src/__tests__/agent-run-data-guard.test.ts +130 -0
- package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
- package/src/__tests__/apiRootBoot.test.ts +202 -0
- package/src/__tests__/avatarInputValidation.test.ts +160 -0
- package/src/__tests__/cacheInvalidationRecordData.test.ts +64 -0
- package/src/__tests__/conversationRunEvents.test.ts +56 -14
- package/src/__tests__/globalCors.test.ts +217 -0
- package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
- package/src/__tests__/magicLinkService.consume.test.ts +6 -0
- package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
- package/src/__tests__/multiTenancy.postgresRewrite.test.ts +181 -0
- package/src/__tests__/multiTenancy.security.test.ts +51 -0
- package/src/__tests__/multiTenancy.test.ts +1 -1
- package/src/__tests__/newUsers.template.test.ts +151 -0
- package/src/__tests__/newUsers.test.ts +74 -5
- package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
- package/src/__tests__/ownedRealtimeSession.test.ts +39 -0
- package/src/__tests__/pushStatusResolver.filter.test.ts +7 -0
- package/src/__tests__/realtimeRecordingHandler.test.ts +652 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
- package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
- package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
- package/src/__tests__/realtimeSessions/helpers.ts +4 -0
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
- package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
- package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
- package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
- package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
- package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
- package/src/__tests__/resolverBase.fls.test.ts +87 -4
- package/src/__tests__/serverExtensionHostOverlay.test.ts +66 -0
- package/src/__tests__/serverExtensionReservedRoots.test.ts +11 -96
- package/src/__tests__/sharedCache.test.ts +312 -0
- package/src/__tests__/widgetGuestElevation.test.ts +44 -0
- package/src/agentSessions/BridgeHostInstance.ts +32 -0
- package/src/agentSessions/HostInstance.ts +124 -28
- package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
- package/src/agentSessions/SessionJanitor.ts +821 -59
- package/src/agentSessions/SessionManager.ts +138 -26
- package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
- package/src/agentSessions/index.ts +10 -3
- package/src/apiRoot.ts +78 -0
- package/src/auth/AuthProviderCatalogRouter.ts +26 -0
- package/src/auth/LoginScreenEngine.ts +50 -0
- package/src/auth/actionAuthorization.ts +116 -0
- package/src/auth/index.ts +4 -8
- package/src/auth/magicLink/MagicLinkRouter.ts +3 -2
- package/src/auth/magicLink/MagicLinkService.ts +6 -3
- package/src/auth/newUsers.ts +99 -11
- package/src/config.ts +171 -5
- package/src/context.ts +15 -10
- package/src/coreRoutes.ts +166 -0
- package/src/generated/graphql-schemas/__mj.ts +1815 -57
- package/src/generic/CacheInvalidationResolver.ts +73 -18
- package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
- package/src/generic/PushStatusResolver.ts +1 -1
- package/src/generic/ResolverBase.ts +335 -326
- package/src/generic/RunViewResolver.ts +21 -0
- package/src/globalCors.ts +71 -0
- package/src/index.ts +231 -300
- package/src/isolatedProvider.ts +22 -0
- package/src/multiTenancy/index.ts +24 -2
- package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
- package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
- package/src/realtimeSessions/README.md +179 -0
- package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
- package/src/realtimeSessions/RealtimeSessionVerificationService.ts +168 -0
- package/src/realtimeSessions/RealtimeVerifyRouter.ts +109 -0
- package/src/realtimeSessions/RecordingAssemblyScheduler.ts +234 -0
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
- package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
- package/src/realtimeSessions/index.ts +18 -0
- package/src/realtimeSessions/ownedRealtimeSession.ts +55 -0
- package/src/realtimeSessions/sessionAccess.ts +145 -0
- package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
- package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
- package/src/realtimeSessions/verificationCore.ts +502 -0
- package/src/realtimeSessions/verificationPages.ts +149 -0
- package/src/realtimeSessions/verificationPolicy.ts +321 -0
- package/src/realtimeSessions/verificationWorkflow.ts +702 -0
- package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
- package/src/resolvers/ActionResolver.ts +84 -21
- package/src/resolvers/AdhocQueryResolver.ts +24 -6
- package/src/resolvers/ArtifactFileResolver.ts +9 -8
- package/src/resolvers/CacheStatsResolver.ts +89 -2
- package/src/resolvers/ClientToolRequestResolver.ts +25 -7
- package/src/resolvers/ConversationRunEventsResolver.ts +15 -11
- package/src/resolvers/DatasetResolver.ts +70 -10
- package/src/resolvers/EntityRecordNameResolver.ts +3 -2
- package/src/resolvers/FetchEntityVectorsResolver.ts +179 -2
- package/src/resolvers/FileResolver.ts +321 -106
- package/src/resolvers/GetDataContextDataResolver.ts +139 -41
- package/src/resolvers/GetDataResolver.ts +6 -3
- package/src/resolvers/HumanHandoffResolver.ts +8 -0
- package/src/resolvers/IntegrationDiscoveryResolver.ts +4 -2
- package/src/resolvers/MCPResolver.ts +8 -5
- package/src/resolvers/QueryResolver.ts +11 -0
- package/src/resolvers/RealtimeBridgeResolver.ts +163 -11
- package/src/resolvers/RealtimeClientSessionResolver.ts +511 -129
- package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
- package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
- package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
- package/src/resolvers/RunAIAgentResolver.ts +116 -14
- package/src/resolvers/RunTemplateResolver.ts +13 -12
- package/src/resolvers/SearchKnowledgeStreamResolver.ts +19 -3
- package/src/resolvers/SqlLoggingConfigResolver.ts +2 -4
- package/src/resolvers/SyncDataResolver.ts +6 -2
- package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
- package/src/resolvers/TaskGraphFrameResolver.ts +33 -1
- package/src/resolvers/TestQuerySQLResolver.ts +68 -13
- package/src/resolvers/TransactionGroupResolver.ts +131 -27
- package/src/resolvers/UserAvatarResolver.ts +111 -0
- package/src/resolvers/UserResolver.ts +2 -16
- package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
- package/src/resolvers/agent-run-data-guard.ts +89 -0
- package/src/resolvers/agentVisionConsentAudit.ts +82 -0
- package/src/resolvers/avatarInputValidation.ts +123 -0
- package/src/resolvers/currentUserRoles.ts +32 -1
- package/src/resolvers/meetings/meetingMutations.ts +21 -0
- package/src/rest/RealtimeRecordingHandler.ts +464 -0
- package/src/rest/ViewOperationsHandler.ts +29 -13
- package/src/rest/setupRESTEndpoints.ts +6 -0
- package/src/serverExtensionReservedRoots.ts +14 -17
- package/src/services/MJStorageBlobStore.ts +31 -30
- package/src/services/StartTaskGraphDispatcher.ts +2 -2
- package/src/services/TaskGraphActionRunner.ts +25 -5
- package/src/services/TaskGraphAgentRunner.ts +188 -11
- package/src/services/TaskGraphContinuationDeliverer.ts +713 -17
- package/src/sharedCache.ts +305 -0
- package/src/types.ts +7 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { EntityPermissionType, FieldValueCollection, EntitySaveOptions, LogError, UserInfo } from '@memberjunction/core';
|
|
1
|
+
import { EntityPermissionType, FieldValueCollection, EntitySaveOptions, IMetadataProvider, LogError, UserInfo } from '@memberjunction/core';
|
|
2
2
|
import { NormalizeUUID } from '@memberjunction/global';
|
|
3
3
|
import { MJFileEntity, MJFileStorageProviderEntity, MJFileStorageAccountEntity } from '@memberjunction/core-entities';
|
|
4
|
-
import { readRealtimeRecordingFile } from '@memberjunction/ai-agents';
|
|
5
4
|
import {
|
|
6
5
|
AppContext,
|
|
7
6
|
Arg,
|
|
@@ -35,14 +34,23 @@ import {
|
|
|
35
34
|
UserContextOptions,
|
|
36
35
|
ExtendedUserContextOptions,
|
|
37
36
|
FileStorageEngine,
|
|
37
|
+
FileStorageBase,
|
|
38
|
+
MultiAccountSearchResult,
|
|
39
|
+
ResolvedFileObject,
|
|
40
|
+
StorageAccessEvaluator,
|
|
41
|
+
StorageAccountAccess,
|
|
42
|
+
StorageAccountAccessDeniedError,
|
|
43
|
+
StorageFileLocation,
|
|
44
|
+
StorageObjectKeyOf,
|
|
45
|
+
StorageObjectMetadata as StoredObjectMetadata,
|
|
38
46
|
} from '@memberjunction/storage';
|
|
39
47
|
import { CreateMJFileInput, MJFileResolver as FileResolverBase, MJFile_, UpdateMJFileInput } from '../generated/generated.js';
|
|
40
48
|
import { FieldMapper } from '@memberjunction/graphql-dataprovider';
|
|
41
49
|
import { GetReadOnlyProvider } from '../util.js';
|
|
42
|
-
import { configInfo } from '../config.js';
|
|
43
50
|
import { MediaAccessKeyManager } from '../rest/MediaAccessKeys.js';
|
|
44
51
|
import { UploadTokenManager } from '../rest/UploadTokenManager.js';
|
|
45
52
|
import { DeriveSidecarPath, ParsePeaksSidecar } from './peaksSidecar.js';
|
|
53
|
+
import { ApiRoute } from '../apiRoot.js';
|
|
46
54
|
|
|
47
55
|
@InputType()
|
|
48
56
|
export class CreateUploadURLInput {
|
|
@@ -434,18 +442,11 @@ export class MediaAccessTokenResult {
|
|
|
434
442
|
ErrorMessage?: string;
|
|
435
443
|
}
|
|
436
444
|
|
|
445
|
+
/** The failure reported for an account `SearchAcrossAccounts` refuses — the same whether it is restricted or does not exist. */
|
|
446
|
+
const SEARCH_ACCOUNT_REFUSED_MESSAGE = 'Access denied: you do not have access to this storage account or it does not exist.';
|
|
447
|
+
|
|
437
448
|
@Resolver(MJFile_)
|
|
438
449
|
export class FileResolver extends FileResolverBase {
|
|
439
|
-
/**
|
|
440
|
-
* Resolves the server's public base URL the same way callbacks/magic-link do:
|
|
441
|
-
* `publicUrl` if configured (e.g. an ngrok URL), else `baseUrl:graphqlPort`. Trailing
|
|
442
|
-
* slash stripped so callers can append `/media/...`.
|
|
443
|
-
*/
|
|
444
|
-
private resolvePublicBaseUrl(): string {
|
|
445
|
-
const base = configInfo.publicUrl || `${configInfo.baseUrl}:${configInfo.graphqlPort}${configInfo.graphqlRootPath || ''}`;
|
|
446
|
-
return base.replace(/\/+$/, '');
|
|
447
|
-
}
|
|
448
|
-
|
|
449
450
|
/**
|
|
450
451
|
* Mints a short-lived signed media-access token for an `MJ: Files` record and returns the
|
|
451
452
|
* authenticated streaming URL (`<publicBase>/media/<fileId>?token=<token>`). Permission-gated
|
|
@@ -469,16 +470,19 @@ export class FileResolver extends FileResolverBase {
|
|
|
469
470
|
if (!loaded) {
|
|
470
471
|
return { Success: false, ErrorMessage: 'You do not have access to this file or it does not exist.' };
|
|
471
472
|
}
|
|
473
|
+
// Storage gate: the token is the capability the /media route honours, so it is minted only when the caller may read
|
|
474
|
+
// the account the row's provider resolves to and every row tracking the object (throws — caught below).
|
|
475
|
+
const resolved = await this.assertFileObjectAccess(fileEntity, 'Read', contextUser, provider);
|
|
472
476
|
|
|
473
477
|
// Access authorized — mint the capability token.
|
|
474
478
|
const { Token, ExpiresAt } = MediaAccessKeyManager.Instance.Sign(fileId, contextUser.ID);
|
|
475
479
|
const fileNameSuffix = fileEntity.Name ? `/${encodeURIComponent(fileEntity.Name)}` : '';
|
|
476
|
-
const url =
|
|
480
|
+
const url = ApiRoute(`/media/${encodeURIComponent(fileId)}${fileNameSuffix}?token=${encodeURIComponent(Token)}`, { absolute: true });
|
|
477
481
|
|
|
478
482
|
// Best-effort: surface precomputed waveform peaks from a peaks.json sidecar beside the file, so
|
|
479
483
|
// the player renders the real waveform instantly without fetching/decoding the audio. Never
|
|
480
484
|
// blocks token minting — any failure just omits Peaks.
|
|
481
|
-
const peaks = await this.tryReadPeaksSidecar(fileEntity,
|
|
485
|
+
const peaks = await this.tryReadPeaksSidecar(fileEntity, resolved?.Driver);
|
|
482
486
|
|
|
483
487
|
return { Success: true, Token, Url: url, ExpiresAt, MimeType: fileEntity.ContentType ?? undefined, Peaks: peaks };
|
|
484
488
|
} catch (error) {
|
|
@@ -492,34 +496,22 @@ export class FileResolver extends FileResolverBase {
|
|
|
492
496
|
* Best-effort read of a `peaks.json` waveform sidecar that sits in the SAME storage folder as the
|
|
493
497
|
* given file (a JSON array of normalized `0..1` numbers, written at capture time). Derives the
|
|
494
498
|
* folder from the file's `ProviderKey` (strips the final path segment), reads `<folder>/peaks.json`
|
|
495
|
-
*
|
|
496
|
-
* `undefined` on ANY failure (no ProviderKey, no
|
|
497
|
-
* peaks as a pure optimization and must never let a
|
|
499
|
+
* through the file's own storage driver — the one {@link assertFileObjectAccess} resolved, so the account is already
|
|
500
|
+
* gated — parses + validates it, and returns sanitized peaks. Returns `undefined` on ANY failure (no ProviderKey, no
|
|
501
|
+
* driver, no sidecar, parse error, garbage) — the caller treats peaks as a pure optimization and must never let a
|
|
502
|
+
* sidecar problem affect token minting.
|
|
498
503
|
*
|
|
499
504
|
* @param file The already-loaded (under the user's context) `MJ: Files` row.
|
|
500
|
-
* @param
|
|
505
|
+
* @param driver The gated driver for the file's account, or undefined when the provider has none.
|
|
501
506
|
* @returns Sanitized `0..1` peaks (length-capped), or `undefined`.
|
|
502
507
|
*/
|
|
503
|
-
private async tryReadPeaksSidecar(file: MJFileEntity,
|
|
508
|
+
private async tryReadPeaksSidecar(file: MJFileEntity, driver: FileStorageBase | undefined): Promise<number[] | undefined> {
|
|
504
509
|
try {
|
|
505
510
|
// The sidecar lives next to the recording: replace the final path segment with peaks.json.
|
|
506
511
|
const sidecarPath = DeriveSidecarPath(file.ProviderKey);
|
|
507
|
-
if (!sidecarPath) {
|
|
512
|
+
if (!sidecarPath || !driver) {
|
|
508
513
|
return undefined;
|
|
509
514
|
}
|
|
510
|
-
|
|
511
|
-
// Resolve the file's storage account → driver (mirror of readRealtimeRecordingFile's pattern).
|
|
512
|
-
await FileStorageEngine.Instance.Config(false, contextUser);
|
|
513
|
-
let accounts = FileStorageEngine.Instance.GetAccountsByProviderID(file.ProviderID);
|
|
514
|
-
if (accounts.length === 0) {
|
|
515
|
-
await FileStorageEngine.Instance.Config(true, contextUser);
|
|
516
|
-
accounts = FileStorageEngine.Instance.GetAccountsByProviderID(file.ProviderID);
|
|
517
|
-
}
|
|
518
|
-
const account = accounts[0];
|
|
519
|
-
if (!account) {
|
|
520
|
-
return undefined;
|
|
521
|
-
}
|
|
522
|
-
const driver = await FileStorageEngine.Instance.GetDriver(account.ID, contextUser);
|
|
523
515
|
const bytes = await driver.GetObject({ fullPath: sidecarPath });
|
|
524
516
|
if (!bytes || bytes.length === 0) {
|
|
525
517
|
return undefined;
|
|
@@ -557,6 +549,75 @@ export class FileResolver extends FileResolverBase {
|
|
|
557
549
|
};
|
|
558
550
|
}
|
|
559
551
|
|
|
552
|
+
/**
|
|
553
|
+
* The storage-account gate. Every route keyed by a client-supplied account ID runs it FIRST — before the account is
|
|
554
|
+
* loaded and before any driver call. Throws `StorageAccountAccessDeniedError` ("You do not have access to this storage
|
|
555
|
+
* account or it does not exist.") unless the caller may use the account for `access`, evaluated per call against
|
|
556
|
+
* `MJ: File Storage Account Permissions` by `StorageAccessEvaluator` (`@memberjunction/storage`). An unknown account
|
|
557
|
+
* gets the same refusal, so the error never reveals whether an account ID is real.
|
|
558
|
+
*/
|
|
559
|
+
private async assertAccountAccess(accountId: string, access: StorageAccountAccess, context: AppContext): Promise<void> {
|
|
560
|
+
const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
|
|
561
|
+
const user = this.GetUserFromPayload(context.userPayload);
|
|
562
|
+
await StorageAccessEvaluator.Instance.AssertAccountAccess(accountId, user, access, md);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* The tracked-file rule for an account-keyed route: when any of `objectNames` in the account backs an `MJ: Files` row,
|
|
567
|
+
* the caller must be able to read that row — the rule {@link GetFileContents} applies to a file ID — else this throws
|
|
568
|
+
* `TrackedFileAccessDeniedError` ("You do not have access to this file or it does not exist."). Keys are compared the
|
|
569
|
+
* way the account's driver addresses them (`FileStorageBase.NormalizeObjectKey`) and case-insensitively; a key whose
|
|
570
|
+
* meaning depends on the provider (`..`, `\`, `%2F`, ...) is refused outright. Applied to every object a route reads
|
|
571
|
+
* or signs, and to every object it overwrites, moves or deletes — the minimum for a write is that the caller may read
|
|
572
|
+
* what it replaces. An object no row tracks is governed by the account gate, which the caller has already passed.
|
|
573
|
+
*/
|
|
574
|
+
private async assertTrackedObjectReadable(accountId: string, objectNames: string[], user: UserInfo, md: IMetadataProvider): Promise<void> {
|
|
575
|
+
const providerID = await this.accountProviderID(accountId, user, md);
|
|
576
|
+
const driver = await FileStorageEngine.Instance.GetDriver(accountId, user);
|
|
577
|
+
await StorageAccessEvaluator.Instance.AssertTrackedObjectsReadable(providerID, objectNames, user, md, driver);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* The storage gate for a route that goes from an `MJ: Files` row (already loaded as the caller) to its object: the
|
|
582
|
+
* account the row's provider resolves to must allow `access`, and every row tracking the object must be readable by
|
|
583
|
+
* the caller (`FileStorageEngine.ResolveFileObject`) — so a row whose client-written `ProviderID`/`ProviderKey`/`Name`
|
|
584
|
+
* aliases someone else's object is refused. `extraKeys` (a rename target) get the tracked-file rule too. When the
|
|
585
|
+
* provider has no storage account the legacy env-configured driver serves the row; there is no account to gate, so the
|
|
586
|
+
* tracked-file rule alone applies. Returns the resolved object, or `null` in that legacy case.
|
|
587
|
+
*/
|
|
588
|
+
private async assertFileObjectAccess(
|
|
589
|
+
file: StorageFileLocation,
|
|
590
|
+
access: StorageAccountAccess,
|
|
591
|
+
user: UserInfo,
|
|
592
|
+
md: IMetadataProvider,
|
|
593
|
+
extraKeys: string[] = [],
|
|
594
|
+
): Promise<ResolvedFileObject | null> {
|
|
595
|
+
const location: StorageFileLocation = { ProviderID: file.ProviderID, ProviderKey: file.ProviderKey ?? null, Name: file.Name };
|
|
596
|
+
const resolved = await FileStorageEngine.Instance.ResolveFileObject(location, user, access, md);
|
|
597
|
+
const keys = resolved ? extraKeys : [StorageObjectKeyOf(location), ...extraKeys];
|
|
598
|
+
if (keys.length > 0) {
|
|
599
|
+
await StorageAccessEvaluator.Instance.AssertTrackedObjectsReadable(file.ProviderID, keys, user, md, resolved?.Driver);
|
|
600
|
+
}
|
|
601
|
+
return resolved;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* The storage provider ID of an account the caller has already passed the gate for, from the engine's cache (refreshed
|
|
606
|
+
* once on a miss). Refuses like the gate when the account still cannot be found.
|
|
607
|
+
*/
|
|
608
|
+
private async accountProviderID(accountId: string, user: UserInfo, md: IMetadataProvider): Promise<string> {
|
|
609
|
+
await FileStorageEngine.Instance.Config(false, user, md);
|
|
610
|
+
let account = FileStorageEngine.Instance.GetAccountById(accountId);
|
|
611
|
+
if (!account) {
|
|
612
|
+
await FileStorageEngine.Instance.RefreshAfterMiss(md);
|
|
613
|
+
account = FileStorageEngine.Instance.GetAccountById(accountId);
|
|
614
|
+
}
|
|
615
|
+
if (!account) {
|
|
616
|
+
throw new StorageAccountAccessDeniedError();
|
|
617
|
+
}
|
|
618
|
+
return account.ProviderID;
|
|
619
|
+
}
|
|
620
|
+
|
|
560
621
|
/**
|
|
561
622
|
* Loads a FileStorageAccount and its associated FileStorageProvider.
|
|
562
623
|
* This is the standard way to get provider information in the enterprise model.
|
|
@@ -606,6 +667,11 @@ export class FileResolver extends FileResolverBase {
|
|
|
606
667
|
const providerEntity = await provider.GetEntityObject<MJFileStorageProviderEntity>('MJ: File Storage Providers', user);
|
|
607
668
|
fileEntity.CheckPermissions(EntityPermissionType.Create, true);
|
|
608
669
|
|
|
670
|
+
// The upload URL signs an overwrite of `Name` (and the row will point at `ProviderKey`): Write on the account the
|
|
671
|
+
// provider resolves to, and the tracked-file rule on both keys, as for every other route that writes an object.
|
|
672
|
+
const location: StorageFileLocation = { ProviderID: input.ProviderID ?? '', ProviderKey: null, Name: input.Name ?? '' };
|
|
673
|
+
await this.assertFileObjectAccess(location, 'Write', user, provider, input.ProviderKey ? [input.ProviderKey] : []);
|
|
674
|
+
|
|
609
675
|
const [sameName] = await this.findBy(provider, 'MJ: Files', { Name: input.Name, ProviderID: input.ProviderID }, context.userPayload.userRecord);
|
|
610
676
|
const NameExists = Boolean(sameName);
|
|
611
677
|
|
|
@@ -650,6 +716,13 @@ export class FileResolver extends FileResolverBase {
|
|
|
650
716
|
const fileEntity = await provider.GetEntityObject<MJFileEntity>('MJ: Files', user);
|
|
651
717
|
fileEntity.CheckPermissions(EntityPermissionType.Create, true);
|
|
652
718
|
|
|
719
|
+
// Write gate on the account the upload lands in — before the staged upload is consumed or any driver is touched.
|
|
720
|
+
// With no account at all there is nothing to gate; UploadFile then refuses with its documented error.
|
|
721
|
+
const targetAccountId = await this.resolveUploadAccountId(input.AccountID, user, provider);
|
|
722
|
+
if (targetAccountId) {
|
|
723
|
+
await this.assertAccountAccess(targetAccountId, 'Write', context);
|
|
724
|
+
}
|
|
725
|
+
|
|
653
726
|
let buffer: Buffer;
|
|
654
727
|
let fileName: string = input.FileName || 'file';
|
|
655
728
|
let mimeType: string = input.MimeType || 'application/octet-stream';
|
|
@@ -674,18 +747,12 @@ export class FileResolver extends FileResolverBase {
|
|
|
674
747
|
};
|
|
675
748
|
}
|
|
676
749
|
|
|
677
|
-
// Ensure FileStorageEngine is configured under the user context
|
|
678
|
-
await FileStorageEngine.Instance.Config(false, user, provider);
|
|
679
|
-
if (input.AccountID && !FileStorageEngine.Instance.GetAccountById(input.AccountID)) {
|
|
680
|
-
await FileStorageEngine.Instance.Config(true, user, provider);
|
|
681
|
-
}
|
|
682
|
-
|
|
683
750
|
const uploadResult = await FileStorageEngine.Instance.UploadFile({
|
|
684
751
|
content: buffer,
|
|
685
752
|
fileName,
|
|
686
753
|
mimeType,
|
|
687
754
|
contextUser: user,
|
|
688
|
-
storageAccountId:
|
|
755
|
+
storageAccountId: targetAccountId,
|
|
689
756
|
categoryID: input.CategoryID,
|
|
690
757
|
description: input.Description,
|
|
691
758
|
pathPrefix: input.PathPrefix,
|
|
@@ -711,6 +778,20 @@ export class FileResolver extends FileResolverBase {
|
|
|
711
778
|
}
|
|
712
779
|
}
|
|
713
780
|
|
|
781
|
+
/**
|
|
782
|
+
* The account an upload will land in: the requested one, else the engine's default (the first active account) —
|
|
783
|
+
* returned as an explicit ID so the account the gate checks is the account `UploadFile` uses. A requested ID is
|
|
784
|
+
* returned as-is: the gate refuses an unknown one without revealing that it does not exist, and `UploadFile` refreshes
|
|
785
|
+
* the engine's cache itself when a permitted account is missing from it.
|
|
786
|
+
*/
|
|
787
|
+
private async resolveUploadAccountId(accountId: string | undefined, user: UserInfo, md: IMetadataProvider): Promise<string | undefined> {
|
|
788
|
+
if (accountId) {
|
|
789
|
+
return accountId;
|
|
790
|
+
}
|
|
791
|
+
await FileStorageEngine.Instance.Config(false, user, md);
|
|
792
|
+
return FileStorageEngine.Instance.ResolveStorageAccount()?.account.ID;
|
|
793
|
+
}
|
|
794
|
+
|
|
714
795
|
@FieldResolver(() => String)
|
|
715
796
|
async DownloadUrl(@Root() file: MJFile_, @Ctx() context: AppContext) {
|
|
716
797
|
const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
|
|
@@ -718,24 +799,18 @@ export class FileResolver extends FileResolverBase {
|
|
|
718
799
|
const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
|
|
719
800
|
fileEntity.CheckPermissions(EntityPermissionType.Read, true);
|
|
720
801
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
if (accounts.length > 0) {
|
|
729
|
-
const account = accounts[0];
|
|
730
|
-
const driver = await FileStorageEngine.Instance.GetDriver(account.ID, user);
|
|
731
|
-
return await driver.CreatePreAuthDownloadUrl(file.ProviderKey ?? file.Name);
|
|
802
|
+
// Storage gate: Read on the account the row's provider resolves to, and the tracked-file rule on its object
|
|
803
|
+
const location: StorageFileLocation = { ProviderID: file.ProviderID, ProviderKey: file.ProviderKey ?? null, Name: file.Name ?? '' };
|
|
804
|
+
const resolved = await this.assertFileObjectAccess(location, 'Read', user, md);
|
|
805
|
+
if (resolved) {
|
|
806
|
+
return await resolved.Driver.CreatePreAuthDownloadUrl(resolved.ObjectKey);
|
|
732
807
|
}
|
|
733
808
|
|
|
734
809
|
const providerEntity = await md.GetEntityObject<MJFileStorageProviderEntity>('MJ: File Storage Providers', user);
|
|
735
810
|
await providerEntity.Load(file.ProviderID);
|
|
736
811
|
|
|
737
812
|
const userContext = this.buildUserContext(context);
|
|
738
|
-
const url = await createDownloadUrl(providerEntity, file
|
|
813
|
+
const url = await createDownloadUrl(providerEntity, StorageObjectKeyOf(file), userContext);
|
|
739
814
|
|
|
740
815
|
return url;
|
|
741
816
|
}
|
|
@@ -743,7 +818,9 @@ export class FileResolver extends FileResolverBase {
|
|
|
743
818
|
/**
|
|
744
819
|
* Returns an `MJ: Files` record's bytes as base64, read server-side through authenticated MJStorage
|
|
745
820
|
* (`GetObject`) — NOT a public pre-signed link. Permission-gated: the file is first loaded under the
|
|
746
|
-
* calling user's context, so MJ row-level security determines access
|
|
821
|
+
* calling user's context, so MJ row-level security determines access; then the storage gate
|
|
822
|
+
* ({@link assertFileObjectAccess}) requires Read on the account the row's provider resolves to and refuses an object
|
|
823
|
+
* another row tracks when the caller cannot read that row. Never throws to the client.
|
|
747
824
|
*
|
|
748
825
|
* @param fileId The `MJ: Files` id whose bytes to return.
|
|
749
826
|
* @returns `{ Success, Base64?, MimeType?, ErrorMessage? }`.
|
|
@@ -762,16 +839,18 @@ export class FileResolver extends FileResolverBase {
|
|
|
762
839
|
return { Success: false, ErrorMessage: 'You do not have access to this file or it does not exist.' };
|
|
763
840
|
}
|
|
764
841
|
|
|
765
|
-
//
|
|
766
|
-
|
|
767
|
-
|
|
842
|
+
// Storage gate, then read the bytes via authenticated MJStorage (server-side GetObject on the file's own account).
|
|
843
|
+
// A refusal throws and is reported below; a row whose provider has no account has no bytes to read.
|
|
844
|
+
const resolved = await this.assertFileObjectAccess(fileEntity, 'Read', contextUser, provider);
|
|
845
|
+
const bytes = resolved ? await resolved.Driver.GetObject({ fullPath: resolved.ObjectKey }) : null;
|
|
846
|
+
if (!bytes || bytes.length === 0) {
|
|
768
847
|
return { Success: false, ErrorMessage: 'The file could not be read from storage.' };
|
|
769
848
|
}
|
|
770
849
|
|
|
771
850
|
return {
|
|
772
851
|
Success: true,
|
|
773
|
-
Base64:
|
|
774
|
-
MimeType:
|
|
852
|
+
Base64: bytes.toString('base64'),
|
|
853
|
+
MimeType: fileEntity.ContentType ?? undefined,
|
|
775
854
|
};
|
|
776
855
|
} catch (error) {
|
|
777
856
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -791,6 +870,8 @@ export class FileResolver extends FileResolverBase {
|
|
|
791
870
|
await fileEntity.Load(input.ID);
|
|
792
871
|
|
|
793
872
|
if (fileEntity.Name !== input.Name) {
|
|
873
|
+
// Renaming moves the object: Write on its account, and the tracked-file rule on the object and the new name
|
|
874
|
+
await this.assertFileObjectAccess(fileEntity, 'Write', user, md, [fileEntity.Name, input.Name]);
|
|
794
875
|
const providerEntity = await md.GetEntityObject<MJFileStorageProviderEntity>('MJ: File Storage Providers', user);
|
|
795
876
|
await providerEntity.Load(fileEntity.ProviderID);
|
|
796
877
|
|
|
@@ -822,12 +903,14 @@ export class FileResolver extends FileResolverBase {
|
|
|
822
903
|
}
|
|
823
904
|
fileEntity.CheckPermissions(EntityPermissionType.Delete, true);
|
|
824
905
|
|
|
825
|
-
// Only delete the object from the provider if it's actually been uploaded
|
|
906
|
+
// Only delete the object from the provider if it's actually been uploaded — after the storage gate: Write on its
|
|
907
|
+
// account, and no other row the caller cannot read may track the object (a row can alias another row's object)
|
|
826
908
|
if (fileEntity.Status === 'Uploaded') {
|
|
909
|
+
await this.assertFileObjectAccess(fileEntity, 'Write', userInfo, md);
|
|
827
910
|
const providerEntity = await md.GetEntityObject<MJFileStorageProviderEntity>('MJ: File Storage Providers', userInfo);
|
|
828
911
|
await providerEntity.Load(fileEntity.ProviderID);
|
|
829
912
|
const userContext = this.buildUserContext(context);
|
|
830
|
-
await deleteObject(providerEntity, fileEntity
|
|
913
|
+
await deleteObject(providerEntity, StorageObjectKeyOf(fileEntity), userContext);
|
|
831
914
|
}
|
|
832
915
|
|
|
833
916
|
return super.DeleteMJFile(ID, options, context, pubSub);
|
|
@@ -844,13 +927,16 @@ export class FileResolver extends FileResolverBase {
|
|
|
844
927
|
const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
|
|
845
928
|
const user = this.GetUserFromPayload(context.userPayload);
|
|
846
929
|
|
|
930
|
+
// Account gate first — before the account is loaded or any driver is touched
|
|
931
|
+
await this.assertAccountAccess(input.AccountID, 'Read', context);
|
|
932
|
+
|
|
847
933
|
// Load the account and its provider
|
|
848
934
|
const { account, provider: providerEntity } = await this.loadAccountAndProvider(input.AccountID, context);
|
|
849
935
|
|
|
850
936
|
console.log('[FileResolver] Provider loaded:', {
|
|
851
937
|
Name: providerEntity.Name,
|
|
852
938
|
ServerDriverKey: providerEntity.ServerDriverKey,
|
|
853
|
-
HasConfiguration: !!providerEntity.
|
|
939
|
+
HasConfiguration: !!providerEntity.Configuration,
|
|
854
940
|
});
|
|
855
941
|
|
|
856
942
|
// Check permissions - user must have read access to Files entity
|
|
@@ -868,8 +954,10 @@ export class FileResolver extends FileResolverBase {
|
|
|
868
954
|
prefixes: result.prefixes,
|
|
869
955
|
});
|
|
870
956
|
|
|
871
|
-
//
|
|
872
|
-
|
|
957
|
+
// Drop objects behind an MJ: Files row the caller cannot read — a listing never names a tracked object the caller
|
|
958
|
+
// is refused — then convert Date objects to ISO strings for GraphQL
|
|
959
|
+
const visible = await this.dropUnreadableListedObjects(input.AccountID, result.objects, user, md);
|
|
960
|
+
const objects = visible.map((obj) => ({
|
|
873
961
|
...obj,
|
|
874
962
|
lastModified: obj.lastModified.toISOString(),
|
|
875
963
|
}));
|
|
@@ -889,6 +977,10 @@ export class FileResolver extends FileResolverBase {
|
|
|
889
977
|
const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
|
|
890
978
|
fileEntity.CheckPermissions(EntityPermissionType.Read, true);
|
|
891
979
|
|
|
980
|
+
// Account gate, then — when the object backs an MJ: Files row — that row must be readable by the caller
|
|
981
|
+
await this.assertAccountAccess(input.AccountID, 'Read', context);
|
|
982
|
+
await this.assertTrackedObjectReadable(input.AccountID, [input.ObjectName], user, md);
|
|
983
|
+
|
|
892
984
|
await FileStorageEngine.Instance.Config(false, user, md);
|
|
893
985
|
const driver = await FileStorageEngine.Instance.GetDriver(input.AccountID, user);
|
|
894
986
|
const downloadUrl = await driver.CreatePreAuthDownloadUrl(input.ObjectName);
|
|
@@ -920,6 +1012,9 @@ export class FileResolver extends FileResolverBase {
|
|
|
920
1012
|
// Check permissions
|
|
921
1013
|
const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
|
|
922
1014
|
fileEntity.CheckPermissions(EntityPermissionType.Create, true);
|
|
1015
|
+
await this.assertAccountAccess(input.AccountID, 'Write', context);
|
|
1016
|
+
// An upload URL can overwrite: the caller must be able to read any MJ: Files row that tracks the target
|
|
1017
|
+
await this.assertTrackedObjectReadable(input.AccountID, [input.ObjectName], user, md);
|
|
923
1018
|
|
|
924
1019
|
await FileStorageEngine.Instance.Config(false, user, md);
|
|
925
1020
|
const driver = await FileStorageEngine.Instance.GetDriver(input.AccountID, user);
|
|
@@ -956,7 +1051,7 @@ export class FileResolver extends FileResolverBase {
|
|
|
956
1051
|
return {
|
|
957
1052
|
Success: true,
|
|
958
1053
|
Token: mint.Token,
|
|
959
|
-
Url: '/media/upload-stage',
|
|
1054
|
+
Url: ApiRoute('/media/upload-stage'),
|
|
960
1055
|
};
|
|
961
1056
|
} catch (err) {
|
|
962
1057
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -972,12 +1067,16 @@ export class FileResolver extends FileResolverBase {
|
|
|
972
1067
|
const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
|
|
973
1068
|
const user = this.GetUserFromPayload(context.userPayload);
|
|
974
1069
|
|
|
1070
|
+
// Account gate first — before the account is loaded or any driver is touched
|
|
1071
|
+
await this.assertAccountAccess(input.AccountID, 'Write', context);
|
|
1072
|
+
|
|
975
1073
|
// Load the account and its provider
|
|
976
1074
|
const { account, provider: providerEntity } = await this.loadAccountAndProvider(input.AccountID, context);
|
|
977
1075
|
|
|
978
1076
|
// Check permissions
|
|
979
1077
|
const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
|
|
980
1078
|
fileEntity.CheckPermissions(EntityPermissionType.Delete, true);
|
|
1079
|
+
await this.assertTrackedObjectReadable(input.AccountID, [input.ObjectName], user, md);
|
|
981
1080
|
|
|
982
1081
|
// Delete the object with extended user context (includes account for credential lookup)
|
|
983
1082
|
const userContext = this.buildExtendedUserContext(context, account);
|
|
@@ -991,12 +1090,17 @@ export class FileResolver extends FileResolverBase {
|
|
|
991
1090
|
const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
|
|
992
1091
|
const user = this.GetUserFromPayload(context.userPayload);
|
|
993
1092
|
|
|
1093
|
+
// Account gate first — before the account is loaded or any driver is touched
|
|
1094
|
+
await this.assertAccountAccess(input.AccountID, 'Write', context);
|
|
1095
|
+
|
|
994
1096
|
// Load the account and its provider
|
|
995
1097
|
const { account, provider: providerEntity } = await this.loadAccountAndProvider(input.AccountID, context);
|
|
996
1098
|
|
|
997
1099
|
// Check permissions
|
|
998
1100
|
const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
|
|
999
1101
|
fileEntity.CheckPermissions(EntityPermissionType.Update, true);
|
|
1102
|
+
// A move reads and removes the source and may overwrite the destination: both get the tracked-file rule
|
|
1103
|
+
await this.assertTrackedObjectReadable(input.AccountID, [input.OldName, input.NewName], user, md);
|
|
1000
1104
|
|
|
1001
1105
|
// Move the object with extended user context (includes account for credential lookup)
|
|
1002
1106
|
const userContext = this.buildExtendedUserContext(context, account);
|
|
@@ -1009,6 +1113,9 @@ export class FileResolver extends FileResolverBase {
|
|
|
1009
1113
|
const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
|
|
1010
1114
|
const user = this.GetUserFromPayload(context.userPayload);
|
|
1011
1115
|
|
|
1116
|
+
// Account gate first — a copy within one account writes to it — before the account is loaded or any driver is touched
|
|
1117
|
+
await this.assertAccountAccess(input.AccountID, 'Write', context);
|
|
1118
|
+
|
|
1012
1119
|
// Load the account and its provider
|
|
1013
1120
|
const { account, provider: providerEntity } = await this.loadAccountAndProvider(input.AccountID, context);
|
|
1014
1121
|
|
|
@@ -1016,6 +1123,9 @@ export class FileResolver extends FileResolverBase {
|
|
|
1016
1123
|
const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
|
|
1017
1124
|
fileEntity.CheckPermissions(EntityPermissionType.Read, true);
|
|
1018
1125
|
fileEntity.CheckPermissions(EntityPermissionType.Create, true);
|
|
1126
|
+
// A copy of a tracked object is an untracked one the caller could then download, and the destination may be
|
|
1127
|
+
// overwritten: both get the tracked-file rule
|
|
1128
|
+
await this.assertTrackedObjectReadable(input.AccountID, [input.SourceName, input.DestinationName], user, md);
|
|
1019
1129
|
|
|
1020
1130
|
// Copy the object with extended user context (includes account for credential lookup)
|
|
1021
1131
|
const userContext = this.buildExtendedUserContext(context, account);
|
|
@@ -1028,6 +1138,9 @@ export class FileResolver extends FileResolverBase {
|
|
|
1028
1138
|
const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
|
|
1029
1139
|
const user = this.GetUserFromPayload(context.userPayload);
|
|
1030
1140
|
|
|
1141
|
+
// Account gate first — before the account is loaded or any driver is touched
|
|
1142
|
+
await this.assertAccountAccess(input.AccountID, 'Write', context);
|
|
1143
|
+
|
|
1031
1144
|
// Load the account and its provider
|
|
1032
1145
|
const { account: accountEntity, provider: providerEntity } = await this.loadAccountAndProvider(input.AccountID, context);
|
|
1033
1146
|
|
|
@@ -1063,6 +1176,13 @@ export class FileResolver extends FileResolverBase {
|
|
|
1063
1176
|
fileEntity.CheckPermissions(EntityPermissionType.Read, true);
|
|
1064
1177
|
fileEntity.CheckPermissions(EntityPermissionType.Create, true);
|
|
1065
1178
|
|
|
1179
|
+
// Account gates first: read the source, write the destination — before either account is loaded or touched
|
|
1180
|
+
await this.assertAccountAccess(input.SourceAccountID, 'Read', context);
|
|
1181
|
+
await this.assertAccountAccess(input.DestinationAccountID, 'Write', context);
|
|
1182
|
+
// Tracked-file rule on both ends: the source (its copy would be untracked), the destination (it may be overwritten)
|
|
1183
|
+
await this.assertTrackedObjectReadable(input.SourceAccountID, [input.SourcePath], user, md);
|
|
1184
|
+
await this.assertTrackedObjectReadable(input.DestinationAccountID, [input.DestinationPath], user, md);
|
|
1185
|
+
|
|
1066
1186
|
// Load the source account and its provider
|
|
1067
1187
|
const { account: sourceAccount, provider: sourceProviderEntity } = await this.loadAccountAndProvider(input.SourceAccountID, context);
|
|
1068
1188
|
|
|
@@ -1110,54 +1230,149 @@ export class FileResolver extends FileResolverBase {
|
|
|
1110
1230
|
const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
|
|
1111
1231
|
fileEntity.CheckPermissions(EntityPermissionType.Read, true);
|
|
1112
1232
|
|
|
1113
|
-
//
|
|
1114
|
-
await
|
|
1115
|
-
const
|
|
1116
|
-
|
|
1117
|
-
|
|
1233
|
+
// Account gate BEFORE any driver call: refused accounts are reported as failed and never searched
|
|
1234
|
+
const { searchable, refused } = await this.partitionSearchAccounts(input.AccountIDs, user, md);
|
|
1235
|
+
const result: MultiAccountSearchResult = searchable.length > 0
|
|
1236
|
+
? await searchAcrossAccounts(searchable, input.Query, {
|
|
1237
|
+
maxResultsPerAccount: input.MaxResultsPerAccount,
|
|
1238
|
+
fileTypes: input.FileTypes,
|
|
1239
|
+
searchContent: input.SearchContent,
|
|
1240
|
+
contextUser: user,
|
|
1241
|
+
})
|
|
1242
|
+
: { accountResults: [], totalResultsReturned: 0, successfulAccounts: 0, failedAccounts: 0 };
|
|
1118
1243
|
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1244
|
+
console.log('[FileResolver] SearchAcrossAccounts result:', {
|
|
1245
|
+
totalResultsReturned: result.totalResultsReturned,
|
|
1246
|
+
successfulAccounts: result.successfulAccounts,
|
|
1247
|
+
failedAccounts: result.failedAccounts,
|
|
1248
|
+
refusedAccounts: refused.length,
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
const kept = await this.dropUnreadableTrackedHits(result.accountResults, user, md);
|
|
1252
|
+
return this.toSearchAcrossAccountsPayload(this.inRequestOrder(input.AccountIDs, [...kept, ...refused]));
|
|
1253
|
+
}
|
|
1122
1254
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1255
|
+
/**
|
|
1256
|
+
* Per-account results in the order the accounts were requested (each requested ID once), whether searched or refused —
|
|
1257
|
+
* so a refused account sits where the caller put it rather than after every searched one.
|
|
1258
|
+
*/
|
|
1259
|
+
private inRequestOrder(requestedIDs: string[], results: AccountSearchResult[]): AccountSearchResult[] {
|
|
1260
|
+
const byID = new Map(results.map((r) => [NormalizeUUID(r.accountID), r]));
|
|
1261
|
+
const ordered: AccountSearchResult[] = [];
|
|
1262
|
+
for (const id of requestedIDs) {
|
|
1263
|
+
const result = byID.get(NormalizeUUID(id));
|
|
1264
|
+
if (result) {
|
|
1265
|
+
ordered.push(result);
|
|
1266
|
+
byID.delete(NormalizeUUID(id));
|
|
1267
|
+
}
|
|
1128
1268
|
}
|
|
1269
|
+
return [...ordered, ...byID.values()];
|
|
1270
|
+
}
|
|
1129
1271
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1272
|
+
/**
|
|
1273
|
+
* Splits the requested accounts into those to search and those refused, before any driver call. An account the
|
|
1274
|
+
* caller may not read is refused with {@link SEARCH_ACCOUNT_REFUSED_MESSAGE} — and so is one that does not exist,
|
|
1275
|
+
* identically, so the response never reveals which IDs are real. Readable accounts are paired with their provider
|
|
1276
|
+
* from the engine's cache (refreshed once if a readable account is missing from it). Duplicate IDs are searched once.
|
|
1277
|
+
*/
|
|
1278
|
+
private async partitionSearchAccounts(
|
|
1279
|
+
accountIDs: string[],
|
|
1280
|
+
user: UserInfo,
|
|
1281
|
+
md: IMetadataProvider,
|
|
1282
|
+
): Promise<{ searchable: AccountSearchInput[]; refused: AccountSearchResult[] }> {
|
|
1283
|
+
const readable = await StorageAccessEvaluator.Instance.AccessibleAccountIDs(accountIDs, user, 'Read', md);
|
|
1284
|
+
await FileStorageEngine.Instance.Config(false, user, md);
|
|
1285
|
+
if ([...readable].some((id) => !FileStorageEngine.Instance.GetAccountWithProvider(id))) {
|
|
1286
|
+
await FileStorageEngine.Instance.RefreshAfterMiss(md);
|
|
1134
1287
|
}
|
|
1135
1288
|
|
|
1136
|
-
|
|
1137
|
-
const
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1289
|
+
const searchable: AccountSearchInput[] = [];
|
|
1290
|
+
const refused: AccountSearchResult[] = [];
|
|
1291
|
+
const seen = new Set<string>();
|
|
1292
|
+
for (const requestedID of accountIDs) {
|
|
1293
|
+
const key = NormalizeUUID(requestedID);
|
|
1294
|
+
if (seen.has(key)) continue;
|
|
1295
|
+
seen.add(key);
|
|
1296
|
+
const pair = readable.has(key) ? FileStorageEngine.Instance.GetAccountWithProvider(requestedID) : null;
|
|
1297
|
+
if (pair) {
|
|
1298
|
+
searchable.push({ accountEntity: pair.account, providerEntity: pair.provider });
|
|
1299
|
+
} else {
|
|
1300
|
+
refused.push(this.refusedAccountResult(requestedID));
|
|
1142
1301
|
}
|
|
1143
1302
|
}
|
|
1303
|
+
return { searchable, refused };
|
|
1304
|
+
}
|
|
1144
1305
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1306
|
+
/** A failed per-account entry for an account the caller may not search; it names nothing beyond the requested ID. */
|
|
1307
|
+
private refusedAccountResult(accountID: string): AccountSearchResult {
|
|
1308
|
+
return {
|
|
1309
|
+
accountID,
|
|
1310
|
+
accountName: '',
|
|
1311
|
+
providerID: '',
|
|
1312
|
+
providerName: '',
|
|
1313
|
+
success: false,
|
|
1314
|
+
errorMessage: SEARCH_ACCOUNT_REFUSED_MESSAGE,
|
|
1315
|
+
results: [],
|
|
1316
|
+
hasMore: false,
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1152
1319
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1320
|
+
/**
|
|
1321
|
+
* Drops search hits that map to an `MJ: Files` row the caller may not read — one batched lookup per account, keyed on
|
|
1322
|
+
* each hit's path and provider object ID (`StorageAccessEvaluator.UnreadableTrackedObjectKeys`, compared the way the
|
|
1323
|
+
* account's driver addresses keys). Hits no row tracks stay; the account gate governs them.
|
|
1324
|
+
*
|
|
1325
|
+
* When any hit is dropped, the provider's `totalMatches` (which counted it) and `nextPageToken` (this route takes no
|
|
1326
|
+
* page token back) are withheld; `hasMore` is kept. How reliable the mapping is depends on the provider: for
|
|
1327
|
+
* object-key stores (S3, Azure Blob, GCS) a hit's path IS the row's `ProviderKey`; for hierarchical providers (Box,
|
|
1328
|
+
* Google Drive, Dropbox, SharePoint) a search path may not match the key a row was written with, and such a miss falls
|
|
1329
|
+
* back to the account gate. `MJ: Files` rows are keyed by provider, not account, so the lookup errs toward dropping.
|
|
1330
|
+
*/
|
|
1331
|
+
private async dropUnreadableTrackedHits(
|
|
1332
|
+
accountResults: AccountSearchResult[],
|
|
1333
|
+
user: UserInfo,
|
|
1334
|
+
md: IMetadataProvider,
|
|
1335
|
+
): Promise<AccountSearchResult[]> {
|
|
1336
|
+
return Promise.all(accountResults.map((ar) => this.dropUnreadableAccountHits(ar, user, md)));
|
|
1337
|
+
}
|
|
1158
1338
|
|
|
1159
|
-
|
|
1160
|
-
|
|
1339
|
+
/** {@link dropUnreadableTrackedHits} for one account's results; if the check cannot run, the account's hits are dropped. */
|
|
1340
|
+
private async dropUnreadableAccountHits(ar: AccountSearchResult, user: UserInfo, md: IMetadataProvider): Promise<AccountSearchResult> {
|
|
1341
|
+
if (!ar.success || ar.results.length === 0) return ar;
|
|
1342
|
+
try {
|
|
1343
|
+
const driver = await FileStorageEngine.Instance.GetDriver(ar.accountID, user);
|
|
1344
|
+
const keys = ar.results.flatMap((r) => [r.path, r.objectId].filter((k): k is string => !!k));
|
|
1345
|
+
const unreadable = await StorageAccessEvaluator.Instance.UnreadableTrackedObjectKeys(ar.providerID, keys, user, md, driver);
|
|
1346
|
+
const results = ar.results.filter((r) => !unreadable.has(r.path) && !(r.objectId && unreadable.has(r.objectId)));
|
|
1347
|
+
return results.length === ar.results.length ? ar : { ...ar, results, totalMatches: undefined, nextPageToken: undefined };
|
|
1348
|
+
} catch (error) {
|
|
1349
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1350
|
+
LogError(`SearchAcrossAccounts: tracked-file check failed for account ${ar.accountID} — its hits are withheld: ${reason}`);
|
|
1351
|
+
return { ...ar, results: [], totalMatches: undefined, nextPageToken: undefined };
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
/**
|
|
1356
|
+
* The listed objects minus those behind an `MJ: Files` row the caller may not read (directories are prefixes, never
|
|
1357
|
+
* tracked objects, and are kept). Keys are compared the way the account's driver addresses them.
|
|
1358
|
+
*/
|
|
1359
|
+
private async dropUnreadableListedObjects(
|
|
1360
|
+
accountId: string,
|
|
1361
|
+
objects: StoredObjectMetadata[],
|
|
1362
|
+
user: UserInfo,
|
|
1363
|
+
md: IMetadataProvider,
|
|
1364
|
+
): Promise<StoredObjectMetadata[]> {
|
|
1365
|
+
const files = objects.filter((o) => !o.isDirectory);
|
|
1366
|
+
if (files.length === 0) return objects;
|
|
1367
|
+
const providerID = await this.accountProviderID(accountId, user, md);
|
|
1368
|
+
const driver = await FileStorageEngine.Instance.GetDriver(accountId, user);
|
|
1369
|
+
const unreadable = await StorageAccessEvaluator.Instance.UnreadableTrackedObjectKeys(providerID, files.map((o) => o.fullPath), user, md, driver);
|
|
1370
|
+
return objects.filter((o) => o.isDirectory || !unreadable.has(o.fullPath));
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
/** Converts per-account results to the GraphQL payload, recomputing the totals from what is actually returned. */
|
|
1374
|
+
private toSearchAcrossAccountsPayload(accountResults: AccountSearchResult[]): SearchAcrossAccountsPayload {
|
|
1375
|
+
const payloadResults: AccountSearchResultPayload[] = accountResults.map((ar: AccountSearchResult) => ({
|
|
1161
1376
|
accountID: ar.accountID,
|
|
1162
1377
|
accountName: ar.accountName,
|
|
1163
1378
|
success: ar.success,
|
|
@@ -1179,10 +1394,10 @@ export class FileResolver extends FileResolverBase {
|
|
|
1179
1394
|
}));
|
|
1180
1395
|
|
|
1181
1396
|
return {
|
|
1182
|
-
accountResults,
|
|
1183
|
-
totalResultsReturned:
|
|
1184
|
-
successfulAccounts:
|
|
1185
|
-
failedAccounts:
|
|
1397
|
+
accountResults: payloadResults,
|
|
1398
|
+
totalResultsReturned: accountResults.reduce((sum, ar) => sum + (ar.success ? ar.results.length : 0), 0),
|
|
1399
|
+
successfulAccounts: accountResults.filter((ar) => ar.success).length,
|
|
1400
|
+
failedAccounts: accountResults.filter((ar) => !ar.success).length,
|
|
1186
1401
|
};
|
|
1187
1402
|
}
|
|
1188
1403
|
}
|