@memberjunction/server 3.3.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 (103) hide show
  1. package/README.md +59 -0
  2. package/dist/auth/BaseAuthProvider.d.ts +1 -0
  3. package/dist/auth/BaseAuthProvider.d.ts.map +1 -1
  4. package/dist/auth/BaseAuthProvider.js +2 -0
  5. package/dist/auth/BaseAuthProvider.js.map +1 -1
  6. package/dist/auth/IAuthProvider.d.ts +1 -0
  7. package/dist/auth/IAuthProvider.d.ts.map +1 -1
  8. package/dist/config.js +2 -2
  9. package/dist/config.js.map +1 -1
  10. package/dist/generated/generated.d.ts +431 -2
  11. package/dist/generated/generated.d.ts.map +1 -1
  12. package/dist/generated/generated.js +3052 -379
  13. package/dist/generated/generated.js.map +1 -1
  14. package/dist/generic/ResolverBase.d.ts +1 -0
  15. package/dist/generic/ResolverBase.d.ts.map +1 -1
  16. package/dist/generic/ResolverBase.js +30 -0
  17. package/dist/generic/ResolverBase.js.map +1 -1
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +2 -2
  21. package/dist/index.js.map +1 -1
  22. package/dist/resolvers/APIKeyResolver.d.ts +2 -1
  23. package/dist/resolvers/APIKeyResolver.d.ts.map +1 -1
  24. package/dist/resolvers/APIKeyResolver.js +4 -1
  25. package/dist/resolvers/APIKeyResolver.js.map +1 -1
  26. package/dist/resolvers/ActionResolver.d.ts +2 -1
  27. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  28. package/dist/resolvers/ActionResolver.js +4 -1
  29. package/dist/resolvers/ActionResolver.js.map +1 -1
  30. package/dist/resolvers/DatasetResolver.d.ts +5 -4
  31. package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
  32. package/dist/resolvers/DatasetResolver.js +7 -4
  33. package/dist/resolvers/DatasetResolver.js.map +1 -1
  34. package/dist/resolvers/EntityCommunicationsResolver.d.ts +2 -1
  35. package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
  36. package/dist/resolvers/EntityCommunicationsResolver.js +3 -1
  37. package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
  38. package/dist/resolvers/GetDataContextDataResolver.d.ts +2 -1
  39. package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
  40. package/dist/resolvers/GetDataContextDataResolver.js +10 -3
  41. package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
  42. package/dist/resolvers/MCPResolver.d.ts +37 -0
  43. package/dist/resolvers/MCPResolver.d.ts.map +1 -0
  44. package/dist/resolvers/MCPResolver.js +363 -0
  45. package/dist/resolvers/MCPResolver.js.map +1 -0
  46. package/dist/resolvers/MergeRecordsResolver.d.ts +2 -1
  47. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  48. package/dist/resolvers/MergeRecordsResolver.js +3 -1
  49. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  50. package/dist/resolvers/QueryResolver.d.ts +2 -1
  51. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  52. package/dist/resolvers/QueryResolver.js +6 -1
  53. package/dist/resolvers/QueryResolver.js.map +1 -1
  54. package/dist/resolvers/ReportResolver.d.ts +2 -1
  55. package/dist/resolvers/ReportResolver.d.ts.map +1 -1
  56. package/dist/resolvers/ReportResolver.js +4 -1
  57. package/dist/resolvers/ReportResolver.js.map +1 -1
  58. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  59. package/dist/resolvers/RunAIAgentResolver.js +2 -0
  60. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  61. package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
  62. package/dist/resolvers/RunAIPromptResolver.js +3 -0
  63. package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
  64. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  65. package/dist/resolvers/RunTemplateResolver.js +1 -0
  66. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  67. package/dist/resolvers/TaskResolver.d.ts.map +1 -1
  68. package/dist/resolvers/TaskResolver.js +1 -0
  69. package/dist/resolvers/TaskResolver.js.map +1 -1
  70. package/dist/resolvers/UserResolver.d.ts.map +1 -1
  71. package/dist/resolvers/UserResolver.js +4 -0
  72. package/dist/resolvers/UserResolver.js.map +1 -1
  73. package/package.json +47 -46
  74. package/src/auth/BaseAuthProvider.ts +3 -0
  75. package/src/auth/IAuthProvider.ts +5 -0
  76. package/src/config.ts +2 -2
  77. package/src/generated/generated.ts +2020 -334
  78. package/src/generic/ResolverBase.ts +89 -3
  79. package/src/index.ts +10 -2
  80. package/src/resolvers/APIKeyResolver.ts +8 -1
  81. package/src/resolvers/ActionResolver.ts +8 -1
  82. package/src/resolvers/DatasetResolver.ts +11 -4
  83. package/src/resolvers/EntityCommunicationsResolver.ts +5 -1
  84. package/src/resolvers/GetDataContextDataResolver.ts +14 -6
  85. package/src/resolvers/MCPResolver.ts +480 -0
  86. package/src/resolvers/MergeRecordsResolver.ts +5 -1
  87. package/src/resolvers/QueryResolver.ts +17 -3
  88. package/src/resolvers/ReportResolver.ts +8 -1
  89. package/src/resolvers/RunAIAgentResolver.ts +6 -0
  90. package/src/resolvers/RunAIPromptResolver.ts +10 -1
  91. package/src/resolvers/RunTemplateResolver.ts +4 -1
  92. package/src/resolvers/TaskResolver.ts +3 -0
  93. package/src/resolvers/UserResolver.ts +15 -3
  94. package/dist/resolvers/AskSkipResolver.d.ts +0 -123
  95. package/dist/resolvers/AskSkipResolver.d.ts.map +0 -1
  96. package/dist/resolvers/AskSkipResolver.js +0 -1788
  97. package/dist/resolvers/AskSkipResolver.js.map +0 -1
  98. package/dist/scheduler/LearningCycleScheduler.d.ts +0 -4
  99. package/dist/scheduler/LearningCycleScheduler.d.ts.map +0 -1
  100. package/dist/scheduler/LearningCycleScheduler.js +0 -4
  101. package/dist/scheduler/LearningCycleScheduler.js.map +0 -1
  102. package/src/resolvers/AskSkipResolver.ts +0 -3446
  103. package/src/scheduler/LearningCycleScheduler.ts +0 -320
@@ -19,8 +19,9 @@ import {
19
19
  } from '@memberjunction/core';
20
20
  import { AuditLogEntity, ErrorLogEntity, UserViewEntityExtended } from '@memberjunction/core-entities';
21
21
  import { SQLServerDataProvider, UserCache } from '@memberjunction/sqlserver-dataprovider';
22
- import { PubSubEngine } from 'type-graphql';
22
+ import { PubSubEngine, AuthorizationError } from 'type-graphql';
23
23
  import { GraphQLError } from 'graphql';
24
+ import { GetAPIKeyEngine } from '@memberjunction/api-keys';
24
25
  import sql from 'mssql';
25
26
  import { httpTransport, CloudEvent, emitterFor } from 'cloudevents';
26
27
 
@@ -504,7 +505,7 @@ export class ResolverBase {
504
505
  if (!userPayload) {
505
506
  throw new Error(`userPayload is null`);
506
507
  }
507
-
508
+
508
509
  // first check permissions, the logged in user must have read permissions on the entity to run the view
509
510
  if (entityInfo) {
510
511
  const userInfo = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload.email.toLowerCase().trim()); // get the user record from MD so we have ROLES attached, don't use the one from payload directly
@@ -516,12 +517,85 @@ export class ResolverBase {
516
517
  if (!userPermissions.CanRead) {
517
518
  throw new Error(`User ${userPayload.email} does not have read permissions on ${entityInfo.Name}`);
518
519
  }
519
- }
520
+ }
520
521
  else {
521
522
  throw new Error(`Entity not found in metadata`);
522
523
  }
523
524
  }
524
525
 
526
+ /**
527
+ * Checks API key scope authorization. Only performs check if request
528
+ * was authenticated via API key (apiKeyHash present in userPayload).
529
+ * For OAuth/JWT auth, this is a no-op.
530
+ *
531
+ * @param scopePath - The scope path (e.g., 'entity:read', 'agent:execute')
532
+ * @param resource - The resource name (e.g., entity name, agent name)
533
+ * @param userPayload - The user payload from context
534
+ * @throws AuthorizationError if API key lacks required scope
535
+ */
536
+ protected async CheckAPIKeyScopeAuthorization(
537
+ scopePath: string,
538
+ resource: string,
539
+ userPayload: UserPayload
540
+ ): Promise<void> {
541
+ // Skip scope check for OAuth/JWT auth (no API key)
542
+ if (!userPayload.apiKeyHash) {
543
+ return;
544
+ }
545
+
546
+ // Get system user for authorization call
547
+ // NOTE: We use system user here because Authorize() needs to run internal
548
+ // database queries (loading scope rules, logging decisions). The system user
549
+ // ensures these queries work regardless of what permissions the API key's
550
+ // user has. The API key's associated user (in userPayload.userRecord) is
551
+ // used later when the actual operation executes - their permissions are
552
+ // the ultimate ceiling that scopes can only narrow, never expand.
553
+ const systemUser = UserCache.Instance.Users.find(u => u.Type === 'System');
554
+ if (!systemUser) {
555
+ throw new Error('System user not found');
556
+ }
557
+
558
+ const apiKeyEngine = GetAPIKeyEngine();
559
+
560
+ // Check for full_access scope first (god power - bypasses all other checks)
561
+ const fullAccessResult = await apiKeyEngine.Authorize(
562
+ userPayload.apiKeyHash,
563
+ 'MJAPI',
564
+ 'full_access',
565
+ '*',
566
+ systemUser,
567
+ { endpoint: '/graphql', method: 'POST' }
568
+ );
569
+
570
+ if (fullAccessResult.Allowed) {
571
+ // full_access granted - skip specific scope check
572
+ return;
573
+ }
574
+
575
+ // Check specific scope
576
+ const result = await apiKeyEngine.Authorize(
577
+ userPayload.apiKeyHash,
578
+ 'MJAPI',
579
+ scopePath,
580
+ resource,
581
+ systemUser,
582
+ {
583
+ endpoint: '/graphql',
584
+ method: 'POST'
585
+ }
586
+ );
587
+
588
+ if (!result.Allowed) {
589
+ // Provide specific, actionable error message
590
+ throw new AuthorizationError(
591
+ `Access denied. This API key requires the '${scopePath}' scope ` +
592
+ `for resource '${resource}' to perform this operation. ` +
593
+ `Please update the API key's scopes or use an API key with appropriate permissions. ` +
594
+ `Denial reason: ${result.Reason}`
595
+ );
596
+ }
597
+ }
598
+
525
599
  /**
526
600
  * Optimized RunViewGenericInternal implementation with:
527
601
  * - Field filtering at source (Fix #7)
@@ -550,6 +624,9 @@ export class ResolverBase {
550
624
  try {
551
625
  if (!viewInfo || !userPayload) return null;
552
626
 
627
+ // Check API key scope authorization for view operations
628
+ await this.CheckAPIKeyScopeAuthorization('view:run', viewInfo.Entity, userPayload);
629
+
553
630
  const md = provider
554
631
  const user = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
555
632
  if (!user) throw new Error(`User ${userPayload?.email} not found in metadata`);
@@ -908,6 +985,9 @@ export class ResolverBase {
908
985
  }
909
986
 
910
987
  protected async CreateRecord(entityName: string, input: any, provider: DatabaseProviderBase, userPayload: UserPayload, pubSub: PubSubEngine) {
988
+ // Check API key scope authorization for entity create operations
989
+ await this.CheckAPIKeyScopeAuthorization('entity:create', entityName, userPayload);
990
+
911
991
  if (await this.BeforeCreate(provider, input)) {
912
992
  // fire event and proceed if it wasn't cancelled
913
993
  const entityObject = await provider.GetEntityObject(entityName, this.GetUserFromPayload(userPayload));
@@ -940,6 +1020,9 @@ export class ResolverBase {
940
1020
  protected async AfterCreate(provider: DatabaseProviderBase, input: any) {}
941
1021
 
942
1022
  protected async UpdateRecord(entityName: string, input: any, provider: DatabaseProviderBase, userPayload: UserPayload, pubSub: PubSubEngine) {
1023
+ // Check API key scope authorization for entity update operations
1024
+ await this.CheckAPIKeyScopeAuthorization('entity:update', entityName, userPayload);
1025
+
943
1026
  if (await this.BeforeUpdate(provider, input)) {
944
1027
  // fire event and proceed if it wasn't cancelled
945
1028
  const userInfo = this.GetUserFromPayload(userPayload);
@@ -1202,6 +1285,9 @@ export class ResolverBase {
1202
1285
  userPayload: UserPayload,
1203
1286
  pubSub: PubSubEngine
1204
1287
  ) {
1288
+ // Check API key scope authorization for entity delete operations
1289
+ await this.CheckAPIKeyScopeAuthorization('entity:delete', entityName, userPayload);
1290
+
1205
1291
  if (await this.BeforeDelete(provider, key)) {
1206
1292
  // fire event and proceed if it wasn't cancelled
1207
1293
  const entityObject = await provider.GetEntityObject(entityName, this.GetUserFromPayload(userPayload));
package/src/index.ts CHANGED
@@ -81,7 +81,15 @@ export { configInfo, DEFAULT_SERVER_CONFIG } from './config.js';
81
81
  export * from './directives/index.js';
82
82
  export * from './entitySubclasses/entityPermissions.server.js';
83
83
  export * from './types.js';
84
- export { TokenExpiredError, getSystemUser } from './auth/index.js';
84
+ export {
85
+ TokenExpiredError,
86
+ getSystemUser,
87
+ getSigningKeys,
88
+ extractUserInfoFromPayload,
89
+ verifyUserRecord,
90
+ AuthProviderFactory,
91
+ IAuthProvider,
92
+ } from './auth/index.js';
85
93
  export * from './auth/APIKeyScopeAuth.js';
86
94
 
87
95
  export * from './generic/PushStatusResolver.js';
@@ -98,7 +106,6 @@ export * from './generic/DeleteOptionsInput.js';
98
106
  export * from './agents/skip-agent.js';
99
107
  export * from './agents/skip-sdk.js';
100
108
 
101
- export * from './resolvers/AskSkipResolver.js';
102
109
  export * from './resolvers/ColorResolver.js';
103
110
  export * from './resolvers/ComponentRegistryResolver.js';
104
111
  export * from './resolvers/DatasetResolver.js';
@@ -115,6 +122,7 @@ export * from './resolvers/TransactionGroupResolver.js';
115
122
  export * from './resolvers/CreateQueryResolver.js';
116
123
  export * from './resolvers/TelemetryResolver.js';
117
124
  export * from './resolvers/APIKeyResolver.js';
125
+ export * from './resolvers/MCPResolver.js';
118
126
  export { GetReadOnlyDataSource, GetReadWriteDataSource, GetReadWriteProvider, GetReadOnlyProvider } from './util.js';
119
127
 
120
128
  export * from './generated/generated.js';
@@ -4,6 +4,7 @@ import { LogError, Metadata } from "@memberjunction/core";
4
4
  import { APIKeyScopeEntity } from "@memberjunction/core-entities";
5
5
  import { GetAPIKeyEngine } from "@memberjunction/api-keys";
6
6
  import { AppContext } from "../types.js";
7
+ import { ResolverBase } from "../generic/ResolverBase.js";
7
8
 
8
9
  /**
9
10
  * Input type for creating a new API key
@@ -90,7 +91,7 @@ export class RevokeAPIKeyResult {
90
91
  * Handles secure server-side API key generation
91
92
  */
92
93
  @Resolver()
93
- export class APIKeyResolver {
94
+ export class APIKeyResolver extends ResolverBase {
94
95
  /**
95
96
  * Creates a new API key with proper server-side cryptographic hashing.
96
97
  *
@@ -109,6 +110,9 @@ export class APIKeyResolver {
109
110
  @Arg("input") input: CreateAPIKeyInput,
110
111
  @Ctx() ctx: AppContext
111
112
  ): Promise<CreateAPIKeyResult> {
113
+ // Check API key scope authorization for API key creation
114
+ await this.CheckAPIKeyScopeAuthorization('apikey:create', '*', ctx.userPayload);
115
+
112
116
  try {
113
117
  // Get the authenticated user
114
118
  const user = ctx.userPayload.userRecord;
@@ -173,6 +177,9 @@ export class APIKeyResolver {
173
177
  @Arg("apiKeyId") apiKeyId: string,
174
178
  @Ctx() ctx: AppContext
175
179
  ): Promise<RevokeAPIKeyResult> {
180
+ // Check API key scope authorization for API key revocation
181
+ await this.CheckAPIKeyScopeAuthorization('apikey:revoke', apiKeyId, ctx.userPayload);
182
+
176
183
  try {
177
184
  const user = ctx.userPayload.userRecord;
178
185
  if (!user) {
@@ -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();