@memberjunction/server 6.2.0-edge.4 → 6.2.0-edge.6
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 +47 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +91 -30
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +311 -81
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +17 -3
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +44 -10
- package/dist/agentSessions/SessionManager.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 +27 -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/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +52 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/initializeProviders.d.ts +10 -0
- package/dist/auth/initializeProviders.d.ts.map +1 -1
- package/dist/auth/initializeProviders.js +37 -1
- package/dist/auth/initializeProviders.js.map +1 -1
- package/dist/auth/localDevAuth.d.ts +79 -0
- package/dist/auth/localDevAuth.d.ts.map +1 -0
- package/dist/auth/localDevAuth.js +439 -0
- package/dist/auth/localDevAuth.js.map +1 -0
- package/dist/auth/magicLink/MagicLinkKeys.d.ts +2 -2
- package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.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/types.d.ts +12 -0
- package/dist/auth/magicLink/types.d.ts.map +1 -1
- package/dist/auth/sessionUserClone.d.ts +1 -1
- package/dist/auth/sessionUserClone.d.ts.map +1 -1
- package/dist/auth/sessionUserClone.js +3 -2
- package/dist/auth/sessionUserClone.js.map +1 -1
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts +292 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +442 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -0
- package/dist/broadcast/index.d.ts +6 -0
- package/dist/broadcast/index.d.ts.map +1 -0
- package/dist/broadcast/index.js +6 -0
- package/dist/broadcast/index.js.map +1 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts +43 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js +144 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts +46 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js +149 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts +49 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js +131 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts +24 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js +51 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js.map +1 -0
- package/dist/cache.d.ts +3 -2
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js.map +1 -1
- package/dist/config.d.ts +632 -22
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +104 -4
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +3 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +29 -8
- 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 +156 -0
- package/dist/coreRoutes.js.map +1 -0
- package/dist/generated/graphql-schemas/__mj.d.ts +1016 -29
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +5125 -434
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +85 -30
- package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
- package/dist/generic/CacheInvalidationResolver.js +175 -60
- package/dist/generic/CacheInvalidationResolver.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +2 -0
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/PubSubManager.d.ts +10 -0
- package/dist/generic/PubSubManager.d.ts.map +1 -1
- package/dist/generic/PubSubManager.js +44 -1
- package/dist/generic/PubSubManager.js.map +1 -1
- package/dist/generic/PushStatusResolver.d.ts +39 -6
- package/dist/generic/PushStatusResolver.d.ts.map +1 -1
- package/dist/generic/PushStatusResolver.js +94 -12
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +168 -69
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +464 -405
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +11 -3
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +91 -13
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/generic/actionOutputFieldSecurity.d.ts +26 -0
- package/dist/generic/actionOutputFieldSecurity.d.ts.map +1 -0
- package/dist/generic/actionOutputFieldSecurity.js +47 -0
- package/dist/generic/actionOutputFieldSecurity.js.map +1 -0
- package/dist/generic/fieldSecurityTransport.d.ts +30 -0
- package/dist/generic/fieldSecurityTransport.d.ts.map +1 -0
- package/dist/generic/fieldSecurityTransport.js +30 -0
- package/dist/generic/fieldSecurityTransport.js.map +1 -0
- 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 +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +230 -222
- package/dist/index.js.map +1 -1
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +6 -1
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.d.ts +12 -0
- package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.js +50 -1
- package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
- 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/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -1
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js +2 -1
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -1
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -1
- package/dist/realtimeSessions/RealtimeVerifyRouter.js +2 -1
- package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -1
- 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.map +1 -1
- package/dist/realtimeSessions/SessionVerificationPortsBase.js +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -1
- package/dist/realtimeSessions/index.d.ts +2 -0
- package/dist/realtimeSessions/index.d.ts.map +1 -1
- package/dist/realtimeSessions/index.js +2 -0
- package/dist/realtimeSessions/index.js.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts +32 -0
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -0
- package/dist/realtimeSessions/ownedRealtimeSession.js +56 -0
- package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -0
- package/dist/realtimeSessions/sessionAccess.d.ts +91 -8
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
- package/dist/realtimeSessions/sessionAccess.js +127 -20
- package/dist/realtimeSessions/sessionAccess.js.map +1 -1
- package/dist/realtimeSessions/sessionConfigStamps.js +1 -1
- package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -1
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -1
- package/dist/realtimeSessions/subscriptionAuthorization.js +5 -2
- package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -1
- package/dist/realtimeSessions/verificationWorkflow.d.ts +2 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -1
- package/dist/realtimeSessions/verificationWorkflow.js +1 -1
- package/dist/realtimeSessions/verificationWorkflow.js.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.js +19 -3
- package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts +67 -7
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js +201 -19
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
- package/dist/realtimeWidget/host-identity.d.ts +1 -1
- package/dist/realtimeWidget/host-identity.js +1 -1
- package/dist/realtimeWidget/widgetCore.d.ts +20 -1
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetCore.js +17 -1
- package/dist/realtimeWidget/widgetCore.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +2 -1
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +19 -1
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +63 -12
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +1 -0
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.d.ts +18 -4
- package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.js +32 -8
- package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
- package/dist/resolvers/AgentSessionResolver.d.ts +10 -4
- package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/AgentSessionResolver.js +40 -17
- package/dist/resolvers/AgentSessionResolver.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/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/EntityCommunicationsResolver.d.ts +13 -1
- package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityCommunicationsResolver.js +50 -14
- package/dist/resolvers/EntityCommunicationsResolver.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 +46 -3
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +211 -50
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
- package/dist/resolvers/FileCategoryResolver.js +1 -0
- package/dist/resolvers/FileCategoryResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts +87 -11
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +292 -105
- 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/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +49 -9
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.d.ts +8 -1
- package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.js +31 -3
- package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
- package/dist/resolvers/PipelineProgressResolver.d.ts +6 -0
- package/dist/resolvers/PipelineProgressResolver.d.ts.map +1 -1
- package/dist/resolvers/PipelineProgressResolver.js +5 -3
- package/dist/resolvers/PipelineProgressResolver.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 +33 -6
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts +2 -0
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +15 -6
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RSUResolver.d.ts.map +1 -1
- package/dist/resolvers/RSUResolver.js +12 -0
- package/dist/resolvers/RSUResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +18 -10
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +92 -54
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +11 -9
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +56 -8
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +241 -67
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +4 -4
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTestResolver.js +3 -1
- package/dist/resolvers/RunTestResolver.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 +4 -0
- package/dist/resolvers/SyncDataResolver.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 +66 -13
- package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts +34 -2
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +150 -27
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/UserViewResolver.d.ts +1 -1
- package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
- package/dist/resolvers/UserViewResolver.js +2 -1
- package/dist/resolvers/UserViewResolver.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/rest/EntityCRUDHandler.d.ts +6 -0
- package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
- package/dist/rest/EntityCRUDHandler.js +19 -5
- package/dist/rest/EntityCRUDHandler.js.map +1 -1
- package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +8 -5
- package/dist/rest/RESTEndpointHandler.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 +9 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +43 -17
- 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/serverRoleConfigUnits.d.ts +29 -0
- package/dist/serverRoleConfigUnits.d.ts.map +1 -0
- package/dist/serverRoleConfigUnits.js +33 -0
- package/dist/serverRoleConfigUnits.js.map +1 -0
- 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 +59 -2
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +626 -16
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/sharedCache.d.ts.map +1 -1
- package/dist/sharedCache.js +3 -0
- package/dist/sharedCache.js.map +1 -1
- package/dist/types.d.ts +23 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +99 -98
- package/src/__tests__/ActionResolver.fieldSecurity.test.ts +237 -0
- package/src/__tests__/AdhocQueryResolver.dialect.test.ts +137 -0
- package/src/__tests__/AgentRunStatusPublisher.test.ts +49 -0
- package/src/__tests__/AgentSessionResolver.ownership.test.ts +137 -0
- package/src/__tests__/CustomColumnPromoterOnePass.test.ts +15 -1
- package/src/__tests__/DatasetResolver.itemFilters.test.ts +201 -0
- package/src/__tests__/EntityCommunicationsResolver.actingUser.test.ts +151 -0
- package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
- package/src/__tests__/EvolutionActsOnWhatItDetects.test.ts +113 -0
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +369 -18
- package/src/__tests__/FileResolver.fieldSecurity.test.ts +82 -0
- package/src/__tests__/FileResolver.storageAccess.test.ts +562 -0
- package/src/__tests__/GetDataContextDataResolver.sqlItems.test.ts +416 -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__/PipelineProgressResolver.filter.test.ts +58 -0
- package/src/__tests__/QuerySystemUserResolver.fieldSecurity.test.ts +49 -0
- package/src/__tests__/RESTEndpointHandler.test.ts +25 -0
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +23 -3
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +324 -46
- package/src/__tests__/RecordingAssemblyScheduler.test.ts +178 -0
- package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +98 -0
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +133 -2
- 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 +243 -7
- package/src/__tests__/ResolverBase.deleteReturnProjection.test.ts +52 -0
- package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +176 -0
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +5 -2
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +23 -1
- package/src/__tests__/ResolverBase.publishStatusUpdate.test.ts +68 -0
- package/src/__tests__/ResolverBase.userViewCacheAndDedup.test.ts +130 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +69 -13
- package/src/__tests__/RunAIAgentResolver.conversationWriter.test.ts +210 -0
- package/src/__tests__/RunAIAgentResolver.fireAndForgetCompletion.test.ts +209 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +20 -28
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +264 -0
- package/src/__tests__/RunAIAgentResolver.serverReply.test.ts +358 -0
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +4 -1
- package/src/__tests__/RunTemplateResolver.test.ts +35 -2
- package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +11 -0
- package/src/__tests__/RunViewResolver.cacheCheckScreen.test.ts +165 -0
- package/src/__tests__/SearchKnowledgeStreamResolver.test.ts +161 -0
- package/src/__tests__/SessionJanitor.test.ts +365 -10
- package/src/__tests__/SessionManager.test.ts +122 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +25 -0
- package/src/__tests__/TaskGraphAgentRunner.test.ts +266 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +468 -23
- package/src/__tests__/TaskGraphFrameResolver.test.ts +22 -0
- package/src/__tests__/TestQuerySQLResolver.test.ts +274 -0
- package/src/__tests__/TransactionGroupResolver.fieldSecurity.test.ts +199 -0
- package/src/__tests__/TransactionGroupResolver.refusals.test.ts +98 -1
- package/src/__tests__/UpdateMJDataContextItem.oldValues.test.ts +220 -0
- package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +15 -0
- package/src/__tests__/ViewOperationsHandler.clauseScreen.test.ts +157 -0
- package/src/__tests__/ViewOperationsHandler.test.ts +7 -2
- package/src/__tests__/WidgetSessionService.test.ts +583 -4
- package/src/__tests__/agent-run-data-guard.test.ts +130 -0
- package/src/__tests__/apiRootBoot.test.ts +202 -0
- package/src/__tests__/buildMagicLinkSessionUser.test.ts +96 -0
- package/src/__tests__/cacheBroadcastProviders.test.ts +403 -0
- package/src/__tests__/cacheInvalidationRecordData.test.ts +109 -0
- package/src/__tests__/conversationRunEvents.test.ts +56 -14
- package/src/__tests__/fieldSecurityFixtures.ts +91 -0
- package/src/__tests__/globalCors.test.ts +217 -0
- package/src/__tests__/localDevAuth.test.ts +196 -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__/orphanedConversationDetailReconciler.test.ts +10 -0
- package/src/__tests__/ownedRealtimeSession.test.ts +39 -0
- package/src/__tests__/pushStatusResolver.filter.test.ts +64 -1
- package/src/__tests__/realtimeRecordingHandler.test.ts +669 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +29 -1
- package/src/__tests__/realtimeSessions/ownedRealtimeSession.anonymous.test.ts +31 -0
- package/src/__tests__/realtimeSessions/ownedRealtimeSession.namedScoped.test.ts +37 -0
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +146 -7
- package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +1 -1
- package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +25 -4
- package/src/__tests__/resolverBase.cloudEventChangedFields.test.ts +157 -0
- package/src/__tests__/resolverBase.fieldSecurityTransport.test.ts +222 -0
- package/src/__tests__/resolverBase.fls.test.ts +146 -97
- package/src/__tests__/restTransport.fieldSecurity.test.ts +148 -0
- package/src/__tests__/runParamsTimeZone.test.ts +179 -0
- package/src/__tests__/runStreams.fieldSecurity.test.ts +100 -0
- package/src/__tests__/runViewProperties.test.ts +114 -0
- package/src/__tests__/scalabilityBenchmarks.test.ts +765 -0
- package/src/__tests__/scalabilityDatabaseIntegration.test.ts +207 -0
- package/src/__tests__/serverExtensionHostOverlay.test.ts +66 -0
- package/src/__tests__/serverExtensionReservedRoots.test.ts +11 -98
- package/src/__tests__/sessionUserClone.test.ts +5 -1
- package/src/__tests__/sharedCache.test.ts +5 -3
- package/src/__tests__/startupSweepOrder.test.ts +40 -0
- package/src/__tests__/unifiedAuth.test.ts +28 -0
- package/src/__tests__/widgetGuestElevation.test.ts +2 -0
- package/src/agentSessions/SessionJanitor.ts +369 -93
- package/src/agentSessions/SessionManager.ts +45 -10
- package/src/apiRoot.ts +78 -0
- package/src/auth/AuthProviderCatalogRouter.ts +29 -0
- package/src/auth/LoginScreenEngine.ts +50 -0
- package/src/auth/index.ts +57 -12
- package/src/auth/initializeProviders.ts +43 -1
- package/src/auth/localDevAuth.ts +528 -0
- package/src/auth/magicLink/MagicLinkKeys.ts +2 -2
- package/src/auth/magicLink/MagicLinkRouter.ts +3 -2
- package/src/auth/magicLink/types.ts +10 -0
- package/src/auth/sessionUserClone.ts +3 -2
- package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +674 -0
- package/src/broadcast/index.ts +5 -0
- package/src/broadcast/providers/AWSIoTCoreBroadcastProvider.ts +180 -0
- package/src/broadcast/providers/AzureWebPubSubBroadcastProvider.ts +182 -0
- package/src/broadcast/providers/GCPPubSubBroadcastProvider.ts +169 -0
- package/src/broadcast/providers/SequencedRoleBucketBroadcastProvider.ts +57 -0
- package/src/cache.ts +2 -1
- package/src/config.ts +115 -5
- package/src/context.ts +32 -8
- package/src/coreRoutes.ts +181 -0
- package/src/generated/graphql-schemas/__mj.ts +3684 -284
- package/src/generic/CacheInvalidationResolver.ts +172 -60
- package/src/generic/OrphanedConversationDetailReconciler.ts +4 -2
- package/src/generic/PubSubManager.ts +54 -1
- package/src/generic/PushStatusResolver.ts +109 -12
- package/src/generic/ResolverBase.ts +540 -431
- package/src/generic/RunViewResolver.ts +97 -15
- package/src/generic/actionOutputFieldSecurity.ts +75 -0
- package/src/generic/fieldSecurityTransport.ts +62 -0
- package/src/globalCors.ts +71 -0
- package/src/index.ts +239 -238
- package/src/integration/CustomColumnPromoter.ts +6 -1
- package/src/logging/variablesLoggingMiddleware.ts +59 -3
- package/src/multiTenancy/index.ts +24 -2
- package/src/realtimeSessions/README.md +7 -3
- package/src/realtimeSessions/RealtimeSessionVerificationService.ts +2 -1
- package/src/realtimeSessions/RealtimeVerifyRouter.ts +2 -1
- package/src/realtimeSessions/RecordingAssemblyScheduler.ts +234 -0
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +1 -0
- package/src/realtimeSessions/index.ts +2 -0
- package/src/realtimeSessions/ownedRealtimeSession.ts +69 -0
- package/src/realtimeSessions/sessionAccess.ts +183 -23
- package/src/realtimeSessions/sessionConfigStamps.ts +1 -1
- package/src/realtimeSessions/subscriptionAuthorization.ts +4 -2
- package/src/realtimeSessions/verificationWorkflow.ts +3 -1
- package/src/realtimeWidget/WidgetRouter.ts +22 -4
- package/src/realtimeWidget/WidgetSessionService.ts +252 -17
- package/src/realtimeWidget/host-identity.ts +1 -1
- package/src/realtimeWidget/widgetCore.ts +30 -1
- package/src/realtimeWidget/widgetGuestElevation.ts +2 -1
- package/src/resolvers/ActionResolver.ts +78 -15
- package/src/resolvers/AdhocQueryResolver.ts +1 -0
- package/src/resolvers/AgentRunStatusPublisher.ts +36 -8
- package/src/resolvers/AgentSessionResolver.ts +46 -18
- package/src/resolvers/ArtifactFileResolver.ts +9 -8
- package/src/resolvers/ConversationRunEventsResolver.ts +15 -11
- package/src/resolvers/DatasetResolver.ts +70 -10
- package/src/resolvers/EntityCommunicationsResolver.ts +56 -15
- package/src/resolvers/EntityRecordNameResolver.ts +3 -2
- package/src/resolvers/FetchEntityVectorsResolver.ts +248 -55
- package/src/resolvers/FileCategoryResolver.ts +1 -0
- package/src/resolvers/FileResolver.ts +334 -109
- package/src/resolvers/GetDataContextDataResolver.ts +139 -41
- package/src/resolvers/GetDataResolver.ts +6 -3
- package/src/resolvers/IntegrationDiscoveryResolver.ts +53 -9
- package/src/resolvers/MergeRecordsResolver.ts +23 -2
- package/src/resolvers/PipelineProgressResolver.ts +12 -3
- package/src/resolvers/QueryResolver.ts +31 -6
- package/src/resolvers/QuerySystemUserResolver.ts +15 -6
- package/src/resolvers/RSUResolver.ts +12 -0
- package/src/resolvers/RealtimeClientSessionResolver.ts +114 -66
- package/src/resolvers/RemoteBrowserActionResolver.ts +15 -10
- package/src/resolvers/RunAIAgentResolver.ts +296 -85
- package/src/resolvers/RunTemplateResolver.ts +6 -4
- package/src/resolvers/RunTestResolver.ts +3 -1
- package/src/resolvers/SearchKnowledgeStreamResolver.ts +19 -3
- package/src/resolvers/SqlLoggingConfigResolver.ts +2 -4
- package/src/resolvers/SyncDataResolver.ts +4 -0
- package/src/resolvers/TaskGraphFrameResolver.ts +33 -1
- package/src/resolvers/TestQuerySQLResolver.ts +69 -13
- package/src/resolvers/TransactionGroupResolver.ts +185 -30
- package/src/resolvers/UserViewResolver.ts +2 -1
- package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
- package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +43 -0
- package/src/resolvers/agent-run-data-guard.ts +89 -0
- package/src/rest/EntityCRUDHandler.ts +22 -5
- package/src/rest/RESTEndpointHandler.ts +8 -5
- package/src/rest/RealtimeRecordingHandler.ts +467 -0
- package/src/rest/ViewOperationsHandler.ts +52 -18
- package/src/rest/setupRESTEndpoints.ts +6 -0
- package/src/serverExtensionReservedRoots.ts +14 -17
- package/src/serverRoleConfigUnits.ts +48 -0
- 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 +738 -16
- package/src/sharedCache.ts +3 -0
- package/src/types.ts +23 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { EntityPermissionType, FieldValueCollection, EntitySaveOptions, LogError, UserInfo } from '@memberjunction/core';
|
|
1
|
+
import { AssertClientInputWritableForUser, 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,24 @@ 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 {
|
|
50
|
+
import { ProjectEntityObjectForCaller, ProjectableEntityObject } from '../generic/fieldSecurityTransport.js';
|
|
43
51
|
import { MediaAccessKeyManager } from '../rest/MediaAccessKeys.js';
|
|
44
52
|
import { UploadTokenManager } from '../rest/UploadTokenManager.js';
|
|
45
53
|
import { DeriveSidecarPath, ParsePeaksSidecar } from './peaksSidecar.js';
|
|
54
|
+
import { ApiRoute } from '../apiRoot.js';
|
|
46
55
|
|
|
47
56
|
@InputType()
|
|
48
57
|
export class CreateUploadURLInput {
|
|
@@ -434,18 +443,11 @@ export class MediaAccessTokenResult {
|
|
|
434
443
|
ErrorMessage?: string;
|
|
435
444
|
}
|
|
436
445
|
|
|
446
|
+
/** The failure reported for an account `SearchAcrossAccounts` refuses — the same whether it is restricted or does not exist. */
|
|
447
|
+
const SEARCH_ACCOUNT_REFUSED_MESSAGE = 'Access denied: you do not have access to this storage account or it does not exist.';
|
|
448
|
+
|
|
437
449
|
@Resolver(MJFile_)
|
|
438
450
|
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
451
|
/**
|
|
450
452
|
* Mints a short-lived signed media-access token for an `MJ: Files` record and returns the
|
|
451
453
|
* authenticated streaming URL (`<publicBase>/media/<fileId>?token=<token>`). Permission-gated
|
|
@@ -469,16 +471,19 @@ export class FileResolver extends FileResolverBase {
|
|
|
469
471
|
if (!loaded) {
|
|
470
472
|
return { Success: false, ErrorMessage: 'You do not have access to this file or it does not exist.' };
|
|
471
473
|
}
|
|
474
|
+
// Storage gate: the token is the capability the /media route honours, so it is minted only when the caller may read
|
|
475
|
+
// the account the row's provider resolves to and every row tracking the object (throws — caught below).
|
|
476
|
+
const resolved = await this.assertFileObjectAccess(fileEntity, 'Read', contextUser, provider);
|
|
472
477
|
|
|
473
478
|
// Access authorized — mint the capability token.
|
|
474
479
|
const { Token, ExpiresAt } = MediaAccessKeyManager.Instance.Sign(fileId, contextUser.ID);
|
|
475
480
|
const fileNameSuffix = fileEntity.Name ? `/${encodeURIComponent(fileEntity.Name)}` : '';
|
|
476
|
-
const url =
|
|
481
|
+
const url = ApiRoute(`/media/${encodeURIComponent(fileId)}${fileNameSuffix}?token=${encodeURIComponent(Token)}`, { absolute: true });
|
|
477
482
|
|
|
478
483
|
// Best-effort: surface precomputed waveform peaks from a peaks.json sidecar beside the file, so
|
|
479
484
|
// the player renders the real waveform instantly without fetching/decoding the audio. Never
|
|
480
485
|
// blocks token minting — any failure just omits Peaks.
|
|
481
|
-
const peaks = await this.tryReadPeaksSidecar(fileEntity,
|
|
486
|
+
const peaks = await this.tryReadPeaksSidecar(fileEntity, resolved?.Driver);
|
|
482
487
|
|
|
483
488
|
return { Success: true, Token, Url: url, ExpiresAt, MimeType: fileEntity.ContentType ?? undefined, Peaks: peaks };
|
|
484
489
|
} catch (error) {
|
|
@@ -492,34 +497,22 @@ export class FileResolver extends FileResolverBase {
|
|
|
492
497
|
* Best-effort read of a `peaks.json` waveform sidecar that sits in the SAME storage folder as the
|
|
493
498
|
* given file (a JSON array of normalized `0..1` numbers, written at capture time). Derives the
|
|
494
499
|
* 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
|
|
500
|
+
* through the file's own storage driver — the one {@link assertFileObjectAccess} resolved, so the account is already
|
|
501
|
+
* gated — parses + validates it, and returns sanitized peaks. Returns `undefined` on ANY failure (no ProviderKey, no
|
|
502
|
+
* driver, no sidecar, parse error, garbage) — the caller treats peaks as a pure optimization and must never let a
|
|
503
|
+
* sidecar problem affect token minting.
|
|
498
504
|
*
|
|
499
505
|
* @param file The already-loaded (under the user's context) `MJ: Files` row.
|
|
500
|
-
* @param
|
|
506
|
+
* @param driver The gated driver for the file's account, or undefined when the provider has none.
|
|
501
507
|
* @returns Sanitized `0..1` peaks (length-capped), or `undefined`.
|
|
502
508
|
*/
|
|
503
|
-
private async tryReadPeaksSidecar(file: MJFileEntity,
|
|
509
|
+
private async tryReadPeaksSidecar(file: MJFileEntity, driver: FileStorageBase | undefined): Promise<number[] | undefined> {
|
|
504
510
|
try {
|
|
505
511
|
// The sidecar lives next to the recording: replace the final path segment with peaks.json.
|
|
506
512
|
const sidecarPath = DeriveSidecarPath(file.ProviderKey);
|
|
507
|
-
if (!sidecarPath) {
|
|
508
|
-
return undefined;
|
|
509
|
-
}
|
|
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) {
|
|
513
|
+
if (!sidecarPath || !driver) {
|
|
520
514
|
return undefined;
|
|
521
515
|
}
|
|
522
|
-
const driver = await FileStorageEngine.Instance.GetDriver(account.ID, contextUser);
|
|
523
516
|
const bytes = await driver.GetObject({ fullPath: sidecarPath });
|
|
524
517
|
if (!bytes || bytes.length === 0) {
|
|
525
518
|
return undefined;
|
|
@@ -557,6 +550,75 @@ export class FileResolver extends FileResolverBase {
|
|
|
557
550
|
};
|
|
558
551
|
}
|
|
559
552
|
|
|
553
|
+
/**
|
|
554
|
+
* The storage-account gate. Every route keyed by a client-supplied account ID runs it FIRST — before the account is
|
|
555
|
+
* loaded and before any driver call. Throws `StorageAccountAccessDeniedError` ("You do not have access to this storage
|
|
556
|
+
* account or it does not exist.") unless the caller may use the account for `access`, evaluated per call against
|
|
557
|
+
* `MJ: File Storage Account Permissions` by `StorageAccessEvaluator` (`@memberjunction/storage`). An unknown account
|
|
558
|
+
* gets the same refusal, so the error never reveals whether an account ID is real.
|
|
559
|
+
*/
|
|
560
|
+
private async assertAccountAccess(accountId: string, access: StorageAccountAccess, context: AppContext): Promise<void> {
|
|
561
|
+
const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
|
|
562
|
+
const user = this.GetUserFromPayload(context.userPayload);
|
|
563
|
+
await StorageAccessEvaluator.Instance.AssertAccountAccess(accountId, user, access, md);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* The tracked-file rule for an account-keyed route: when any of `objectNames` in the account backs an `MJ: Files` row,
|
|
568
|
+
* the caller must be able to read that row — the rule {@link GetFileContents} applies to a file ID — else this throws
|
|
569
|
+
* `TrackedFileAccessDeniedError` ("You do not have access to this file or it does not exist."). Keys are compared the
|
|
570
|
+
* way the account's driver addresses them (`FileStorageBase.NormalizeObjectKey`) and case-insensitively; a key whose
|
|
571
|
+
* meaning depends on the provider (`..`, `\`, `%2F`, ...) is refused outright. Applied to every object a route reads
|
|
572
|
+
* or signs, and to every object it overwrites, moves or deletes — the minimum for a write is that the caller may read
|
|
573
|
+
* what it replaces. An object no row tracks is governed by the account gate, which the caller has already passed.
|
|
574
|
+
*/
|
|
575
|
+
private async assertTrackedObjectReadable(accountId: string, objectNames: string[], user: UserInfo, md: IMetadataProvider): Promise<void> {
|
|
576
|
+
const providerID = await this.accountProviderID(accountId, user, md);
|
|
577
|
+
const driver = await FileStorageEngine.Instance.GetDriver(accountId, user);
|
|
578
|
+
await StorageAccessEvaluator.Instance.AssertTrackedObjectsReadable(providerID, objectNames, user, md, driver);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* The storage gate for a route that goes from an `MJ: Files` row (already loaded as the caller) to its object: the
|
|
583
|
+
* account the row's provider resolves to must allow `access`, and every row tracking the object must be readable by
|
|
584
|
+
* the caller (`FileStorageEngine.ResolveFileObject`) — so a row whose client-written `ProviderID`/`ProviderKey`/`Name`
|
|
585
|
+
* aliases someone else's object is refused. `extraKeys` (a rename target) get the tracked-file rule too. When the
|
|
586
|
+
* provider has no storage account the legacy env-configured driver serves the row; there is no account to gate, so the
|
|
587
|
+
* tracked-file rule alone applies. Returns the resolved object, or `null` in that legacy case.
|
|
588
|
+
*/
|
|
589
|
+
private async assertFileObjectAccess(
|
|
590
|
+
file: StorageFileLocation,
|
|
591
|
+
access: StorageAccountAccess,
|
|
592
|
+
user: UserInfo,
|
|
593
|
+
md: IMetadataProvider,
|
|
594
|
+
extraKeys: string[] = [],
|
|
595
|
+
): Promise<ResolvedFileObject | null> {
|
|
596
|
+
const location: StorageFileLocation = { ProviderID: file.ProviderID, ProviderKey: file.ProviderKey ?? null, Name: file.Name };
|
|
597
|
+
const resolved = await FileStorageEngine.Instance.ResolveFileObject(location, user, access, md);
|
|
598
|
+
const keys = resolved ? extraKeys : [StorageObjectKeyOf(location), ...extraKeys];
|
|
599
|
+
if (keys.length > 0) {
|
|
600
|
+
await StorageAccessEvaluator.Instance.AssertTrackedObjectsReadable(file.ProviderID, keys, user, md, resolved?.Driver);
|
|
601
|
+
}
|
|
602
|
+
return resolved;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* The storage provider ID of an account the caller has already passed the gate for, from the engine's cache (refreshed
|
|
607
|
+
* once on a miss). Refuses like the gate when the account still cannot be found.
|
|
608
|
+
*/
|
|
609
|
+
private async accountProviderID(accountId: string, user: UserInfo, md: IMetadataProvider): Promise<string> {
|
|
610
|
+
await FileStorageEngine.Instance.Config(false, user, md);
|
|
611
|
+
let account = FileStorageEngine.Instance.GetAccountById(accountId);
|
|
612
|
+
if (!account) {
|
|
613
|
+
await FileStorageEngine.Instance.RefreshAfterMiss(md);
|
|
614
|
+
account = FileStorageEngine.Instance.GetAccountById(accountId);
|
|
615
|
+
}
|
|
616
|
+
if (!account) {
|
|
617
|
+
throw new StorageAccountAccessDeniedError();
|
|
618
|
+
}
|
|
619
|
+
return account.ProviderID;
|
|
620
|
+
}
|
|
621
|
+
|
|
560
622
|
/**
|
|
561
623
|
* Loads a FileStorageAccount and its associated FileStorageProvider.
|
|
562
624
|
* This is the standard way to get provider information in the enterprise model.
|
|
@@ -605,6 +667,13 @@ export class FileResolver extends FileResolverBase {
|
|
|
605
667
|
const fileEntity = await provider.GetEntityObject<MJFileEntity>('MJ: Files', user);
|
|
606
668
|
const providerEntity = await provider.GetEntityObject<MJFileStorageProviderEntity>('MJ: File Storage Providers', user);
|
|
607
669
|
fileEntity.CheckPermissions(EntityPermissionType.Create, true);
|
|
670
|
+
// Field-level security: refuse a key the caller may not read, or that names no field, before anything else
|
|
671
|
+
AssertClientInputWritableForUser(fileEntity.EntityInfo, { ...input }, user);
|
|
672
|
+
|
|
673
|
+
// The upload URL signs an overwrite of `Name` (and the row will point at `ProviderKey`): Write on the account the
|
|
674
|
+
// provider resolves to, and the tracked-file rule on both keys, as for every other route that writes an object.
|
|
675
|
+
const location: StorageFileLocation = { ProviderID: input.ProviderID ?? '', ProviderKey: null, Name: input.Name ?? '' };
|
|
676
|
+
await this.assertFileObjectAccess(location, 'Write', user, provider, input.ProviderKey ? [input.ProviderKey] : []);
|
|
608
677
|
|
|
609
678
|
const [sameName] = await this.findBy(provider, 'MJ: Files', { Name: input.Name, ProviderID: input.ProviderID }, context.userPayload.userRecord);
|
|
610
679
|
const NameExists = Boolean(sameName);
|
|
@@ -627,7 +696,7 @@ export class FileResolver extends FileResolverBase {
|
|
|
627
696
|
if (!saved) {
|
|
628
697
|
console.error('[CreateFile] File save failed:', fileEntity.LatestResult?.CompleteMessage);
|
|
629
698
|
}
|
|
630
|
-
const File =
|
|
699
|
+
const File = this.fileResponse(fileEntity, user, provider);
|
|
631
700
|
|
|
632
701
|
return { File, UploadUrl, NameExists };
|
|
633
702
|
}
|
|
@@ -650,6 +719,13 @@ export class FileResolver extends FileResolverBase {
|
|
|
650
719
|
const fileEntity = await provider.GetEntityObject<MJFileEntity>('MJ: Files', user);
|
|
651
720
|
fileEntity.CheckPermissions(EntityPermissionType.Create, true);
|
|
652
721
|
|
|
722
|
+
// Write gate on the account the upload lands in — before the staged upload is consumed or any driver is touched.
|
|
723
|
+
// With no account at all there is nothing to gate; UploadFile then refuses with its documented error.
|
|
724
|
+
const targetAccountId = await this.resolveUploadAccountId(input.AccountID, user, provider);
|
|
725
|
+
if (targetAccountId) {
|
|
726
|
+
await this.assertAccountAccess(targetAccountId, 'Write', context);
|
|
727
|
+
}
|
|
728
|
+
|
|
653
729
|
let buffer: Buffer;
|
|
654
730
|
let fileName: string = input.FileName || 'file';
|
|
655
731
|
let mimeType: string = input.MimeType || 'application/octet-stream';
|
|
@@ -674,18 +750,12 @@ export class FileResolver extends FileResolverBase {
|
|
|
674
750
|
};
|
|
675
751
|
}
|
|
676
752
|
|
|
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
753
|
const uploadResult = await FileStorageEngine.Instance.UploadFile({
|
|
684
754
|
content: buffer,
|
|
685
755
|
fileName,
|
|
686
756
|
mimeType,
|
|
687
757
|
contextUser: user,
|
|
688
|
-
storageAccountId:
|
|
758
|
+
storageAccountId: targetAccountId,
|
|
689
759
|
categoryID: input.CategoryID,
|
|
690
760
|
description: input.Description,
|
|
691
761
|
pathPrefix: input.PathPrefix,
|
|
@@ -693,8 +763,7 @@ export class FileResolver extends FileResolverBase {
|
|
|
693
763
|
});
|
|
694
764
|
|
|
695
765
|
const loaded = await fileEntity.Load(uploadResult.FileID);
|
|
696
|
-
const
|
|
697
|
-
const File = loaded ? (mapper.MapFields({ ...fileEntity.GetAll() }) as unknown as MJFile_) : undefined;
|
|
766
|
+
const File = loaded ? (this.fileResponse(fileEntity, user, provider) as unknown as MJFile_) : undefined;
|
|
698
767
|
|
|
699
768
|
return {
|
|
700
769
|
Success: true,
|
|
@@ -711,6 +780,28 @@ export class FileResolver extends FileResolverBase {
|
|
|
711
780
|
}
|
|
712
781
|
}
|
|
713
782
|
|
|
783
|
+
/**
|
|
784
|
+
* The file record for a response: its field values as the caller may read them, in GraphQL field
|
|
785
|
+
* names. The record was just saved or reloaded, and a save re-reads the whole row.
|
|
786
|
+
*/
|
|
787
|
+
private fileResponse(fileEntity: ProjectableEntityObject, user: UserInfo, provider: IMetadataProvider): Record<string, unknown> {
|
|
788
|
+
return new FieldMapper().MapFields({ ...ProjectEntityObjectForCaller(fileEntity, user, provider) });
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* The account an upload will land in: the requested one, else the engine's default (the first active account) —
|
|
793
|
+
* returned as an explicit ID so the account the gate checks is the account `UploadFile` uses. A requested ID is
|
|
794
|
+
* returned as-is: the gate refuses an unknown one without revealing that it does not exist, and `UploadFile` refreshes
|
|
795
|
+
* the engine's cache itself when a permitted account is missing from it.
|
|
796
|
+
*/
|
|
797
|
+
private async resolveUploadAccountId(accountId: string | undefined, user: UserInfo, md: IMetadataProvider): Promise<string | undefined> {
|
|
798
|
+
if (accountId) {
|
|
799
|
+
return accountId;
|
|
800
|
+
}
|
|
801
|
+
await FileStorageEngine.Instance.Config(false, user, md);
|
|
802
|
+
return FileStorageEngine.Instance.ResolveStorageAccount()?.account.ID;
|
|
803
|
+
}
|
|
804
|
+
|
|
714
805
|
@FieldResolver(() => String)
|
|
715
806
|
async DownloadUrl(@Root() file: MJFile_, @Ctx() context: AppContext) {
|
|
716
807
|
const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
|
|
@@ -718,24 +809,18 @@ export class FileResolver extends FileResolverBase {
|
|
|
718
809
|
const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
|
|
719
810
|
fileEntity.CheckPermissions(EntityPermissionType.Read, true);
|
|
720
811
|
|
|
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);
|
|
812
|
+
// Storage gate: Read on the account the row's provider resolves to, and the tracked-file rule on its object
|
|
813
|
+
const location: StorageFileLocation = { ProviderID: file.ProviderID, ProviderKey: file.ProviderKey ?? null, Name: file.Name ?? '' };
|
|
814
|
+
const resolved = await this.assertFileObjectAccess(location, 'Read', user, md);
|
|
815
|
+
if (resolved) {
|
|
816
|
+
return await resolved.Driver.CreatePreAuthDownloadUrl(resolved.ObjectKey);
|
|
732
817
|
}
|
|
733
818
|
|
|
734
819
|
const providerEntity = await md.GetEntityObject<MJFileStorageProviderEntity>('MJ: File Storage Providers', user);
|
|
735
820
|
await providerEntity.Load(file.ProviderID);
|
|
736
821
|
|
|
737
822
|
const userContext = this.buildUserContext(context);
|
|
738
|
-
const url = await createDownloadUrl(providerEntity, file
|
|
823
|
+
const url = await createDownloadUrl(providerEntity, StorageObjectKeyOf(file), userContext);
|
|
739
824
|
|
|
740
825
|
return url;
|
|
741
826
|
}
|
|
@@ -743,7 +828,9 @@ export class FileResolver extends FileResolverBase {
|
|
|
743
828
|
/**
|
|
744
829
|
* Returns an `MJ: Files` record's bytes as base64, read server-side through authenticated MJStorage
|
|
745
830
|
* (`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
|
|
831
|
+
* calling user's context, so MJ row-level security determines access; then the storage gate
|
|
832
|
+
* ({@link assertFileObjectAccess}) requires Read on the account the row's provider resolves to and refuses an object
|
|
833
|
+
* another row tracks when the caller cannot read that row. Never throws to the client.
|
|
747
834
|
*
|
|
748
835
|
* @param fileId The `MJ: Files` id whose bytes to return.
|
|
749
836
|
* @returns `{ Success, Base64?, MimeType?, ErrorMessage? }`.
|
|
@@ -762,16 +849,18 @@ export class FileResolver extends FileResolverBase {
|
|
|
762
849
|
return { Success: false, ErrorMessage: 'You do not have access to this file or it does not exist.' };
|
|
763
850
|
}
|
|
764
851
|
|
|
765
|
-
//
|
|
766
|
-
|
|
767
|
-
|
|
852
|
+
// Storage gate, then read the bytes via authenticated MJStorage (server-side GetObject on the file's own account).
|
|
853
|
+
// A refusal throws and is reported below; a row whose provider has no account has no bytes to read.
|
|
854
|
+
const resolved = await this.assertFileObjectAccess(fileEntity, 'Read', contextUser, provider);
|
|
855
|
+
const bytes = resolved ? await resolved.Driver.GetObject({ fullPath: resolved.ObjectKey }) : null;
|
|
856
|
+
if (!bytes || bytes.length === 0) {
|
|
768
857
|
return { Success: false, ErrorMessage: 'The file could not be read from storage.' };
|
|
769
858
|
}
|
|
770
859
|
|
|
771
860
|
return {
|
|
772
861
|
Success: true,
|
|
773
|
-
Base64:
|
|
774
|
-
MimeType:
|
|
862
|
+
Base64: bytes.toString('base64'),
|
|
863
|
+
MimeType: fileEntity.ContentType ?? undefined,
|
|
775
864
|
};
|
|
776
865
|
} catch (error) {
|
|
777
866
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -791,6 +880,8 @@ export class FileResolver extends FileResolverBase {
|
|
|
791
880
|
await fileEntity.Load(input.ID);
|
|
792
881
|
|
|
793
882
|
if (fileEntity.Name !== input.Name) {
|
|
883
|
+
// Renaming moves the object: Write on its account, and the tracked-file rule on the object and the new name
|
|
884
|
+
await this.assertFileObjectAccess(fileEntity, 'Write', user, md, [fileEntity.Name, input.Name]);
|
|
794
885
|
const providerEntity = await md.GetEntityObject<MJFileStorageProviderEntity>('MJ: File Storage Providers', user);
|
|
795
886
|
await providerEntity.Load(fileEntity.ProviderID);
|
|
796
887
|
|
|
@@ -822,12 +913,14 @@ export class FileResolver extends FileResolverBase {
|
|
|
822
913
|
}
|
|
823
914
|
fileEntity.CheckPermissions(EntityPermissionType.Delete, true);
|
|
824
915
|
|
|
825
|
-
// Only delete the object from the provider if it's actually been uploaded
|
|
916
|
+
// Only delete the object from the provider if it's actually been uploaded — after the storage gate: Write on its
|
|
917
|
+
// account, and no other row the caller cannot read may track the object (a row can alias another row's object)
|
|
826
918
|
if (fileEntity.Status === 'Uploaded') {
|
|
919
|
+
await this.assertFileObjectAccess(fileEntity, 'Write', userInfo, md);
|
|
827
920
|
const providerEntity = await md.GetEntityObject<MJFileStorageProviderEntity>('MJ: File Storage Providers', userInfo);
|
|
828
921
|
await providerEntity.Load(fileEntity.ProviderID);
|
|
829
922
|
const userContext = this.buildUserContext(context);
|
|
830
|
-
await deleteObject(providerEntity, fileEntity
|
|
923
|
+
await deleteObject(providerEntity, StorageObjectKeyOf(fileEntity), userContext);
|
|
831
924
|
}
|
|
832
925
|
|
|
833
926
|
return super.DeleteMJFile(ID, options, context, pubSub);
|
|
@@ -844,13 +937,16 @@ export class FileResolver extends FileResolverBase {
|
|
|
844
937
|
const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
|
|
845
938
|
const user = this.GetUserFromPayload(context.userPayload);
|
|
846
939
|
|
|
940
|
+
// Account gate first — before the account is loaded or any driver is touched
|
|
941
|
+
await this.assertAccountAccess(input.AccountID, 'Read', context);
|
|
942
|
+
|
|
847
943
|
// Load the account and its provider
|
|
848
944
|
const { account, provider: providerEntity } = await this.loadAccountAndProvider(input.AccountID, context);
|
|
849
945
|
|
|
850
946
|
console.log('[FileResolver] Provider loaded:', {
|
|
851
947
|
Name: providerEntity.Name,
|
|
852
948
|
ServerDriverKey: providerEntity.ServerDriverKey,
|
|
853
|
-
HasConfiguration: !!providerEntity.
|
|
949
|
+
HasConfiguration: !!providerEntity.Configuration,
|
|
854
950
|
});
|
|
855
951
|
|
|
856
952
|
// Check permissions - user must have read access to Files entity
|
|
@@ -868,8 +964,10 @@ export class FileResolver extends FileResolverBase {
|
|
|
868
964
|
prefixes: result.prefixes,
|
|
869
965
|
});
|
|
870
966
|
|
|
871
|
-
//
|
|
872
|
-
|
|
967
|
+
// Drop objects behind an MJ: Files row the caller cannot read — a listing never names a tracked object the caller
|
|
968
|
+
// is refused — then convert Date objects to ISO strings for GraphQL
|
|
969
|
+
const visible = await this.dropUnreadableListedObjects(input.AccountID, result.objects, user, md);
|
|
970
|
+
const objects = visible.map((obj) => ({
|
|
873
971
|
...obj,
|
|
874
972
|
lastModified: obj.lastModified.toISOString(),
|
|
875
973
|
}));
|
|
@@ -889,6 +987,10 @@ export class FileResolver extends FileResolverBase {
|
|
|
889
987
|
const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
|
|
890
988
|
fileEntity.CheckPermissions(EntityPermissionType.Read, true);
|
|
891
989
|
|
|
990
|
+
// Account gate, then — when the object backs an MJ: Files row — that row must be readable by the caller
|
|
991
|
+
await this.assertAccountAccess(input.AccountID, 'Read', context);
|
|
992
|
+
await this.assertTrackedObjectReadable(input.AccountID, [input.ObjectName], user, md);
|
|
993
|
+
|
|
892
994
|
await FileStorageEngine.Instance.Config(false, user, md);
|
|
893
995
|
const driver = await FileStorageEngine.Instance.GetDriver(input.AccountID, user);
|
|
894
996
|
const downloadUrl = await driver.CreatePreAuthDownloadUrl(input.ObjectName);
|
|
@@ -920,6 +1022,9 @@ export class FileResolver extends FileResolverBase {
|
|
|
920
1022
|
// Check permissions
|
|
921
1023
|
const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
|
|
922
1024
|
fileEntity.CheckPermissions(EntityPermissionType.Create, true);
|
|
1025
|
+
await this.assertAccountAccess(input.AccountID, 'Write', context);
|
|
1026
|
+
// An upload URL can overwrite: the caller must be able to read any MJ: Files row that tracks the target
|
|
1027
|
+
await this.assertTrackedObjectReadable(input.AccountID, [input.ObjectName], user, md);
|
|
923
1028
|
|
|
924
1029
|
await FileStorageEngine.Instance.Config(false, user, md);
|
|
925
1030
|
const driver = await FileStorageEngine.Instance.GetDriver(input.AccountID, user);
|
|
@@ -956,7 +1061,7 @@ export class FileResolver extends FileResolverBase {
|
|
|
956
1061
|
return {
|
|
957
1062
|
Success: true,
|
|
958
1063
|
Token: mint.Token,
|
|
959
|
-
Url: '/media/upload-stage',
|
|
1064
|
+
Url: ApiRoute('/media/upload-stage'),
|
|
960
1065
|
};
|
|
961
1066
|
} catch (err) {
|
|
962
1067
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -972,12 +1077,16 @@ export class FileResolver extends FileResolverBase {
|
|
|
972
1077
|
const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
|
|
973
1078
|
const user = this.GetUserFromPayload(context.userPayload);
|
|
974
1079
|
|
|
1080
|
+
// Account gate first — before the account is loaded or any driver is touched
|
|
1081
|
+
await this.assertAccountAccess(input.AccountID, 'Write', context);
|
|
1082
|
+
|
|
975
1083
|
// Load the account and its provider
|
|
976
1084
|
const { account, provider: providerEntity } = await this.loadAccountAndProvider(input.AccountID, context);
|
|
977
1085
|
|
|
978
1086
|
// Check permissions
|
|
979
1087
|
const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
|
|
980
1088
|
fileEntity.CheckPermissions(EntityPermissionType.Delete, true);
|
|
1089
|
+
await this.assertTrackedObjectReadable(input.AccountID, [input.ObjectName], user, md);
|
|
981
1090
|
|
|
982
1091
|
// Delete the object with extended user context (includes account for credential lookup)
|
|
983
1092
|
const userContext = this.buildExtendedUserContext(context, account);
|
|
@@ -991,12 +1100,17 @@ export class FileResolver extends FileResolverBase {
|
|
|
991
1100
|
const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
|
|
992
1101
|
const user = this.GetUserFromPayload(context.userPayload);
|
|
993
1102
|
|
|
1103
|
+
// Account gate first — before the account is loaded or any driver is touched
|
|
1104
|
+
await this.assertAccountAccess(input.AccountID, 'Write', context);
|
|
1105
|
+
|
|
994
1106
|
// Load the account and its provider
|
|
995
1107
|
const { account, provider: providerEntity } = await this.loadAccountAndProvider(input.AccountID, context);
|
|
996
1108
|
|
|
997
1109
|
// Check permissions
|
|
998
1110
|
const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
|
|
999
1111
|
fileEntity.CheckPermissions(EntityPermissionType.Update, true);
|
|
1112
|
+
// A move reads and removes the source and may overwrite the destination: both get the tracked-file rule
|
|
1113
|
+
await this.assertTrackedObjectReadable(input.AccountID, [input.OldName, input.NewName], user, md);
|
|
1000
1114
|
|
|
1001
1115
|
// Move the object with extended user context (includes account for credential lookup)
|
|
1002
1116
|
const userContext = this.buildExtendedUserContext(context, account);
|
|
@@ -1009,6 +1123,9 @@ export class FileResolver extends FileResolverBase {
|
|
|
1009
1123
|
const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
|
|
1010
1124
|
const user = this.GetUserFromPayload(context.userPayload);
|
|
1011
1125
|
|
|
1126
|
+
// Account gate first — a copy within one account writes to it — before the account is loaded or any driver is touched
|
|
1127
|
+
await this.assertAccountAccess(input.AccountID, 'Write', context);
|
|
1128
|
+
|
|
1012
1129
|
// Load the account and its provider
|
|
1013
1130
|
const { account, provider: providerEntity } = await this.loadAccountAndProvider(input.AccountID, context);
|
|
1014
1131
|
|
|
@@ -1016,6 +1133,9 @@ export class FileResolver extends FileResolverBase {
|
|
|
1016
1133
|
const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
|
|
1017
1134
|
fileEntity.CheckPermissions(EntityPermissionType.Read, true);
|
|
1018
1135
|
fileEntity.CheckPermissions(EntityPermissionType.Create, true);
|
|
1136
|
+
// A copy of a tracked object is an untracked one the caller could then download, and the destination may be
|
|
1137
|
+
// overwritten: both get the tracked-file rule
|
|
1138
|
+
await this.assertTrackedObjectReadable(input.AccountID, [input.SourceName, input.DestinationName], user, md);
|
|
1019
1139
|
|
|
1020
1140
|
// Copy the object with extended user context (includes account for credential lookup)
|
|
1021
1141
|
const userContext = this.buildExtendedUserContext(context, account);
|
|
@@ -1028,6 +1148,9 @@ export class FileResolver extends FileResolverBase {
|
|
|
1028
1148
|
const md = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true });
|
|
1029
1149
|
const user = this.GetUserFromPayload(context.userPayload);
|
|
1030
1150
|
|
|
1151
|
+
// Account gate first — before the account is loaded or any driver is touched
|
|
1152
|
+
await this.assertAccountAccess(input.AccountID, 'Write', context);
|
|
1153
|
+
|
|
1031
1154
|
// Load the account and its provider
|
|
1032
1155
|
const { account: accountEntity, provider: providerEntity } = await this.loadAccountAndProvider(input.AccountID, context);
|
|
1033
1156
|
|
|
@@ -1063,6 +1186,13 @@ export class FileResolver extends FileResolverBase {
|
|
|
1063
1186
|
fileEntity.CheckPermissions(EntityPermissionType.Read, true);
|
|
1064
1187
|
fileEntity.CheckPermissions(EntityPermissionType.Create, true);
|
|
1065
1188
|
|
|
1189
|
+
// Account gates first: read the source, write the destination — before either account is loaded or touched
|
|
1190
|
+
await this.assertAccountAccess(input.SourceAccountID, 'Read', context);
|
|
1191
|
+
await this.assertAccountAccess(input.DestinationAccountID, 'Write', context);
|
|
1192
|
+
// Tracked-file rule on both ends: the source (its copy would be untracked), the destination (it may be overwritten)
|
|
1193
|
+
await this.assertTrackedObjectReadable(input.SourceAccountID, [input.SourcePath], user, md);
|
|
1194
|
+
await this.assertTrackedObjectReadable(input.DestinationAccountID, [input.DestinationPath], user, md);
|
|
1195
|
+
|
|
1066
1196
|
// Load the source account and its provider
|
|
1067
1197
|
const { account: sourceAccount, provider: sourceProviderEntity } = await this.loadAccountAndProvider(input.SourceAccountID, context);
|
|
1068
1198
|
|
|
@@ -1110,54 +1240,149 @@ export class FileResolver extends FileResolverBase {
|
|
|
1110
1240
|
const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
|
|
1111
1241
|
fileEntity.CheckPermissions(EntityPermissionType.Read, true);
|
|
1112
1242
|
|
|
1113
|
-
//
|
|
1114
|
-
await
|
|
1115
|
-
const
|
|
1116
|
-
|
|
1117
|
-
|
|
1243
|
+
// Account gate BEFORE any driver call: refused accounts are reported as failed and never searched
|
|
1244
|
+
const { searchable, refused } = await this.partitionSearchAccounts(input.AccountIDs, user, md);
|
|
1245
|
+
const result: MultiAccountSearchResult = searchable.length > 0
|
|
1246
|
+
? await searchAcrossAccounts(searchable, input.Query, {
|
|
1247
|
+
maxResultsPerAccount: input.MaxResultsPerAccount,
|
|
1248
|
+
fileTypes: input.FileTypes,
|
|
1249
|
+
searchContent: input.SearchContent,
|
|
1250
|
+
contextUser: user,
|
|
1251
|
+
})
|
|
1252
|
+
: { accountResults: [], totalResultsReturned: 0, successfulAccounts: 0, failedAccounts: 0 };
|
|
1118
1253
|
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1254
|
+
console.log('[FileResolver] SearchAcrossAccounts result:', {
|
|
1255
|
+
totalResultsReturned: result.totalResultsReturned,
|
|
1256
|
+
successfulAccounts: result.successfulAccounts,
|
|
1257
|
+
failedAccounts: result.failedAccounts,
|
|
1258
|
+
refusedAccounts: refused.length,
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
const kept = await this.dropUnreadableTrackedHits(result.accountResults, user, md);
|
|
1262
|
+
return this.toSearchAcrossAccountsPayload(this.inRequestOrder(input.AccountIDs, [...kept, ...refused]));
|
|
1263
|
+
}
|
|
1122
1264
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1265
|
+
/**
|
|
1266
|
+
* Per-account results in the order the accounts were requested (each requested ID once), whether searched or refused —
|
|
1267
|
+
* so a refused account sits where the caller put it rather than after every searched one.
|
|
1268
|
+
*/
|
|
1269
|
+
private inRequestOrder(requestedIDs: string[], results: AccountSearchResult[]): AccountSearchResult[] {
|
|
1270
|
+
const byID = new Map(results.map((r) => [NormalizeUUID(r.accountID), r]));
|
|
1271
|
+
const ordered: AccountSearchResult[] = [];
|
|
1272
|
+
for (const id of requestedIDs) {
|
|
1273
|
+
const result = byID.get(NormalizeUUID(id));
|
|
1274
|
+
if (result) {
|
|
1275
|
+
ordered.push(result);
|
|
1276
|
+
byID.delete(NormalizeUUID(id));
|
|
1277
|
+
}
|
|
1128
1278
|
}
|
|
1279
|
+
return [...ordered, ...byID.values()];
|
|
1280
|
+
}
|
|
1129
1281
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1282
|
+
/**
|
|
1283
|
+
* Splits the requested accounts into those to search and those refused, before any driver call. An account the
|
|
1284
|
+
* caller may not read is refused with {@link SEARCH_ACCOUNT_REFUSED_MESSAGE} — and so is one that does not exist,
|
|
1285
|
+
* identically, so the response never reveals which IDs are real. Readable accounts are paired with their provider
|
|
1286
|
+
* from the engine's cache (refreshed once if a readable account is missing from it). Duplicate IDs are searched once.
|
|
1287
|
+
*/
|
|
1288
|
+
private async partitionSearchAccounts(
|
|
1289
|
+
accountIDs: string[],
|
|
1290
|
+
user: UserInfo,
|
|
1291
|
+
md: IMetadataProvider,
|
|
1292
|
+
): Promise<{ searchable: AccountSearchInput[]; refused: AccountSearchResult[] }> {
|
|
1293
|
+
const readable = await StorageAccessEvaluator.Instance.AccessibleAccountIDs(accountIDs, user, 'Read', md);
|
|
1294
|
+
await FileStorageEngine.Instance.Config(false, user, md);
|
|
1295
|
+
if ([...readable].some((id) => !FileStorageEngine.Instance.GetAccountWithProvider(id))) {
|
|
1296
|
+
await FileStorageEngine.Instance.RefreshAfterMiss(md);
|
|
1134
1297
|
}
|
|
1135
1298
|
|
|
1136
|
-
|
|
1137
|
-
const
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1299
|
+
const searchable: AccountSearchInput[] = [];
|
|
1300
|
+
const refused: AccountSearchResult[] = [];
|
|
1301
|
+
const seen = new Set<string>();
|
|
1302
|
+
for (const requestedID of accountIDs) {
|
|
1303
|
+
const key = NormalizeUUID(requestedID);
|
|
1304
|
+
if (seen.has(key)) continue;
|
|
1305
|
+
seen.add(key);
|
|
1306
|
+
const pair = readable.has(key) ? FileStorageEngine.Instance.GetAccountWithProvider(requestedID) : null;
|
|
1307
|
+
if (pair) {
|
|
1308
|
+
searchable.push({ accountEntity: pair.account, providerEntity: pair.provider });
|
|
1309
|
+
} else {
|
|
1310
|
+
refused.push(this.refusedAccountResult(requestedID));
|
|
1142
1311
|
}
|
|
1143
1312
|
}
|
|
1313
|
+
return { searchable, refused };
|
|
1314
|
+
}
|
|
1144
1315
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1316
|
+
/** A failed per-account entry for an account the caller may not search; it names nothing beyond the requested ID. */
|
|
1317
|
+
private refusedAccountResult(accountID: string): AccountSearchResult {
|
|
1318
|
+
return {
|
|
1319
|
+
accountID,
|
|
1320
|
+
accountName: '',
|
|
1321
|
+
providerID: '',
|
|
1322
|
+
providerName: '',
|
|
1323
|
+
success: false,
|
|
1324
|
+
errorMessage: SEARCH_ACCOUNT_REFUSED_MESSAGE,
|
|
1325
|
+
results: [],
|
|
1326
|
+
hasMore: false,
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1152
1329
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1330
|
+
/**
|
|
1331
|
+
* Drops search hits that map to an `MJ: Files` row the caller may not read — one batched lookup per account, keyed on
|
|
1332
|
+
* each hit's path and provider object ID (`StorageAccessEvaluator.UnreadableTrackedObjectKeys`, compared the way the
|
|
1333
|
+
* account's driver addresses keys). Hits no row tracks stay; the account gate governs them.
|
|
1334
|
+
*
|
|
1335
|
+
* When any hit is dropped, the provider's `totalMatches` (which counted it) and `nextPageToken` (this route takes no
|
|
1336
|
+
* page token back) are withheld; `hasMore` is kept. How reliable the mapping is depends on the provider: for
|
|
1337
|
+
* object-key stores (S3, Azure Blob, GCS) a hit's path IS the row's `ProviderKey`; for hierarchical providers (Box,
|
|
1338
|
+
* Google Drive, Dropbox, SharePoint) a search path may not match the key a row was written with, and such a miss falls
|
|
1339
|
+
* back to the account gate. `MJ: Files` rows are keyed by provider, not account, so the lookup errs toward dropping.
|
|
1340
|
+
*/
|
|
1341
|
+
private async dropUnreadableTrackedHits(
|
|
1342
|
+
accountResults: AccountSearchResult[],
|
|
1343
|
+
user: UserInfo,
|
|
1344
|
+
md: IMetadataProvider,
|
|
1345
|
+
): Promise<AccountSearchResult[]> {
|
|
1346
|
+
return Promise.all(accountResults.map((ar) => this.dropUnreadableAccountHits(ar, user, md)));
|
|
1347
|
+
}
|
|
1158
1348
|
|
|
1159
|
-
|
|
1160
|
-
|
|
1349
|
+
/** {@link dropUnreadableTrackedHits} for one account's results; if the check cannot run, the account's hits are dropped. */
|
|
1350
|
+
private async dropUnreadableAccountHits(ar: AccountSearchResult, user: UserInfo, md: IMetadataProvider): Promise<AccountSearchResult> {
|
|
1351
|
+
if (!ar.success || ar.results.length === 0) return ar;
|
|
1352
|
+
try {
|
|
1353
|
+
const driver = await FileStorageEngine.Instance.GetDriver(ar.accountID, user);
|
|
1354
|
+
const keys = ar.results.flatMap((r) => [r.path, r.objectId].filter((k): k is string => !!k));
|
|
1355
|
+
const unreadable = await StorageAccessEvaluator.Instance.UnreadableTrackedObjectKeys(ar.providerID, keys, user, md, driver);
|
|
1356
|
+
const results = ar.results.filter((r) => !unreadable.has(r.path) && !(r.objectId && unreadable.has(r.objectId)));
|
|
1357
|
+
return results.length === ar.results.length ? ar : { ...ar, results, totalMatches: undefined, nextPageToken: undefined };
|
|
1358
|
+
} catch (error) {
|
|
1359
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1360
|
+
LogError(`SearchAcrossAccounts: tracked-file check failed for account ${ar.accountID} — its hits are withheld: ${reason}`);
|
|
1361
|
+
return { ...ar, results: [], totalMatches: undefined, nextPageToken: undefined };
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/**
|
|
1366
|
+
* The listed objects minus those behind an `MJ: Files` row the caller may not read (directories are prefixes, never
|
|
1367
|
+
* tracked objects, and are kept). Keys are compared the way the account's driver addresses them.
|
|
1368
|
+
*/
|
|
1369
|
+
private async dropUnreadableListedObjects(
|
|
1370
|
+
accountId: string,
|
|
1371
|
+
objects: StoredObjectMetadata[],
|
|
1372
|
+
user: UserInfo,
|
|
1373
|
+
md: IMetadataProvider,
|
|
1374
|
+
): Promise<StoredObjectMetadata[]> {
|
|
1375
|
+
const files = objects.filter((o) => !o.isDirectory);
|
|
1376
|
+
if (files.length === 0) return objects;
|
|
1377
|
+
const providerID = await this.accountProviderID(accountId, user, md);
|
|
1378
|
+
const driver = await FileStorageEngine.Instance.GetDriver(accountId, user);
|
|
1379
|
+
const unreadable = await StorageAccessEvaluator.Instance.UnreadableTrackedObjectKeys(providerID, files.map((o) => o.fullPath), user, md, driver);
|
|
1380
|
+
return objects.filter((o) => o.isDirectory || !unreadable.has(o.fullPath));
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
/** Converts per-account results to the GraphQL payload, recomputing the totals from what is actually returned. */
|
|
1384
|
+
private toSearchAcrossAccountsPayload(accountResults: AccountSearchResult[]): SearchAcrossAccountsPayload {
|
|
1385
|
+
const payloadResults: AccountSearchResultPayload[] = accountResults.map((ar: AccountSearchResult) => ({
|
|
1161
1386
|
accountID: ar.accountID,
|
|
1162
1387
|
accountName: ar.accountName,
|
|
1163
1388
|
success: ar.success,
|
|
@@ -1179,10 +1404,10 @@ export class FileResolver extends FileResolverBase {
|
|
|
1179
1404
|
}));
|
|
1180
1405
|
|
|
1181
1406
|
return {
|
|
1182
|
-
accountResults,
|
|
1183
|
-
totalResultsReturned:
|
|
1184
|
-
successfulAccounts:
|
|
1185
|
-
failedAccounts:
|
|
1407
|
+
accountResults: payloadResults,
|
|
1408
|
+
totalResultsReturned: accountResults.reduce((sum, ar) => sum + (ar.success ? ar.results.length : 0), 0),
|
|
1409
|
+
successfulAccounts: accountResults.filter((ar) => ar.success).length,
|
|
1410
|
+
failedAccounts: accountResults.filter((ar) => !ar.success).length,
|
|
1186
1411
|
};
|
|
1187
1412
|
}
|
|
1188
1413
|
}
|