@memberjunction/server 6.1.4 → 6.1.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 +30 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/SessionManager.d.ts +5 -2
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +17 -8
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
- package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
- package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
- package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
- package/dist/agentSessions/index.d.ts +1 -0
- package/dist/agentSessions/index.d.ts.map +1 -1
- package/dist/agentSessions/index.js +1 -0
- package/dist/agentSessions/index.js.map +1 -1
- package/dist/auth/actionAuthorization.d.ts +40 -0
- package/dist/auth/actionAuthorization.d.ts.map +1 -0
- package/dist/auth/actionAuthorization.js +94 -0
- package/dist/auth/actionAuthorization.js.map +1 -0
- package/dist/auth/exampleNewUserSubClass.js +2 -1
- package/dist/auth/exampleNewUserSubClass.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts +17 -15
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +55 -38
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/magicLink/magicLinkCore.d.ts +14 -33
- package/dist/auth/magicLink/magicLinkCore.d.ts.map +1 -1
- package/dist/auth/magicLink/magicLinkCore.js +20 -52
- package/dist/auth/magicLink/magicLinkCore.js.map +1 -1
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +15 -11
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/context.d.ts +6 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +24 -34
- package/dist/context.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +3417 -3428
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +44 -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/ResolverBase.d.ts +95 -11
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +186 -76
- 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/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -45
- package/dist/index.js.map +1 -1
- package/dist/isolatedProvider.d.ts +20 -0
- package/dist/isolatedProvider.d.ts.map +1 -0
- package/dist/isolatedProvider.js +22 -0
- package/dist/isolatedProvider.js.map +1 -0
- package/dist/multiTenancy/index.d.ts.map +1 -1
- package/dist/multiTenancy/index.js +22 -2
- package/dist/multiTenancy/index.js.map +1 -1
- package/dist/postgresqlCompat.d.ts +8 -0
- package/dist/postgresqlCompat.d.ts.map +1 -0
- package/dist/postgresqlCompat.js +19 -0
- package/dist/postgresqlCompat.js.map +1 -0
- package/dist/postgresqlPoolSettings.d.ts +108 -0
- package/dist/postgresqlPoolSettings.d.ts.map +1 -0
- package/dist/postgresqlPoolSettings.js +134 -0
- package/dist/postgresqlPoolSettings.js.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +4 -2
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +4 -2
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +31 -5
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +75 -14
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts +35 -17
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +92 -166
- package/dist/resolvers/AdhocQueryResolver.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 +8 -23
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +11 -48
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +2 -2
- package/dist/resolvers/FetchEntityVectorsResolver.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 +13 -1
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +2 -1
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +83 -22
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +174 -72
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +2 -1
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +12 -11
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.d.ts +1 -0
- package/dist/resolvers/SearchKnowledgeResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.js +5 -0
- package/dist/resolvers/SearchKnowledgeResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.js +1 -0
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +6 -2
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/VectorizeEntityResolver.d.ts +1 -1
- package/dist/resolvers/VectorizeEntityResolver.d.ts.map +1 -1
- package/dist/resolvers/VectorizeEntityResolver.js +14 -10
- package/dist/resolvers/VectorizeEntityResolver.js.map +1 -1
- package/dist/resolvers/adhocTimeout.d.ts +7 -0
- package/dist/resolvers/adhocTimeout.d.ts.map +1 -0
- package/dist/resolvers/adhocTimeout.js +12 -0
- package/dist/resolvers/adhocTimeout.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 +29 -17
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +21 -8
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +43 -16
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/package.json +92 -92
- package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
- package/src/__tests__/AdhocQueryResolver.bugs.test.ts +14 -35
- package/src/__tests__/AdhocQueryResolver.pagination.test.ts +6 -2
- package/src/__tests__/AdhocQueryResolver.scopeGuard.test.ts +3 -3
- package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
- package/src/__tests__/DatasetResolver.itemFilters.test.ts +201 -0
- package/src/__tests__/EntityRecordNameResolver.fieldSecurity.test.ts +27 -72
- package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
- package/src/__tests__/GetDataContextDataResolver.sqlItems.test.ts +416 -0
- package/src/__tests__/ListOperationsResolver.adhocScreen.test.ts +137 -0
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +310 -31
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +227 -0
- package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
- package/src/__tests__/ResolverBase.balancedClause.test.ts +155 -0
- package/src/__tests__/ResolverBase.clauseUnscopedRead.test.ts +127 -0
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +24 -2
- package/src/__tests__/ResolverBase.isaPromotionCreate.test.ts +412 -0
- package/src/__tests__/RunTemplateResolver.test.ts +132 -0
- package/src/__tests__/RunViewResolver.cacheCheckScreen.test.ts +165 -0
- package/src/__tests__/SessionManager.test.ts +94 -17
- package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +131 -0
- package/src/__tests__/TaskGraphActionRunner.storableOutputs.test.ts +76 -0
- package/src/__tests__/UpdateMJDataContextItem.oldValues.test.ts +220 -0
- package/src/__tests__/VectorizeEntityResolver.test.ts +106 -0
- package/src/__tests__/ViewOperationsHandler.clauseScreen.test.ts +157 -0
- package/src/__tests__/actionAuthorization.test.ts +130 -0
- package/src/__tests__/adhocTimeoutClamp.test.ts +26 -0
- package/src/__tests__/cacheInvalidationRecordData.test.ts +64 -0
- package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
- package/src/__tests__/magicLink.test.ts +15 -76
- package/src/__tests__/magicLinkService.consume.test.ts +230 -0
- package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
- package/src/__tests__/multiTenancy.postgresRewrite.test.ts +181 -0
- package/src/__tests__/multiTenancy.security.test.ts +51 -0
- package/src/__tests__/multiTenancy.test.ts +1 -1
- package/src/__tests__/newUsers.test.ts +62 -5
- package/src/__tests__/postgresqlPoolSettings.test.ts +131 -0
- package/src/__tests__/translateBracketsToPG.test.ts +20 -0
- package/src/agentSessions/SessionManager.ts +23 -25
- package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
- package/src/agentSessions/index.ts +1 -0
- package/src/auth/actionAuthorization.ts +119 -0
- package/src/auth/exampleNewUserSubClass.ts +2 -2
- package/src/auth/magicLink/MagicLinkService.ts +61 -39
- package/src/auth/magicLink/magicLinkCore.ts +19 -56
- package/src/auth/newUsers.ts +15 -11
- package/src/context.ts +25 -34
- package/src/generated/graphql-schemas/__mj.ts +3417 -3428
- package/src/generic/CacheInvalidationResolver.ts +74 -19
- package/src/generic/ResolverBase.ts +207 -80
- package/src/generic/RunViewResolver.ts +17 -0
- package/src/index.ts +49 -46
- package/src/isolatedProvider.ts +22 -0
- package/src/multiTenancy/index.ts +24 -2
- package/src/postgresqlCompat.ts +21 -0
- package/src/postgresqlPoolSettings.ts +213 -0
- package/src/realtimeWidget/widgetGuestElevation.ts +4 -2
- package/src/resolvers/ActionResolver.ts +84 -21
- package/src/resolvers/AdhocQueryResolver.ts +105 -187
- package/src/resolvers/DatasetResolver.ts +70 -10
- package/src/resolvers/EntityRecordNameResolver.ts +11 -52
- package/src/resolvers/FetchEntityVectorsResolver.ts +2 -2
- package/src/resolvers/GetDataContextDataResolver.ts +139 -41
- package/src/resolvers/GetDataResolver.ts +14 -1
- package/src/resolvers/QuerySystemUserResolver.ts +2 -1
- package/src/resolvers/RealtimeClientSessionResolver.ts +196 -77
- package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
- package/src/resolvers/RunAIAgentResolver.ts +4 -1
- package/src/resolvers/RunTemplateResolver.ts +13 -12
- package/src/resolvers/SearchKnowledgeResolver.ts +4 -0
- package/src/resolvers/SearchKnowledgeSystemUserResolver.ts +1 -0
- package/src/resolvers/SyncDataResolver.ts +6 -2
- package/src/resolvers/VectorizeEntityResolver.ts +16 -11
- package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
- package/src/resolvers/adhocTimeout.ts +12 -0
- package/src/rest/ViewOperationsHandler.ts +37 -18
- package/src/services/TaskGraphActionRunner.ts +45 -16
- package/dist/resolvers/adhoc-query-helpers.d.ts +0 -27
- package/dist/resolvers/adhoc-query-helpers.d.ts.map +0 -1
- package/dist/resolvers/adhoc-query-helpers.js +0 -42
- package/dist/resolvers/adhoc-query-helpers.js.map +0 -1
- package/src/__tests__/adhoc-query-helpers.test.ts +0 -50
- package/src/resolvers/adhoc-query-helpers.ts +0 -50
package/src/index.ts
CHANGED
|
@@ -31,8 +31,10 @@ import { WebSocketServer } from 'ws';
|
|
|
31
31
|
import { RealtimeProxyServer } from './realtimeProxy/RealtimeProxyServer.js';
|
|
32
32
|
import buildApolloServer from './apolloServer/index.js';
|
|
33
33
|
import { configInfo, configFilePath, dbDatabase, dbHost, dbPort, dbUsername, graphqlPort, graphqlRootPath, mj_core_schema, websiteRunFromPackage, RESTApiOptions } from './config.js';
|
|
34
|
+
import { TranslateBracketsToPG } from './postgresqlCompat.js';
|
|
35
|
+
import { BuildPostgreSQLConnectionConfig, DescribeReadOnlyLoginOverreach, PostgreSQLReadOnlyPool, ResolvePostgreSQLEndpoint, ResolvePostgreSQLReadOnlyCredentials, ToPGPoolConfig } from './postgresqlPoolSettings.js';
|
|
34
36
|
import { default as jwt } from 'jsonwebtoken';
|
|
35
|
-
import { contextFunction, createUnifiedAuthMiddleware, getUserPayload } from './context.js';
|
|
37
|
+
import { contextFunction, CreateReadOnlyProvider, createUnifiedAuthMiddleware, getUserPayload } from './context.js';
|
|
36
38
|
import { UserPayload } from './types.js';
|
|
37
39
|
import { requireSystemUserDirective, publicDirective } from './directives/index.js';
|
|
38
40
|
import { variablesLoggingMiddleware } from './logging/variablesLoggingMiddleware.js';
|
|
@@ -60,6 +62,8 @@ import { createAuthProviderCatalogRouter, AUTH_CATALOG_MOUNT_PATH } from './auth
|
|
|
60
62
|
import { GetAPIKeyEngine } from '@memberjunction/api-keys';
|
|
61
63
|
import { RedisLocalStorageProvider } from '@memberjunction/redis-provider';
|
|
62
64
|
import { GenericDatabaseProvider } from '@memberjunction/generic-database-provider';
|
|
65
|
+
import { ActionEngineServer } from '@memberjunction/actions';
|
|
66
|
+
import { CreateAdhocSQLAuthorizer } from './resolvers/AdhocQueryResolver.js';
|
|
63
67
|
import { PubSubManager } from './generic/PubSubManager.js';
|
|
64
68
|
import { IntegrationProgressEmitter } from '@memberjunction/integration-progress-artifacts';
|
|
65
69
|
import { PublishIntegrationProgress } from './resolvers/IntegrationProgressResolver.js';
|
|
@@ -108,6 +112,7 @@ export * from 'type-graphql';
|
|
|
108
112
|
// later importers, which makes schema build fail on ViewResult.RowCount.
|
|
109
113
|
export { Int, Float, ID } from 'type-graphql';
|
|
110
114
|
export { NewUserBase } from './auth/newUsers.js';
|
|
115
|
+
export { CreateIsolatedProvider } from './isolatedProvider.js';
|
|
111
116
|
export { configInfo, DEFAULT_SERVER_CONFIG } from './config.js';
|
|
112
117
|
export { ServerExtensionLoader, BaseServerExtension, DefaultServerExtensionServiceRegistry } from '@memberjunction/server-extensions-core';
|
|
113
118
|
export type {
|
|
@@ -316,25 +321,13 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
316
321
|
startupLog.BeginPhase('Connecting to database');
|
|
317
322
|
startupLog.LogIf('verbose', 'Database type: PostgreSQL');
|
|
318
323
|
const pg = await import('pg');
|
|
319
|
-
const { PostgreSQLDataProvider, PostgreSQLProviderConfigData } = await import('@memberjunction/postgresql-dataprovider');
|
|
324
|
+
const { PostgreSQLDataProvider, PostgreSQLProviderConfigData, MJPostgresTypes } = await import('@memberjunction/postgresql-dataprovider');
|
|
320
325
|
|
|
321
|
-
const
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
const pgPool = new pg.default.Pool({
|
|
328
|
-
host: pgHost,
|
|
329
|
-
port: pgPort,
|
|
330
|
-
user: pgUser,
|
|
331
|
-
password: pgPass,
|
|
332
|
-
database: pgDatabase,
|
|
333
|
-
max: configInfo.databaseSettings.connectionPool?.max ?? 50,
|
|
334
|
-
min: configInfo.databaseSettings.connectionPool?.min ?? 5,
|
|
335
|
-
idleTimeoutMillis: configInfo.databaseSettings.connectionPool?.idleTimeoutMillis ?? 30000,
|
|
336
|
-
connectionTimeoutMillis: configInfo.databaseSettings.connectionPool?.acquireTimeoutMillis ?? 30000,
|
|
337
|
-
});
|
|
326
|
+
const pgEndpoint = ResolvePostgreSQLEndpoint();
|
|
327
|
+
const { Host: pgHost, Port: pgPort, User: pgUser, Database: pgDatabase } = pgEndpoint;
|
|
328
|
+
// Every API pool carries the statement and idle-in-transaction timeouts from connection #1.
|
|
329
|
+
const pgConnectionConfig = BuildPostgreSQLConnectionConfig(pgEndpoint, configInfo.databaseSettings, 'api');
|
|
330
|
+
const pgPool = new pg.default.Pool(ToPGPoolConfig(pgConnectionConfig));
|
|
338
331
|
|
|
339
332
|
// Verify connection
|
|
340
333
|
const testClient = await pgPool.connect();
|
|
@@ -342,6 +335,28 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
342
335
|
testClient.release();
|
|
343
336
|
startupLog.LogIf('verbose', `PostgreSQL pool connected to ${pgHost}:${pgPort}/${pgDatabase}`);
|
|
344
337
|
|
|
338
|
+
// A read-only pool opened with the read-only login, as SQL Server has. Read-only per-request
|
|
339
|
+
// providers (TestQuerySQL and other caller-supplied SQL) share it instead of the primary pool.
|
|
340
|
+
const pgReadOnlyCredentials = ResolvePostgreSQLReadOnlyCredentials(configInfo);
|
|
341
|
+
if (pgReadOnlyCredentials) {
|
|
342
|
+
// A pool a provider runs on must carry the provider's type parsers, so BIGINT and NUMERIC
|
|
343
|
+
// come back as numbers here as they do on the provider's own pool.
|
|
344
|
+
const readOnlyPgPool = new pg.default.Pool({
|
|
345
|
+
...ToPGPoolConfig(BuildPostgreSQLConnectionConfig({ ...pgEndpoint, ...pgReadOnlyCredentials }, configInfo.databaseSettings, 'read-only')),
|
|
346
|
+
types: MJPostgresTypes,
|
|
347
|
+
});
|
|
348
|
+
const readOnlyTestClient = await readOnlyPgPool.connect();
|
|
349
|
+
try {
|
|
350
|
+
for (const warning of await DescribeReadOnlyLoginOverreach(readOnlyTestClient, mj_core_schema)) {
|
|
351
|
+
LogStatus(`WARNING: ${warning}`);
|
|
352
|
+
}
|
|
353
|
+
} finally {
|
|
354
|
+
readOnlyTestClient.release();
|
|
355
|
+
}
|
|
356
|
+
PostgreSQLReadOnlyPool.Instance.Pool = readOnlyPgPool;
|
|
357
|
+
startupLog.LogIf('verbose', 'Read-only PostgreSQL pool has been initialized.');
|
|
358
|
+
}
|
|
359
|
+
|
|
345
360
|
// Create a DataSourceInfo with a MSSQL-compatible wrapper around pg.Pool
|
|
346
361
|
// This allows existing code (types, util, context) to work without changes
|
|
347
362
|
const mssqlCompatPool = createMSSQLCompatPool(pgPool);
|
|
@@ -355,15 +370,6 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
355
370
|
}));
|
|
356
371
|
|
|
357
372
|
// Set up the PostgreSQL provider
|
|
358
|
-
const pgConnectionConfig = {
|
|
359
|
-
Host: pgHost,
|
|
360
|
-
Port: pgPort,
|
|
361
|
-
Database: pgDatabase,
|
|
362
|
-
User: pgUser,
|
|
363
|
-
Password: pgPass,
|
|
364
|
-
MaxConnections: configInfo.databaseSettings.connectionPool?.max ?? 50,
|
|
365
|
-
MinConnections: configInfo.databaseSettings.connectionPool?.min ?? 5,
|
|
366
|
-
};
|
|
367
373
|
const pgConfigData = new PostgreSQLProviderConfigData(
|
|
368
374
|
pgConnectionConfig,
|
|
369
375
|
mj_core_schema,
|
|
@@ -397,7 +403,7 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
397
403
|
if (poolAny._pgPool) {
|
|
398
404
|
const thePgPool = poolAny._pgPool as import('pg').Pool;
|
|
399
405
|
// Translate SQL Server bracket syntax to PostgreSQL double-quote syntax
|
|
400
|
-
const pgQuery =
|
|
406
|
+
const pgQuery = TranslateBracketsToPG(query);
|
|
401
407
|
const result = await thePgPool.query(pgQuery);
|
|
402
408
|
return result.rows;
|
|
403
409
|
}
|
|
@@ -416,21 +422,21 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
416
422
|
const pgCodegenPass = process.env.CODEGEN_DB_PASSWORD;
|
|
417
423
|
if (pgCodegenUser && pgCodegenPass) {
|
|
418
424
|
try {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
425
|
+
// CodeGen and DDL work runs long statements across every entity, so this pool gets the
|
|
426
|
+
// long CodeGen timeout rather than the API one, as the SQL Server CodeGen pool does.
|
|
427
|
+
const codegenPgConnectionConfig = BuildPostgreSQLConnectionConfig(
|
|
428
|
+
{ ...pgEndpoint, User: pgCodegenUser, Password: pgCodegenPass },
|
|
429
|
+
configInfo.databaseSettings,
|
|
430
|
+
'codegen',
|
|
431
|
+
);
|
|
432
|
+
const codegenPgPool = new pg.default.Pool(ToPGPoolConfig(codegenPgConnectionConfig));
|
|
427
433
|
const codegenTestClient = await codegenPgPool.connect();
|
|
428
434
|
await codegenTestClient.query('SELECT 1');
|
|
429
435
|
codegenTestClient.release();
|
|
430
436
|
|
|
431
437
|
const { RuntimeSchemaManager } = await import('@memberjunction/schema-engine');
|
|
432
438
|
const codegenPgConfigData = new PostgreSQLProviderConfigData(
|
|
433
|
-
|
|
439
|
+
codegenPgConnectionConfig,
|
|
434
440
|
mj_core_schema,
|
|
435
441
|
cacheRefreshInterval / 1000, // ms → seconds
|
|
436
442
|
);
|
|
@@ -734,6 +740,11 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
|
|
|
734
740
|
// This must happen before any request handler calls GetAPIKeyEngine()
|
|
735
741
|
GetAPIKeyEngine();
|
|
736
742
|
|
|
743
|
+
// Actions that run caller-supplied SQL (Run Ad-hoc Query) run only on the read-only login, with
|
|
744
|
+
// ExecuteAdhocQuery's table check and timeout limit.
|
|
745
|
+
ActionEngineServer.Instance.SetReadOnlyProviderFactory(() => CreateReadOnlyProvider(dataSources));
|
|
746
|
+
ActionEngineServer.Instance.SetAdhocSQLAuthorizer(CreateAdhocSQLAuthorizer(configInfo.databaseSettings.requestTimeout));
|
|
747
|
+
|
|
737
748
|
setupComplete$.next(true);
|
|
738
749
|
raiseEvent('setupComplete', dataSources, null, this);
|
|
739
750
|
|
|
@@ -2079,11 +2090,3 @@ function createMSSQLCompatPool(pgPool: import('pg').Pool): sql.ConnectionPool {
|
|
|
2079
2090
|
return wrapper as unknown as sql.ConnectionPool;
|
|
2080
2091
|
}
|
|
2081
2092
|
|
|
2082
|
-
/**
|
|
2083
|
-
* Translates SQL Server bracket-quoted identifiers to PostgreSQL double-quoted identifiers.
|
|
2084
|
-
* Converts [schema].[table] to "schema"."table" and handles common T-SQL patterns.
|
|
2085
|
-
*/
|
|
2086
|
-
function translateBracketsToPG(sql: string): string {
|
|
2087
|
-
// Replace [identifier] with "identifier"
|
|
2088
|
-
return sql.replace(/\[([^\]]+)\]/g, '"$1"');
|
|
2089
|
-
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { DatabaseProviderBase, Metadata } from '@memberjunction/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A fresh Read-Write provider with its OWN transaction state, sharing the server's connection pool.
|
|
5
|
+
*
|
|
6
|
+
* A provider's ambient transaction (`BeginTransaction` / `CommitTransaction` / `RollbackTransaction`)
|
|
7
|
+
* is a field on the provider INSTANCE. Two units of work that open transactions on one instance nest
|
|
8
|
+
* into each other, and every write anyone else makes through that instance while one is open lands
|
|
9
|
+
* inside it (MJ#2140; `entityTransactionScope.ts`). The process-global `Metadata.Provider` is shared
|
|
10
|
+
* by every request, scheduled job and background service, so transactional work must never run on
|
|
11
|
+
* it: one transaction left open there silently swallows every later global-provider write until the
|
|
12
|
+
* process restarts.
|
|
13
|
+
*
|
|
14
|
+
* GraphQL resolvers already get a per-request provider from `AppContext.providers`. Code that runs
|
|
15
|
+
* outside one, such as the auth flow before the context exists, calls this.
|
|
16
|
+
*
|
|
17
|
+
* Cheap: it is the global provider's `CreateIndependentInstance()` (SQL Server and PostgreSQL), which
|
|
18
|
+
* reuses the already-loaded metadata and the shared pool.
|
|
19
|
+
*/
|
|
20
|
+
export async function CreateIsolatedProvider(): Promise<DatabaseProviderBase> {
|
|
21
|
+
return (Metadata.Provider as DatabaseProviderBase).CreateIndependentInstance(); // global-provider-ok: borrows only its pool and metadata, never its transaction state
|
|
22
|
+
}
|
|
@@ -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';
|
|
@@ -147,6 +149,25 @@ function isAdminUser(user: UserInfo, adminRoles: string[]): boolean {
|
|
|
147
149
|
);
|
|
148
150
|
}
|
|
149
151
|
|
|
152
|
+
/** Dialects to read an ExtraFilter with when the active provider's dialect cannot be resolved. */
|
|
153
|
+
const ALL_FILTER_DIALECTS: SQLParserDialect[] = [new SQLServerDialect(), new PostgreSQLDialect()];
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Throws unless the caller's ExtraFilter stays inside the parentheses the tenant predicate is
|
|
157
|
+
* ANDed after. A filter such as `1=1) OR (1=1` would close them early and leave the tenant
|
|
158
|
+
* predicate on one side of an OR the caller controls.
|
|
159
|
+
*/
|
|
160
|
+
function assertExtraFilterBalanced(extraFilter: string): void {
|
|
161
|
+
const provider = Metadata.Provider; // global-provider-ok: same rationale as isEntityScoped above — hooks carry no per-request provider
|
|
162
|
+
const dialects = provider instanceof DatabaseProviderBase ? [provider.Dialect] : ALL_FILTER_DIALECTS;
|
|
163
|
+
for (const dialect of dialects) {
|
|
164
|
+
const check = IsBalancedSQLFragment(extraFilter, dialect);
|
|
165
|
+
if (!check.IsBalanced) {
|
|
166
|
+
throw new Error(`[MultiTenancy] Invalid ExtraFilter: ${check.Reason}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
150
171
|
/**
|
|
151
172
|
* Creates a PreRunViewHook that auto-injects tenant WHERE clauses
|
|
152
173
|
* into RunView queries for scoped entities.
|
|
@@ -192,7 +213,8 @@ export function createTenantPreRunViewHook(config: MultiTenancyConfig): PreRunVi
|
|
|
192
213
|
|
|
193
214
|
// Inject the tenant filter
|
|
194
215
|
if (params.ExtraFilter && typeof params.ExtraFilter === 'string' && params.ExtraFilter.trim().length > 0) {
|
|
195
|
-
|
|
216
|
+
assertExtraFilterBalanced(params.ExtraFilter);
|
|
217
|
+
params.ExtraFilter = `(${params.ExtraFilter}) AND (${tenantFilter})`;
|
|
196
218
|
} else {
|
|
197
219
|
params.ExtraFilter = tenantFilter;
|
|
198
220
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { LexSQL } from '@memberjunction/sql-parser';
|
|
2
|
+
import { SQLServerDialect } from '@memberjunction/sql-dialect';
|
|
3
|
+
|
|
4
|
+
const sqlServer = new SQLServerDialect();
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Translates SQL Server bracket-quoted identifiers (`[Name]`) to PostgreSQL double-quoted ones
|
|
8
|
+
* (`"Name"`), for SQL written in SQL Server syntax that runs on PostgreSQL. Only identifiers
|
|
9
|
+
* change: brackets inside string literals and comments are left as written. A `]]` in a name
|
|
10
|
+
* becomes `]`, and a `"` in a name is doubled.
|
|
11
|
+
*/
|
|
12
|
+
export function TranslateBracketsToPG(sql: string): string {
|
|
13
|
+
return LexSQL(sql, sqlServer)
|
|
14
|
+
.map(t => t.Kind === 'identifier' && t.Text.startsWith('[') ? toDoubleQuoted(t.Text) : t.Text)
|
|
15
|
+
.join('');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function toDoubleQuoted(bracketed: string): string {
|
|
19
|
+
const name = bracketed.slice(1, bracketed.endsWith(']') ? -1 : undefined).replace(/]]/g, ']');
|
|
20
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
21
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection settings for MJAPI's PostgreSQL pools, derived from the same `databaseSettings`
|
|
3
|
+
* values SQL Server reads, with the same defaults.
|
|
4
|
+
*
|
|
5
|
+
* | Setting | SQL Server | PostgreSQL |
|
|
6
|
+
* |--------------------------------------|----------------------------------|----------------------------------------------|
|
|
7
|
+
* | `requestTimeout` | driver request timeout | `statement_timeout` on every backend |
|
|
8
|
+
* | `connectionPool.max` / `min` | pool size | pool size |
|
|
9
|
+
* | `connectionPool.idleTimeoutMillis` | idle connection eviction | idle connection eviction |
|
|
10
|
+
* | `connectionPool.acquireTimeoutMillis`| wait for a pooled connection | `connectionTimeoutMillis` (see below) |
|
|
11
|
+
* | `connectionTimeout` | time to open a connection | not used |
|
|
12
|
+
*
|
|
13
|
+
* Where the two cannot match exactly:
|
|
14
|
+
* - The `pg` pool has one timeout covering both waiting for a free slot and opening a connection.
|
|
15
|
+
* It is fed from `acquireTimeoutMillis`; `connectionTimeout` is not used on PostgreSQL.
|
|
16
|
+
* - SQL Server's `requestTimeout` is a client-side timer over the whole request, including reading
|
|
17
|
+
* rows. PostgreSQL's `statement_timeout` is server-side execution time per statement, so a query
|
|
18
|
+
* that executes quickly and returns its rows slowly is not cut off on PostgreSQL.
|
|
19
|
+
* - `idle_in_transaction_session_timeout` has no SQL Server counterpart. It is derived as twice the
|
|
20
|
+
* statement timeout, so a transaction left open by an abandoned request is ended and its locks
|
|
21
|
+
* released.
|
|
22
|
+
* - MJAPI opens a raw compatibility pool and a provider pool, each up to `connectionPool.max`, plus
|
|
23
|
+
* a CodeGen pool of 10 and, when a read-only login is configured, a read-only pool of at most 10,
|
|
24
|
+
* where SQL Server opens one pool (and a read-only one). PostgreSQL's default `max_connections`
|
|
25
|
+
* is 100, so the read-only and CodeGen pools are kept small.
|
|
26
|
+
*/
|
|
27
|
+
import type { PGConnectionConfig } from '@memberjunction/postgresql-dataprovider';
|
|
28
|
+
import type { Pool, PoolConfig } from 'pg';
|
|
29
|
+
import { BaseSingleton } from '@memberjunction/global';
|
|
30
|
+
import type { DatabaseSettingsInfo } from './config.js';
|
|
31
|
+
|
|
32
|
+
/** Statement timeout for the CodeGen/DDL pool, matching the SQL Server CodeGen pool. */
|
|
33
|
+
export const CODEGEN_STATEMENT_TIMEOUT_MS = 600000;
|
|
34
|
+
|
|
35
|
+
/** Pool size for the CodeGen/DDL pool. */
|
|
36
|
+
const CODEGEN_MAX_CONNECTIONS = 10;
|
|
37
|
+
|
|
38
|
+
/** Largest pool size for the read-only pool, which serves only caller-supplied SQL. */
|
|
39
|
+
const READ_ONLY_MAX_CONNECTIONS = 10;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Which pool the settings are for: API traffic, the read-only pool for caller-supplied SQL, or
|
|
43
|
+
* CodeGen and DDL work.
|
|
44
|
+
*/
|
|
45
|
+
export type PostgreSQLPoolPurpose = 'api' | 'read-only' | 'codegen';
|
|
46
|
+
|
|
47
|
+
/** Where a PostgreSQL pool connects and as whom. */
|
|
48
|
+
export interface PostgreSQLEndpoint {
|
|
49
|
+
Host: string;
|
|
50
|
+
Port: number;
|
|
51
|
+
Database: string;
|
|
52
|
+
User: string;
|
|
53
|
+
Password: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Reads the PostgreSQL endpoint from `PG_*` environment variables, then `DB_*` ones. */
|
|
57
|
+
export function ResolvePostgreSQLEndpoint(env: NodeJS.ProcessEnv = process.env): PostgreSQLEndpoint {
|
|
58
|
+
return {
|
|
59
|
+
Host: env.PG_HOST || env.DB_HOST || 'localhost',
|
|
60
|
+
Port: parseInt(env.PG_PORT || env.DB_PORT || '5432', 10),
|
|
61
|
+
Database: env.PG_DATABASE || env.DB_DATABASE || '',
|
|
62
|
+
User: env.PG_USERNAME || env.DB_USERNAME || 'postgres',
|
|
63
|
+
Password: env.PG_PASSWORD || env.DB_PASSWORD || ''
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The idle-in-transaction limit for a given statement timeout; 0 means none. */
|
|
68
|
+
export function IdleInTransactionTimeoutFor(statementTimeoutMs: number): number {
|
|
69
|
+
return statementTimeoutMs > 0 ? statementTimeoutMs * 2 : 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Builds the provider connection config for one of MJAPI's PostgreSQL pools. */
|
|
73
|
+
export function BuildPostgreSQLConnectionConfig(
|
|
74
|
+
endpoint: PostgreSQLEndpoint,
|
|
75
|
+
settings: DatabaseSettingsInfo,
|
|
76
|
+
purpose: PostgreSQLPoolPurpose
|
|
77
|
+
): PGConnectionConfig {
|
|
78
|
+
const pool = settings.connectionPool;
|
|
79
|
+
if (purpose === 'codegen') {
|
|
80
|
+
return {
|
|
81
|
+
...endpoint,
|
|
82
|
+
MaxConnections: CODEGEN_MAX_CONNECTIONS,
|
|
83
|
+
StatementTimeoutMs: CODEGEN_STATEMENT_TIMEOUT_MS,
|
|
84
|
+
IdleInTransactionSessionTimeoutMs: 0
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const statementTimeoutMs = Math.max(0, settings.requestTimeout);
|
|
88
|
+
const apiMax = pool?.max ?? 50;
|
|
89
|
+
const apiMin = pool?.min ?? 5;
|
|
90
|
+
const readOnly = purpose === 'read-only';
|
|
91
|
+
return {
|
|
92
|
+
...endpoint,
|
|
93
|
+
MaxConnections: readOnly ? Math.min(READ_ONLY_MAX_CONNECTIONS, apiMax) : apiMax,
|
|
94
|
+
MinConnections: readOnly ? 0 : apiMin,
|
|
95
|
+
IdleTimeoutMillis: pool?.idleTimeoutMillis ?? 30000,
|
|
96
|
+
ConnectionTimeoutMillis: pool?.acquireTimeoutMillis ?? 30000,
|
|
97
|
+
StatementTimeoutMs: statementTimeoutMs,
|
|
98
|
+
IdleInTransactionSessionTimeoutMs: IdleInTransactionTimeoutFor(statementTimeoutMs)
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Maps a provider connection config onto `pg.Pool` options, for the pools MJAPI creates directly
|
|
104
|
+
* rather than through the provider. Timeouts that are unset or 0 are left out.
|
|
105
|
+
*/
|
|
106
|
+
export function ToPGPoolConfig(config: PGConnectionConfig): PoolConfig {
|
|
107
|
+
const pool: PoolConfig = {
|
|
108
|
+
host: config.Host,
|
|
109
|
+
port: config.Port,
|
|
110
|
+
database: config.Database,
|
|
111
|
+
user: config.User,
|
|
112
|
+
password: config.Password,
|
|
113
|
+
max: config.MaxConnections
|
|
114
|
+
};
|
|
115
|
+
if (config.MinConnections !== undefined) pool.min = config.MinConnections;
|
|
116
|
+
if (config.IdleTimeoutMillis !== undefined) pool.idleTimeoutMillis = config.IdleTimeoutMillis;
|
|
117
|
+
if (config.ConnectionTimeoutMillis !== undefined) pool.connectionTimeoutMillis = config.ConnectionTimeoutMillis;
|
|
118
|
+
if (config.StatementTimeoutMs) pool.statement_timeout = config.StatementTimeoutMs;
|
|
119
|
+
if (config.IdleInTransactionSessionTimeoutMs) pool.idle_in_transaction_session_timeout = config.IdleInTransactionSessionTimeoutMs;
|
|
120
|
+
return pool;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Credentials for a read-only database login. */
|
|
124
|
+
export interface ReadOnlyCredentials {
|
|
125
|
+
User: string;
|
|
126
|
+
Password: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The read-only login for PostgreSQL: the `dbReadOnlyUsername` / `dbReadOnlyPassword` settings
|
|
131
|
+
* SQL Server uses (which default from `DB_READ_ONLY_USERNAME` / `DB_READ_ONLY_PASSWORD`), then
|
|
132
|
+
* `PG_READ_ONLY_USERNAME` / `PG_READ_ONLY_PASSWORD`. `null` when neither pair is complete.
|
|
133
|
+
*/
|
|
134
|
+
export function ResolvePostgreSQLReadOnlyCredentials(
|
|
135
|
+
configured: { dbReadOnlyUsername?: string; dbReadOnlyPassword?: string },
|
|
136
|
+
env: NodeJS.ProcessEnv = process.env
|
|
137
|
+
): ReadOnlyCredentials | null {
|
|
138
|
+
if (configured.dbReadOnlyUsername && configured.dbReadOnlyPassword) {
|
|
139
|
+
return { User: configured.dbReadOnlyUsername, Password: configured.dbReadOnlyPassword };
|
|
140
|
+
}
|
|
141
|
+
if (env.PG_READ_ONLY_USERNAME && env.PG_READ_ONLY_PASSWORD) {
|
|
142
|
+
return { User: env.PG_READ_ONLY_USERNAME, Password: env.PG_READ_ONLY_PASSWORD };
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Holds MJAPI's PostgreSQL read-only pool, opened once at startup with the read-only login.
|
|
149
|
+
* Per-request read-only providers share it, so they connect as the read-only user rather than
|
|
150
|
+
* through the read-write pool.
|
|
151
|
+
*/
|
|
152
|
+
export class PostgreSQLReadOnlyPool extends BaseSingleton<PostgreSQLReadOnlyPool> {
|
|
153
|
+
private _pool: Pool | null = null;
|
|
154
|
+
|
|
155
|
+
/** Use {@link PostgreSQLReadOnlyPool.Instance}. */
|
|
156
|
+
public constructor() {
|
|
157
|
+
super();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public static get Instance(): PostgreSQLReadOnlyPool {
|
|
161
|
+
return PostgreSQLReadOnlyPool.getInstance<PostgreSQLReadOnlyPool>();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** The read-only pool, or `null` when no read-only login is configured. */
|
|
165
|
+
public get Pool(): Pool | null {
|
|
166
|
+
return this._pool;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public set Pool(pool: Pool | null) {
|
|
170
|
+
this._pool = pool;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** What {@link DescribeReadOnlyLoginOverreach} reads about the read-only login. */
|
|
175
|
+
export interface ReadOnlyLoginGrants {
|
|
176
|
+
ReadsServerFiles: boolean;
|
|
177
|
+
ReadableBaseTables: number;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** The one method {@link DescribeReadOnlyLoginOverreach} needs from a connection; a `pg` client has it. */
|
|
181
|
+
export interface ReadOnlyLoginCheckClient {
|
|
182
|
+
query(sql: string, params: unknown[]): Promise<{ rows: ReadOnlyLoginGrants[] }>; // case-violation-ok-legacy-back-compat: mirrors the pg client's query method, which a pg client must satisfy as-is
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* What the PostgreSQL read-only login can do beyond reading entity base views, as warnings to log at
|
|
187
|
+
* startup. Ad-hoc SQL and `TestQuerySQL` run as this login, and SQL can reach data through functions
|
|
188
|
+
* that no check of its table references sees (`query_to_xml`, file readers), so the login's own
|
|
189
|
+
* grants are the only complete limit. It should hold `SELECT` on the entity base views and nothing
|
|
190
|
+
* else: not on the base tables, and not membership of `pg_read_server_files`. Empty when the login
|
|
191
|
+
* is that narrow.
|
|
192
|
+
*
|
|
193
|
+
* @param client - A connection opened as the read-only login.
|
|
194
|
+
* @param coreSchema - The MJ core schema, whose base tables the login should not read.
|
|
195
|
+
*/
|
|
196
|
+
export async function DescribeReadOnlyLoginOverreach(client: ReadOnlyLoginCheckClient, coreSchema: string): Promise<string[]> {
|
|
197
|
+
const result = await client.query(
|
|
198
|
+
`SELECT pg_has_role(current_user, 'pg_read_server_files', 'MEMBER') AS "ReadsServerFiles",
|
|
199
|
+
(SELECT count(*)::int FROM pg_catalog.pg_tables t
|
|
200
|
+
WHERE t.schemaname = $1
|
|
201
|
+
AND has_table_privilege(current_user, quote_ident(t.schemaname) || '.' || quote_ident(t.tablename), 'SELECT')) AS "ReadableBaseTables"`,
|
|
202
|
+
[coreSchema],
|
|
203
|
+
);
|
|
204
|
+
const row = result.rows[0];
|
|
205
|
+
const warnings: string[] = [];
|
|
206
|
+
if (row?.ReadableBaseTables > 0) {
|
|
207
|
+
warnings.push(`The PostgreSQL read-only login can read ${row.ReadableBaseTables} base table(s) in schema "${coreSchema}". Ad-hoc SQL and TestQuerySQL run as this login; grant it SELECT on the entity base views only.`);
|
|
208
|
+
}
|
|
209
|
+
if (row?.ReadsServerFiles) {
|
|
210
|
+
warnings.push('The PostgreSQL read-only login is a member of pg_read_server_files, so SQL run as it can read files on the database server. Revoke that membership.');
|
|
211
|
+
}
|
|
212
|
+
return warnings;
|
|
213
|
+
}
|
|
@@ -85,8 +85,10 @@ export async function resolveWidgetGuestRunContext(
|
|
|
85
85
|
* A scoped anonymous session (`IsMagicLinkAnonymous` + `MagicLinkScope.ResourceID`) holds only the
|
|
86
86
|
* narrow relay grants its invite's role carries — deliberately NOT the AI run entities, whose rows
|
|
87
87
|
* leak the rendered system prompt. Unlike {@link resolveWidgetGuestRunContext} no widget instance is
|
|
88
|
-
* required:
|
|
89
|
-
*
|
|
88
|
+
* required: the realtime target is the session config's `targetAgentID`, which the owner can edit, so
|
|
89
|
+
* the relay re-checks `CanRun` on it for the CALLER before this elevation. Direct actions are refused
|
|
90
|
+
* for these callers before elevation, so only the delegated agent run and the run-entity writes
|
|
91
|
+
* execute as the system user.
|
|
90
92
|
*
|
|
91
93
|
* PUBLIC WEB-WIDGET guests are deliberately EXCLUDED (returned unchanged): their seeded role writes
|
|
92
94
|
* run rows under the guest principal, which the `Widget Guest: Own Agent Runs` RLS read filter
|