aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.41k stars 2.12k forks source link

DataStore with @auth - subscriptionError ... MissingFieldArgument: Missing field argument editors #7069

Open hmisonne opened 3 years ago

hmisonne commented 3 years ago

Describe the bug

I built a React Native App and I am following the amplify docs to add owner authorization. I am using withAuthenticator to register and sign in users through AWS Cognito.

Adding an @auth rule to a model with multiple editors will throw a subscription Error :

DataStore - subscriptionError Connection failed: {"errors":[{"message":"Validation error of type MissingFieldArgument: Missing field argument editors @ 'onCreateDraft'"}]}

Having just one owner won't generate any errors.

To Reproduce Steps to reproduce the behavior:

  1. Add @auth parameter to a graphQL model
type Draft @model
  @auth(rules: [
    { allow: owner }
  ]) {
  id: ID!
  title: String!
  content: String
  owner: String
}
  1. Update @auth parameter with multiple editors
type Draft @model
  @auth(rules: [
     { allow: owner, ownerField: "editors", operations: [update, read] }
  ]) {
  id: ID!
  title: String!
  content: String
  owner: String
  editors: [String]
}
  1. Run amplify update api , amplify push, npm run amplify-modelgem

  2. Run the app locally and see the error (I am using a "GroceryList" model instead of "Draft"): image

Expected behavior I am expecting the app to run without errors

Code Snippet

type Product @model 
 @auth(rules: [
    { allow: owner },
  ]){
  id: ID!
  groceryList: GroceryList @connection(name: "GroceryListProducts")
  name: String!
  checked: Boolean!
  unit: String!
  quantity: Int!
  category: String!
}

type GroceryList @model 
 @auth(rules: [
     { allow: owner, ownerField: "editors", operations: [update, read] },
  ]){
  id: ID!
  name: String!
  description: String
  products: [Product] @connection(name: "GroceryListProducts")
  owner: String
  editors: [String]
}
import React from "react";
import Amplify from "aws-amplify";
import config from "./aws-exports";
import { withAuthenticator } from "aws-amplify-react-native";

Amplify.configure({
  ...config,
  Analytics: {
    disabled: true,
  },
});

const App = () => {
  return (
     ....
  );
};

export default withAuthenticator(App);

What is Configured?

Output of running npx envinfo --system --binaries --browsers --npmPackages --npmGlobalPackages

System:
    OS: Windows 10 10.0.18362
    CPU: (8) x64 Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz
    Memory: 5.38 GB / 15.78 GB
  Binaries:
    Node: 12.16.2 - C:\Program Files\nodejs\node.EXE
    npm: 6.14.4 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Chrome: 86.0.4240.111
    Edge: Spartan (44.18362.449.0)
    Internet Explorer: 11.0.18362.1
  npmPackages:
    @babel/core: ~7.9.0 => 7.9.6
    @iconify/icons-mdi: ^1.0.142 => 1.0.142
    @iconify/react: ^1.1.3 => 1.1.3
    @react-native-community/masked-view: 0.1.10 => 0.1.10
    @react-native-community/netinfo: 5.9.6 => 5.9.6
    @react-native-community/picker: 1.6.6 => 1.6.6
    @react-navigation/bottom-tabs: ^5.9.1 => 5.9.1
    @react-navigation/native: ^5.7.5 => 5.7.5
    @react-navigation/stack: ^5.9.2 => 5.9.2
    aws-amplify: ^3.3.2 => 3.3.2
    aws-amplify-react-native: ^4.2.7 => 4.2.7
    babel-polyfill: ^6.26.0 => 6.26.0
    expo: ~39.0.2 => 39.0.3
    expo-font: ^8.3.0 => 8.3.0
    expo-splash-screen: ^0.6.2 => 0.6.2
    expo-status-bar: ~1.0.2 => 1.0.2
    ini: ^1.3.5 => 1.3.5
    inquirer: ^6.5.1 => 6.5.2
    jest: ^26.4.2 => 26.4.2
    react: 16.13.1 => 16.13.1
    react-dom: 16.13.1 => 16.13.1
    react-native: https://github.com/expo/react-native/archive/sdk-39.0.2.tar.gz => 0.63.2
    react-native-gesture-handler: ~1.7.0 => 1.7.0
    react-native-reanimated: ~1.13.0 => 1.13.1
    react-native-safe-area-context: 3.1.4 => 3.1.4
    react-native-screens: ~2.10.1 => 2.10.1
    react-native-web: ~0.13.12 => 0.13.14
    react-redux: ^7.2.1 => 7.2.1
    react-redux-loading-bar: ^5.0.0 => 5.0.0
    react-test-renderer: ^16.13.1 => 16.13.1
    redux: ^4.0.5 => 4.0.5
    redux-thunk: ^2.3.0 => 2.3.0
  npmGlobalPackages:
    @aws-amplify/cli: 4.31.1
    @ionic/cli: 6.7.0
    bower: 1.8.8
    create-react-native-app: 3.1.0
    exp: 57.2.1
    expo-cli: 3.27.12
    express-generator: 4.16.1
    gulp: 4.0.2
    sequelize-cli: 6.1.0
    serverless: 1.71.3
    sharp-cli: 1.14.1
    wscat: 4.0.0

Environment:

amhinson commented 3 years ago

Thanks for opening the issue with excellent details @hmisonne! Unfortunately, having an array of owners with DataStore isn't currently supported due to how AppSync handles owner based auth with an array of owners and how the subscriptions need to be created. This is a limitation we're aware of, and we are currently working on updating the documentation to more accurately reflect any potential limitations you might run into with DataStore right now like this.

With that said, I'd recommend exploring static group authorization to see if you are able to address your use case in a similar way.

We will continue to track this issue and provide any updates here.

hmisonne commented 3 years ago

Thanks @amhinson, I looked at the static group authorization, but unfortunately it won't work for my use case.

I really hope that this new feature will be available soon!

giulio-dds commented 3 years ago

Unfortunately I ran into the same problem. I have a multi-tenant application that should allow data editing for some users dynamically. By design, there is no way to achieve this functionality with static groups because the authorization changes from record to record.

Do you recommend to leave the Datastore way for now @amhinson ?

undefobj commented 3 years ago

@hmisonne @giulio-dds can you give us more specifics on your use cases? This will help us give feedback to the AppSync service team so that we can better convey the requirements into their roadmap for 2021.

hmisonne commented 3 years ago

Hi @undefobj ,

In my use case, I am building a Grocery List App for shoppers who want to share and update a grocery list. It is the same concept as Tricount or Slitwise where users share expenses and can see live updates.

Having just one owner is not compatible with my concept since I need multiple users to get access to one grocery list. Not having the @auth directive is not ideal since the users will receive the data from the entire database.

The only way I found to work around this issue was to use SelectiveSync, but it comes with a lot of challenges:

giulio-dds commented 3 years ago

Hi @undefobj , we are developing a browser based ERP for transport logistics in Amplify + React.js. ERP owners (i.e. carriers) can receive orders from their customers through an app (Amplify + React Native) which allows read, writing and updating of shipment information recorded on an Orders table in DynamoDB.

Customers and carriers must be able to update the status and details of orders, based on the flow of logistics during transport (tracking, customer feedback, courier notifications, goods transported, invoicing, etc.). Therefore, both the customer and the carrier must be able to update the information on an order.

It would be easy with static groups if the ERP were for a single company, however in our case we have a network of carriers (with their customers) and the informations recorded in the table must necessarily be dynamic with multiple level of authorization to optimize the access and security of the data.

In a few words, all carrier's tenants (managed by cognito group auth) and customer specific user, have access to the order record and in both cases they can perform read, write and update operations.

abdielou commented 3 years ago

@giulio-dds @hmisonne

I ran into the same issue and implemented a workaround.

You can see the details here https://github.com/aws-amplify/amplify-cli/issues/4794#issuecomment-677935432

In a gist, you need an alternative to get your notifications somewhere else (since it's impossible to subscribe based on an array filter), and you need to hack/modify the DataStore to inject incoming messages from your alternate subscription.

I have a version of the DataStore that allows passing in an Observable object. This custom observable serves as a replacement of the failing subscriptions due to the @auth array filed. The client is then responsible for passing in the events that will mutate the DataStore indexedDB. I have a model with an array based ownership (like your "editors"), and I am using the DataStore just fine.

jazwiecki commented 3 years ago

If it helps get this prioritized, I'll add my use case, which seems to be roughly similar to some of the other ones listed here: near-real time sync of information stored in one instance of an object with a group of users, only one of whom has Create/Delete permission. The expected number of groups will exceed the number supported by Cognito, so my understanding is that I have to use a [String] ownerfield for owners with Read and Update. Going to see if I can re-work it to get around this.

hanna-becker commented 3 years ago

This is also a limitation for my use case. We're creating a tool where groups of people collaborate on projects. It works fine so far with the non-Datastore API (String array of userIDs per project as owner field), but we were really keen on upgrading to Datastore for the offline features, cascading mutations, more intuitive API, etc. We'll be really happy when this becomes available. :)