@memberjunction/server 6.2.0-edge.7 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -1
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +2 -1
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +6 -8
- package/dist/context.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +839 -0
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +14652 -10253
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +8 -0
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/globalCors.d.ts.map +1 -1
- package/dist/globalCors.js +1 -0
- package/dist/globalCors.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -8
- package/dist/index.js.map +1 -1
- package/dist/logging/StartupLogger.d.ts +50 -0
- package/dist/logging/StartupLogger.d.ts.map +1 -1
- package/dist/logging/StartupLogger.js +15 -22
- package/dist/logging/StartupLogger.js.map +1 -1
- package/dist/middleware/BaseServerMiddleware.d.ts +21 -3
- package/dist/middleware/BaseServerMiddleware.d.ts.map +1 -1
- package/dist/middleware/BaseServerMiddleware.js +4 -2
- package/dist/middleware/BaseServerMiddleware.js.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.d.ts +10 -0
- package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.js +18 -2
- package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
- package/dist/resolvers/AutotagPipelineResolver.d.ts +1 -1
- package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
- package/dist/resolvers/AutotagPipelineResolver.js +59 -15
- package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
- package/dist/resolvers/InfoResolver.js +1 -1
- package/dist/resolvers/InfoResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts +2 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +12 -6
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +7 -8
- package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
- package/dist/resolvers/TagGovernanceResolver.d.ts +12 -0
- package/dist/resolvers/TagGovernanceResolver.d.ts.map +1 -1
- package/dist/resolvers/TagGovernanceResolver.js +84 -0
- package/dist/resolvers/TagGovernanceResolver.js.map +1 -1
- package/dist/resolvers/TelemetryResolver.d.ts +13 -8
- package/dist/resolvers/TelemetryResolver.d.ts.map +1 -1
- package/dist/resolvers/TelemetryResolver.js +40 -9
- package/dist/resolvers/TelemetryResolver.js.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.js +34 -4
- package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +55 -2
- package/dist/util.js.map +1 -1
- package/package.json +107 -100
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +4 -0
- package/src/__tests__/ResolverBase.diagnosticSenderRegistrations.test.ts +26 -0
- package/src/__tests__/RunAIAgentResolver.streaming.test.ts +28 -4
- package/src/__tests__/TagGovernanceAndAutotagResolvers.test.ts +261 -0
- package/src/__tests__/TagGovernanceResolver.test.ts +60 -2
- package/src/__tests__/globalCors.test.ts +12 -0
- package/src/__tests__/review-capture-valid-token-log.test.ts +98 -0
- package/src/__tests__/runStreams.fieldSecurity.test.ts +5 -5
- package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +2 -1
- package/src/context.ts +7 -9
- package/src/generated/graphql-schemas/__mj.ts +9449 -6383
- package/src/generic/ResolverBase.ts +8 -0
- package/src/globalCors.ts +1 -0
- package/src/index.ts +45 -9
- package/src/logging/StartupLogger.ts +24 -0
- package/src/middleware/BaseServerMiddleware.ts +20 -3
- package/src/resolvers/AgentRunStatusPublisher.ts +27 -2
- package/src/resolvers/AutotagPipelineResolver.ts +57 -13
- package/src/resolvers/InfoResolver.ts +1 -1
- package/src/resolvers/QuerySystemUserResolver.ts +19 -7
- package/src/resolvers/SqlLoggingConfigResolver.ts +7 -9
- package/src/resolvers/TagGovernanceResolver.ts +65 -0
- package/src/resolvers/TelemetryResolver.ts +44 -9
- package/src/resolvers/meetings/meetingMutations.ts +37 -7
- package/src/util.ts +55 -3
|
@@ -3,8 +3,11 @@ import { AppContext } from '../types.js';
|
|
|
3
3
|
import {
|
|
4
4
|
TelemetryManager,
|
|
5
5
|
TelemetryCategory,
|
|
6
|
-
TelemetryInsightSeverity
|
|
6
|
+
TelemetryInsightSeverity,
|
|
7
|
+
AuthorizationEvaluator,
|
|
7
8
|
} from '@memberjunction/core';
|
|
9
|
+
import { IsScopeLimitedPrincipal } from '../auth/scopeLimitedPrincipal.js';
|
|
10
|
+
import { GetReadOnlyProvider } from '../util.js';
|
|
8
11
|
|
|
9
12
|
// ============================================================================
|
|
10
13
|
// GraphQL Types for TelemetryEvent
|
|
@@ -318,12 +321,38 @@ export class TelemetryInsightFilterInput {
|
|
|
318
321
|
|
|
319
322
|
@Resolver()
|
|
320
323
|
export class TelemetryResolver {
|
|
324
|
+
/**
|
|
325
|
+
* Enforces that the caller is an authenticated, non-scope-limited user who holds either
|
|
326
|
+
* Owner privileges or the `'Diagnostics: Run Sessions'` authorization.
|
|
327
|
+
*/
|
|
328
|
+
private requireDiagnosticsAccess(context: AppContext): void {
|
|
329
|
+
const user = context?.userPayload?.userRecord;
|
|
330
|
+
if (!user || IsScopeLimitedPrincipal(user)) {
|
|
331
|
+
throw new Error('Access denied: server telemetry requires an authenticated non-scope-limited user.');
|
|
332
|
+
}
|
|
333
|
+
if (user.Type?.trim().toLowerCase() === 'owner') {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const authorizations = GetReadOnlyProvider(context.providers, { allowFallbackToReadWrite: true })?.Authorizations ?? [];
|
|
337
|
+
const diagAuth = authorizations.find(
|
|
338
|
+
(a) => a.Name?.trim().toLowerCase() === 'diagnostics: run sessions'
|
|
339
|
+
);
|
|
340
|
+
if (diagAuth) {
|
|
341
|
+
const evaluator = new AuthorizationEvaluator();
|
|
342
|
+
if (evaluator.UserCanExecuteWithAncestors(diagAuth, user, authorizations)) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
throw new Error('Access denied: server telemetry requires Owner privileges or the "Diagnostics: Run Sessions" authorization.');
|
|
347
|
+
}
|
|
348
|
+
|
|
321
349
|
/**
|
|
322
350
|
* Get telemetry settings
|
|
323
|
-
*
|
|
351
|
+
* Requires Owner privileges or 'Diagnostics: Run Sessions' authorization
|
|
324
352
|
*/
|
|
325
353
|
@Query(() => TelemetrySettingsGQL)
|
|
326
|
-
GetServerTelemetrySettings(@Ctx()
|
|
354
|
+
GetServerTelemetrySettings(@Ctx() context: AppContext): TelemetrySettingsGQL {
|
|
355
|
+
this.requireDiagnosticsAccess(context);
|
|
327
356
|
const tm = TelemetryManager.Instance;
|
|
328
357
|
const settings = tm.Settings;
|
|
329
358
|
|
|
@@ -365,7 +394,8 @@ export class TelemetryResolver {
|
|
|
365
394
|
* Get telemetry statistics
|
|
366
395
|
*/
|
|
367
396
|
@Query(() => TelemetryStatsGQL)
|
|
368
|
-
GetServerTelemetryStats(@Ctx()
|
|
397
|
+
GetServerTelemetryStats(@Ctx() context: AppContext): TelemetryStatsGQL {
|
|
398
|
+
this.requireDiagnosticsAccess(context);
|
|
369
399
|
const tm = TelemetryManager.Instance;
|
|
370
400
|
const stats = tm.GetStats();
|
|
371
401
|
|
|
@@ -391,9 +421,10 @@ export class TelemetryResolver {
|
|
|
391
421
|
*/
|
|
392
422
|
@Query(() => [TelemetryEventGQL])
|
|
393
423
|
GetServerTelemetryEvents(
|
|
394
|
-
@Ctx()
|
|
424
|
+
@Ctx() context: AppContext,
|
|
395
425
|
@Arg('filter', () => TelemetryEventFilterInput, { nullable: true }) filter?: TelemetryEventFilterInput
|
|
396
426
|
): TelemetryEventGQL[] {
|
|
427
|
+
this.requireDiagnosticsAccess(context);
|
|
397
428
|
const tm = TelemetryManager.Instance;
|
|
398
429
|
|
|
399
430
|
const events = tm.GetEvents({
|
|
@@ -427,9 +458,10 @@ export class TelemetryResolver {
|
|
|
427
458
|
*/
|
|
428
459
|
@Query(() => [TelemetryPatternGQL])
|
|
429
460
|
GetServerTelemetryPatterns(
|
|
430
|
-
@Ctx()
|
|
461
|
+
@Ctx() context: AppContext,
|
|
431
462
|
@Arg('filter', () => TelemetryPatternFilterInput, { nullable: true }) filter?: TelemetryPatternFilterInput
|
|
432
463
|
): TelemetryPatternGQL[] {
|
|
464
|
+
this.requireDiagnosticsAccess(context);
|
|
433
465
|
const tm = TelemetryManager.Instance;
|
|
434
466
|
|
|
435
467
|
const patterns = tm.GetPatterns({
|
|
@@ -468,9 +500,10 @@ export class TelemetryResolver {
|
|
|
468
500
|
*/
|
|
469
501
|
@Query(() => [TelemetryInsightGQL])
|
|
470
502
|
GetServerTelemetryInsights(
|
|
471
|
-
@Ctx()
|
|
503
|
+
@Ctx() context: AppContext,
|
|
472
504
|
@Arg('filter', () => TelemetryInsightFilterInput, { nullable: true }) filter?: TelemetryInsightFilterInput
|
|
473
505
|
): TelemetryInsightGQL[] {
|
|
506
|
+
this.requireDiagnosticsAccess(context);
|
|
474
507
|
const tm = TelemetryManager.Instance;
|
|
475
508
|
|
|
476
509
|
const insights = tm.GetInsights({
|
|
@@ -500,9 +533,10 @@ export class TelemetryResolver {
|
|
|
500
533
|
*/
|
|
501
534
|
@Query(() => [TelemetryPatternGQL])
|
|
502
535
|
GetServerTelemetryDuplicates(
|
|
503
|
-
@Ctx()
|
|
536
|
+
@Ctx() context: AppContext,
|
|
504
537
|
@Arg('minCount', () => Int, { defaultValue: 2 }) minCount: number
|
|
505
538
|
): TelemetryPatternGQL[] {
|
|
539
|
+
this.requireDiagnosticsAccess(context);
|
|
506
540
|
const tm = TelemetryManager.Instance;
|
|
507
541
|
const patterns = tm.GetDuplicates(minCount);
|
|
508
542
|
|
|
@@ -534,7 +568,8 @@ export class TelemetryResolver {
|
|
|
534
568
|
* Get currently active (in-progress) events
|
|
535
569
|
*/
|
|
536
570
|
@Query(() => [TelemetryEventGQL])
|
|
537
|
-
GetServerTelemetryActiveEvents(@Ctx()
|
|
571
|
+
GetServerTelemetryActiveEvents(@Ctx() context: AppContext): TelemetryEventGQL[] {
|
|
572
|
+
this.requireDiagnosticsAccess(context);
|
|
538
573
|
const tm = TelemetryManager.Instance;
|
|
539
574
|
const events = tm.GetActiveEvents();
|
|
540
575
|
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
RunView,
|
|
6
6
|
UserInfo,
|
|
7
7
|
} from '@memberjunction/core';
|
|
8
|
-
import { EscapeSQLString, UUIDsEqual } from '@memberjunction/global';
|
|
8
|
+
import { EscapeSQLString, IndexByUUID, UUIDsEqual } from '@memberjunction/global';
|
|
9
9
|
import type {
|
|
10
10
|
MJMeetingEntity,
|
|
11
11
|
MJMeetingParticipantEntity,
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
UpdateMeetingInput,
|
|
26
26
|
RSVPMeetingInput,
|
|
27
27
|
MeetingParticipantType,
|
|
28
|
+
MeetingParticipantInput,
|
|
28
29
|
} from './meetingTypes.js';
|
|
29
30
|
import * as defaultHelpers from './meetingHelpers.js';
|
|
30
31
|
|
|
@@ -34,6 +35,39 @@ function resolveHelpers(overrides?: Partial<MeetingHelpers>): MeetingHelpers {
|
|
|
34
35
|
return { ...defaultHelpers, ...overrides };
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Indexes a meeting's participants by UserID, AgentID and lower-cased ExternalEmail, and returns a
|
|
40
|
+
* lookup that finds the participant an update input refers to. When the input matches different
|
|
41
|
+
* participants on different keys, the one listed first in `existing` wins, as a single
|
|
42
|
+
* `existing.find` over all three keys would answer.
|
|
43
|
+
*/
|
|
44
|
+
function indexParticipants(
|
|
45
|
+
existing: MeetingParticipantType[],
|
|
46
|
+
): (input: MeetingParticipantInput) => MeetingParticipantType | undefined {
|
|
47
|
+
const byUserID = IndexByUUID(existing.filter((e) => e.UserID), (e) => e.UserID);
|
|
48
|
+
const byAgentID = IndexByUUID(existing.filter((e) => e.AgentID), (e) => e.AgentID);
|
|
49
|
+
const byEmail = new Map<string, MeetingParticipantType>();
|
|
50
|
+
const position = new Map<MeetingParticipantType, number>();
|
|
51
|
+
existing.forEach((e, i) => {
|
|
52
|
+
position.set(e, i);
|
|
53
|
+
const email = e.ExternalEmail?.toLowerCase();
|
|
54
|
+
if (email && !byEmail.has(email)) byEmail.set(email, e);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return (input) => {
|
|
58
|
+
const candidates = [
|
|
59
|
+
input.UserID ? byUserID.Get(input.UserID) : undefined,
|
|
60
|
+
input.AgentID ? byAgentID.Get(input.AgentID) : undefined,
|
|
61
|
+
input.ExternalEmail ? byEmail.get(input.ExternalEmail.toLowerCase()) : undefined,
|
|
62
|
+
];
|
|
63
|
+
let first: MeetingParticipantType | undefined;
|
|
64
|
+
for (const candidate of candidates) {
|
|
65
|
+
if (candidate && (!first || (position.get(candidate) ?? 0) < (position.get(first) ?? 0))) first = candidate;
|
|
66
|
+
}
|
|
67
|
+
return first;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
37
71
|
export async function ExecuteCreateMeeting(
|
|
38
72
|
provider: IMetadataProvider,
|
|
39
73
|
user: UserInfo,
|
|
@@ -235,15 +269,11 @@ export async function ExecuteUpdateMeeting(
|
|
|
235
269
|
// If participants specified in update, sync participants (upsert by UserID/AgentID/ExternalEmail)
|
|
236
270
|
if (input.Participants !== undefined) {
|
|
237
271
|
const existing = await h.LoadParticipants(meeting.ID, user, provider);
|
|
272
|
+
const findExisting = indexParticipants(existing);
|
|
238
273
|
|
|
239
274
|
for (const pInput of input.Participants) {
|
|
240
275
|
const isSelf = pInput.UserID && UUIDsEqual(pInput.UserID, user.ID);
|
|
241
|
-
const match =
|
|
242
|
-
(e) =>
|
|
243
|
-
(pInput.UserID && e.UserID && UUIDsEqual(e.UserID, pInput.UserID)) ||
|
|
244
|
-
(pInput.AgentID && e.AgentID && UUIDsEqual(e.AgentID, pInput.AgentID)) ||
|
|
245
|
-
(pInput.ExternalEmail && e.ExternalEmail && e.ExternalEmail.toLowerCase() === pInput.ExternalEmail.toLowerCase()),
|
|
246
|
-
);
|
|
276
|
+
const match = findExisting(pInput);
|
|
247
277
|
|
|
248
278
|
if (match) {
|
|
249
279
|
if (!isSelf && pInput.Role) {
|
package/src/util.ts
CHANGED
|
@@ -6,7 +6,8 @@ import { URL } from 'url';
|
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import { DataSourceInfo, ProviderInfo } from './types';
|
|
8
8
|
import sql from 'mssql';
|
|
9
|
-
import { DatabaseProviderBase } from '@memberjunction/core';
|
|
9
|
+
import { DatabaseProviderBase, GetDiagnosticContext, IsDiagnosticCaptureSuppressed } from '@memberjunction/core';
|
|
10
|
+
import { GetGlobalObjectStore } from '@memberjunction/global';
|
|
10
11
|
|
|
11
12
|
const gzip = promisify(gzipCallback);
|
|
12
13
|
|
|
@@ -249,8 +250,59 @@ export async function sendPostRequest(url: string, payload: any, useCompression:
|
|
|
249
250
|
sqlQuery = sqlQuery.replace(/\?/g, () => `@p${paramIndex++}`);
|
|
250
251
|
}
|
|
251
252
|
}
|
|
252
|
-
const
|
|
253
|
-
|
|
253
|
+
const startTime = performance.now();
|
|
254
|
+
const notifyPoolSqlObservers = (rowCount: number, errorMessage?: string): void => {
|
|
255
|
+
if (typeof IsDiagnosticCaptureSuppressed === 'function' && IsDiagnosticCaptureSuppressed()) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const observers = GetGlobalObjectStore()?.['___MJ_SQL_OBSERVERS___'] as
|
|
259
|
+
| Set<{ onSqlExecuted: (obs: Record<string, unknown>) => void }>
|
|
260
|
+
| undefined;
|
|
261
|
+
if (!observers || observers.size === 0) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const ctx = typeof GetDiagnosticContext === 'function' ? GetDiagnosticContext() : undefined;
|
|
265
|
+
const obs: Record<string, unknown> = {
|
|
266
|
+
ts: Date.now(),
|
|
267
|
+
sql: sqlQuery,
|
|
268
|
+
parameterTypes: Array.isArray(parameters)
|
|
269
|
+
? parameters.map((v, idx) => ({
|
|
270
|
+
name: `p${idx}`,
|
|
271
|
+
sqlType: v === null ? 'null' : typeof v,
|
|
272
|
+
byteLength: typeof v === 'string' ? v.length : 0,
|
|
273
|
+
}))
|
|
274
|
+
: [],
|
|
275
|
+
durationMs: performance.now() - startTime,
|
|
276
|
+
rowCount,
|
|
277
|
+
isMutation: false,
|
|
278
|
+
description: 'ConnectionPool.query',
|
|
279
|
+
...(errorMessage ? { errorMessage } : {}),
|
|
280
|
+
...(ctx
|
|
281
|
+
? {
|
|
282
|
+
diagnosticContext: ctx,
|
|
283
|
+
sessionId: ctx.sessionId ?? ctx.SessionID,
|
|
284
|
+
traceId: ctx.traceId ?? ctx.TraceID,
|
|
285
|
+
spanId: ctx.spanId ?? ctx.SpanID,
|
|
286
|
+
}
|
|
287
|
+
: {}),
|
|
288
|
+
};
|
|
289
|
+
for (const observer of observers) {
|
|
290
|
+
try {
|
|
291
|
+
observer.onSqlExecuted(obs);
|
|
292
|
+
} catch {
|
|
293
|
+
// Observer errors must never affect SQL execution
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
try {
|
|
298
|
+
const result = await request.query(sqlQuery);
|
|
299
|
+
const rows = result.recordset || [];
|
|
300
|
+
notifyPoolSqlObservers(rows.length);
|
|
301
|
+
return rows;
|
|
302
|
+
} catch (err) {
|
|
303
|
+
notifyPoolSqlObservers(0, err instanceof Error ? err.message : String(err));
|
|
304
|
+
throw err;
|
|
305
|
+
}
|
|
254
306
|
};
|
|
255
307
|
return extendedPool;
|
|
256
308
|
}
|