Skip to content

Commit

Permalink
feat: feature/@key (aws-amplify#1463)
Browse files Browse the repository at this point in the history
* Initial @key implementation

* Adding support for GSI/LSIs with @key.

* Adding support for create, update, delete, & get with updated index structures. Still need to update list as well as implement the new query resolver using existing parts. The last change is to handle the partial key update scenario in update operations.

* Adding support for listX.

* Adding custom-index test steps and fixing lint issue

* Updating testing steps

* Update testing-custom-indexes.md

* Updating instructions

* Adding @auth support for @key queryFields and adding more tests

* pulling testing instruction updates

* Rearranging for logical consistency

* Fixing compilation errors

* fixing compilation errors

* Fixing TSC errors

* Moving to new sort key structure and fixing tests

* Fixing circle ci issue

* Fixing test

* Fixing circleci build error

* Adding updated auth snapshots

* Adding update mutation validation to secondary keys to protect composite sort key integrity

* Fixing list with filter operations

* Updating snapshots

* Addressing PR comments
  • Loading branch information
mikeparisstuff authored and kaustavghosh06 committed May 29, 2019
1 parent cb6f438 commit 00ed819
Show file tree
Hide file tree
Showing 31 changed files with 3,211 additions and 286 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const SearchableModelTransformer = require('graphql-elasticsearch-transformer').
const VersionedModelTransformer = require('graphql-versioned-transformer').default;
const FunctionTransformer = require('graphql-function-transformer').default;
const HTTPTransformer = require('graphql-http-transformer').default;
const KeyTransformer = require('graphql-key-transformer').default;
const providerName = require('./constants').ProviderName;
const TransformPackage = require('graphql-transformer-core');

Expand Down Expand Up @@ -211,10 +212,13 @@ async function transformGraphQLSchema(context, options) {
const transformerList = [
new DynamoDBModelTransformer(getModelConfig(project)),
new ModelConnectionTransformer(),
new ModelAuthTransformer({ authMode }),
new VersionedModelTransformer(),
new FunctionTransformer(),
new HTTPTransformer(),
new KeyTransformer(),
// TODO: Build dependency mechanism into transformers. Auth runs last
// so any resolvers that need to be protected will already be created.
new ModelAuthTransformer({ authMode }),
];

if (usedDirectives.includes('searchable')) {
Expand Down
1 change: 1 addition & 0 deletions packages/amplify-provider-awscloudformation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"graphql-dynamodb-transformer": "3.7.0",
"graphql-elasticsearch-transformer": "3.6.0",
"graphql-function-transformer": "1.0.2",
"graphql-key-transformer": "^1.0.0",
"graphql-http-transformer": "3.4.6",
"graphql-transformer-common": "3.7.0",
"graphql-transformer-core": "3.6.3",
Expand Down
30 changes: 26 additions & 4 deletions packages/graphql-auth-transformer/src/ModelAuthTransformer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Transformer, TransformerContext, InvalidDirectiveError, gql } from 'graphql-transformer-core'
import { Transformer, TransformerContext, InvalidDirectiveError, gql, getDirectiveArguments } from 'graphql-transformer-core'
import GraphQLAPI from 'cloudform-types/types/appSync/graphQlApi'
import { ResourceFactory } from './resources'
import { AuthRule, ModelQuery, ModelMutation, ModelOperation } from './AuthRule'
Expand Down Expand Up @@ -186,6 +186,7 @@ export class ModelAuthTransformer extends Transformer {
this.protectGetQuery(ctx, ResolverResourceIDs.DynamoDBGetResolverResourceID(def.name.value), queryRules.get)
this.protectListQuery(ctx, ResolverResourceIDs.DynamoDBListResolverResourceID(def.name.value), queryRules.list)
this.protectConnections(ctx, def, operationRules.read)
this.protectQueries(ctx, def, operationRules.read)
}

public field = (
Expand Down Expand Up @@ -834,12 +835,11 @@ All @auth directives used on field definitions are performed when the field is r
*/
private protectConnections(ctx: TransformerContext, def: ObjectTypeDefinitionNode, rules: AuthRule[]) {
const thisModelName = def.name.value;
const connectionResolvers = {};
for (const inputDef of ctx.inputDocument.definitions) {
if (inputDef.kind === Kind.OBJECT_TYPE_DEFINITION) {
for (const field of inputDef.fields) {
const returnTypeName = getBaseType(field.type)
if (hasDirective(field, 'connection') && returnTypeName === thisModelName) {
if (fieldHasDirective(field, 'connection') && returnTypeName === thisModelName) {
const resolverResourceId = ResolverResourceIDs.ResolverResourceID(inputDef.name.value, field.name.value)
if (isListType(field.type)) {
this.protectListQuery(ctx, resolverResourceId, rules)
Expand All @@ -852,6 +852,26 @@ All @auth directives used on field definitions are performed when the field is r
}
}

/**
* When read operations are protected via @auth, all secondary @key query resolvers will be protected.
* Find the directives & update their resolvers with auth logic
*/
private protectQueries(ctx: TransformerContext, def: ObjectTypeDefinitionNode, rules: AuthRule[]) {
const secondaryKeyDirectivesWithQueries = (def.directives || []).filter(d => {
const isKey = d.name.value === 'key';
const args = getDirectiveArguments(d);
// @key with a name is a secondary key.
const isSecondaryKey = Boolean(args.name);
const hasQueryField = Boolean(args.queryField);
return isKey && isSecondaryKey && hasQueryField;
});
for (const keyWithQuery of secondaryKeyDirectivesWithQueries) {
const args = getDirectiveArguments(keyWithQuery);
const resolverResourceId = ResolverResourceIDs.ResolverResourceID(ctx.getQueryTypeName(), args.queryField);
this.protectListQuery(ctx, resolverResourceId, rules)
}
}

private getOwnerRules(rules: AuthRule[]): AuthRule[] {
return rules.filter(rule => rule.allow === 'owner');
}
Expand All @@ -866,12 +886,14 @@ All @auth directives used on field definitions are performed when the field is r

}

function hasDirective(field: FieldDefinitionNode, directiveName: string): boolean {
function fieldHasDirective(field: FieldDefinitionNode, directiveName: string): boolean {
return field.directives && field.directives.length && Boolean(field.directives.find(
(d: DirectiveNode) => d.name.value === directiveName
))
}



function isTruthyOrNull(obj: any): boolean {
return obj || obj === null;
}
Expand Down
Loading

0 comments on commit 00ed819

Please sign in to comment.