@memberjunction/server 6.2.0-edge.4 → 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/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 +5 -1
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +13 -1
- 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 +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/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1 -0
- 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/config.d.ts +136 -22
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +43 -4
- package/dist/config.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 +369 -3
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +1750 -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/PushStatusResolver.js +1 -1
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +103 -44
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +264 -305
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +12 -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 +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +112 -160
- package/dist/index.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/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 +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 +6 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
- package/dist/realtimeSessions/sessionAccess.js +7 -3
- package/dist/realtimeSessions/sessionAccess.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/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 +18 -3
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +100 -35
- 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/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +4 -2
- package/dist/resolvers/IntegrationDiscoveryResolver.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/RealtimeClientSessionResolver.d.ts +2 -8
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +10 -26
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +35 -6
- package/dist/resolvers/RunAIAgentResolver.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/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/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/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 +54 -2
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +598 -15
- 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/package.json +99 -98
- package/src/__tests__/DatasetResolver.itemFilters.test.ts +201 -0
- package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +163 -15
- 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__/RecordingAssemblyScheduler.test.ts +178 -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 +209 -6
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +5 -2
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +1 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +5 -3
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +258 -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 +27 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +25 -0
- package/src/__tests__/TaskGraphAgentRunner.test.ts +266 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +356 -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__/ViewOperationsHandler.clauseScreen.test.ts +157 -0
- package/src/__tests__/agent-run-data-guard.test.ts +130 -0
- package/src/__tests__/apiRootBoot.test.ts +202 -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__/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__/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__/serverExtensionHostOverlay.test.ts +66 -0
- package/src/__tests__/serverExtensionReservedRoots.test.ts +11 -98
- package/src/__tests__/sharedCache.test.ts +5 -3
- package/src/agentSessions/SessionJanitor.ts +369 -93
- package/src/agentSessions/SessionManager.ts +13 -1
- package/src/apiRoot.ts +78 -0
- package/src/auth/AuthProviderCatalogRouter.ts +26 -0
- package/src/auth/LoginScreenEngine.ts +50 -0
- package/src/auth/index.ts +1 -0
- package/src/auth/magicLink/MagicLinkRouter.ts +3 -2
- package/src/config.ts +46 -5
- package/src/coreRoutes.ts +166 -0
- package/src/generated/graphql-schemas/__mj.ts +1273 -10
- package/src/generic/CacheInvalidationResolver.ts +73 -18
- package/src/generic/PushStatusResolver.ts +1 -1
- package/src/generic/ResolverBase.ts +304 -325
- package/src/generic/RunViewResolver.ts +17 -0
- package/src/globalCors.ts +71 -0
- package/src/index.ts +116 -174
- package/src/multiTenancy/index.ts +24 -2
- package/src/realtimeSessions/RealtimeSessionVerificationService.ts +2 -1
- package/src/realtimeSessions/RealtimeVerifyRouter.ts +2 -1
- package/src/realtimeSessions/RecordingAssemblyScheduler.ts +234 -0
- package/src/realtimeSessions/index.ts +2 -0
- package/src/realtimeSessions/ownedRealtimeSession.ts +55 -0
- package/src/realtimeSessions/sessionAccess.ts +7 -3
- package/src/resolvers/ArtifactFileResolver.ts +9 -8
- 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 +121 -39
- package/src/resolvers/FileResolver.ts +321 -106
- package/src/resolvers/GetDataContextDataResolver.ts +139 -41
- package/src/resolvers/GetDataResolver.ts +6 -3
- package/src/resolvers/IntegrationDiscoveryResolver.ts +4 -2
- package/src/resolvers/QueryResolver.ts +11 -0
- package/src/resolvers/RealtimeClientSessionResolver.ts +10 -33
- package/src/resolvers/RunAIAgentResolver.ts +39 -4
- package/src/resolvers/SearchKnowledgeStreamResolver.ts +19 -3
- package/src/resolvers/SqlLoggingConfigResolver.ts +2 -4
- package/src/resolvers/TaskGraphFrameResolver.ts +33 -1
- package/src/resolvers/TestQuerySQLResolver.ts +68 -13
- package/src/resolvers/TransactionGroupResolver.ts +131 -27
- package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
- package/src/resolvers/agent-run-data-guard.ts +89 -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 +704 -14
- package/src/sharedCache.ts +3 -0
|
@@ -1212,6 +1212,8 @@ export class RunViewResolver extends ResolverBase {
|
|
|
1212
1212
|
@Ctx() { providers, userPayload }: AppContext
|
|
1213
1213
|
): Promise<RunViewsWithCacheCheckOutput> {
|
|
1214
1214
|
try {
|
|
1215
|
+
// Same scope as RunViews: this is the transport RunViews uses for a CacheLocal batch.
|
|
1216
|
+
await this.CheckAPIKeyScopeAuthorization('view:batch', '*', userPayload);
|
|
1215
1217
|
const provider = GetReadOnlyProvider(providers, { allowFallbackToReadWrite: true });
|
|
1216
1218
|
|
|
1217
1219
|
// Cast provider to SQLServerDataProvider to access RunViewsWithCacheCheck method
|
|
@@ -1220,6 +1222,21 @@ export class RunViewResolver extends ResolverBase {
|
|
|
1220
1222
|
throw new Error('Provider does not support RunViewsWithCacheCheck');
|
|
1221
1223
|
}
|
|
1222
1224
|
|
|
1225
|
+
// SECURITY: same GraphQL-boundary clause screen as RunViews, before the provider runs.
|
|
1226
|
+
const user = this.GetUserFromPayload(userPayload);
|
|
1227
|
+
for (const item of input) {
|
|
1228
|
+
this.screenClientViewClauses(
|
|
1229
|
+
{
|
|
1230
|
+
extraFilter: item.params.ExtraFilter,
|
|
1231
|
+
orderBy: item.params.OrderBy,
|
|
1232
|
+
userSearchString: item.params.UserSearchString,
|
|
1233
|
+
overrideExcludeFilter: item.params.OverrideExcludeFilter,
|
|
1234
|
+
},
|
|
1235
|
+
provider,
|
|
1236
|
+
user,
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1223
1240
|
// Convert GraphQL input types to core types
|
|
1224
1241
|
const coreParams: RunViewWithCacheCheckParams[] = input.map(item => ({
|
|
1225
1242
|
params: {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import cors from 'cors';
|
|
2
|
+
import type { RequestHandler } from 'express';
|
|
3
|
+
import { IsPathUnderServerExtensionRoot, SkipForServerExtensionRoots } from '@memberjunction/server-extensions-core';
|
|
4
|
+
import type { CorsConfig } from './config.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The `cors` section of the MJServer config that the global CORS middleware reads: `allowedOrigins`
|
|
8
|
+
* (`['*']` by default — every origin, without credentials), `allowCredentials` (ignored, always
|
|
9
|
+
* false, under the wildcard) and `maxAge` (preflight cache lifetime in seconds, 86400 by default).
|
|
10
|
+
* Partial, because tests and hosts may pass only the keys they care about.
|
|
11
|
+
*/
|
|
12
|
+
export type GlobalCorsSettings = Partial<CorsConfig>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Builds MJServer's global CORS middleware, mounted before auth so 401 responses carry CORS
|
|
16
|
+
* headers (without them the browser hides the error code and the client never refreshes its token).
|
|
17
|
+
*
|
|
18
|
+
* `extensionOwnedRoots` are the RootPaths of server extensions that declare `OwnsCORS: true`
|
|
19
|
+
* (see `GetCorsOwningServerExtensionRoots`). The global middleware skips requests under those
|
|
20
|
+
* roots entirely, so it neither reflects an origin onto the extension's responses nor answers
|
|
21
|
+
* its preflights; the extension's own CORS handling runs instead.
|
|
22
|
+
*/
|
|
23
|
+
export function CreateGlobalCorsMiddleware(
|
|
24
|
+
settings: GlobalCorsSettings | undefined,
|
|
25
|
+
extensionOwnedRoots: readonly string[] = [],
|
|
26
|
+
): RequestHandler {
|
|
27
|
+
const corsAllowed = settings?.allowedOrigins ?? ['*'];
|
|
28
|
+
const corsWildcard = corsAllowed.includes('*');
|
|
29
|
+
// SECURITY: never combine credentials with a wildcard/reflect-any-origin policy. When
|
|
30
|
+
// allowedOrigins is ['*'] the origin callback reflects the caller's Origin, and pairing that
|
|
31
|
+
// with Access-Control-Allow-Credentials: true lets any site a signed-in user visits make
|
|
32
|
+
// credentialed cross-origin reads. MJ's primary auth is a Bearer token (not auto-sent
|
|
33
|
+
// cross-origin), so dropping credentials under the wildcard default is safe; deployments that
|
|
34
|
+
// genuinely need credentialed CORS must configure an explicit allowedOrigins list.
|
|
35
|
+
const globalCors = cors<cors.CorsRequest>({
|
|
36
|
+
origin: (origin, callback) => {
|
|
37
|
+
// Allow all origins when ['*'] (default/backward-compatible),
|
|
38
|
+
// or when no Origin header (server-to-server calls).
|
|
39
|
+
if (corsWildcard || !origin || corsAllowed.includes(origin)) {
|
|
40
|
+
callback(null, true);
|
|
41
|
+
} else {
|
|
42
|
+
callback(new Error(`Origin ${origin} not allowed by CORS`));
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
credentials: corsWildcard ? false : (settings?.allowCredentials ?? true),
|
|
46
|
+
maxAge: settings?.maxAge ?? 86400,
|
|
47
|
+
}) as RequestHandler;
|
|
48
|
+
return SkipForServerExtensionRoots(globalCors, extensionOwnedRoots);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Terminates a preflight under an extension-owned root that no extension answered.
|
|
53
|
+
*
|
|
54
|
+
* Mount it after every server extension (both phases) and before the core routes. Without it, an
|
|
55
|
+
* unanswered `OPTIONS` request falls through to the next CORS-enabled mount — the GraphQL endpoint
|
|
56
|
+
* at `/` by default, whose `cors()` approves every origin with `Access-Control-Allow-Origin: *` —
|
|
57
|
+
* so an extension that owns its CORS but forgets an `OPTIONS` handler would be opened to every
|
|
58
|
+
* origin rather than closed. The answer is a bare 404 with no CORS headers, which the browser
|
|
59
|
+
* treats as a refused preflight.
|
|
60
|
+
*
|
|
61
|
+
* @param extensionOwnedRoots - RootPaths of extensions that declare `OwnsCORS: true`
|
|
62
|
+
*/
|
|
63
|
+
export function CreateExtensionOwnedPreflightFallback(extensionOwnedRoots: readonly string[]): RequestHandler {
|
|
64
|
+
return (req, res, next) => {
|
|
65
|
+
if (req.method === 'OPTIONS' && IsPathUnderServerExtensionRoot(req.path, extensionOwnedRoots)) {
|
|
66
|
+
res.status(404).end();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
next();
|
|
70
|
+
};
|
|
71
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ dotenv.config({ quiet: true });
|
|
|
4
4
|
|
|
5
5
|
import { expressMiddleware } from '@as-integrations/express5';
|
|
6
6
|
import { mergeSchemas } from '@graphql-tools/schema';
|
|
7
|
-
import { Metadata, DatabasePlatform, SetProvider, StartupManager as StartupManagerImport, BaseEntity, BaseEntityEvent, RunView, DatabaseProviderBase, ProviderBase, ResolveStartupMode } from '@memberjunction/core';
|
|
7
|
+
import { Metadata, DatabasePlatform, SetProvider, StartupManager as StartupManagerImport, BaseEntity, BaseEntityEvent, RunView, DatabaseProviderBase, ProviderBase, ResolveStartupMode, UserInfo } from '@memberjunction/core';
|
|
8
8
|
import { UserCache, resolveDbPlatformFromEnv } from '@memberjunction/generic-database-provider';
|
|
9
9
|
import { MJGlobal, MJEventType, UUIDsEqual, ShutdownRegistry } from '@memberjunction/global';
|
|
10
10
|
import { setupSQLServerClient, SQLServerDataProvider, SQLServerProviderConfigData } from '@memberjunction/sqlserver-dataprovider';
|
|
@@ -44,18 +44,12 @@ import { StartupLogger } from './logging/StartupLogger.js';
|
|
|
44
44
|
import { AuthProviderFactory } from '@memberjunction/auth-providers';
|
|
45
45
|
import createMSSQLConfig from './orm.js';
|
|
46
46
|
import { setupRESTEndpoints } from './rest/setupRESTEndpoints.js';
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
50
|
-
import {
|
|
51
|
-
import { REALTIME_SDP_EXCHANGE_PATH } from '@memberjunction/ai';
|
|
52
|
-
import { createMagicLinkHandler, createMagicLinkJwksRouter, registerMagicLinkAuthProvider, MAGIC_LINK_MOUNT_PATH } from './auth/magicLink/index.js';
|
|
53
|
-
import { createWidgetHandler, WIDGET_MOUNT_PATH } from './realtimeWidget/index.js';
|
|
47
|
+
import { CreateRealtimeRecordingRouter, REALTIME_RECORDING_MOUNT_PATH } from './rest/RealtimeRecordingHandler.js';
|
|
48
|
+
import { LoadOwnedRealtimeSession } from './realtimeSessions/ownedRealtimeSession.js';
|
|
49
|
+
import { LoadSessionConversationExternalID } from './realtimeSessions/sessionAccess.js';
|
|
50
|
+
import { RecordingAssemblyScheduler } from './realtimeSessions/RecordingAssemblyScheduler.js';
|
|
54
51
|
import {
|
|
55
|
-
CreateRealtimeVerifyRouter,
|
|
56
|
-
REALTIME_VERIFY_MOUNT_PATH,
|
|
57
52
|
RealtimeSessionEventService,
|
|
58
|
-
RealtimeSessionVerificationService,
|
|
59
53
|
ParseReplicatedRealtimeSessionEvent,
|
|
60
54
|
type RealtimeSessionEventTopicPayload,
|
|
61
55
|
} from './realtimeSessions/index.js';
|
|
@@ -67,7 +61,6 @@ import { ScheduledJobsService } from './services/ScheduledJobsService.js';
|
|
|
67
61
|
import { IntegrationSyncWorkerService } from './services/IntegrationSyncWorkerService.js';
|
|
68
62
|
import { LocalCacheManager, StartupManager, TelemetryManager, TelemetryLevel, LogStatus, LogError, SetVerboseLogging } from '@memberjunction/core';
|
|
69
63
|
import { getSystemUser, validateAuthProvidersRegistered } from './auth/index.js';
|
|
70
|
-
import { createAuthProviderCatalogRouter, AUTH_CATALOG_MOUNT_PATH } from './auth/AuthProviderCatalogRouter.js';
|
|
71
64
|
import { GetAPIKeyEngine } from '@memberjunction/api-keys';
|
|
72
65
|
import { ActionEngineServer } from '@memberjunction/actions';
|
|
73
66
|
import { CreateAdhocSQLAuthorizer } from './resolvers/AdhocQueryResolver.js';
|
|
@@ -87,7 +80,7 @@ import type { RedisLocalStorageProvider } from '@memberjunction/redis-provider';
|
|
|
87
80
|
import { IntegrationProgressEmitter } from '@memberjunction/integration-progress-artifacts';
|
|
88
81
|
import { PublishIntegrationProgress } from './resolvers/IntegrationProgressResolver.js';
|
|
89
82
|
import { RegisterRSUProgressBridge } from './integration/RSUProgressBridge.js';
|
|
90
|
-
import { ClientToolRequestManager, AgentRunWatchdog } from '@memberjunction/ai-agents';
|
|
83
|
+
import { ClientToolRequestManager, AgentRunWatchdog, SqlRealtimeRecordingLedger } from '@memberjunction/ai-agents';
|
|
91
84
|
import { BindBridgeEngineHostInstance, SessionJanitor, SetHostInstancePort, StartBridgeOrphanReconciliation } from './agentSessions/index.js';
|
|
92
85
|
import { StartTaskGraphDispatcher } from './services/StartTaskGraphDispatcher.js';
|
|
93
86
|
import { MJServerWorkQueueProviderSource, StartWorkQueueHost } from './services/WorkQueueHostService.js';
|
|
@@ -103,8 +96,11 @@ import {
|
|
|
103
96
|
MJCompanyIntegrationFieldMapEntity,
|
|
104
97
|
MJScheduledJobEntity,
|
|
105
98
|
} from '@memberjunction/core-entities';
|
|
106
|
-
import { ServerExtensionLoader, ServerExtensionConfig, mergeServerExtensionConfigs, prepareServerExtensionConfigs, describeServerExtensionMount, InstallMediaUpgradeDispatcher, IsGraphQLWsPath } from '@memberjunction/server-extensions-core';
|
|
107
|
-
import {
|
|
99
|
+
import { ServerExtensionLoader, ServerExtensionConfig, mergeServerExtensionConfigs, prepareServerExtensionConfigs, describeServerExtensionMount, InstallMediaUpgradeDispatcher, IsGraphQLWsPath, GetCorsOwningServerExtensionRoots } from '@memberjunction/server-extensions-core';
|
|
100
|
+
import { CreateExtensionOwnedPreflightFallback, CreateGlobalCorsMiddleware } from './globalCors.js';
|
|
101
|
+
import { CoreReservedServerExtensionRoots } from './serverExtensionReservedRoots.js';
|
|
102
|
+
import { ApiRoute, ApiRootPath, ConfiguredPublicUrl } from './apiRoot.js';
|
|
103
|
+
import { MountHealthcheckRoute, MountPublicCoreRoutes, MountExtensionHealthRoute, MountAuthCatalogRoute, MountAuthenticatedCoreRoutes } from './coreRoutes.js';
|
|
108
104
|
import { MetadataCacheRefreshIntervalSeconds } from './providerConfigUnits.js';
|
|
109
105
|
import { CreateMetadataRefreshSignalHandler, METADATA_REFRESH_SIGNAL } from './metadataRefreshSignal.js';
|
|
110
106
|
|
|
@@ -142,6 +138,7 @@ export { Int, Float, ID } from 'type-graphql';
|
|
|
142
138
|
export { NewUserBase } from './auth/newUsers.js';
|
|
143
139
|
export { CreateIsolatedProvider } from './isolatedProvider.js';
|
|
144
140
|
export { configInfo, DEFAULT_SERVER_CONFIG } from './config.js';
|
|
141
|
+
export { ApiRoute, ApiRootPath, ConfiguredPublicUrl, JoinApiRoute, NormalizeApiRootPath, type ApiRouteOptions } from './apiRoot.js';
|
|
145
142
|
export { ServerExtensionLoader, BaseServerExtension, DefaultServerExtensionServiceRegistry } from '@memberjunction/server-extensions-core';
|
|
146
143
|
export type {
|
|
147
144
|
ServerExtensionConfig,
|
|
@@ -251,6 +248,7 @@ export * from './resolvers/FileCategoryResolver.js';
|
|
|
251
248
|
export * from './resolvers/FileResolver.js';
|
|
252
249
|
export * from './rest/MediaAccessKeys.js';
|
|
253
250
|
export * from './rest/MediaStreamHandler.js';
|
|
251
|
+
export * from './rest/RealtimeRecordingHandler.js';
|
|
254
252
|
export * from './resolvers/InfoResolver.js';
|
|
255
253
|
export * from './resolvers/PotentialDuplicateRecordResolver.js';
|
|
256
254
|
export * from './resolvers/DuplicateEntryCheckResolver.js';
|
|
@@ -303,7 +301,15 @@ const localPath = (p: string) => {
|
|
|
303
301
|
return resolvedPath;
|
|
304
302
|
};
|
|
305
303
|
|
|
306
|
-
|
|
304
|
+
/**
|
|
305
|
+
* Creates the Express application MJServer serves on. `X-Powered-By` is disabled: it only
|
|
306
|
+
* advertises the framework to scanners. {@link Serve} disables it again on a host-supplied app.
|
|
307
|
+
*/
|
|
308
|
+
export const CreateApp = (): Application => {
|
|
309
|
+
const app = express();
|
|
310
|
+
app.disable('x-powered-by');
|
|
311
|
+
return app;
|
|
312
|
+
};
|
|
307
313
|
|
|
308
314
|
/** @deprecated Use {@link CreateApp}. */
|
|
309
315
|
export const createApp = CreateApp;
|
|
@@ -839,6 +845,8 @@ const setupComplete$ = new ReplaySubject(1);
|
|
|
839
845
|
if (sharedCache) {
|
|
840
846
|
await LocalCacheManager.Instance.SetStorageProvider(sharedCache);
|
|
841
847
|
}
|
|
848
|
+
// The derived per-entry ceiling, so the boot log says what the largest cacheable entry actually is.
|
|
849
|
+
startupLog.LogIf('verbose', `LocalCacheManager: largest cacheable entry ${Math.floor(LocalCacheManager.Instance.MaxEntrySizeBytes / 1024 / 1024)} MB (maxEntryPercentOfCache: ${configInfo.cacheSettings?.maxEntryPercentOfCache ?? 'auto'})`);
|
|
842
850
|
const engineSweepMs = StartEngineSweeper(configInfo.cacheSettings);
|
|
843
851
|
startupLog.LogIf('verbose', engineSweepMs > 0 ? `Engine/database sweep every ${engineSweepMs / 1000}s` : 'Engine/database sweep disabled');
|
|
844
852
|
const userCacheCheckMs = StartUserCacheChecks(configInfo.cacheSettings);
|
|
@@ -1270,6 +1278,9 @@ const setupComplete$ = new ReplaySubject(1);
|
|
|
1270
1278
|
await apolloServer.start();
|
|
1271
1279
|
|
|
1272
1280
|
// Fix #8: Add compression for better throughput performance
|
|
1281
|
+
// Never advertise the framework (`X-Powered-By: Express`) — the host may have passed its own app.
|
|
1282
|
+
app.disable('x-powered-by');
|
|
1283
|
+
|
|
1273
1284
|
app.use(compression({
|
|
1274
1285
|
// Don't compress responses smaller than 1KB
|
|
1275
1286
|
threshold: 1024,
|
|
@@ -1290,114 +1301,23 @@ const setupComplete$ = new ReplaySubject(1);
|
|
|
1290
1301
|
level: 6
|
|
1291
1302
|
}));
|
|
1292
1303
|
|
|
1293
|
-
// Health check
|
|
1294
|
-
//
|
|
1295
|
-
|
|
1296
|
-
// clients (e.g. MJExplorer's connectivity poller) can read the response.
|
|
1297
|
-
app.get('/healthcheck', cors<cors.CorsRequest>(), (_req, res) => {
|
|
1298
|
-
res.status(200).json({ status: 'ok' });
|
|
1299
|
-
});
|
|
1304
|
+
// Health check — before every middleware contribution and the auth middleware, so cloud platform
|
|
1305
|
+
// probes never generate auth noise. Every core route mounts under MJAPI_BASE_PATH (see apiRoot.ts).
|
|
1306
|
+
MountHealthcheckRoute(app);
|
|
1300
1307
|
|
|
1301
1308
|
// Apply middleware-contributed pre-auth handlers (after compression, before routes)
|
|
1302
1309
|
for (const mw of mwPreAuth) {
|
|
1303
1310
|
app.use(mw);
|
|
1304
1311
|
}
|
|
1305
1312
|
|
|
1306
|
-
// ───
|
|
1307
|
-
|
|
1313
|
+
// ─── Public core routes (unauthenticated, registered BEFORE auth) ─────
|
|
1314
|
+
// OAuth callback, eSignature, media, realtime SDP broker, magic-link, widget, realtime verify — see
|
|
1315
|
+
// coreRoutes.ts. New custom routes belong in Server Extensions, never in serve().
|
|
1316
|
+
// oauthPublicUrl is the configured public URL, derived as it always was; it is also the magic-link JWT
|
|
1317
|
+
// issuer, so it stays untrimmed. Outward links use ApiRoute(…, { absolute: true }) instead.
|
|
1318
|
+
const oauthPublicUrl = ConfiguredPublicUrl();
|
|
1308
1319
|
startupLog.LogIf('verbose', `[OAuth] publicUrl: ${oauthPublicUrl}`);
|
|
1309
|
-
|
|
1310
|
-
let oauthAuthenticatedRouter: ReturnType<typeof createOAuthCallbackHandler>['authenticatedRouter'] | undefined;
|
|
1311
|
-
if (oauthPublicUrl) {
|
|
1312
|
-
const { callbackRouter, authenticatedRouter } = createOAuthCallbackHandler({
|
|
1313
|
-
publicUrl: oauthPublicUrl,
|
|
1314
|
-
successRedirectUrl: `${oauthPublicUrl}/oauth/success`,
|
|
1315
|
-
errorRedirectUrl: `${oauthPublicUrl}/oauth/error`,
|
|
1316
|
-
// Constrains where a caller-supplied frontendReturnUrl may point (open-redirect guard).
|
|
1317
|
-
allowedFrontendOrigins: configInfo.cors?.allowedOrigins ?? ['*']
|
|
1318
|
-
});
|
|
1319
|
-
oauthAuthenticatedRouter = authenticatedRouter;
|
|
1320
|
-
|
|
1321
|
-
const oauthCors = cors<cors.CorsRequest>();
|
|
1322
|
-
|
|
1323
|
-
// OAuth callback is unauthenticated (called by external auth server)
|
|
1324
|
-
app.use('/oauth', oauthCors, callbackRouter);
|
|
1325
|
-
startupLog.LogIf('verbose', '[OAuth] Callback route registered at /oauth/callback');
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
// ─── Core HTTP Routes (Static Core Infrastructure) ─────────────────────────
|
|
1329
|
-
// IMPORTANT ARCHITECTURAL NOTE:
|
|
1330
|
-
// The route mounts below are strictly reserved for core framework infrastructure (OAuth, eSignature,
|
|
1331
|
-
// media streaming, realtime WebRTC broker, magic-link, widgets).
|
|
1332
|
-
// ALL new custom routes, external webhooks, vendor adapters, and application integrations MUST be
|
|
1333
|
-
// implemented as Server Extensions (subclasses of BaseServerExtension in @memberjunction/server-extensions-core)
|
|
1334
|
-
// mounted via ServerExtensionLoader — NEVER hardcoded as ad-hoc app.use() in serve().
|
|
1335
|
-
// See guides/SERVER_EXTENSIONS_GUIDE.md for complete patterns and best practices.
|
|
1336
|
-
|
|
1337
|
-
// ─── eSignature webhook (unauthenticated, registered BEFORE auth) ─────
|
|
1338
|
-
// Called by external signature providers (DocuSign Connect, etc.) without an MJ bearer token.
|
|
1339
|
-
// The provider DRIVER verifies the payload signature/HMAC; MJ auth does not apply here.
|
|
1340
|
-
app.use('/esignature', cors<cors.CorsRequest>(), createSignatureWebhookHandler());
|
|
1341
|
-
startupLog.LogIf('verbose', '[eSignature] Webhook route registered at /esignature/webhook/:driverKey');
|
|
1342
|
-
|
|
1343
|
-
// ─── Authenticated media streaming (token-gated, registered BEFORE auth) ─────
|
|
1344
|
-
// The `?token=` query param (minted by CreateMediaAccessToken after a per-user permission
|
|
1345
|
-
// check) is the capability — the route verifies it itself, so it does NOT use the MJ bearer
|
|
1346
|
-
// auth middleware (an <audio>/<video> element can't send Authorization headers).
|
|
1347
|
-
app.use('/media', cors<cors.CorsRequest>(), createMediaStreamRouter());
|
|
1348
|
-
startupLog.LogIf('verbose', '[Media] Streaming route registered at /media/:fileId');
|
|
1349
|
-
|
|
1350
|
-
// ─── Realtime WebRTC SDP broker (ticket-gated, registered BEFORE auth) ───────
|
|
1351
|
-
if (configInfo.realtime?.enabled) {
|
|
1352
|
-
app.use(REALTIME_SDP_EXCHANGE_PATH, cors<cors.CorsRequest>(), createRealtimeSdpBrokerRouter());
|
|
1353
|
-
startupLog.LogIf('verbose', `[Realtime] WebRTC SDP broker registered at ${REALTIME_SDP_EXCHANGE_PATH}`);
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
// ─── Magic-link routes (MJ-issued, app-scoped external access) ───────────
|
|
1357
|
-
// Public router (JWKS + redeem) mounts BEFORE the auth middleware; the
|
|
1358
|
-
// authenticated invite-creation router mounts AFTER it (see below).
|
|
1359
|
-
let widgetAuthenticatedRouter: ReturnType<typeof createWidgetHandler>['authenticatedRouter'] | undefined;
|
|
1360
|
-
let magicLinkAuthenticatedRouter: ReturnType<typeof createMagicLinkHandler>['authenticatedRouter'] | undefined;
|
|
1361
|
-
if (configInfo.magicLink?.enabled) {
|
|
1362
|
-
const { publicRouter, authenticatedRouter } = createMagicLinkHandler(oauthPublicUrl, configInfo.magicLink);
|
|
1363
|
-
magicLinkAuthenticatedRouter = authenticatedRouter;
|
|
1364
|
-
registerMagicLinkAuthProvider(oauthPublicUrl, configInfo.magicLink);
|
|
1365
|
-
app.use(MAGIC_LINK_MOUNT_PATH, cors<cors.CorsRequest>(), publicRouter);
|
|
1366
|
-
startupLog.LogIf('verbose', `[MagicLink] Public routes registered at ${MAGIC_LINK_MOUNT_PATH}/redeem and ${MAGIC_LINK_MOUNT_PATH}/jwks.json`);
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
// ─── Public web widget: guest-session mint (PUBLIC, before auth mw) ───────
|
|
1370
|
-
// Visitors hold no MJ JWT yet, so POST /widget/session is public. It reuses the
|
|
1371
|
-
// magic-link RS256 key + `magic-link` auth provider (ensured idempotently inside
|
|
1372
|
-
// createWidgetHandler), so it stands on its own even if magicLink.enabled is false.
|
|
1373
|
-
if (configInfo.widget?.enabled) {
|
|
1374
|
-
const { publicRouter: widgetRouter, authenticatedRouter: widgetAuthRouter } = createWidgetHandler(oauthPublicUrl, configInfo.widget);
|
|
1375
|
-
widgetAuthenticatedRouter = widgetAuthRouter;
|
|
1376
|
-
app.use(WIDGET_MOUNT_PATH, cors<cors.CorsRequest>(), widgetRouter);
|
|
1377
|
-
// The widget reuses the magic-link RS256 key + auth provider to validate guest
|
|
1378
|
-
// tokens, which validates by fetching the JWKS at MAGIC_LINK_MOUNT_PATH/jwks.json.
|
|
1379
|
-
// When magic-link itself is disabled its public router (which serves JWKS) never
|
|
1380
|
-
// mounts, so publish the key here — otherwise every guest token fails validation
|
|
1381
|
-
// (the auth middleware can't fetch the public key) and the widget 401s.
|
|
1382
|
-
if (!configInfo.magicLink?.enabled) {
|
|
1383
|
-
app.use(MAGIC_LINK_MOUNT_PATH, cors<cors.CorsRequest>(), createMagicLinkJwksRouter());
|
|
1384
|
-
startupLog.LogIf('verbose', `[Widget] Published reused signing key at ${MAGIC_LINK_MOUNT_PATH}/jwks.json (magic-link flow disabled)`);
|
|
1385
|
-
}
|
|
1386
|
-
startupLog.LogIf('verbose', `[Widget] Public routes registered at ${WIDGET_MOUNT_PATH}/session and ${WIDGET_MOUNT_PATH}/session/refresh`);
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
// ─── Realtime identity verification: PUBLIC verify-link routes (before auth mw) ───
|
|
1390
|
-
// The person opening the emailed link holds no MJ token — the single-use token is the capability.
|
|
1391
|
-
// GET only renders a confirm button; the POST redeems (mail scanners GET every link). Mounted only
|
|
1392
|
-
// when verification is enabled AND able to send mail AND able to protect its codes (see
|
|
1393
|
-
// realtime.identityVerification config: communicationProvider + hmacSecret).
|
|
1394
|
-
if (configInfo.realtime?.identityVerification?.enabled && !RealtimeSessionVerificationService.Instance.IsEnabled) {
|
|
1395
|
-
console.warn('[RealtimeVerify] realtime.identityVerification.enabled is true but verification is unavailable: it needs a communicationProvider and an hmacSecret of at least 16 characters.');
|
|
1396
|
-
}
|
|
1397
|
-
if (RealtimeSessionVerificationService.Instance.IsEnabled) {
|
|
1398
|
-
app.use(REALTIME_VERIFY_MOUNT_PATH, cors<cors.CorsRequest>(), CreateRealtimeVerifyRouter());
|
|
1399
|
-
startupLog.LogIf('verbose', `[RealtimeVerify] Public routes registered at ${REALTIME_VERIFY_MOUNT_PATH}/:token (GET) and ${REALTIME_VERIFY_MOUNT_PATH} (POST)`);
|
|
1400
|
-
}
|
|
1320
|
+
const coreAuthenticatedRouters = MountPublicCoreRoutes(app, oauthPublicUrl, startupLog);
|
|
1401
1321
|
|
|
1402
1322
|
// ─── Server extensions loader & shared service registry ───────────────────
|
|
1403
1323
|
const extensionLoader = new ServerExtensionLoader();
|
|
@@ -1468,9 +1388,9 @@ const setupComplete$ = new ReplaySubject(1);
|
|
|
1468
1388
|
|
|
1469
1389
|
// Open App packages publish their extensions; host mj.config.cjs overlays by DriverClass
|
|
1470
1390
|
// (and remains the only source for host-only extensions such as Slack/Teams).
|
|
1471
|
-
//
|
|
1472
|
-
//
|
|
1473
|
-
//
|
|
1391
|
+
// Every RootPath is relative to MJAPI_BASE_PATH: apiRootPath composes the real mount path, and
|
|
1392
|
+
// extraReservedRoots (also relative) must list every core mount in coreRoutes.ts, otherwise an
|
|
1393
|
+
// Open App can claim it.
|
|
1474
1394
|
const extensionConfigs = prepareServerExtensionConfigs(
|
|
1475
1395
|
mergeServerExtensionConfigs(
|
|
1476
1396
|
options?.serverExtensions ?? [],
|
|
@@ -1479,7 +1399,8 @@ const setupComplete$ = new ReplaySubject(1);
|
|
|
1479
1399
|
{
|
|
1480
1400
|
onInvalid: (message) => LogError(message),
|
|
1481
1401
|
onOverlap: (message) => LogStatus(message),
|
|
1482
|
-
extraReservedRoots:
|
|
1402
|
+
extraReservedRoots: CoreReservedServerExtensionRoots(),
|
|
1403
|
+
apiRootPath: ApiRootPath(),
|
|
1483
1404
|
},
|
|
1484
1405
|
);
|
|
1485
1406
|
for (const cfg of extensionConfigs) {
|
|
@@ -1490,27 +1411,14 @@ const setupComplete$ = new ReplaySubject(1);
|
|
|
1490
1411
|
// Without this, the browser blocks 401 responses from the auth middleware
|
|
1491
1412
|
// because they lack Access-Control-Allow-Origin headers, preventing the
|
|
1492
1413
|
// client from reading the error code and triggering token refresh.
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
//
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
app.use(cors<cors.CorsRequest>({
|
|
1502
|
-
origin: (origin, callback) => {
|
|
1503
|
-
// Allow all origins when ['*'] (default/backward-compatible),
|
|
1504
|
-
// or when no Origin header (server-to-server calls).
|
|
1505
|
-
if (corsWildcard || !origin || corsAllowed.includes(origin)) {
|
|
1506
|
-
callback(null, true);
|
|
1507
|
-
} else {
|
|
1508
|
-
callback(new Error(`Origin ${origin} not allowed by CORS`));
|
|
1509
|
-
}
|
|
1510
|
-
},
|
|
1511
|
-
credentials: corsWildcard ? false : (configInfo.cors?.allowCredentials ?? true),
|
|
1512
|
-
maxAge: configInfo.cors?.maxAge ?? 86400,
|
|
1513
|
-
}));
|
|
1414
|
+
// Extensions that declare `OwnsCORS: true` (ServerExtensionConfig) are exempt: the global
|
|
1415
|
+
// middleware skips their RootPaths so it neither reflects an origin onto their responses nor
|
|
1416
|
+
// answers their preflights — the extension's own OPTIONS handling and origin policy run instead.
|
|
1417
|
+
const corsOwnedExtensionRoots = GetCorsOwningServerExtensionRoots(extensionConfigs);
|
|
1418
|
+
if (corsOwnedExtensionRoots.length > 0) {
|
|
1419
|
+
LogStatus(`Global CORS skips extension-owned roots: ${corsOwnedExtensionRoots.join(', ')}`);
|
|
1420
|
+
}
|
|
1421
|
+
app.use(CreateGlobalCorsMiddleware(configInfo.cors, corsOwnedExtensionRoots));
|
|
1514
1422
|
|
|
1515
1423
|
// ─── Pre-auth server extensions (before auth — extensions handle their own auth) ─────
|
|
1516
1424
|
// Slack uses HMAC signature verification, Teams uses Bot Framework JWT validation.
|
|
@@ -1521,23 +1429,12 @@ const setupComplete$ = new ReplaySubject(1);
|
|
|
1521
1429
|
phase: 'pre-auth',
|
|
1522
1430
|
httpServer,
|
|
1523
1431
|
publicUrl: oauthPublicUrl,
|
|
1432
|
+
apiRootPath: ApiRootPath(),
|
|
1524
1433
|
});
|
|
1525
1434
|
}
|
|
1526
1435
|
|
|
1527
|
-
|
|
1528
|
-
app
|
|
1529
|
-
const results = await extensionLoader.HealthCheckAll();
|
|
1530
|
-
const allHealthy = results.length === 0 || results.every(r => r.Healthy);
|
|
1531
|
-
res.status(allHealthy ? 200 : 503).json({ extensions: results });
|
|
1532
|
-
});
|
|
1533
|
-
|
|
1534
|
-
// ─── Public authentication-provider catalog (PUBLIC, before auth mw) ──────
|
|
1535
|
-
// The browser needs the provider list BEFORE it holds a token, so this is necessarily
|
|
1536
|
-
// unauthenticated and must mount ahead of the auth middleware. It publishes only the
|
|
1537
|
-
// non-secret allow-list (see AuthProviderEngine.GetPublicCatalog) — the same values a
|
|
1538
|
-
// single-provider SPA already compiled into its bundle.
|
|
1539
|
-
app.use(AUTH_CATALOG_MOUNT_PATH, cors<cors.CorsRequest>(), createAuthProviderCatalogRouter());
|
|
1540
|
-
startupLog.LogIf('verbose', `[Auth] Public provider catalog registered at ${AUTH_CATALOG_MOUNT_PATH}/providers`);
|
|
1436
|
+
MountExtensionHealthRoute(app, extensionLoader);
|
|
1437
|
+
MountAuthCatalogRoute(app, startupLog);
|
|
1541
1438
|
|
|
1542
1439
|
// ─── Unified auth middleware (replaces both REST authMiddleware and contextFunction auth) ─────
|
|
1543
1440
|
app.use(CreateUnifiedAuthMiddleware(dataSources));
|
|
@@ -1550,25 +1447,16 @@ const setupComplete$ = new ReplaySubject(1);
|
|
|
1550
1447
|
app.use(mw);
|
|
1551
1448
|
}
|
|
1552
1449
|
|
|
1553
|
-
// ───
|
|
1554
|
-
|
|
1555
|
-
const oauthCors = cors<cors.CorsRequest>();
|
|
1556
|
-
app.use('/oauth', oauthCors, BodyParser.json(), oauthAuthenticatedRouter);
|
|
1557
|
-
startupLog.LogIf('verbose', '[OAuth] Authenticated routes registered at /oauth/status, /oauth/initiate, and /oauth/exchange');
|
|
1558
|
-
}
|
|
1559
|
-
|
|
1560
|
-
// ─── Magic-link authenticated route (invite creation) ─────────────────────
|
|
1561
|
-
if (magicLinkAuthenticatedRouter) {
|
|
1562
|
-
app.use(MAGIC_LINK_MOUNT_PATH, cors<cors.CorsRequest>(), magicLinkAuthenticatedRouter);
|
|
1563
|
-
startupLog.LogIf('verbose', `[MagicLink] Authenticated route registered at ${MAGIC_LINK_MOUNT_PATH}/create`);
|
|
1564
|
-
}
|
|
1450
|
+
// ─── Authenticated core routes: OAuth status/initiate/exchange, magic-link create, widget resolve-identity ─────
|
|
1451
|
+
MountAuthenticatedCoreRoutes(app, coreAuthenticatedRouters, startupLog);
|
|
1565
1452
|
|
|
1566
|
-
// ───
|
|
1567
|
-
//
|
|
1568
|
-
//
|
|
1569
|
-
if (
|
|
1570
|
-
|
|
1571
|
-
|
|
1453
|
+
// ─── Realtime recording shards + terminal signal (authenticated, owner-gated) ─────
|
|
1454
|
+
// The browser uploads a client-direct call's recording as Opus shards and a keepalive terminal request;
|
|
1455
|
+
// assembly runs off the request path on the scheduler. Needs a SQL provider: the ledger calls procs.
|
|
1456
|
+
if (configInfo.realtime?.enabled) {
|
|
1457
|
+
if (mountRealtimeRecordingRoutes(app)) {
|
|
1458
|
+
startupLog.LogIf('verbose', `[Realtime] Recording routes registered at ${ApiRoute(REALTIME_RECORDING_MOUNT_PATH)}/:agentSessionId/shard/:index and /end`);
|
|
1459
|
+
}
|
|
1572
1460
|
}
|
|
1573
1461
|
|
|
1574
1462
|
// ─── Post-auth server extensions ──────────────────────────────────────────
|
|
@@ -1579,15 +1467,24 @@ const setupComplete$ = new ReplaySubject(1);
|
|
|
1579
1467
|
phase: 'post-auth',
|
|
1580
1468
|
httpServer,
|
|
1581
1469
|
publicUrl: oauthPublicUrl,
|
|
1470
|
+
apiRootPath: ApiRootPath(),
|
|
1582
1471
|
});
|
|
1583
1472
|
}
|
|
1584
1473
|
|
|
1474
|
+
// Every extension has now had its chance at the request: a preflight under an extension-owned
|
|
1475
|
+
// root that none of them answered is refused here, instead of falling through to the GraphQL
|
|
1476
|
+
// mount's cors(), which would approve it for every origin.
|
|
1477
|
+
if (corsOwnedExtensionRoots.length > 0) {
|
|
1478
|
+
app.use(CreateExtensionOwnedPreflightFallback(corsOwnedExtensionRoots));
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1585
1481
|
// ─── Cross-extension lifecycle hook: OnAllExtensionsMounted ───────────────
|
|
1586
1482
|
// Called after all extensions across both pre-auth and post-auth phases are mounted.
|
|
1587
1483
|
// Extensions can wire themselves to services registered by other extensions or core.
|
|
1588
1484
|
await extensionLoader.NotifyAllExtensionsMounted({
|
|
1589
1485
|
httpServer,
|
|
1590
1486
|
publicUrl: oauthPublicUrl,
|
|
1487
|
+
apiRootPath: ApiRootPath(),
|
|
1591
1488
|
});
|
|
1592
1489
|
|
|
1593
1490
|
// Install the single path-routing WebSocket-upgrade dispatcher AFTER all extensions across both
|
|
@@ -2280,3 +2177,48 @@ function createMSSQLCompatPool(pgPool: import('pg').Pool): sql.ConnectionPool {
|
|
|
2280
2177
|
return wrapper as unknown as sql.ConnectionPool;
|
|
2281
2178
|
}
|
|
2282
2179
|
|
|
2180
|
+
// Defined below serve() on purpose: serverExtensionReservedRoots.test.ts treats every app.use() that appears
|
|
2181
|
+
// textually before the unified auth middleware as a pre-auth mount, and these routes are mounted post-auth.
|
|
2182
|
+
/**
|
|
2183
|
+
* Mounts the realtime recording shard/terminal routes (post-auth) and starts the assembly scheduler they
|
|
2184
|
+
* hand work to. Skipped, with a warning, when the server's provider is not a SQL provider (the ledger calls
|
|
2185
|
+
* stored procedures). Without a system user the routes still store shards, and the janitor assembles them.
|
|
2186
|
+
* Returns whether the routes were mounted.
|
|
2187
|
+
*/
|
|
2188
|
+
function mountRealtimeRecordingRoutes(app: Application): boolean {
|
|
2189
|
+
const provider = Metadata.Provider; // global-provider-ok: REST endpoint — no per-request provider injection in REST middleware yet; the ledger's procs run as the server's SQL login either way
|
|
2190
|
+
if (!(provider instanceof DatabaseProviderBase)) {
|
|
2191
|
+
console.warn('[Realtime] Recording routes not mounted: the server provider is not a DatabaseProviderBase');
|
|
2192
|
+
return false;
|
|
2193
|
+
}
|
|
2194
|
+
const graceMs = (): number => configInfo.realtime.recordingAssemblyGraceSeconds * 1000;
|
|
2195
|
+
const systemUser = UserCache.Instance.GetSystemUser();
|
|
2196
|
+
if (systemUser) {
|
|
2197
|
+
RecordingAssemblyScheduler.Instance.Start({ Provider: provider, SystemUser: systemUser, GraceMs: graceMs });
|
|
2198
|
+
} else {
|
|
2199
|
+
LogError('[Realtime] Recording assembly scheduler not started: no system user; the session janitor assembles recordings instead');
|
|
2200
|
+
}
|
|
2201
|
+
app.use(ApiRoute(REALTIME_RECORDING_MOUNT_PATH), cors<cors.CorsRequest>(), CreateRealtimeRecordingRouter({
|
|
2202
|
+
ResolveCaller: req => {
|
|
2203
|
+
const user: UserInfo | undefined = req.userPayload?.userRecord;
|
|
2204
|
+
return user ? { ContextUser: user, Provider: provider } : null;
|
|
2205
|
+
},
|
|
2206
|
+
LoadOwnedSession: LoadOwnedRealtimeSession,
|
|
2207
|
+
LoadConversationExternalID: LoadSessionConversationExternalID,
|
|
2208
|
+
CreateLedger: (ledgerProvider, contextUser) => new SqlRealtimeRecordingLedger(ledgerProvider, contextUser),
|
|
2209
|
+
Scheduler: RecordingAssemblyScheduler.Instance,
|
|
2210
|
+
GraceMs: graceMs,
|
|
2211
|
+
SystemUser: () => UserCache.Instance.GetSystemUser() ?? null,
|
|
2212
|
+
}));
|
|
2213
|
+
return true;
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
// Public-edge building blocks for Open Apps that run their own pre-auth edge and mint
|
|
2217
|
+
// scope-limited guest sessions (e.g. a ballot edge: verify a credential, then mint a short-lived
|
|
2218
|
+
// session scoped to one resource). They are the same pieces WidgetSessionService uses; a generic
|
|
2219
|
+
// public-widget session mint remains the long-term replacement.
|
|
2220
|
+
export { BuildSessionClaims, MagicLinkKeyManager, MagicLinkService, HashToken, GenerateRawToken } from './auth/magicLink/index.js';
|
|
2221
|
+
export type { MagicLinkJWTClaims } from './auth/magicLink/index.js';
|
|
2222
|
+
export { VerifyHostAssertion } from './realtimeWidget/host-identity.js';
|
|
2223
|
+
export type { HostAssertedIdentity, HostAssertionResult } from './realtimeWidget/host-identity.js';
|
|
2224
|
+
export { IsScopeLimitedPrincipal } from './auth/scopeLimitedPrincipal.js';
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
import type { RequestHandler } from 'express';
|
|
11
11
|
import type { PreRunViewHook, PreSaveHook } from '@memberjunction/core';
|
|
12
|
-
import { Metadata, type UserInfo, type TenantContext } from '@memberjunction/core';
|
|
12
|
+
import { DatabaseProviderBase, Metadata, type UserInfo, type TenantContext } from '@memberjunction/core';
|
|
13
|
+
import { PostgreSQLDialect, SQLServerDialect, type SQLParserDialect } from '@memberjunction/sql-dialect';
|
|
14
|
+
import { IsBalancedSQLFragment } from '@memberjunction/sql-parser';
|
|
13
15
|
import type { MultiTenancyConfig } from '../config.js';
|
|
14
16
|
import type { UserPayload } from '../types.js';
|
|
15
17
|
import { CloneUserForSessionContext } from '../auth/sessionUserClone.js';
|
|
@@ -161,6 +163,25 @@ function isAdminUser(user: UserInfo, adminRoles: string[]): boolean {
|
|
|
161
163
|
);
|
|
162
164
|
}
|
|
163
165
|
|
|
166
|
+
/** Dialects to read an ExtraFilter with when the active provider's dialect cannot be resolved. */
|
|
167
|
+
const ALL_FILTER_DIALECTS: SQLParserDialect[] = [new SQLServerDialect(), new PostgreSQLDialect()];
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Throws unless the caller's ExtraFilter stays inside the parentheses the tenant predicate is
|
|
171
|
+
* ANDed after. A filter such as `1=1) OR (1=1` would close them early and leave the tenant
|
|
172
|
+
* predicate on one side of an OR the caller controls.
|
|
173
|
+
*/
|
|
174
|
+
function assertExtraFilterBalanced(extraFilter: string): void {
|
|
175
|
+
const provider = Metadata.Provider; // global-provider-ok: same rationale as isEntityScoped above — hooks carry no per-request provider
|
|
176
|
+
const dialects = provider instanceof DatabaseProviderBase ? [provider.Dialect] : ALL_FILTER_DIALECTS;
|
|
177
|
+
for (const dialect of dialects) {
|
|
178
|
+
const check = IsBalancedSQLFragment(extraFilter, dialect);
|
|
179
|
+
if (!check.IsBalanced) {
|
|
180
|
+
throw new Error(`[MultiTenancy] Invalid ExtraFilter: ${check.Reason}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
164
185
|
/**
|
|
165
186
|
* Creates a PreRunViewHook that auto-injects tenant WHERE clauses
|
|
166
187
|
* into RunView queries for scoped entities.
|
|
@@ -206,7 +227,8 @@ export function CreateTenantPreRunViewHook(config: MultiTenancyConfig): PreRunVi
|
|
|
206
227
|
|
|
207
228
|
// Inject the tenant filter
|
|
208
229
|
if (params.ExtraFilter && typeof params.ExtraFilter === 'string' && params.ExtraFilter.trim().length > 0) {
|
|
209
|
-
|
|
230
|
+
assertExtraFilterBalanced(params.ExtraFilter);
|
|
231
|
+
params.ExtraFilter = `(${params.ExtraFilter}) AND (${tenantFilter})`;
|
|
210
232
|
} else {
|
|
211
233
|
params.ExtraFilter = tenantFilter;
|
|
212
234
|
}
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
type VerificationOperationResult,
|
|
31
31
|
type VerificationSettings,
|
|
32
32
|
} from './verificationWorkflow.js';
|
|
33
|
+
import { ConfiguredPublicUrl } from '../apiRoot.js';
|
|
33
34
|
|
|
34
35
|
/** Purpose label for principal-resolution diagnostics. */
|
|
35
36
|
const PRINCIPAL_PURPOSE = 'RealtimeVerification';
|
|
@@ -37,7 +38,7 @@ const PRINCIPAL_PURPOSE = 'RealtimeVerification';
|
|
|
37
38
|
/** The base URL emailed links are built from: explicit setting, else the server's public URL (same derivation magic links use). */
|
|
38
39
|
function resolvePublicBaseUrl(explicit: string | undefined): string | undefined {
|
|
39
40
|
const candidate =
|
|
40
|
-
explicit?.trim() ||
|
|
41
|
+
explicit?.trim() || ConfiguredPublicUrl();
|
|
41
42
|
return candidate ? TrimTrailingSlashes(candidate) : undefined;
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -31,6 +31,7 @@ import { LogError } from '@memberjunction/core';
|
|
|
31
31
|
import { configInfo } from '../config.js';
|
|
32
32
|
import { RealtimeSessionVerificationService } from './RealtimeSessionVerificationService.js';
|
|
33
33
|
import { BuildVerifyConfirmHtml, BuildVerifyErrorHtml, BuildVerifyFailureHtml, BuildVerifySuccessHtml } from './verificationPages.js';
|
|
34
|
+
import { ApiRoute } from '../apiRoot.js';
|
|
34
35
|
|
|
35
36
|
/** Mount path of the public verify routes. Under `/realtime`, which is already reserved against server extensions. */
|
|
36
37
|
export const REALTIME_VERIFY_MOUNT_PATH = '/realtime/verify';
|
|
@@ -79,7 +80,7 @@ export function CreateRealtimeVerifyRouter(
|
|
|
79
80
|
// SAFE: renders the confirmation button. Never consumes the token (see the module header).
|
|
80
81
|
router.get('/:token', limiter, (req: Request, res: Response) => {
|
|
81
82
|
const token = typeof req.params.token === 'string' ? req.params.token : '';
|
|
82
|
-
sendPage(res, 200, BuildVerifyConfirmHtml(token, REALTIME_VERIFY_MOUNT_PATH));
|
|
83
|
+
sendPage(res, 200, BuildVerifyConfirmHtml(token, ApiRoute(REALTIME_VERIFY_MOUNT_PATH)));
|
|
83
84
|
});
|
|
84
85
|
|
|
85
86
|
// The side-effecting redemption, gated by a human gesture.
|