Authorize based on field value in another document in AppSync GraphQL custom authentication via Lambda Resolver - graphql

I am new to Amplify Datastore & AppSync w/ GraphQL, but in Firestore, you can write an auth rule like: allow delete: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true [https://firebase.google.com/docs/firestore/security/rules-conditions#access_other_documents] -> which would grab the document at /users/{id}/ and I can access the admin field to write a security rule logic.
How do you do the same in a Lambda function resolver?
I am aware that the solution may involve a Lambda resolver (https://stackoverflow.com/a/68581796/9824103) but I cannot find any reference to reading a specific document and doing logic to authorize or deny an operation based on a field value in a document. I am only asking how to do this specific thing. Thank you!
I followed https://docs.amplify.aws/cli/graphql/authorization-rules/#custom-authorization-rule to create a custom authorization rule via adding the #rule directive: type MyModel #model #auth(rules: [{ allow: custom }]) and.. although the lambda function isn't really getting called when I try to write a listMyModel or createMyModel (any hints as to why that would be great), I am focused on writing the lambda function to read query a document and check a certain field to meet my custom auth condition.
fyi, I am using Flutter based amplify-cli.

Related

Is it possible to map a subscription parameter to an array at the mutation output?

I have a theoretical question. As I know subscription parameters must exist as a field in the returning type of the mutation. This means that the type of parameter must also match the type of the field in the returning object of the mutation. Am I right? Suppose I get an array with channels ids in the mutation response. I only send one channel id as a parameter in the subscription. Is it possible to map a subscription parameter to an array at the mutation output? If the channel id exists in the array (field channelsIds), the subscription must work. Is it possible to write this logic in the scheme itself, or is it technically impossible?
GraphQL schema:
schema {
mutation: Mutation
subscription: Subscription
}
type Mutation {
testMutation(input: TestMutationInput): TestMutationOutput
}
type TestMutationOutput {
channelsIds: [String!]!
userId: String!
userEmail: String
userPhoneNumber: String
}
type Subscription {
watchTestMutation(channelId: String!): TestMutationOutput
#aws_subscribe(mutations: ["testMutation"])
}
If I understand you correctly you want to filter based on if the mutation's returned value is in an array that is passed as an argument to the subscription. Sorry to say that is not possible at this time. Subscription filters only evaluate to true or false and cannot accommodate any logic other than that.
At the end of October 2020, I contacted AWS support for advice on this issue. I think this answer may be useful to someone, so I post their answer.
Please allow me to inform you that the use-case that you have
mentioned in the case is currently not possible via AppSync. I
understand that the lack of the feature may be causing inconvenience.
There is an internal feature request already with the AppSync team to
incorporate this feature and I have added a +1 on your behalf. It is
worth noting, that once this feature request is with the team, it will
be up to the team as to if/when this potential infrastructure feature
is implemented, and because of the limited visibility into the
progress of internal development processes, I won’t be able to provide
an ETA regarding its release. I would request you to keep an eye on
the what's new page or the AWS Blogs as all new feature requests and
enhancements are posted there[1-3].
However we can suggest a couple of workarounds in this case:
Filter the required fields on client side itself after receiving the values on the client-side from AppSync.
If the values to be filtered are very limited we can use a fake mutation made with the help of a resolver mapped to “None” Data
source. In this flow, we would create a lambda function that uses a
DynamoDB stream as the trigger. The Lambda function is triggered
whenever there's an update to the DynamoDB table.

We can then include logic in the Lambda function to filter the
required fields and perform a mutation to AppSync. In AppSync, the
mutation which was called by lambda would configured using a resolver
mapped to a “None” Data source. The None data source type passes the
request mapping template directly to the response mapping template.
And when we subscribe to this mutation, we will directly get the
filtered data from Lambda that was used to call this mutation. Please
refer to [4] for a step-by-step description of this process.
But please note that this workaround is cumbersome and would require a lot of changes if the required field values keep changing. Workaround 1(handling it on the client-side) is usually the preferred way to handle this use-case.
Resources:
[1] https://blogs.amazon.com/
[2] https://aws.amazon.com/new/
[3] https://aws.amazon.com/releasenotes/
[4] https://aws.amazon.com/premiumsupport/knowledge-center/appsync-notify-subscribers-real-time/

How to get the current user from a Lambda resolver in AWS Amplify (GraphQL api and NodeJS Lambda)?

I have a graphql Api and added a lambda function to resolve a mutation.
type Mutation {
addTeamMember(email: String!, teamId:ID!): String #function(name: "add-team-member-${env}")
}
From the lambda I want to retrieve the authenticated user that sent the request to perform additional validations, ¿How to retrieve it from the request information?
strong text
I printed the contents of the first lambda parameter and found it:
Access it with:
event.identity.username

aws amplify graphql computed field inline resolver

For complex field resolver, I know #function directive is the way to go.
But how about those very simple computed fields. #function directive with lambda is a little too much. I see prisma has inline javascript function supported. see the answer of similar quest.
Not sure whether aws-amplify graphql support inline function.
You can override the default VTL resolver for the field, just adding the logic you want.
This article goes into detail on a simple use case (similar to yours I think)
Just a few steps
Add the field to your schema
Build your API
Look for the auto-generated resolver (amplify/backend/api/client/build/resolvers) The naming convention is straight-forward.
Copy it to amplify/backend/api/client/resolvers
Change it as needed
Push your changes to Amplify
In the article he has just added a new set item
## [Start] Prepare DynamoDB PutItem Request. **
$util.qr($context.args.input.put("createdAt", $util.time.nowISO8601()))
$util.qr($context.args.input.put("updatedAt", $util.time.nowISO8601()))
# The next line was added
$util.qr($context.args.input.put("active", false))
AWS has some tutorials over VTL that you might want to take a look.
And Amplify has more docs on custom resolvers using VTL

Verify graphql query

I'm building a simple platform using graphql as api gateway and a frontend that send some queries to this api, I'm blocked on how can I validate a query before run it to avoid malicious query to be ran. I was thinking to use persistgraphql but I just noticed that is now archived so I'm not sure if it's a good idea to use it, the second problem is that the api and the frontend are in 2 different repo so I didn't find yet a solution to whitelisting the query in the frontend and use this whitelist in the api...what's the best solution to whitelist a query with graphql?
If your concern is limiting access to certain fields based on who is making the request, then you should implement some kind of authorization strategy. You can populate the context with information about the logged in user and then use this information inside your resolvers for the fields you want to protect to determine whether the value of the field should be returned or not.
const resolvers = {
User: {
somePrivateField: (user, args, ctx) => {
// Make sure the request is from a logged in user and the user making the
// request is the same as the requested user OR the user is an admin
if (ctx.user && ( ctx.user.id === user.id || ctx.user.isAdmin )) {
return user.somePrivateField
}
// throw an error or just return null or undefined to resolve the field to
// null in the event authorization fails
}
}
}
More sophisticated strategies are possible using directives or existing libraries like graphql-shield.
Of course, certain fields that may exist on your database model -- like passwords -- should probably never be exposed in your API in the first place.

Can API-Gateway check the input parameters and reject (without passing it to Lambda) an HTTP request?

I would like to use API-Gateway (plus Lambda) to realize a PetStore Restful API. One of the API entry point is and the body of the POST request needs to contain 'name'. See the PetStore definition snippet below:
/pets/
POST
definitions:
NewPet:
required:
- name
properties:
name:
type: string
tag:
type: string
Can I make API-Gateway smart enough to reject requests that does not contain the required field (in this case, 'name'), without calling the Lambda (yes, I understand I am able to check the input fields inside the Lambda function, but I wonder if I can avoid it)? -- this would save a lot of time and complexity to deal with the corner case.
This can be achieved through using request validation set in the "method request" settings.
If you want to validate the request body rather than query string you need to create a model;
APIs > {ApiName} > Models
In the sidebar.
Create your model using JSON Schema
In the API Gateway, choose to validate the request body then select your created model in the dropdown.
API Gateway config page

Resources