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.43k stars 2.13k forks source link

Datastore not syncing to dynamo on first attempt & DataStore not allowing public user assuming IAM role #5632

Closed ryanweaver718 closed 4 years ago

ryanweaver718 commented 4 years ago

I have not been able to get DS working on two entirely separate production apps. I have however been able to get it functional using a test application given the following schema.

type User @auth(rules: [{ allow: groups, groups: ["Admin"] }, { allow: owner, ownerField: "owner" }]) @model {
  id: ID!
  owner: String!
  email: String!
  orders: [Order] @connection(name: "UserOrders")
}

type Order @auth(rules: [{ allow: groups, groups: ["Admin"] }, { allow: owner, ownerField: "owner" }]) @model {
  id: ID!
  owner: String!
  cost: Int!
  status: String!
  user: User @connection(name: "UserOrders")
}

type Test @model {
  id: ID!
  test: String!
}

But datastore does not work properly when using this schema:

type User
  @auth(rules: [{allow: owner}, {allow: groups, groups: ["Admin"]}])
  @key(fields: ["email"])
  @model {
  email: String!
  name: String
  owner: String!
  phone: String
  ownedAuthorities: [String]
  orders: [Order] @connection(name: "UserOrders")
}

type Order
  @searchable
  @auth(
    rules: [
      {allow: groups, groups: ["Admin"]}
      {allow: owner, ownerField: "owner", operations: [read]}
    ]
  )
  @model(subscriptions: {level: off}) {
  id: ID!
  user: User @connection(name: "UserOrders")
  date: String!
  datetime: String!
  owner: String!
  items: [OrderItem] @connection(name: "OrderItems")
  authorities: [String]
  amount: Int!
  refundAmount: Int!
  email: String
  stripeId: String
  status: String
}

type OrderItem
  @auth(
    rules: [
      {allow: groups, groups: ["Admin"]}
      {allow: owner, ownerField: "owner", operations: [read]}
    ]
  )
  # @key(name: "byStatusByDate", fields: ["status", "date"])
  @model {
  id: ID!
  mcNumber: String
  date: String!
  type: String!
  owner: String!
  status: String!
  cost: Int!
  mcRequestNumber: String
  grantDate: String
  applicantName: String
  email: String
  userName: String
  backupContact: String
  specialInstructions: String
  order: Order @connection(name: "OrderItems")
}

type Contact
  @model
  @auth(
    rules: [
      {allow: groups, groups: ["Admin"], operations: [read, create, update, delete]}
      {allow: public, provider: iam, operations: [create]}
      {allow: groups, groups: ["everyone"], operations: [create]}
    ]
  ) 
  {
  id: ID!
  name: String!
  email: String!
  content: String!
}

type Authority
  @searchable
  @auth(
    rules: [
      {allow: groups, groups: ["Admin"], operations: [read, create, update, delete]}
      {allow: public, provider: iam, operations: [read]}
      {allow: groups, groups: ["everyone"], operations: [read]}
    ]
  )
  @model {
  id: ID!
  authorityDate: String!
  dot: String
}

It syncs fine locally. If I create a mutation using the API.graphql it writes to dynamo with all of the DataStore fields (ie: typenmae, version, ___lastChangedAt). But when writing the following code I can sync locally but not to dynamo.

import {Order, User, Contact} from '../../models';
import {API, Auth, DataStore} from 'aws-amplify';
....
const ds = async () => {
    try {
      const owner = (await Auth.currentAuthenticatedUser()).username;
      const user = await DataStore.save(
        new User({
          email: 'test@email.com',
          name: 'testing',
          owner,
        }),
      );
      console.log(user);

      const contact = await DataStore.save(
        new Contact({
          name: 'TEST CONTACT',
          email: 'test@email.com',
          content: 'TEST CONTENT',
        }),
      );
      console.log(contact);
    } catch (e) {
      console.log(e);
    }

I have read through various threads and have tried the following: 1.) Removing all Auth on the contact schema and testing that way, still doesn't sync to the cloud. 2.) Pull down and rebuild the api twice. 3.) Run through the update to make sure Auto Merge has been selected, and then also trying optimistic concurrency to see if there has been a change. 4.) Attempt to create the models using both "amplify codegen models" and I also tried installing amplify-app and running "npm run amplify-modelgen" 5.) Importing DataStore from the latest aws-amplify, also tried importing from here @aws-amplify/datastore

here is a list of my dependencies

    "dependencies": {
        "@aws-amplify/datastore": "^2.0.9",
        "@date-io/date-fns": "1.3.13",
        "@material-ui/core": "^4.9.5",
        "@material-ui/icons": "^4.9.1",
        "@material-ui/lab": "^4.0.0-alpha.49",
        "@material-ui/pickers": "^3.2.10",
        "@testing-library/jest-dom": "^4.2.4",
        "@testing-library/react": "^9.3.2",
        "@testing-library/user-event": "^7.1.2",
        "aws-amplify": "^3.0.9",
        "aws-amplify-react": "^3.1.7",
        "aws-appsync": "^3.0.2",
        "axios": "^0.19.2",
        "clsx": "^1.1.0",
        "date-fns": "^2.11.1",
        "dotenv": "^8.2.0",
        "lodash": "^4.17.15",
        "moment": "^2.24.0",
        "react": "^16.13.0",
        "react-dom": "^16.13.0",
        "react-drop-zone": "^3.0.7",
        "react-dropzone": "^10.2.1",
        "react-google-button": "^0.7.1",
        "react-responsive-picture": "^3.2.2",
        "react-router-dom": "^5.1.2",
        "react-scripts": "^3.4.0",
        "react-stripe-checkout": "^2.6.3",
        "stripe": "^8.39.0"
    },
SebSchwartz commented 4 years ago

We had the same problem when we started using DataStore and the only thing we could do to debug is an awful and very long step by step deployment to see what's blocking the sync. It was really long and painful to debug those problems and no places where we can find a list of things to be careful with...

One thing i'm spotting in your schema is the lack of id:ID! inside User. Every models should have a mandatory id.

rpostulart commented 4 years ago

This might help: https://github.com/aws-amplify/amplify-js/issues/5174

ryanweaver718 commented 4 years ago

@rpostulart So I did try the DataStore.configure() and DataStore.configure(awsconfig) as shown in the link you sent. Unfortunately none of those worked for me.

@SebSchwartz thanks for the response, that at least gives some hope that this can be resolved.

So an update, I attempted to tear down my api and rebuild with only the following:

type Contact
  @model
  @auth(
    rules: [
      {allow: groups, groups: ["Admin"], operations: [read, create, update, delete]}
      {allow: public, provider: iam, operations: [create]}
      {allow: groups, groups: ["everyone"], operations: [create]}
    ]
  ) 
  {
  id: ID!
  name: String!
  email: String!
  content: String!
}

I do have this working SEMI successfully. Now it has turned into a new error (which may have been the same for above):

My code is as follows:

      const contact = await DataStore.save(
        new Contact({
          name: faker.name.firstName(),
          email: faker.internet.email(),
          content: faker.internet.color(),
        }),
      );
      console.log(contact);

when I call the function to execute this command, and I run the following Query:

      let users = await DataStore.query(Contact);
      console.log(users);

I do successfully see the contact being created in the locale datastore. BUT... No matter what I have tried, the conact only gets persisted to the dynamo tables AFTER running the function TWICE. So for example

(3) [Model, Model, Model]
0: Model
content: "#080274"
email: "Jairo78@gmail.com"
id: "019015a3-adfa-48f2-82e6-5eb6a01accac"
name: "Elwin"
_deleted: null
_lastChangedAt: 1588271300199
_version: 1
__proto__: Object
1: Model
content: "#6a735f"
email: "Magnus_Hegmann@gmail.com"
id: "134d63a5-3764-49ca-90f0-58c0f2fab8aa"
name: "Reyna"
_deleted: null
_lastChangedAt: 1588271229424
_version: 1
__proto__: Object
2: Model
content: "#5f4452"
email: "Sincere.Leffler49@yahoo.com"
id: "eb8268e4-f3f3-4b3a-aca6-be86f4d225d3"
name: "Phyllis"
_deleted: undefined
_lastChangedAt: undefined
_version: undefined
__proto__: Object
length: 3
__proto__: Array(0)

The above code is shown in the browser console. I refreshed the page and ran the function THREE times. But as I stated above, no matter what I do, the first time I execute the function on a page refresh (in the case it was "Phyllis"), that object won't sync to dynamo but all future creations will.

Any thoughts on why this may be happening?

elorzafe commented 4 years ago

@ryanweaver718

Can you share a more complete code snippet? For example:

ryanweaver718 commented 4 years ago

@elorzafe

I removed the Datastore.configure() code completely as I didn't see that have any impact, but if you think that needs to be in there I can add it back in. (That was in the index.js of the app and I ran it right beneath Amplify.configure(awsconfig))

For login right now I'm using the hoisted ui for oath. The datastore function is in a react functional component, the function goes as follows:

import {API, Auth, DataStore} from 'aws-amplify';
..........
..........
  const ds = async () => {
    try {
      const contact = await DataStore.save(
        new Contact({
          name: faker.name.firstName(),
          email: faker.internet.email(),
          content: faker.internet.color(),
        }),
      );
      console.log(contact);
      let users = await DataStore.query(Contact);
      console.log(users);
    } catch (e) {
      console.log(e);
    }
  };

Within the component I have a material-ui button that just executes this on click.

<Button onClick={ds}>Run</Button>

When I click this on the first page reload it runs and saves to the local store, but the first "Contact" doesn't persist to dynamo. On the second and future clicks everything syncs except that first Contact (which still does exist in the local store but not the remote)

Let me know if you need any more info and I can provide.

It looks like this is not the first time this issue was raised about the first Datastore.save() not syncing the first item with Dyanmo. https://github.com/aws-amplify/amplify-js/issues/4535 But I see no resolution to the issue above, and it was closed


UPDATE (upon reading https://github.com/aws-amplify/amplify-js/issues/5115) I placed the following code in a function called within a useEffect in the main App.js file

DataStore.observe().subscribe().unsubscribe()
DataStore.observe().subscribe()

Upon doing this, I am able to get my first create of a "Contact" to sync with dynamo. BUT only most of the time, it seems to sync somewhat unpredictably.

Now there seems to be another issue, I am unable to get a non signed in user to be able to sync data to the cloud, they can create locally but it won't go to the cloud.

  @auth(
    rules: [
      {allow: groups, groups: ["Admin"], operations: [read, create, update, delete]}
      {allow: public, provider: iam, operations: [create]}
      {allow: groups, groups: ["everyone"], operations: [create]}
    ]
  )

This is my current auth constraints. When I create data in the local storage with a non signed in user it saves, and when I signed in all the data syncs to dynamo. I then tried trimming down the auth to this:

  @auth(
    rules: [
      {allow: groups, groups: ["Admin"], operations: [read, update, delete]}
    ]
  )

And the same problem from above persists.

Not sure why, maybe someone can bring some insight?

rpostulart commented 4 years ago

Do you have a console error with the non signed in user?

ryanweaver718 commented 4 years ago

@rpostulart yes I do, the error comes up from the following code. When I run it in the useEffect in App.js

    DataStore.observe().subscribe().unsubscribe();
    DataStore.observe().subscribe();
rpostulart commented 4 years ago

What is the error you see?

ryanweaver718 commented 4 years ago

image

rpostulart commented 4 years ago

Yes, with IAM you still need a cognito token, check my repo's app.js

https://github.com/alowa-apps/kwizz

You need to add this

` Auth.currentCredentials() .then(d => console.log("data: ", d)) .catch(e => console.log("error: ", e));

`

ryanweaver718 commented 4 years ago

@rpostulart Thank you for sharing, maybe I'm not completely understanding how this works. I know for example when I want to do a mutation with a public user that assume an IAM role, it looks something like this:

const createdContact = await API.graphql({
  mutation: mutations.createContact,
  variables: {input: contactDetails},
  authMode: 'AWS_IAM'
});

With the above code, I can create a contact within react successfully. I did look at your app (thanks for sharing) it looks like you call the Auth.currentUser() in the constructor and the DataStore calls in componentDidMount.

Based off your recommendation I setup my code as follows within App.js

  useEffect(() => {
    init();
  }, []);
const init = async () => {
    Auth.currentCredentials()
      .then((d) => {
        DataStore.observe().subscribe().unsubscribe();
        DataStore.observe().subscribe();
      })
      .catch((e) => console.log('error: ', e));
...
}

I was able to get in the ".then" but my log of it was

NotAuthorizedException: Access to Identity 'us-east-1:1c4ac521-72e8-482f-******************' is forbidden.

Andy I was still getting the errors in the image I sent you above. Just fyi, I do have the custom auth settings via the cli to allow non signed in users to access app as well as the api to use both cognito pools and IAM roles.

Any thoughts?

blydewright commented 4 years ago

Hi :) I'm experiencing the same issue of the first object not being synced upon a DataStore.save(). Subsequent DataStore.save()'s correctly sync. Both first and subsequent objects are correctly saved to local storage (in this case, my browser's indexedDb).

I am executing these saves within a click handler. This happens in both mock environment, and cloud using the auto-generated schema (Blog, Post, Comment) from amplify api add using authentication as API key.

It seems like the first save() method does some kind of initialization without synchronizing that object, but allows subsequent saves to work correctly. When additional saves are made in other handlers, they work correctly. So it's the first save after initializing.

Let me know if more info is needed.

github-actions[bot] commented 3 years ago

This issue has been automatically locked since there hasn't been any recent activity after it was closed. Please open a new issue for related bugs.

Looking for a help forum? We recommend joining the Amplify Community Discord server *-help channels or Discussions for those types of questions.