@aws-amplify/data-schema 1.1.1 → 1.1.2

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.
@@ -287,6 +287,13 @@ function transformFunctionHandler(
287
287
  return { gqlHandlerContent, lambdaFunctionDefinition };
288
288
  }
289
289
 
290
+ type CustomTypeAuthRules =
291
+ | {
292
+ typeName: string;
293
+ authRules: Authorization<any, any, any>[];
294
+ }
295
+ | undefined;
296
+
290
297
  function customOperationToGql(
291
298
  typeName: string,
292
299
  typeDef: InternalCustom,
@@ -296,7 +303,8 @@ function customOperationToGql(
296
303
  getRefType: ReturnType<typeof getRefTypeForSchema>,
297
304
  ): {
298
305
  gqlField: string;
299
- models: [string, any][];
306
+ implicitTypes: [string, any][];
307
+ customTypeAuthRules: CustomTypeAuthRules;
300
308
  lambdaFunctionDefinition: LambdaFunctionDefinition;
301
309
  customSqlDataSourceStrategy: CustomSqlDataSourceStrategy | undefined;
302
310
  } {
@@ -309,37 +317,60 @@ function customOperationToGql(
309
317
  } = typeDef.data;
310
318
 
311
319
  let callSignature: string = typeName;
312
- const implicitModels: [string, any][] = [];
320
+ const implicitTypes: [string, any][] = [];
321
+
322
+ // When Custom Operations are defined with a Custom Type return type,
323
+ // the Custom Type inherits the operation's auth rules
324
+ let customTypeAuthRules: CustomTypeAuthRules = undefined;
313
325
 
314
326
  const { authString } = isCustom
315
- ? calculateCustomAuth(authorization)
327
+ ? mapToNativeAppSyncAuthDirectives(authorization, true)
316
328
  : calculateAuth(authorization);
317
329
 
318
330
  /**
319
331
  *
320
332
  * @param returnType The return type from the `data` field of a customer operation.
321
333
  * @param refererTypeName The type the refers {@link returnType} by `a.ref()`.
322
- * @param shouldAddCustomTypeToImplicitModels A flag indicates wether it should push
323
- * the return type resolved CustomType to the `implicitModels` list.
334
+ * @param shouldAddCustomTypeToImplicitTypes A flag indicates wether it should push
335
+ * the return type resolved CustomType to the `implicitTypes` list.
324
336
  * @returns
325
337
  */
326
338
  const resolveReturnTypeNameFromReturnType = (
327
339
  returnType: any,
328
340
  {
329
341
  refererTypeName,
330
- shouldAddCustomTypeToImplicitModels = true,
342
+ shouldAddCustomTypeToImplicitTypes = true,
331
343
  }: {
332
344
  refererTypeName: string;
333
- shouldAddCustomTypeToImplicitModels?: boolean;
345
+ shouldAddCustomTypeToImplicitTypes?: boolean;
334
346
  },
335
347
  ): string => {
336
348
  if (isRefField(returnType)) {
349
+ const { type } = getRefType(returnType.data.link, typeName);
350
+
351
+ if (type === 'CustomType') {
352
+ customTypeAuthRules = {
353
+ typeName: returnType.data.link,
354
+ authRules: authorization,
355
+ };
356
+ }
357
+
337
358
  return refFieldToGql(returnType?.data);
338
359
  } else if (isCustomType(returnType)) {
339
360
  const returnTypeName = `${capitalize(refererTypeName)}ReturnType`;
340
- if (shouldAddCustomTypeToImplicitModels) {
341
- implicitModels.push([returnTypeName, returnType]);
361
+ if (shouldAddCustomTypeToImplicitTypes) {
362
+ customTypeAuthRules = {
363
+ typeName: returnTypeName,
364
+ authRules: authorization,
365
+ };
366
+
367
+ implicitTypes.push([returnTypeName, returnType]);
342
368
  }
369
+ return returnTypeName;
370
+ } else if (isEnumType(returnType)) {
371
+ const returnTypeName = `${capitalize(refererTypeName)}ReturnType`;
372
+ implicitTypes.push([returnTypeName, returnType]);
373
+
343
374
  return returnTypeName;
344
375
  } else if (isScalarField(returnType)) {
345
376
  return scalarFieldToGql(returnType?.data);
@@ -359,7 +390,7 @@ function customOperationToGql(
359
390
  def.data.returnType,
360
391
  {
361
392
  refererTypeName: subscriptionSource[0].data.link,
362
- shouldAddCustomTypeToImplicitModels: false,
393
+ shouldAddCustomTypeToImplicitTypes: false,
363
394
  },
364
395
  );
365
396
  } else {
@@ -372,9 +403,14 @@ function customOperationToGql(
372
403
  }
373
404
 
374
405
  if (Object.keys(fieldArgs).length > 0) {
375
- const { gqlFields, models } = processFields(typeName, fieldArgs, {}, {});
406
+ const { gqlFields, implicitTypes } = processFields(
407
+ typeName,
408
+ fieldArgs,
409
+ {},
410
+ {},
411
+ );
376
412
  callSignature += `(${gqlFields.join(', ')})`;
377
- implicitModels.push(...models);
413
+ implicitTypes.push(...implicitTypes);
378
414
  }
379
415
 
380
416
  const handler = handlers && handlers[0];
@@ -438,7 +474,8 @@ function customOperationToGql(
438
474
  const gqlField = `${callSignature}: ${returnTypeName} ${gqlHandlerContent}${authString}`;
439
475
  return {
440
476
  gqlField,
441
- models: implicitModels,
477
+ implicitTypes: implicitTypes,
478
+ customTypeAuthRules,
442
479
  lambdaFunctionDefinition,
443
480
  customSqlDataSourceStrategy,
444
481
  };
@@ -669,24 +706,33 @@ function calculateAuth(authorization: Authorization<any, any, any>[]) {
669
706
 
670
707
  type AuthRule = ReturnType<typeof accessData>;
671
708
 
672
- function validateCustomAuthRule(rule: AuthRule) {
709
+ function validateCustomHandlerAuthRule(rule: AuthRule) {
673
710
  if (rule.groups && rule.provider === 'oidc') {
674
711
  throw new Error('OIDC group auth is not supported with a.handler.custom');
675
712
  }
713
+
714
+ // not currently supported with handler.custom (JS Resolvers), but will be in the future
715
+ if (rule.provider === 'identityPool' || (rule.provider as string) === 'iam') {
716
+ throw new Error(
717
+ "identityPool-based auth (allow.guest() and allow.authenticated('identityPool')) is not supported with a.handler.custom",
718
+ );
719
+ }
676
720
  }
677
721
 
678
- function getCustomAuthProvider(rule: AuthRule): string {
722
+ function getAppSyncAuthDirectiveFromRule(rule: AuthRule): string {
679
723
  const strategyDict: Record<string, Record<string, string>> = {
680
724
  public: {
681
725
  default: '@aws_api_key',
682
726
  apiKey: '@aws_api_key',
683
727
  iam: '@aws_iam',
728
+ identityPool: '@aws_iam',
684
729
  },
685
730
  private: {
686
731
  default: '@aws_cognito_user_pools',
687
732
  userPools: '@aws_cognito_user_pools',
688
733
  oidc: '@aws_oidc',
689
734
  iam: '@aws_iam',
735
+ identityPool: '@aws_iam',
690
736
  },
691
737
  groups: {
692
738
  default: '@aws_cognito_user_pools',
@@ -718,28 +764,32 @@ function getCustomAuthProvider(rule: AuthRule): string {
718
764
  return stratProvider;
719
765
  }
720
766
 
721
- function calculateCustomAuth(authorization: Authorization<any, any, any>[]) {
722
- const rules: string[] = [];
767
+ function mapToNativeAppSyncAuthDirectives(
768
+ authorization: Authorization<any, any, any>[],
769
+ isCustomHandler: boolean,
770
+ ) {
771
+ const rules = new Set<string>();
723
772
 
724
773
  for (const entry of authorization) {
725
774
  const rule = accessData(entry);
726
775
 
727
- validateCustomAuthRule(rule);
728
- const provider = getCustomAuthProvider(rule);
776
+ isCustomHandler && validateCustomHandlerAuthRule(rule);
777
+
778
+ const provider = getAppSyncAuthDirectiveFromRule(rule);
729
779
 
730
780
  if (rule.groups) {
731
781
  // example: (cognito_groups: ["Bloggers", "Readers"])
732
- rules.push(
782
+ rules.add(
733
783
  `${provider}(cognito_groups: [${rule.groups
734
784
  .map((group) => `"${group}"`)
735
785
  .join(', ')}])`,
736
786
  );
737
787
  } else {
738
- rules.push(provider);
788
+ rules.add(provider);
739
789
  }
740
790
  }
741
791
 
742
- const authString = rules.join(' ');
792
+ const authString = [...rules].join(' ');
743
793
 
744
794
  return { authString };
745
795
  }
@@ -780,7 +830,9 @@ function processFields(
780
830
  secondaryIndexes: TransformedSecondaryIndexes = {},
781
831
  ) {
782
832
  const gqlFields: string[] = [];
783
- const models: [string, any][] = [];
833
+ // stores nested, field-level type definitions (custom types and enums)
834
+ // the need to be hoisted to top-level schema types and processed accordingly
835
+ const implicitTypes: [string, any][] = [];
784
836
 
785
837
  validateImpliedFields(fields, impliedFields);
786
838
 
@@ -811,7 +863,7 @@ function processFields(
811
863
  // enum type name conflicts
812
864
  const enumName = `${capitalize(typeName)}${capitalize(fieldName)}`;
813
865
 
814
- models.push([enumName, fieldDef]);
866
+ implicitTypes.push([enumName, fieldDef]);
815
867
 
816
868
  gqlFields.push(
817
869
  `${fieldName}: ${enumFieldToGql(enumName, secondaryIndexes[fieldName])}`,
@@ -823,7 +875,7 @@ function processFields(
823
875
  fieldName,
824
876
  )}`;
825
877
 
826
- models.push([customTypeName, fieldDef]);
878
+ implicitTypes.push([customTypeName, fieldDef]);
827
879
 
828
880
  gqlFields.push(`${fieldName}: ${customTypeName}`);
829
881
  } else {
@@ -840,7 +892,7 @@ function processFields(
840
892
  }
841
893
  }
842
894
 
843
- return { gqlFields, models };
895
+ return { gqlFields, implicitTypes };
844
896
  }
845
897
 
846
898
  type TransformedSecondaryIndexes = {
@@ -1045,6 +1097,46 @@ const getRefTypeForSchema = (schema: InternalSchema) => {
1045
1097
  return getRefType;
1046
1098
  };
1047
1099
 
1100
+ /**
1101
+ * Sorts top-level schema types to where Custom Types are processed last
1102
+ * This allows us to accrue and then apply inherited auth rules for custom types from custom operations
1103
+ * that reference them in their return values
1104
+ */
1105
+ const sortTopLevelTypes = (topLevelTypes: [string, any][]) => {
1106
+ return topLevelTypes.sort(
1107
+ ([_typeNameA, typeDefA], [_typeNameB, typeDefB]) => {
1108
+ if (
1109
+ (isCustomType(typeDefA) && isCustomType(typeDefB)) ||
1110
+ (!isCustomType(typeDefA) && !isCustomType(typeDefB))
1111
+ ) {
1112
+ return 0;
1113
+ } else if (isCustomType(typeDefA) && !isCustomType(typeDefB)) {
1114
+ return 1;
1115
+ } else {
1116
+ return -1;
1117
+ }
1118
+ },
1119
+ );
1120
+ };
1121
+
1122
+ /**
1123
+ * Builds up dictionary of Custom Type name - array of inherited auth rules
1124
+ */
1125
+ const mergeCustomTypeAuthRules = (
1126
+ existing: Record<string, Authorization<any, any, any>[]>,
1127
+ added: CustomTypeAuthRules,
1128
+ ) => {
1129
+ if (!added) return;
1130
+
1131
+ const { typeName, authRules } = added;
1132
+
1133
+ if (typeName in existing) {
1134
+ existing[typeName] = [...existing[typeName], ...authRules];
1135
+ } else {
1136
+ existing[typeName] = authRules;
1137
+ }
1138
+ };
1139
+
1048
1140
  const schemaPreprocessor = (
1049
1141
  schema: InternalSchema,
1050
1142
  ): {
@@ -1060,6 +1152,13 @@ const schemaPreprocessor = (
1060
1152
  const customMutations = [];
1061
1153
  const customSubscriptions = [];
1062
1154
 
1155
+ // Dict of auth rules to be applied to custom types
1156
+ // Inherited from the auth configured on the custom operations that return these custom types
1157
+ const customTypeInheritedAuthRules: Record<
1158
+ string,
1159
+ Authorization<any, any, any>[]
1160
+ > = {};
1161
+
1063
1162
  const jsFunctions: JsResolver[] = [];
1064
1163
  const lambdaFunctions: LambdaFunctionDefinition = {};
1065
1164
  const customSqlDataSourceStrategies: CustomSqlDataSourceStrategy[] = [];
@@ -1071,7 +1170,8 @@ const schemaPreprocessor = (
1071
1170
 
1072
1171
  const staticSchema =
1073
1172
  schema.data.configuration.database.engine === 'dynamodb' ? false : true;
1074
- const topLevelTypes = Object.entries(schema.data.types);
1173
+
1174
+ const topLevelTypes = sortTopLevelTypes(Object.entries(schema.data.types));
1075
1175
 
1076
1176
  const { schemaAuth, functionSchemaAccess } = extractFunctionSchemaAccess(
1077
1177
  schema.data.authorization,
@@ -1116,7 +1216,15 @@ const schemaPreprocessor = (
1116
1216
  ),
1117
1217
  );
1118
1218
 
1119
- const authString = '';
1219
+ let customAuth = '';
1220
+ if (typeName in customTypeInheritedAuthRules) {
1221
+ const { authString } = mapToNativeAppSyncAuthDirectives(
1222
+ customTypeInheritedAuthRules[typeName],
1223
+ false,
1224
+ );
1225
+ customAuth = authString;
1226
+ }
1227
+
1120
1228
  const authFields = {};
1121
1229
 
1122
1230
  const fieldLevelAuthRules = processFieldLevelAuthRules(
@@ -1124,25 +1232,26 @@ const schemaPreprocessor = (
1124
1232
  authFields,
1125
1233
  );
1126
1234
 
1127
- const { gqlFields, models } = processFields(
1235
+ const { gqlFields, implicitTypes } = processFields(
1128
1236
  typeName,
1129
1237
  fields,
1130
1238
  authFields,
1131
1239
  fieldLevelAuthRules,
1132
1240
  );
1133
1241
 
1134
- topLevelTypes.push(...models);
1242
+ topLevelTypes.push(...implicitTypes);
1135
1243
 
1136
1244
  const joined = gqlFields.join('\n ');
1137
1245
 
1138
- const model = `type ${typeName} ${authString}\n{\n ${joined}\n}`;
1246
+ const model = `type ${typeName} ${customAuth}\n{\n ${joined}\n}`;
1139
1247
  gqlModels.push(model);
1140
1248
  } else if (isCustomOperation(typeDef)) {
1141
1249
  const { typeName: opType } = typeDef.data;
1142
1250
 
1143
1251
  const {
1144
1252
  gqlField,
1145
- models,
1253
+ implicitTypes,
1254
+ customTypeAuthRules,
1146
1255
  jsFunctionForField,
1147
1256
  lambdaFunctionDefinition,
1148
1257
  customSqlDataSourceStrategy,
@@ -1154,9 +1263,13 @@ const schemaPreprocessor = (
1154
1263
  getRefType,
1155
1264
  );
1156
1265
 
1266
+ mergeCustomTypeAuthRules(
1267
+ customTypeInheritedAuthRules,
1268
+ customTypeAuthRules,
1269
+ );
1157
1270
  Object.assign(lambdaFunctions, lambdaFunctionDefinition);
1158
1271
 
1159
- topLevelTypes.push(...models);
1272
+ topLevelTypes.push(...implicitTypes);
1160
1273
 
1161
1274
  if (jsFunctionForField) {
1162
1275
  jsFunctions.push(jsFunctionForField);
@@ -1204,7 +1317,7 @@ const schemaPreprocessor = (
1204
1317
 
1205
1318
  validateStaticFields(fields, authFields);
1206
1319
 
1207
- const { gqlFields, models } = processFields(
1320
+ const { gqlFields, implicitTypes } = processFields(
1208
1321
  typeName,
1209
1322
  fields,
1210
1323
  authFields,
@@ -1213,7 +1326,7 @@ const schemaPreprocessor = (
1213
1326
  partitionKey,
1214
1327
  );
1215
1328
 
1216
- topLevelTypes.push(...models);
1329
+ topLevelTypes.push(...implicitTypes);
1217
1330
 
1218
1331
  const joined = gqlFields.join('\n ');
1219
1332
  // TODO: update @model(timestamps: null) once a longer term solution gets
@@ -1255,7 +1368,7 @@ const schemaPreprocessor = (
1255
1368
  authFields,
1256
1369
  );
1257
1370
 
1258
- const { gqlFields, models } = processFields(
1371
+ const { gqlFields, implicitTypes } = processFields(
1259
1372
  typeName,
1260
1373
  fields,
1261
1374
  authFields,
@@ -1264,7 +1377,7 @@ const schemaPreprocessor = (
1264
1377
  partitionKey,
1265
1378
  transformedSecondaryIndexes,
1266
1379
  );
1267
- topLevelTypes.push(...models);
1380
+ topLevelTypes.push(...implicitTypes);
1268
1381
 
1269
1382
  const joined = gqlFields.join('\n ');
1270
1383
 
@@ -1521,7 +1634,8 @@ function transformCustomOperations(
1521
1634
 
1522
1635
  const {
1523
1636
  gqlField,
1524
- models,
1637
+ implicitTypes,
1638
+ customTypeAuthRules,
1525
1639
  lambdaFunctionDefinition,
1526
1640
  customSqlDataSourceStrategy,
1527
1641
  } = customOperationToGql(
@@ -1535,7 +1649,8 @@ function transformCustomOperations(
1535
1649
 
1536
1650
  return {
1537
1651
  gqlField,
1538
- models,
1652
+ implicitTypes,
1653
+ customTypeAuthRules,
1539
1654
  jsFunctionForField,
1540
1655
  lambdaFunctionDefinition,
1541
1656
  customSqlDataSourceStrategy,