Search code examples
graphqlaws-amplify

Lambda resolver not populating graphql field


I am trying to setup a graphql API using AWS Amplify which creates a notes system in which any authenticated user can create a note, as well as read any note created by a user that shares a cognito group with them.

I can see in the corresponding dynamoDB that the groups field is not being set, meaning that my fetch requests are returning an empty list.

I am using amplify gen 1 v6, and have been using these docs as reference, but am stuck on why it's not working and can't find any errors to go off.

I began with the schema:

type Todo @model @auth(rules: [
    { allow: groups, groupsField: "group"}
]) {
  id: ID!
  text: String!
  groups: [String] @function(name: "setGroupField-${env}")
}

but was getting authentication errors when creating, and so changed it to:

type Todo @model @auth(rules: [
    { allow: groups, groupsField: "group", operations: [update, read] }
    { allow: owner, operations: [create] }
]) {
  id: ID!
  text: String!
  groups: [String] @function(name: "setGroupField-${env}")
}

that fixed the authentication error when creating. I have also tried setting groups as a required field, and recieved errors that the create request did not fulfill the schema requirements.

I have the following setGroupField lambda resolver in both cases:

import json
import boto3

def handler(event, context):
    # Extract the username and groups from the identity information
    identity = event['identity']
    groups = identity['claims'].get('cognito:groups', [])
    
    print(groups)
    # Check if the user belongs to a group
    if not groups:
        raise Exception("User is not part of any group.")
    
    return groups

The print statement in the resolver has verified that it is successfully getting the correct groups.

Update:

I have tried using the Amplify generated UI components, which give the user the ability to set the group field (not something I want, but thought it would be worth a try) and it is now setting the group field, but to the user given value, not that returned by the lambda resolver.


Solution

  • I re-read through the new docs and noticed the statement:

    While @model automatically generates dedicated "create", "read", "update", "delete", and "subscription" queries or mutations for you, there are some cases where you want to define a stand-alone query or mutation.

    Changing my schema and lambda to the following creates the expected behaviour:

    type Todo 
        @model(mutations: 
            { create: null, update: null, delete: null }
        )
        @auth(rules: [
            { allow: groups, groupsField: "group", operations: [read] }
            { allow: owner, operations: [create] }
        ])
    {
      id: ID!
      text: String!
      group: String!
      owner: String!
      createdAt: AWSDateTime
    }
    
    input createTodoInput {
        text: String!
        group: String!
    }
    
    type Mutation {
        createTodoSecure(input: createTodoInput): Todo @function(name: "setGroupField-${env}")
    }
    
    import json
    import boto3
    from datetime import datetime
    import uuid
    import os
    
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table(os.environ['API_ACTIONSTAKEN_TODOTABLE_NAME'])
    
    def handler(event, context):
        # Extract the username and groups from the identity information
        identity = event['identity']
        groups = identity['claims'].get('cognito:groups', [])
        username = identity['username']
    
        print(event)
        
        # Check if the user belongs to a group
        if not groups:
            raise Exception("User is not part of any group.")
        
        # Extract input fields
        input_data = event['arguments']['input']
        text = input_data['text']
        group = input_data['group']
        
        # Ensure the user is part of the specified group
        if group not in groups:
            raise Exception("User is not authorized to create a todo in this group.")
        
        # Generate a unique ID for the new todo item
        todo_id = str(uuid.uuid4())
        
        # Prepare the item
        item = {
            'id': todo_id,
            'text': text,
            'group': group,
            'owner': username,
            'createdAt': datetime.utcnow().isoformat(timespec='seconds') + 'Z',
            'updatedAt': datetime.utcnow().isoformat(timespec='seconds') + 'Z',
            '_lastChangedAt': 0,
            '_version': 1,
        }
    
        # Write the item to DynamoDB
        table.put_item(Item=item)
        
        # Return the ID of the created todo item
        return item