@aws-amplify/graphql-model-transformer 0.7.1-graphql-vnext-dev-preview.10 → 0.7.1-graphql-vnext-dev-preview.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws-amplify/graphql-model-transformer",
3
- "version": "0.7.1-graphql-vnext-dev-preview.10",
3
+ "version": "0.7.1-graphql-vnext-dev-preview.11",
4
4
  "description": "Amplify graphql @model transformer",
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,8 +28,8 @@
28
28
  "test-watch": "jest --watch"
29
29
  },
30
30
  "dependencies": {
31
- "@aws-amplify/graphql-transformer-core": "0.11.0-graphql-vnext-dev-preview.10",
32
- "@aws-amplify/graphql-transformer-interfaces": "1.10.2-graphql-vnext-dev-preview.10",
31
+ "@aws-amplify/graphql-transformer-core": "0.11.0-graphql-vnext-dev-preview.11",
32
+ "@aws-amplify/graphql-transformer-interfaces": "1.10.2-graphql-vnext-dev-preview.11",
33
33
  "@aws-cdk/assets": "~1.124.0",
34
34
  "@aws-cdk/aws-applicationautoscaling": "~1.124.0",
35
35
  "@aws-cdk/aws-appsync": "~1.124.0",
@@ -58,7 +58,7 @@
58
58
  "constructs": "^3.3.125",
59
59
  "graphql": "^14.5.8",
60
60
  "graphql-mapping-template": "4.18.4",
61
- "graphql-transformer-common": "4.20.0",
61
+ "graphql-transformer-common": "4.20.1-graphql-vnext-dev-preview.11",
62
62
  "lodash": "^4.17.21",
63
63
  "md5": "^2.3.0"
64
64
  },
@@ -83,5 +83,5 @@
83
83
  ],
84
84
  "collectCoverage": true
85
85
  },
86
- "gitHead": "5b1112889d1f79c787c564dc73dc08f4b992d451"
86
+ "gitHead": "16e9cc68157644de729457cec7dd51a8ce353228"
87
87
  }
@@ -1215,4 +1215,59 @@ describe('ModelTransformer: ', () => {
1215
1215
  expect(todoStack).toBeDefined();
1216
1216
  expect(todoStack.Parameters).toMatchObject(modelParams);
1217
1217
  });
1218
+
1219
+ it('global auth enabled should add apiKey if not default mode of auth', () => {
1220
+ const validSchema = `
1221
+ type Post @model {
1222
+ id: ID!
1223
+ title: String!
1224
+ tags: [Tag]
1225
+ }
1226
+
1227
+ type Tag {
1228
+ id: ID
1229
+ tags: [Tag]
1230
+ }`;
1231
+ const transformer = new GraphQLTransform({
1232
+ authConfig: {
1233
+ defaultAuthentication: {
1234
+ authenticationType: 'AMAZON_COGNITO_USER_POOLS',
1235
+ },
1236
+ additionalAuthenticationProviders: [
1237
+ {
1238
+ authenticationType: 'API_KEY',
1239
+ },
1240
+ ],
1241
+ },
1242
+ sandboxModeEnabled: true,
1243
+ transformers: [new ModelTransformer()],
1244
+ });
1245
+ const out = transformer.transform(validSchema);
1246
+ expect(out).toBeDefined();
1247
+
1248
+ const schema = parse(out.schema);
1249
+ validateModelSchema(schema);
1250
+
1251
+ const postType = getObjectType(schema, 'Post')!;
1252
+ expect(postType).toBeDefined();
1253
+ expect(postType.directives).toBeDefined();
1254
+ expect(postType.directives!.some(dir => dir.name.value === 'aws_api_key')).toEqual(true);
1255
+
1256
+ const tagType = getObjectType(schema, 'Tag')!;
1257
+ expect(tagType).toBeDefined();
1258
+ expect(tagType.directives).toBeDefined();
1259
+ expect(tagType.directives!.some(dir => dir.name.value === 'aws_api_key')).toEqual(true);
1260
+
1261
+ // check operations
1262
+ const queryType = getObjectType(schema, 'Query')!;
1263
+ expect(queryType).toBeDefined();
1264
+ const mutationType = getObjectType(schema, 'Mutation')!;
1265
+ expect(mutationType).toBeDefined();
1266
+ const subscriptionType = getObjectType(schema, 'Subscription')!;
1267
+ expect(subscriptionType).toBeDefined();
1268
+
1269
+ for (const field of [...queryType.fields!, ...mutationType.fields!, ...subscriptionType.fields!]) {
1270
+ expect(field.directives!.some(dir => dir.name.value === 'aws_api_key')).toEqual(true);
1271
+ }
1272
+ });
1218
1273
  });
@@ -14,3 +14,5 @@ export const BOOLEAN_FUNCTIONS = new Set<string>(['attributeExists', 'attributeT
14
14
  export const ATTRIBUTE_TYPES = ['binary', 'binarySet', 'bool', 'list', 'map', 'number', 'numberSet', 'string', 'stringSet', '_null'];
15
15
 
16
16
  export const OPERATION_KEY = '__operation';
17
+
18
+ export const API_KEY_DIRECTIVE = 'aws_api_key';
@@ -53,8 +53,10 @@ import {
53
53
  toPascalCase,
54
54
  } from 'graphql-transformer-common';
55
55
  import {
56
+ addDirectivesToOperation,
56
57
  addModelConditionInputs,
57
58
  createEnumModelFilters,
59
+ extendTypeWithDirectives,
58
60
  makeCreateInputField,
59
61
  makeDeleteInputField,
60
62
  makeListQueryFilterInput,
@@ -62,6 +64,7 @@ import {
62
64
  makeModelSortDirectionEnumObject,
63
65
  makeMutationConditionInput,
64
66
  makeUpdateInputField,
67
+ propagateApiKeyToNestedTypes,
65
68
  } from './graphql-types';
66
69
  import {
67
70
  generateAuthExpressionForSandboxMode,
@@ -84,6 +87,7 @@ import {
84
87
  import { FieldWrapper, InputObjectDefinitionWrapper, ObjectDefinitionWrapper } from './wrappers/object-definition-wrapper';
85
88
  import { CfnRole } from '@aws-cdk/aws-iam';
86
89
  import md5 from 'md5';
90
+ import { API_KEY_DIRECTIVE } from './definitions';
87
91
 
88
92
  export type Nullable<T> = T | null;
89
93
  export type OptionalAndNullable<T> = Partial<T>;
@@ -260,6 +264,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme
260
264
  this.ensureModelSortDirectionEnum(ctx);
261
265
  for (const type of this.typesWithModelDirective) {
262
266
  const def = ctx.output.getObject(type)!;
267
+ const hasAuth = def.directives!.some(dir => dir.name.value === 'auth');
263
268
 
264
269
  // add Non Model type inputs
265
270
  this.createNonModelInputs(ctx, def);
@@ -279,6 +284,24 @@ export class ModelTransformer extends TransformerModelBase implements Transforme
279
284
  if (ctx.isProjectUsingDataStore()) {
280
285
  this.addModelSyncFields(ctx, type);
281
286
  }
287
+ // global auth check
288
+ if (!hasAuth && ctx.sandboxModeEnabled && ctx.authConfig.defaultAuthentication.authenticationType !== 'API_KEY') {
289
+ const apiKeyDirArray = [makeDirective(API_KEY_DIRECTIVE, [])];
290
+ extendTypeWithDirectives(ctx, def.name.value, apiKeyDirArray);
291
+ propagateApiKeyToNestedTypes(ctx as TransformerContextProvider, def, new Set<string>());
292
+ for (let operationField of queryFields) {
293
+ const operationName = operationField.name.value;
294
+ addDirectivesToOperation(ctx, ctx.output.getQueryTypeName()!, operationName, apiKeyDirArray);
295
+ }
296
+ for (let operationField of mutationFields) {
297
+ const operationName = operationField.name.value;
298
+ addDirectivesToOperation(ctx, ctx.output.getMutationTypeName()!, operationName, apiKeyDirArray);
299
+ }
300
+ for (let operationField of subscriptionsFields) {
301
+ const operationName = operationField.name.value;
302
+ addDirectivesToOperation(ctx, ctx.output.getSubscriptionTypeName()!, operationName, apiKeyDirArray);
303
+ }
304
+ }
282
305
  }
283
306
  };
284
307
 
@@ -307,10 +330,12 @@ export class ModelTransformer extends TransformerModelBase implements Transforme
307
330
  default:
308
331
  throw new Error('Unknown query field type');
309
332
  }
333
+ // TODO: add mechanism to add an auth like rule to all non auth @models
334
+ // this way we can just depend on auth to add the check
310
335
  resolver.addToSlot(
311
336
  'postAuth',
312
337
  MappingTemplate.s3MappingTemplateFromString(
313
- generateAuthExpressionForSandboxMode(context),
338
+ generateAuthExpressionForSandboxMode(context.sandboxModeEnabled),
314
339
  `${query.typeName}.${query.fieldName}.{slotName}.{slotIndex}.req.vtl`,
315
340
  ),
316
341
  );
@@ -337,7 +362,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme
337
362
  resolver.addToSlot(
338
363
  'postAuth',
339
364
  MappingTemplate.s3MappingTemplateFromString(
340
- generateAuthExpressionForSandboxMode(context),
365
+ generateAuthExpressionForSandboxMode(context.sandboxModeEnabled),
341
366
  `${mutation.typeName}.${mutation.fieldName}.{slotName}.{slotIndex}.req.vtl`,
342
367
  ),
343
368
  );
@@ -385,7 +410,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme
385
410
  resolver.addToSlot(
386
411
  'postAuth',
387
412
  MappingTemplate.s3MappingTemplateFromString(
388
- generateAuthExpressionForSandboxMode(context),
413
+ generateAuthExpressionForSandboxMode(context.sandboxModeEnabled),
389
414
  `${subscription.typeName}.${subscription.fieldName}.{slotName}.{slotIndex}.req.vtl`,
390
415
  ),
391
416
  );
@@ -1,14 +1,20 @@
1
- import { TransformerTransformSchemaStepContextProvider } from '@aws-amplify/graphql-transformer-interfaces';
1
+ import { TransformerContextProvider, TransformerTransformSchemaStepContextProvider } from '@aws-amplify/graphql-transformer-interfaces';
2
2
  import {
3
+ DirectiveNode,
3
4
  EnumTypeDefinitionNode,
4
5
  FieldDefinitionNode,
5
6
  InputObjectTypeDefinitionNode,
6
7
  Kind,
8
+ NamedTypeNode,
7
9
  ObjectTypeDefinitionNode,
8
10
  TypeDefinitionNode,
9
11
  } from 'graphql';
10
12
  import {
13
+ blankObjectExtension,
11
14
  DEFAULT_SCALARS,
15
+ extendFieldWithDirectives,
16
+ extensionWithDirectives,
17
+ getBaseType,
12
18
  makeArgument,
13
19
  makeDirective,
14
20
  makeField,
@@ -29,6 +35,7 @@ import {
29
35
  SIZE_CONDITIONS,
30
36
  STRING_CONDITIONS,
31
37
  STRING_FUNCTIONS,
38
+ API_KEY_DIRECTIVE,
32
39
  } from '../definitions';
33
40
  import {
34
41
  EnumWrapper,
@@ -245,7 +252,96 @@ export function makeEnumFilterInput(fieldWrapper: FieldWrapper): InputObjectType
245
252
  return input.serialize();
246
253
  }
247
254
 
255
+ export const addDirectivesToField = (
256
+ ctx: TransformerTransformSchemaStepContextProvider,
257
+ typeName: string,
258
+ fieldName: string,
259
+ directives: Array<DirectiveNode>,
260
+ ) => {
261
+ const type = ctx.output.getType(typeName) as ObjectTypeDefinitionNode;
262
+ if (type) {
263
+ const field = type.fields?.find(f => f.name.value === fieldName);
264
+ if (field) {
265
+ const newFields = [...type.fields!.filter(f => f.name.value !== field.name.value), extendFieldWithDirectives(field, directives)];
266
+
267
+ const newType = {
268
+ ...type,
269
+ fields: newFields,
270
+ };
271
+
272
+ ctx.output.putType(newType);
273
+ }
274
+ }
275
+ };
276
+
277
+ export const addDirectivesToOperation = (
278
+ ctx: TransformerTransformSchemaStepContextProvider,
279
+ typeName: string,
280
+ operationName: string,
281
+ directives: Array<DirectiveNode>,
282
+ ) => {
283
+ // add directives to the given operation
284
+ addDirectivesToField(ctx, typeName, operationName, directives);
285
+
286
+ // add the directives to the result type of the operation
287
+ const type = ctx.output.getType(typeName) as ObjectTypeDefinitionNode;
288
+ if (type) {
289
+ const field = type.fields!.find(f => f.name.value === operationName);
290
+
291
+ if (field) {
292
+ const returnFieldType = field.type as NamedTypeNode;
293
+
294
+ if (returnFieldType.name) {
295
+ const returnTypeName = returnFieldType.name.value;
296
+
297
+ extendTypeWithDirectives(ctx, returnTypeName, directives);
298
+ }
299
+ }
300
+ }
301
+ };
302
+
303
+ export const extendTypeWithDirectives = (
304
+ ctx: TransformerTransformSchemaStepContextProvider,
305
+ typeName: string,
306
+ directives: Array<DirectiveNode>,
307
+ ): void => {
308
+ let objectTypeExtension = blankObjectExtension(typeName);
309
+ objectTypeExtension = extensionWithDirectives(objectTypeExtension, directives);
310
+ ctx.output.addObjectExtension(objectTypeExtension);
311
+ };
312
+
248
313
  export function makeModelSortDirectionEnumObject(): EnumTypeDefinitionNode {
249
314
  const name = 'ModelSortDirection';
250
315
  return EnumWrapper.create(name, ['ASC', 'DESC']).serialize();
251
316
  }
317
+ // the smaller version of it's @auth equivalent since we only support
318
+ // apikey as the only global auth rule
319
+ export const propagateApiKeyToNestedTypes = (
320
+ ctx: TransformerContextProvider,
321
+ def: ObjectTypeDefinitionNode,
322
+ seenNonModelTypes: Set<string>,
323
+ ) => {
324
+ const nonModelTypePredicate = (fieldType: TypeDefinitionNode): TypeDefinitionNode | undefined => {
325
+ if (fieldType) {
326
+ if (fieldType.kind !== 'ObjectTypeDefinition') {
327
+ return undefined;
328
+ }
329
+ const typeModel = fieldType.directives!.find(dir => dir.name.value === 'model');
330
+ return typeModel !== undefined ? undefined : fieldType;
331
+ }
332
+ return fieldType;
333
+ };
334
+ const nonModelFieldTypes = def
335
+ .fields!.map(f => ctx.output.getType(getBaseType(f.type)) as TypeDefinitionNode)
336
+ .filter(nonModelTypePredicate);
337
+ for (const nonModelFieldType of nonModelFieldTypes) {
338
+ const nonModelName = nonModelFieldType.name.value;
339
+ const hasSeenType = seenNonModelTypes.has(nonModelName);
340
+ const hasApiKey = nonModelFieldType.directives?.some(dir => dir.name.value === API_KEY_DIRECTIVE) ?? false;
341
+ if (!hasSeenType && !hasApiKey) {
342
+ seenNonModelTypes.add(nonModelName);
343
+ extendTypeWithDirectives(ctx, nonModelName, [makeDirective(API_KEY_DIRECTIVE, [])]);
344
+ propagateApiKeyToNestedTypes(ctx, nonModelFieldType as ObjectTypeDefinitionNode, seenNonModelTypes);
345
+ }
346
+ }
347
+ };
@@ -96,10 +96,8 @@ export const generateResolverKey = (typeName: string, fieldName: string): string
96
96
 
97
97
  /**
98
98
  * Util function to generate sandbox mode expression
99
- * @param ctx context to get sandbox mode
100
99
  */
101
- export const generateAuthExpressionForSandboxMode = (ctx: any): string => {
102
- const enabled = ctx.resourceHelper.api.sandboxModeEnabled;
100
+ export const generateAuthExpressionForSandboxMode = (enabled: boolean): string => {
103
101
  let exp;
104
102
 
105
103
  if (enabled) exp = iff(notEquals(methodCall(ref('util.authType')), str(API_KEY)), methodCall(ref('util.unauthorized')));