@memberjunction/server 3.2.0 → 3.4.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.
Files changed (127) hide show
  1. package/README.md +106 -1
  2. package/dist/auth/APIKeyScopeAuth.d.ts +51 -0
  3. package/dist/auth/APIKeyScopeAuth.d.ts.map +1 -0
  4. package/dist/auth/APIKeyScopeAuth.js +163 -0
  5. package/dist/auth/APIKeyScopeAuth.js.map +1 -0
  6. package/dist/auth/BaseAuthProvider.d.ts +1 -0
  7. package/dist/auth/BaseAuthProvider.d.ts.map +1 -1
  8. package/dist/auth/BaseAuthProvider.js +2 -0
  9. package/dist/auth/BaseAuthProvider.js.map +1 -1
  10. package/dist/auth/IAuthProvider.d.ts +1 -0
  11. package/dist/auth/IAuthProvider.d.ts.map +1 -1
  12. package/dist/auth/index.d.ts +1 -0
  13. package/dist/auth/index.d.ts.map +1 -1
  14. package/dist/auth/index.js +1 -0
  15. package/dist/auth/index.js.map +1 -1
  16. package/dist/config.js +2 -2
  17. package/dist/config.js.map +1 -1
  18. package/dist/context.d.ts +8 -1
  19. package/dist/context.d.ts.map +1 -1
  20. package/dist/context.js +44 -7
  21. package/dist/context.js.map +1 -1
  22. package/dist/generated/generated.d.ts +681 -2
  23. package/dist/generated/generated.d.ts.map +1 -1
  24. package/dist/generated/generated.js +10627 -6409
  25. package/dist/generated/generated.js.map +1 -1
  26. package/dist/generic/ResolverBase.d.ts +3 -2
  27. package/dist/generic/ResolverBase.d.ts.map +1 -1
  28. package/dist/generic/ResolverBase.js +52 -4
  29. package/dist/generic/ResolverBase.js.map +1 -1
  30. package/dist/generic/RunViewResolver.d.ts +29 -1
  31. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  32. package/dist/generic/RunViewResolver.js +143 -0
  33. package/dist/generic/RunViewResolver.js.map +1 -1
  34. package/dist/index.d.ts +4 -2
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +4 -2
  37. package/dist/index.js.map +1 -1
  38. package/dist/resolvers/APIKeyResolver.d.ts +24 -0
  39. package/dist/resolvers/APIKeyResolver.d.ts.map +1 -0
  40. package/dist/resolvers/APIKeyResolver.js +194 -0
  41. package/dist/resolvers/APIKeyResolver.js.map +1 -0
  42. package/dist/resolvers/ActionResolver.d.ts +2 -1
  43. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  44. package/dist/resolvers/ActionResolver.js +4 -1
  45. package/dist/resolvers/ActionResolver.js.map +1 -1
  46. package/dist/resolvers/DatasetResolver.d.ts +5 -4
  47. package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
  48. package/dist/resolvers/DatasetResolver.js +7 -4
  49. package/dist/resolvers/DatasetResolver.js.map +1 -1
  50. package/dist/resolvers/EntityCommunicationsResolver.d.ts +2 -1
  51. package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
  52. package/dist/resolvers/EntityCommunicationsResolver.js +3 -1
  53. package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
  54. package/dist/resolvers/GetDataContextDataResolver.d.ts +2 -1
  55. package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
  56. package/dist/resolvers/GetDataContextDataResolver.js +10 -3
  57. package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
  58. package/dist/resolvers/MCPResolver.d.ts +37 -0
  59. package/dist/resolvers/MCPResolver.d.ts.map +1 -0
  60. package/dist/resolvers/MCPResolver.js +363 -0
  61. package/dist/resolvers/MCPResolver.js.map +1 -0
  62. package/dist/resolvers/MergeRecordsResolver.d.ts +2 -1
  63. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  64. package/dist/resolvers/MergeRecordsResolver.js +3 -1
  65. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  66. package/dist/resolvers/QueryResolver.d.ts +2 -1
  67. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  68. package/dist/resolvers/QueryResolver.js +6 -1
  69. package/dist/resolvers/QueryResolver.js.map +1 -1
  70. package/dist/resolvers/ReportResolver.d.ts +2 -1
  71. package/dist/resolvers/ReportResolver.d.ts.map +1 -1
  72. package/dist/resolvers/ReportResolver.js +4 -1
  73. package/dist/resolvers/ReportResolver.js.map +1 -1
  74. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  75. package/dist/resolvers/RunAIAgentResolver.js +3 -1
  76. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  77. package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
  78. package/dist/resolvers/RunAIPromptResolver.js +3 -0
  79. package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
  80. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  81. package/dist/resolvers/RunTemplateResolver.js +1 -0
  82. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  83. package/dist/resolvers/TaskResolver.d.ts.map +1 -1
  84. package/dist/resolvers/TaskResolver.js +1 -0
  85. package/dist/resolvers/TaskResolver.js.map +1 -1
  86. package/dist/resolvers/UserResolver.d.ts.map +1 -1
  87. package/dist/resolvers/UserResolver.js +35 -1
  88. package/dist/resolvers/UserResolver.js.map +1 -1
  89. package/dist/types.d.ts +4 -1
  90. package/dist/types.d.ts.map +1 -1
  91. package/dist/types.js.map +1 -1
  92. package/package.json +47 -45
  93. package/src/auth/APIKeyScopeAuth.ts +366 -0
  94. package/src/auth/BaseAuthProvider.ts +3 -0
  95. package/src/auth/IAuthProvider.ts +5 -0
  96. package/src/auth/index.ts +1 -0
  97. package/src/config.ts +2 -2
  98. package/src/context.ts +91 -9
  99. package/src/generated/generated.ts +6327 -3668
  100. package/src/generic/ResolverBase.ts +127 -8
  101. package/src/generic/RunViewResolver.ts +132 -5
  102. package/src/index.ts +12 -2
  103. package/src/resolvers/APIKeyResolver.ts +241 -0
  104. package/src/resolvers/ActionResolver.ts +8 -1
  105. package/src/resolvers/DatasetResolver.ts +11 -4
  106. package/src/resolvers/EntityCommunicationsResolver.ts +5 -1
  107. package/src/resolvers/GetDataContextDataResolver.ts +14 -6
  108. package/src/resolvers/MCPResolver.ts +480 -0
  109. package/src/resolvers/MergeRecordsResolver.ts +5 -1
  110. package/src/resolvers/QueryResolver.ts +17 -3
  111. package/src/resolvers/ReportResolver.ts +8 -1
  112. package/src/resolvers/RunAIAgentResolver.ts +7 -1
  113. package/src/resolvers/RunAIPromptResolver.ts +10 -1
  114. package/src/resolvers/RunTemplateResolver.ts +4 -1
  115. package/src/resolvers/TaskResolver.ts +3 -0
  116. package/src/resolvers/UserResolver.ts +52 -4
  117. package/src/types.ts +7 -2
  118. package/dist/resolvers/AskSkipResolver.d.ts +0 -123
  119. package/dist/resolvers/AskSkipResolver.d.ts.map +0 -1
  120. package/dist/resolvers/AskSkipResolver.js +0 -1788
  121. package/dist/resolvers/AskSkipResolver.js.map +0 -1
  122. package/dist/scheduler/LearningCycleScheduler.d.ts +0 -4
  123. package/dist/scheduler/LearningCycleScheduler.d.ts.map +0 -1
  124. package/dist/scheduler/LearningCycleScheduler.js +0 -4
  125. package/dist/scheduler/LearningCycleScheduler.js.map +0 -1
  126. package/src/resolvers/AskSkipResolver.ts +0 -3446
  127. package/src/scheduler/LearningCycleScheduler.ts +0 -320
@@ -0,0 +1,241 @@
1
+ import { Resolver, Mutation, Arg, Ctx } from "type-graphql";
2
+ import { Field, InputType, ObjectType } from "type-graphql";
3
+ import { LogError, Metadata } from "@memberjunction/core";
4
+ import { APIKeyScopeEntity } from "@memberjunction/core-entities";
5
+ import { GetAPIKeyEngine } from "@memberjunction/api-keys";
6
+ import { AppContext } from "../types.js";
7
+ import { ResolverBase } from "../generic/ResolverBase.js";
8
+
9
+ /**
10
+ * Input type for creating a new API key
11
+ */
12
+ @InputType()
13
+ export class CreateAPIKeyInput {
14
+ /**
15
+ * Human-readable label for the API key
16
+ */
17
+ @Field()
18
+ Label: string;
19
+
20
+ /**
21
+ * Optional description of what the key is used for
22
+ */
23
+ @Field(() => String, { nullable: true })
24
+ Description?: string;
25
+
26
+ /**
27
+ * Optional expiration date for the key
28
+ */
29
+ @Field(() => Date, { nullable: true })
30
+ ExpiresAt?: Date;
31
+
32
+ /**
33
+ * Optional array of scope IDs to assign to this key
34
+ */
35
+ @Field(() => [String], { nullable: true })
36
+ ScopeIDs?: string[];
37
+ }
38
+
39
+ /**
40
+ * Result type for API key creation
41
+ * Returns the raw key ONCE - it cannot be recovered after this
42
+ */
43
+ @ObjectType()
44
+ export class CreateAPIKeyResult {
45
+ /**
46
+ * Whether the key was created successfully
47
+ */
48
+ @Field()
49
+ Success: boolean;
50
+
51
+ /**
52
+ * The raw API key - show this to the user ONCE
53
+ * This cannot be recovered after the initial response
54
+ */
55
+ @Field(() => String, { nullable: true })
56
+ RawKey?: string;
57
+
58
+ /**
59
+ * The database ID of the created API key
60
+ */
61
+ @Field(() => String, { nullable: true })
62
+ APIKeyID?: string;
63
+
64
+ /**
65
+ * Error message if creation failed
66
+ */
67
+ @Field(() => String, { nullable: true })
68
+ Error?: string;
69
+ }
70
+
71
+ /**
72
+ * Result type for API key revocation
73
+ */
74
+ @ObjectType()
75
+ export class RevokeAPIKeyResult {
76
+ /**
77
+ * Whether the key was revoked successfully
78
+ */
79
+ @Field()
80
+ Success: boolean;
81
+
82
+ /**
83
+ * Error message if revocation failed
84
+ */
85
+ @Field(() => String, { nullable: true })
86
+ Error?: string;
87
+ }
88
+
89
+ /**
90
+ * Resolver for API key operations
91
+ * Handles secure server-side API key generation
92
+ */
93
+ @Resolver()
94
+ export class APIKeyResolver extends ResolverBase {
95
+ /**
96
+ * Creates a new API key with proper server-side cryptographic hashing.
97
+ *
98
+ * This mutation:
99
+ * 1. Generates a cryptographically secure API key using APIKeyEngine
100
+ * 2. Stores only the SHA-256 hash in the database (never the raw key)
101
+ * 3. Returns the raw key ONCE - it cannot be recovered after this call
102
+ * 4. Optionally assigns scope permissions to the key
103
+ *
104
+ * @param input The creation parameters
105
+ * @param ctx The GraphQL context with authenticated user
106
+ * @returns The raw key (show once!) and database ID
107
+ */
108
+ @Mutation(() => CreateAPIKeyResult)
109
+ async CreateAPIKey(
110
+ @Arg("input") input: CreateAPIKeyInput,
111
+ @Ctx() ctx: AppContext
112
+ ): Promise<CreateAPIKeyResult> {
113
+ // Check API key scope authorization for API key creation
114
+ await this.CheckAPIKeyScopeAuthorization('apikey:create', '*', ctx.userPayload);
115
+
116
+ try {
117
+ // Get the authenticated user
118
+ const user = ctx.userPayload.userRecord;
119
+ if (!user) {
120
+ return {
121
+ Success: false,
122
+ Error: "User is not authenticated"
123
+ };
124
+ }
125
+
126
+ // Use APIKeyEngine to create the API key with proper server-side crypto
127
+ const apiKeyEngine = GetAPIKeyEngine();
128
+ const result = await apiKeyEngine.CreateAPIKey(
129
+ {
130
+ UserId: user.ID,
131
+ Label: input.Label,
132
+ Description: input.Description,
133
+ ExpiresAt: input.ExpiresAt
134
+ },
135
+ user
136
+ );
137
+
138
+ if (!result.Success) {
139
+ return {
140
+ Success: false,
141
+ Error: result.Error || "Failed to create API key"
142
+ };
143
+ }
144
+
145
+ // Save scope associations if provided
146
+ if (input.ScopeIDs && input.ScopeIDs.length > 0 && result.APIKeyId) {
147
+ await this.saveScopeAssociations(result.APIKeyId, input.ScopeIDs, user);
148
+ }
149
+
150
+ return {
151
+ Success: true,
152
+ RawKey: result.RawKey,
153
+ APIKeyID: result.APIKeyId
154
+ };
155
+ } catch (e) {
156
+ const error = e as Error;
157
+ LogError(`Error in CreateAPIKey resolver: ${error.message}`);
158
+ return {
159
+ Success: false,
160
+ Error: `Error creating API key: ${error.message}`
161
+ };
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Revokes an API key, permanently disabling it.
167
+ *
168
+ * Once revoked, an API key cannot be reactivated. Users must create a new key.
169
+ * This uses APIKeyEngine.RevokeAPIKey() for consistency.
170
+ *
171
+ * @param apiKeyId The database ID of the API key to revoke
172
+ * @param ctx The GraphQL context with authenticated user
173
+ * @returns Success status
174
+ */
175
+ @Mutation(() => RevokeAPIKeyResult)
176
+ async RevokeAPIKey(
177
+ @Arg("apiKeyId") apiKeyId: string,
178
+ @Ctx() ctx: AppContext
179
+ ): Promise<RevokeAPIKeyResult> {
180
+ // Check API key scope authorization for API key revocation
181
+ await this.CheckAPIKeyScopeAuthorization('apikey:revoke', apiKeyId, ctx.userPayload);
182
+
183
+ try {
184
+ const user = ctx.userPayload.userRecord;
185
+ if (!user) {
186
+ return {
187
+ Success: false,
188
+ Error: "User is not authenticated"
189
+ };
190
+ }
191
+
192
+ const apiKeyEngine = GetAPIKeyEngine();
193
+ const result = await apiKeyEngine.RevokeAPIKey(apiKeyId, user);
194
+
195
+ if (result) {
196
+ return { Success: true };
197
+ } else {
198
+ return {
199
+ Success: false,
200
+ Error: "Failed to revoke API key. It may not exist or you may not have permission."
201
+ };
202
+ }
203
+ } catch (e) {
204
+ const error = e as Error;
205
+ LogError(`Error in RevokeAPIKey resolver: ${error.message}`);
206
+ return {
207
+ Success: false,
208
+ Error: `Error revoking API key: ${error.message}`
209
+ };
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Saves scope associations for the newly created API key
215
+ * @param apiKeyId The ID of the created API key
216
+ * @param scopeIds Array of scope IDs to associate
217
+ * @param user The context user
218
+ */
219
+ private async saveScopeAssociations(
220
+ apiKeyId: string,
221
+ scopeIds: string[],
222
+ user: any
223
+ ): Promise<void> {
224
+ const md = new Metadata();
225
+
226
+ for (const scopeId of scopeIds) {
227
+ try {
228
+ const keyScope = await md.GetEntityObject<APIKeyScopeEntity>(
229
+ 'MJ: API Key Scopes',
230
+ user
231
+ );
232
+ keyScope.APIKeyID = apiKeyId;
233
+ keyScope.ScopeID = scopeId;
234
+ await keyScope.Save();
235
+ } catch (error) {
236
+ LogError(`Error saving scope association for API key ${apiKeyId}, scope ${scopeId}: ${error}`);
237
+ // Continue with other scopes even if one fails
238
+ }
239
+ }
240
+ }
241
+ }
@@ -8,6 +8,7 @@ import { KeyValuePairInput } from "../generic/KeyValuePairInput.js";
8
8
  import { AppContext, ProviderInfo } from "../types.js";
9
9
  import { CopyScalarsAndArrays } from "@memberjunction/global";
10
10
  import { GetReadOnlyProvider } from "../util.js";
11
+ import { ResolverBase } from "../generic/ResolverBase.js";
11
12
 
12
13
  /**
13
14
  * Input type for action parameters
@@ -171,7 +172,7 @@ export class ActionResultOutput {
171
172
  * Handles running actions and entity actions through GraphQL
172
173
  */
173
174
  @Resolver()
174
- export class ActionResolver {
175
+ export class ActionResolver extends ResolverBase {
175
176
  /**
176
177
  * Mutation for running an action
177
178
  * @param input The input parameters for running the action
@@ -184,6 +185,9 @@ export class ActionResolver {
184
185
  @Ctx() ctx: AppContext
185
186
  ): Promise<ActionResultOutput> {
186
187
  try {
188
+ // Check API key scope authorization for action execution
189
+ await this.CheckAPIKeyScopeAuthorization('action:execute', input.ActionID, ctx.userPayload);
190
+
187
191
  // Get the user from context
188
192
  const user = ctx.userPayload.userRecord;
189
193
  if (!user) {
@@ -326,6 +330,9 @@ export class ActionResolver {
326
330
  @Ctx() ctx: AppContext
327
331
  ): Promise<ActionResultOutput> {
328
332
  try {
333
+ // Check API key scope authorization for entity action execution
334
+ await this.CheckAPIKeyScopeAuthorization('action:execute', input.EntityActionID, ctx.userPayload);
335
+
329
336
  const user = ctx.userPayload.userRecord;
330
337
  if (!user) {
331
338
  throw new Error("User is not authenticated");
@@ -2,6 +2,7 @@ import { Arg, Ctx, Field, InputType, Int, ObjectType, Query, Resolver } from 'ty
2
2
  import { AppContext } from '../types.js';
3
3
  import { LogError, Metadata } from '@memberjunction/core';
4
4
  import { GetReadOnlyProvider } from '../util.js';
5
+ import { ResolverBase } from '../generic/ResolverBase.js';
5
6
 
6
7
  @ObjectType()
7
8
  export class DatasetResultType {
@@ -35,13 +36,16 @@ export class DatasetItemFilterTypeGQL {
35
36
 
36
37
 
37
38
  @Resolver(DatasetResultType)
38
- export class DatasetResolverExtended {
39
+ export class DatasetResolverExtended extends ResolverBase {
39
40
  @Query(() => DatasetResultType)
40
41
  async GetDatasetByName(
41
42
  @Arg('DatasetName', () => String) DatasetName: string,
42
- @Ctx() {providers}: AppContext,
43
+ @Ctx() { providers, userPayload }: AppContext,
43
44
  @Arg('ItemFilters', () => [DatasetItemFilterTypeGQL], { nullable: 'itemsAndList' }) ItemFilters?: DatasetItemFilterTypeGQL[]
44
45
  ) {
46
+ // Check API key scope authorization for dataset read
47
+ await this.CheckAPIKeyScopeAuthorization('dataset:read', DatasetName, userPayload);
48
+
45
49
  try {
46
50
  const md = GetReadOnlyProvider(providers, {allowFallbackToReadWrite: true});
47
51
  const result = await md.GetDatasetByName(DatasetName, ItemFilters);
@@ -86,13 +90,16 @@ export class DatasetStatusResultType {
86
90
  }
87
91
 
88
92
  @Resolver(DatasetStatusResultType)
89
- export class DatasetStatusResolver {
93
+ export class DatasetStatusResolver extends ResolverBase {
90
94
  @Query(() => DatasetStatusResultType)
91
95
  async GetDatasetStatusByName(
92
96
  @Arg('DatasetName', () => String) DatasetName: string,
93
- @Ctx() {providers}: AppContext,
97
+ @Ctx() { providers, userPayload }: AppContext,
94
98
  @Arg('ItemFilters', () => [DatasetItemFilterTypeGQL], { nullable: 'itemsAndList' }) ItemFilters?: DatasetItemFilterTypeGQL[]
95
99
  ) {
100
+ // Check API key scope authorization for dataset read
101
+ await this.CheckAPIKeyScopeAuthorization('dataset:read', DatasetName, userPayload);
102
+
96
103
  try {
97
104
  const md = GetReadOnlyProvider(providers, {allowFallbackToReadWrite: true});
98
105
  const result = await md.GetDatasetStatusByName(DatasetName, ItemFilters);
@@ -8,6 +8,7 @@ import { GraphQLJSONObject } from 'graphql-type-json';
8
8
  import { TemplateEngineServer } from '@memberjunction/templates';
9
9
  import { EntityCommunicationParams } from '@memberjunction/entity-communications-base';
10
10
  import { z } from 'zod';
11
+ import { ResolverBase } from '../generic/ResolverBase.js';
11
12
 
12
13
  @InputType()
13
14
  export class CommunicationProviderMessageType {
@@ -166,7 +167,7 @@ export class RunEntityCommunicationResultType {
166
167
  }
167
168
 
168
169
  @Resolver(RunEntityCommunicationResultType)
169
- export class ReportResolver {
170
+ export class ReportResolver extends ResolverBase {
170
171
  @Query(() => RunEntityCommunicationResultType)
171
172
  async RunEntityCommunicationByViewID(
172
173
  @Arg('entityID', () => String) entityID: string,
@@ -178,6 +179,9 @@ export class ReportResolver {
178
179
  @Arg('includeProcessedMessages', () => Boolean) includeProcessedMessages: boolean,
179
180
  @Ctx() { userPayload }: AppContext
180
181
  ): Promise<RunEntityCommunicationResultType> {
182
+ // Check API key scope authorization for communication send
183
+ await this.CheckAPIKeyScopeAuthorization('communication:send', entityID, userPayload);
184
+
181
185
  try {
182
186
  await EntityCommunicationsEngine.Instance.Config(false, userPayload.userRecord);
183
187
  const newMessage = new Message(message as unknown as Message);
@@ -1,9 +1,10 @@
1
- import { Arg, Ctx, Field, ObjectType, Query } from "type-graphql";
1
+ import { Arg, Ctx, Field, ObjectType, Query, Resolver } from "type-graphql";
2
2
  import { AppContext } from "../types.js";
3
3
  import { DataContext } from "@memberjunction/data-context";
4
4
  import { GetReadOnlyDataSource, GetReadOnlyProvider } from "../util.js";
5
5
  import { Metadata } from "@memberjunction/core";
6
6
  import { DataContextItemEntity } from "@memberjunction/core-entities";
7
+ import { ResolverBase } from "../generic/ResolverBase.js";
7
8
 
8
9
  @ObjectType()
9
10
  export class GetDataContextItemDataOutputType {
@@ -39,16 +40,20 @@ export class GetDataContextDataOutputType {
39
40
  }
40
41
 
41
42
 
42
- export class GetDataContextDataResolver {
43
+ @Resolver()
44
+ export class GetDataContextDataResolver extends ResolverBase {
43
45
  /**
44
- * Returns data for a given data context item.
45
- * @param DataContextItemID
46
+ * Returns data for a given data context item.
47
+ * @param DataContextItemID
46
48
  */
47
49
  @Query(() => GetDataContextItemDataOutputType)
48
50
  async GetDataContextItemData(
49
51
  @Arg('DataContextItemID', () => String) DataContextItemID: string,
50
52
  @Ctx() appCtx: AppContext
51
53
  ) {
54
+ // Check API key scope authorization for data context read
55
+ await this.CheckAPIKeyScopeAuthorization('datacontext:read', DataContextItemID, appCtx.userPayload);
56
+
52
57
  try {
53
58
  const ds = GetReadOnlyDataSource(appCtx.dataSources, {
54
59
  allowFallbackToReadWrite: true,
@@ -92,14 +97,17 @@ export class GetDataContextDataResolver {
92
97
  }
93
98
 
94
99
  /**
95
- * Returns data for a given data context.
96
- * @param DataContextID
100
+ * Returns data for a given data context.
101
+ * @param DataContextID
97
102
  */
98
103
  @Query(() => GetDataContextDataOutputType)
99
104
  async GetDataContextData(
100
105
  @Arg('DataContextID', () => String) DataContextID: string,
101
106
  @Ctx() appCtx: AppContext
102
107
  ) {
108
+ // Check API key scope authorization for data context read
109
+ await this.CheckAPIKeyScopeAuthorization('datacontext:read', DataContextID, appCtx.userPayload);
110
+
103
111
  try {
104
112
  // our job here is to load the entire data context, so we do that with the Data Context object
105
113
  const dc = new DataContext();