Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AWS AppSync Event Subscription Filtering on Cognito User

I have the following Schema:

input CreateEventInput {
    userID: String!
    eventID: ID!
    type: String!
    data: String
    dateTime: AWSDateTime!
}

type Mutation {
    createEvent(input: CreateEventInput!): event
}

type Subscription {
    onCreateEvent(): event
        @aws_subscribe(mutations: ["createEvent"])

The createEvent resolver sets the userID like:

"key" : {
        "userID" : $util.dynamodb.toDynamoDBJson($context.identity.username),
        "eventID" : $util.dynamodb.toDynamoDBJson($util.autoId())
    }

I'd like to limit the subscription so that only records where the userID = $context.identity.username are returned to the user.

Does anyone know how to set this up? I think I need a resolver on the subscription, but I can't find a clear example of this where you have a Primary partition key (userID) and Primary sort key (eventID).

I would really appreciate any help or guidance. I can change the schema or DB if needed.

Update:

I believe I can set the Subscription Response Mapping Template to something like:

#if(${context.identity.username} != ${context.arguments.userID})
    $utils.unauthorized()
#else
##User is authorized, but we return null to continue
    null
#end

However, I'm at a loss what to put in the Request Mapping Template.

like image 899
Drew Avatar asked Mar 06 '23 04:03

Drew


1 Answers

I think the first step to filter subscriptions based on user is easiest done with a minor update to your schema, by breaking the 'input' shape into individual inputs to the mutation. Specifically:

type mutation {
  createEvent(userID: String!, eventID: ID!, type: String!, 
    data: String, dateTime: AWSDateTime!): event
}
... other stuff...
type Subscription {
  onCreateEvent(userId: String!): event
  @aws_subscribe(mutations: ["createEvent"])
}

A few notes on this:

1) This assumes you want that to be a requirement on the subscription. If not, if you want it to be an optional rule, remove the !. By your comment, I believe you would want it.

2) Subscription filters (which is what the userId parameter is in the subscription operation) require that the filters be in the response for the mutation. So be sure that when you're defining the operation on your client, you include userId in the response there.

3) This is required to apply the subscription filter. The service won't know what a userId is unless it's a direct input to the mutation, having it inside and input shape won't work.

Now, as far as nailing down that a user can't just subscribe to someone else's username. I believe you were looking at this docs page. This will work, is totally valid, and could be completed with the something close to the example in that docs page, but it's based around having a permissions lookup table and a Dynamo resolver. If you don't have one or would prefer to avoid using one, a little adjustment should be able to make it work with a none/local resolver. Without a permissions table or anything to check against, I'd strongly recommend a local/none resolver.

Specifically, I believe you could move what you have in the response mapping template to your new none/local resolver's mapping template...

#if(${context.identity.username} != ${context.arguments.userID})
    $utils.unauthorized()
#else
##User is authorized, but we return null to continue
    null
#end

...and have the response mapping template be a default response, then you'd have it without unnecessary infrastructure in a permissions table or dead code that sets up a dynamo interaction that does not happen. Instead, all this will do is check the username in the input against the username in the Cognito token.

like image 83
Jeff Bailey Avatar answered Apr 07 '23 16:04

Jeff Bailey