Closed defish1962 closed 11 months ago
Update: It appears that the DataStore.save isn't working because the currentGroup value is not being retrieved from the claims identity. If I run the mutation in the AppSync console, the claims.identity.custom:currentGroup value is populated. When I edit the ToDo and do a DataStore.save in the app, the resolver is showing the value of the claims.identity.custom:currentGroup = null.
User pool authentication is being used in both cases, so I don't understand why the currentGroup custom attribute isn't available to the resolver.
OK, the AppSync console passes the id token to the AppSync API and therefore the resolvers can access any custom attributes added to the Cognito user pool. Apparently the DataStore passes the access token, which does not contain the custom attributes.
Hi @defish1962 thanks for raising this issue and apologies for the delay 🙏
You are correct, Amplify does not explicitly support multi-tenant use cases out of the box. It typically requires extra configuration, advanced workflows, and workarounds.
However, the issues you mentioned should be addressable:
the AppSync console passes the id token to the AppSync API and therefore the resolvers can access any custom attributes added to the Cognito user pool. Apparently the DataStore passes the access token, which does not contain the custom attributes.
This is true, but you can configure the graphql_headers
of the API category to send the ID token instead. I just tested this to confirm and it does apply to DataStore graphql requests as well.
Amplify.configure({
...awsConfig,
API: {
...awsConfig,
graphql_headers: async () => {
const session = await Auth.currentSession();
return {
Authorization: session.getIdToken().getJwtToken(),
};
},
},
});
You can't use the OwnersField with the DataStore and you have to write custom resolvers to implement fine grained security
DataStore does support custom ownerFields. I just tried with this schema and front end code:
type Todo @model @auth(rules: [{ allow: owner, ownerField: "author" }]) {
id: ID!
name: String!
description: String
author: String!
}
you can use either the format of sub::username
or just the username
. Both seem to work and sync just fine for separate users. The records locally will only show the username.
const createTodo = async () => {
const sub = user.getSignInUserSession()?.getAccessToken().payload.sub;
console.log(sub);
try {
const todo = await DataStore.save(
new Todo({
name: "test",
author: user.username,
})
);
console.log(todo);
} catch (error) {
console.error(error);
}
};
createTodo request
createTodo response
What issues were you having with using a custom ownerField
in a DataStore-enabled API?
Hey 👋 , This issue is being closed due to inactivity. If you are still experiencing the same problem and need further assistance, please feel free to leave a comment. This will enable us to reopen the issue and provide you with the necessary support.
Amplify CLI Version
12.0.0
Question
I added a function to the MutationUpdateToDo resolver pipeline and now when I call the DataStore.save function in my app, the changes aren't written to DynamoDB.
I created a Lambda function to validate the user in a multi-tenant environment and am calling it in a request mapping template in an AppSync function I created and added to the pipeline resolver. This is to prevent someone from modifying data that doesn't belong to their associated group from outside the app (groupID is a custom attribute in Cognito and pulled from the JWT Token to compare with the groupID on the row they are trying to access).
Now, when I change the ToDo item in the app, the change does not get written to the DynamoDB table. I understand that modified pipelines aren't really supported with the DataStore, but I think there should be a way to add a function that doesn't modify data, and only authorizes the user to continue, or prevents them from continuing the action, without messing up the DataStore's behavior.
Here is my request mapping template:
`#* The value of 'payload' after the template has been evaluated will be passed as the event to AWS Lambda. # $util.log.info("In the Request mapping template!")
{ "operation": "Invoke", "payload": { "version": "2018-05-29", "operation": "Invoke", "payload": { "tableName": "$ctx.stash.get('tableName')", "primaryKey": { "id": "$ctx.args.input.id" }, "groupID": "$ctx.identity.claims.get('custom:currentGroup')" } } } `
and here is the response mapping template:
`## Raise a GraphQL field error in case of a datasource invocation error
if($ctx.error)
$util.error($ctx.error.message, $ctx.error.type)
end
$util.log.info("In the response mapping template")
if($context.result)
else
end
$util.toJson($context.result)`
I placed the new function at the beginning of the pipeline. It is working exactly as expected when the updateToDo mutation is called from the AppSync console.
Any suggestions or pointers would be greatly appreciated.