@memberjunction/server 5.38.0 → 5.39.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/README.md +67 -0
- package/dist/apolloServer/index.d.ts +0 -8
- package/dist/apolloServer/index.d.ts.map +1 -1
- package/dist/apolloServer/index.js +61 -0
- package/dist/apolloServer/index.js.map +1 -1
- package/dist/config.d.ts +68 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +24 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +2 -21
- package/dist/context.js.map +1 -1
- package/dist/generated/generated.d.ts +75 -0
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +314 -1
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/FireAndForgetHeartbeat.d.ts +51 -0
- package/dist/generic/FireAndForgetHeartbeat.d.ts.map +1 -0
- package/dist/generic/FireAndForgetHeartbeat.js +44 -0
- package/dist/generic/FireAndForgetHeartbeat.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +120 -2
- package/dist/index.js.map +1 -1
- package/dist/logging/NoLog.d.ts +50 -0
- package/dist/logging/NoLog.d.ts.map +1 -0
- package/dist/logging/NoLog.js +80 -0
- package/dist/logging/NoLog.js.map +1 -0
- package/dist/logging/bootAudit.d.ts +43 -0
- package/dist/logging/bootAudit.d.ts.map +1 -0
- package/dist/logging/bootAudit.js +83 -0
- package/dist/logging/bootAudit.js.map +1 -0
- package/dist/logging/boundaryLogPayload.d.ts +18 -0
- package/dist/logging/boundaryLogPayload.d.ts.map +1 -0
- package/dist/logging/boundaryLogPayload.js +18 -0
- package/dist/logging/boundaryLogPayload.js.map +1 -0
- package/dist/logging/secretRedactor.d.ts +23 -0
- package/dist/logging/secretRedactor.d.ts.map +1 -0
- package/dist/logging/secretRedactor.js +53 -0
- package/dist/logging/secretRedactor.js.map +1 -0
- package/dist/logging/shortenForLog.d.ts +8 -0
- package/dist/logging/shortenForLog.d.ts.map +1 -0
- package/dist/logging/shortenForLog.js +21 -0
- package/dist/logging/shortenForLog.js.map +1 -0
- package/dist/logging/variablesLoggingMiddleware.d.ts +22 -0
- package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -0
- package/dist/logging/variablesLoggingMiddleware.js +127 -0
- package/dist/logging/variablesLoggingMiddleware.js.map +1 -0
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +8 -4
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +259 -2
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +1276 -117
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/IntegrationProgressResolver.d.ts +90 -0
- package/dist/resolvers/IntegrationProgressResolver.d.ts.map +1 -0
- package/dist/resolvers/IntegrationProgressResolver.js +196 -0
- package/dist/resolvers/IntegrationProgressResolver.js.map +1 -0
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +19 -14
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +26 -5
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTestResolver.js +12 -2
- package/dist/resolvers/RunTestResolver.js.map +1 -1
- package/dist/resolvers/SearchEntitiesResolver.d.ts +46 -0
- package/dist/resolvers/SearchEntitiesResolver.d.ts.map +1 -0
- package/dist/resolvers/SearchEntitiesResolver.js +216 -0
- package/dist/resolvers/SearchEntitiesResolver.js.map +1 -0
- package/dist/resolvers/VectorizeEntityResolver.d.ts.map +1 -1
- package/dist/resolvers/VectorizeEntityResolver.js +14 -1
- package/dist/resolvers/VectorizeEntityResolver.js.map +1 -1
- package/dist/services/ScheduledJobsService.d.ts.map +1 -1
- package/dist/services/ScheduledJobsService.js +14 -2
- package/dist/services/ScheduledJobsService.js.map +1 -1
- package/package.json +75 -74
- package/src/__tests__/NoLog.test.ts +76 -0
- package/src/__tests__/bootAudit.test.ts +188 -0
- package/src/__tests__/boundaryLogPayload.test.ts +31 -0
- package/src/__tests__/getDataTokenRedaction.test.ts +84 -0
- package/src/__tests__/secretRedactor.test.ts +163 -0
- package/src/__tests__/subscriptionRedaction.test.ts +217 -0
- package/src/apolloServer/index.ts +58 -0
- package/src/config.ts +27 -0
- package/src/context.ts +2 -19
- package/src/generated/generated.ts +239 -1
- package/src/generic/FireAndForgetHeartbeat.ts +85 -0
- package/src/generic/__tests__/FireAndForgetHeartbeat.test.ts +99 -0
- package/src/index.ts +137 -2
- package/src/logging/NoLog.ts +88 -0
- package/src/logging/bootAudit.ts +117 -0
- package/src/logging/boundaryLogPayload.ts +19 -0
- package/src/logging/secretRedactor.ts +82 -0
- package/src/logging/shortenForLog.ts +17 -0
- package/src/logging/variablesLoggingMiddleware.ts +191 -0
- package/src/resolvers/GetDataResolver.ts +9 -5
- package/src/resolvers/IntegrationDiscoveryResolver.ts +1111 -120
- package/src/resolvers/IntegrationProgressResolver.ts +220 -0
- package/src/resolvers/MCPResolver.ts +18 -14
- package/src/resolvers/RunAIAgentResolver.ts +28 -5
- package/src/resolvers/RunTestResolver.ts +14 -2
- package/src/resolvers/SearchEntitiesResolver.ts +173 -0
- package/src/resolvers/VectorizeEntityResolver.ts +14 -1
- package/src/resolvers/__tests__/IntegrationProgressResolver.test.ts +170 -0
- package/src/services/ScheduledJobsService.ts +15 -2
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import type { MiddlewareFn } from 'type-graphql';
|
|
2
|
+
import { getMetadataStorage } from 'type-graphql';
|
|
3
|
+
import { getNamedType } from 'graphql';
|
|
4
|
+
import type { GraphQLArgument } from 'graphql';
|
|
5
|
+
import { configInfo } from '../config.js';
|
|
6
|
+
import type { AppContext } from '../types.js';
|
|
7
|
+
import { redactArg } from './secretRedactor.js';
|
|
8
|
+
import { hasNoLogParameter, getNoLogFields } from './NoLog.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Structural shape of a type-graphql `@Arg` parameter metadata entry. Mirrors the
|
|
12
|
+
* subset of `ArgParamMetadata` we read (`kind`, `index`, `name`, `target`, `methodName`)
|
|
13
|
+
* without depending on type-graphql's internal type exports (which are not part of its
|
|
14
|
+
* public API).
|
|
15
|
+
*/
|
|
16
|
+
type MinimalArgParamMeta = {
|
|
17
|
+
kind: 'arg';
|
|
18
|
+
index: number;
|
|
19
|
+
name: string;
|
|
20
|
+
target: Function;
|
|
21
|
+
methodName: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type MinimalParamMeta = MinimalArgParamMeta | { kind: 'args' | 'context' | 'root' | 'info' | 'pubSub' | 'custom'; index: number };
|
|
25
|
+
|
|
26
|
+
type MinimalResolverMeta = {
|
|
27
|
+
schemaName: string;
|
|
28
|
+
target: Function;
|
|
29
|
+
methodName: string;
|
|
30
|
+
params?: MinimalParamMeta[];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type ResolverEntry = {
|
|
34
|
+
target: Function;
|
|
35
|
+
methodName: string;
|
|
36
|
+
params: ReadonlyArray<MinimalParamMeta>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
let resolverLookup: Map<string, ResolverEntry> | undefined;
|
|
40
|
+
|
|
41
|
+
function getResolverLookup(): Map<string, ResolverEntry> {
|
|
42
|
+
if (resolverLookup !== undefined) {
|
|
43
|
+
return resolverLookup;
|
|
44
|
+
}
|
|
45
|
+
const storage = getMetadataStorage() as unknown as {
|
|
46
|
+
queries: MinimalResolverMeta[];
|
|
47
|
+
mutations: MinimalResolverMeta[];
|
|
48
|
+
subscriptions: MinimalResolverMeta[];
|
|
49
|
+
inputTypes: Array<{ name: string; target: Function }>;
|
|
50
|
+
};
|
|
51
|
+
const lookup = new Map<string, ResolverEntry>();
|
|
52
|
+
const collect = (entries: ReadonlyArray<MinimalResolverMeta>): void => {
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
lookup.set(entry.schemaName, {
|
|
55
|
+
target: entry.target,
|
|
56
|
+
methodName: entry.methodName,
|
|
57
|
+
params: entry.params ?? [],
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
collect(storage.queries);
|
|
62
|
+
collect(storage.mutations);
|
|
63
|
+
collect(storage.subscriptions);
|
|
64
|
+
resolverLookup = lookup;
|
|
65
|
+
return lookup;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function findInputClass(typeName: string): Function | undefined {
|
|
69
|
+
const storage = getMetadataStorage() as unknown as { inputTypes: Array<{ name: string; target: Function }> };
|
|
70
|
+
const found = storage.inputTypes.find((t) => t.name === typeName);
|
|
71
|
+
return found?.target;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Type guard for `MinimalArgParamMeta`. */
|
|
75
|
+
function isArgParam(p: MinimalParamMeta): p is MinimalArgParamMeta {
|
|
76
|
+
return p.kind === 'arg';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const warnedResolverSignatures = new Set<string>();
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Variables-logging middleware. Default-off; gated on `loggingSettings.graphql.logVariables`.
|
|
83
|
+
*
|
|
84
|
+
* NOTE: this middleware is NOT the primary leak defense. The primary defense is the removal
|
|
85
|
+
* of the `variables` field from the always-on log line in `context.ts`. This middleware
|
|
86
|
+
* provides an opt-in, redacted verbose-echo path for developers debugging locally.
|
|
87
|
+
*
|
|
88
|
+
* When enabled, fires once per root resolver call (skips field resolvers via
|
|
89
|
+
* `info.path.prev === undefined`). For each `@Arg`:
|
|
90
|
+
* - Derives the GraphQL input type name from the schema (NOT from `args[i].constructor.name`,
|
|
91
|
+
* because type-graphql v2.0.0-beta.3 passes raw plain-object args to middleware).
|
|
92
|
+
* - Looks up `@NoLog` marks (parameter-level on the resolver, field-level on the input class).
|
|
93
|
+
* - Delegates redaction to `redactArg`.
|
|
94
|
+
*
|
|
95
|
+
* Emits a single `console.dir` line with shape `{ operation, args }`. Note: the key names
|
|
96
|
+
* differ from the always-on log line (`operationName` vs `operation`, `variables` vs `args`)
|
|
97
|
+
* — operators with log-grep automation should update their patterns.
|
|
98
|
+
*/
|
|
99
|
+
export const variablesLoggingMiddleware: MiddlewareFn<AppContext> = async (action, next) => {
|
|
100
|
+
// Field resolvers (info.path.prev !== undefined) are skipped — only root @Query/@Mutation/@Subscription.
|
|
101
|
+
if (action.info.path.prev !== undefined) {
|
|
102
|
+
return next();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Hot-path zero-cost short-circuit when the flag is off.
|
|
106
|
+
if (!configInfo.loggingSettings.graphql.logVariables) {
|
|
107
|
+
return next();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const provider = action.context.providers?.[0]?.provider;
|
|
111
|
+
if (!provider) {
|
|
112
|
+
return next();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const fieldDef = action.info.parentType.getFields()[action.info.fieldName];
|
|
116
|
+
if (!fieldDef) {
|
|
117
|
+
return next();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const resolverEntry = getResolverLookup().get(action.info.fieldName);
|
|
121
|
+
const argParams: ReadonlyArray<MinimalArgParamMeta> = (resolverEntry?.params ?? []).filter(isArgParam);
|
|
122
|
+
|
|
123
|
+
const redactedArgs: Record<string, unknown> = {};
|
|
124
|
+
let hasUndecoratedCustomArg = false;
|
|
125
|
+
for (const argDef of fieldDef.args as ReadonlyArray<GraphQLArgument>) {
|
|
126
|
+
const inputTypeName = getNamedType(argDef.type).name;
|
|
127
|
+
const rawValue = action.args[argDef.name];
|
|
128
|
+
|
|
129
|
+
const paramMeta = argParams.find((p) => p.name === argDef.name);
|
|
130
|
+
const noLogParameter = paramMeta && resolverEntry
|
|
131
|
+
? hasNoLogParameter(resolverEntry.target, resolverEntry.methodName, paramMeta.index)
|
|
132
|
+
: false;
|
|
133
|
+
|
|
134
|
+
const inputClass = findInputClass(inputTypeName);
|
|
135
|
+
const noLogFields = inputClass ? getNoLogFields(inputClass) : new Set<string>();
|
|
136
|
+
|
|
137
|
+
redactedArgs[argDef.name] = redactArg({
|
|
138
|
+
inputTypeName,
|
|
139
|
+
rawValue,
|
|
140
|
+
provider,
|
|
141
|
+
noLogParameter,
|
|
142
|
+
noLogFields,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (!noLogParameter && isCustomArg(inputTypeName, noLogFields, provider)) {
|
|
146
|
+
hasUndecoratedCustomArg = true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// eslint-disable-next-line no-console
|
|
151
|
+
console.dir(
|
|
152
|
+
{ operation: action.info.fieldName, args: redactedArgs },
|
|
153
|
+
{ depth: null, breakLength: 200 },
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
if (hasUndecoratedCustomArg && resolverEntry) {
|
|
157
|
+
const signature = `${resolverEntry.target.name}.${resolverEntry.methodName}`;
|
|
158
|
+
if (!warnedResolverSignatures.has(signature)) {
|
|
159
|
+
warnedResolverSignatures.add(signature);
|
|
160
|
+
console.warn(
|
|
161
|
+
`[mj:NoLog] Resolver ${signature} has un-decorated, non-metadata-bound argument(s). ` +
|
|
162
|
+
`Variables for this resolver were logged in plaintext while logVariables=true is active. ` +
|
|
163
|
+
`Apply @NoLog to sensitive arguments to suppress this.`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return next();
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Heuristic: an arg is "custom" (i.e. needs `@NoLog` discipline because metadata can't redact it)
|
|
173
|
+
* when its GraphQL input type is not a known `Create<X>Input` / `Update<X>Input` for an entity
|
|
174
|
+
* in the provider, AND no field-level `@NoLog` marks are present on the input class.
|
|
175
|
+
*/
|
|
176
|
+
function isCustomArg(
|
|
177
|
+
inputTypeName: string,
|
|
178
|
+
noLogFields: ReadonlySet<string>,
|
|
179
|
+
provider: { Entities: ReadonlyArray<{ ClassName: string }> },
|
|
180
|
+
): boolean {
|
|
181
|
+
if (noLogFields.size > 0) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
const match = /^(Create|Update)(?<name>.+)Input$/.exec(inputTypeName);
|
|
185
|
+
if (!match || !match.groups?.name) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
const className = match.groups.name;
|
|
189
|
+
const found = provider.Entities.find((e) => e.ClassName === className);
|
|
190
|
+
return !found;
|
|
191
|
+
}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { Arg, Ctx, Field, InputType, ObjectType, Query } from 'type-graphql';
|
|
2
2
|
import { AppContext } from '../types.js';
|
|
3
|
-
import { LogError, LogStatus, Metadata } from '@memberjunction/core';
|
|
3
|
+
import { LogError, LogStatus, LogStatusEx, Metadata } from '@memberjunction/core';
|
|
4
4
|
import { QueryCompositionEngine } from '@memberjunction/generic-database-provider';
|
|
5
5
|
import { RequireSystemUser } from '../directives/RequireSystemUser.js';
|
|
6
|
+
import { NoLog } from '../logging/NoLog.js';
|
|
6
7
|
import { v4 as uuidv4 } from 'uuid';
|
|
7
8
|
import { GetReadOnlyDataSource, GetReadOnlyProvider } from '../util.js';
|
|
8
9
|
import { getDbType } from '../index.js';
|
|
9
10
|
import { getSystemUser } from '../auth/index.js';
|
|
10
11
|
import sql from 'mssql';
|
|
11
12
|
|
|
12
|
-
@InputType()
|
|
13
|
+
@InputType()
|
|
13
14
|
export class GetDataInputType {
|
|
14
15
|
@Field(() => String)
|
|
16
|
+
@NoLog
|
|
15
17
|
Token: string;
|
|
16
18
|
|
|
17
19
|
@Field(() => [String])
|
|
@@ -116,9 +118,11 @@ export class GetDataResolver {
|
|
|
116
118
|
@Ctx() context: AppContext
|
|
117
119
|
): Promise<GetDataOutputType> {
|
|
118
120
|
try {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
LogStatusEx({
|
|
122
|
+
message: `GetDataResolver.GetData() ---- IMPORTANT - temporarily using the same connection as rest of the server, we need to separately create a READ ONLY CONNECTION and pass that in the AppContext so we can use that special connection here to ensure we are using a lower privileged connection for this operation to prevent mutation from being possible.`,
|
|
123
|
+
verboseOnly: true,
|
|
124
|
+
});
|
|
125
|
+
LogStatus(`GetData invoked: ${input.Queries.length} queries`);
|
|
122
126
|
|
|
123
127
|
// validate the token
|
|
124
128
|
if (!isTokenValid(input.Token)) {
|