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.4k stars 2.11k forks source link

DataStore command datastore.save is not inserting the user.username or user.attributes.sub #12400

Open magic-fruits opened 8 months ago

magic-fruits commented 8 months ago

Before opening, please confirm:

App Id

i-use-amplify-studio-i-dont-know-which-of-all-arn-to-put-here-sorry

Region

us-east-1

Environment name

Amplify

Figma File Version (if applicable)

No response

Amplify CLI Version

No response

If applicable, what version of Node.js are you using?

No response

What operating system are you using?

No response

Browser type?

No response

Describe the bug

Hi !

How can I make "DataStore" framework insert the user.attribute.sub or user.username into the "owner" field of a GraphQL type?

This guys did that: https://youtu.be/D8gSc7wDRic?si=tmF1f7j-hr5RkJx_

I see that they only used "datastore.save" command, and as you can see, they didnt filled "owner" inside the app. Also, they make the React app compare the user.attributes.sub with the post.owner field and the app automatically recognized that they were the same.

I used that datasore.save command but the "owner" field is "null". I even tryed to set it manually, but it simply doesnt recieve the ID.

My code actually creates the post, but the "owner" field is empty. My process was: I created the Schema in my Code Editor, then I push it to Amplify Studio. It created the types in the "data" section. After that, I enabled the "Enable owner authorization" in Amplify Studio in the "Post" type.

The "Post" type has this Authorization Rules: Authenticated and unauthenticated scopes: Anyone authenticated with API Key can Create, Read, Update, and Delete Product

The "Post" type has: Owner-based scopes:

Enable owner authorization (yes, enabled) Owner-based authorization allows you to tie a data record to a user. Owners can read, create, update, and delete the record.

Allow the owner to perform these operations on their own records: Create, Read, Update, Delete

After that, the "Post" type got an additional field called "owner". Once I pulled the changes to my code editor, there schema.graphql file didnt got the "owner" field, but you can see it in AWS AppSync.

It would be wonderfull if you could help me by telling me where can I add a "console.log" to check or see what is allowing to enter the new "post" but is preventing to put the "user ID" into the "owner" field. Or you can tell me if there is a Hub function, or DataStore.suscribe or similar to identify that. It can be an option if you tell me if the error can be identified with CluodWatch or any other service that reads every answer received and answered by the server.

In the "schema" section of the AWS AppSync console, I clicked on "pipeline" of the "owner" property of the "Product" type. There is this code:

$util.qr($ctx.stash.put("typeName", "Post"))
$util.qr($ctx.stash.put("fieldName", "owner"))
$util.qr($ctx.stash.put("conditions", []))
$util.qr($ctx.stash.put("metadata", {}))
$util.qr($ctx.stash.metadata.put("dataSourceType", "NONE"))
$util.qr($ctx.stash.metadata.put("apiId", "somecode"))
$util.qr($ctx.stash.put("connectionAttributes", {}))

"user" is generating a big JSON object that has the next properties: Session, attributes, authenticationFlowType, client, keyPrefix, pool, preferredMFA, signInUserSession, storage, userDataKey and username", it doesn't have an "isAuthenticated" property.

It has this object (I replaced every sensitive code with "...". If there is no three points "..." its because there wasnt any code or numer)

Session: null
attributes: {sub: '…', email_verified: true, name: '….', phone_number_verified: false, phone_number: '…', …}

authenticationFlowType: "USER_SRP_AUTH"

client: e {endpoint: 'https://cognito-idp.us-....amazonaws.com/', fetchOptions: {…}}

keyPrefix: "CognitoIdentityServiceProvider…."

pool: e {userPoolId: 'us-east-1_...', clientId: '….', client: e, advancedSecurityDataCollectionFlag: true, storage: Storage, …}

preferredMFA: "NOMFA"

signInUserSession: e {idToken: r, refreshToken: e, accessToken: r, clockDrift: 0}accessToken: r {jwtToken: '…', payload: {…}}clockDrift: 0idToken: r {jwtToken: '…', payload: {…}}refreshToken: e {token: '…'}[[Prototype]]: Object

storage: Storage {CognitoIdentityServiceProvider…..LastAuthUser: '…', CognitoIdentityServiceProvider…..idToken: '…', CognitoIdentityServiceProvider…..userData: '{"UserAttributes":[{"Name":"sub","Value":"……Username":"…"}', amplify-signin-with-hostedUI: 'false', ab.storage.messagingSessionStart….: '{"v":…}', …}

userDataKey: "CognitoIdentityServiceProvider…..userData"

username: "…""

That was a sumary of the "user" JSON object, in which I replaced every code with "..."

Would this make more clear what is blocking the fulfillment of the "owner" property? Take into account that the item is succesfully being created, with the version and time filled automatically.

{
    "channel": "datastore",
    "payload": {
        "event": "nonApplicableDataReceived",
        "data": {
            "errors": [
                {
                    "path": [
                        "syncUsers",
                        "items",
                        0,
                        "_version"
                    ],
                    "locations": null,
                    "message": "Cannot return null for non-nullable type: 'Int' within parent 'User' (/syncUsers/items[0]/_version)"
                },
                {
                    "path": [
                        "syncUsers",
                        "items",
                        0,
                        "_lastChangedAt"
                    ],
                    "locations": null,
                    "message": "Cannot return null for non-nullable type: 'AWSTimestamp' within parent 'User' (/syncUsers/items[0]/_lastChangedAt)"
                }
            ],
            "modelName": "User"
        }
    },
    "source": "",
    "patternInfo": []
}

This is the Schema:

type Post @model @auth(rules: [{allow: public}, {allow: owner}]) {
  id: ID!
  content: String!
  blogID: ID @index(name: "byBlog")
  blog: Blog @belongsTo(fields: ["blogID"])
  owner: String  # This one was added lately, and it allows me to insert the "owner" value "manually" (I mean, with the Javascript form in the app), but it keeps being "null" if don´t set it manually.
}

type Blog @model @auth(rules: [{allow: public}, {allow: owner}]) {
  id: ID!
  blog: String!
  posts: [Post] @hasMany(indexName: "byBlog", fields: ["id"])
}

This is my package.json:

{
  "name": "mega-blog",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@aws-amplify/interactions": "^5.2.3",
    "@aws-amplify/ui-react": "^5.0.1",
    "@aws-amplify/ui-react-v1": "npm:@aws-amplify/ui-react@1.2.9",
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "aws-amplify": "^5.3.3",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-icons": "^4.9.0",
    "react-router-dom": "^6.13.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

This is the code of the two files, "index.js" and "App.js":

index.js

import { Amplify, API, graphqlOperation, Interactions, Auth, AuthModeStrategyType } from 'aws-amplify';
import { withAuthenticator } from '@aws-amplify/ui-react';
import awsconfig from './aws-exports';
Amplify.configure({
  ...awsconfig,
  DataStore: {
    authModeStrategyType: AuthModeStrategyType.MULTI_AUTH
  }
});

App.js

import { Amplify, API, graphqlOperation, Auth } from 'aws-amplify';
import { withAuthenticator } from '@aws-amplify/ui-react';
import { DataStore } from '@aws-amplify/datastore';
import { Post, Blog } from './models';

const App = ({ signOut, user }) => {
  const [post, setPost] = useState('');
  const [blogID, setBlogID] = useState('');
  // AWS Video
  const [currentUser, setCurrentUser] = useState();

  const handleSubmit = async (e) => {
    e.preventDefault();
    async function checkLoginState() {
      try {
        const currentUser = await Auth.currentAuthenticatedUser();
        if (currentUser) {
          console.log('Usuario autenticado con Auth:', currentUser);
          setCurrentUser(currentUser);
        }
      } catch(err) {
        console.log('No se pudo atenticar el usuario:', err);
      }
    };
    checkLoginState(); // It succesfully brings the user JSON object from Cognito

    // Create a new post
    const post = new Post({
      content,
      blogID,
    });

    // Save the post to the DataStore
    await DataStore.save(product);

    // Reset the form inputs
    setPost('');
    setBlogID('');
    // Display a success message
    alert('Posted successfully');
  };
..

return..
    <div className="add-post-form">
      <h2>Agregar Post</h2>
      <form onSubmit={handleSubmit}>
        <div className="form-group">
          <label htmlFor="post-post">Post</label>
          <TextField
            id="post-post"
            style={styles.input}
            value={post}
            onChange={(e) => setPost(e.target.value)}
            placeholder="What do you want to share..."
            required
          />
        </div>
        <button type="submit">Add Post</button>
      </form>
    </div>
...

export default withAuthenticator(App);

Expected behavior

The "owner" field of the GraphQL type "Post" should be filled.

Reproduction steps

  1. I activate the form
  2. The form activates the function "handleSubmit"
  3. The function has been tested in two configurations: a) without the "owner" field specified, and b) specifiyng the "owner" field. This is in the variable "post" that is inside the "handleSubmit" function.
  4. The system actually fill the GraphQL Type, but the "owner" field still "null". I checked that in AppSync.

Project Identifier

No response

Additional information

I created the app with the amplify command to create ReactJS apps. I am using Cloud9 The app is hosted in Amplify I am using GraphQL shcema The authetication is with Cognito, and it has activated the "users" section inside the Amplify App. Actually I dont know which arn do you need from all of that. Sorry for the answer in the arn section.

ykethan commented 8 months ago

Hey @magic-fruits , 👋 thanks for raising this! I'm going to transfer this over to our JS repository for better assistance 🙂.

cwomack commented 8 months ago

Hey there, @magic-fruits 👋. Would you mind sharing your package.json so we can see dependencies/versions that you're using?

chrisbonifacio commented 8 months ago

Hi @magic-fruits 👋 thanks for raising this issue. Can you share your GraphQL schema as well?

Amplify should be auto-populating the owner field for you as long as you have a { allow: owner } auth rule on the model.

For more info on owner authorization, please refer to our docs: https://docs.amplify.aws/cli-legacy/graphql-transformer/auth/#owner-authorization

magic-fruits commented 8 months ago

@cwomack and @chrisbonifacio thank you very much for your attention. I just updated the post with the info that you suggestd: "schema" and "package.json".

The "owner" wasn't in the original schema file, but it exists since I activated the "Enable owner authorization" in Amplify Studio and then I pull the changes. This new field can be checked in the type through AWS AppSync as "owner: String" in the schema section, or in the "queries" section. I added the "owner" field to the original "graphql.schema" and from that moment I could set it manually, but if you don't set it manually it keeps being "null" when you see it in AppSync. The problem is when you try to manipulate the data inside the React App with something like:

{post.owner === user.username && (
              <span style={{ color: 'red' }}> Editar</span>
            )}

it just dont work, because owner is "null" if I dont set it manually and after I added it to the "graphql.schema" file. This example was shown in the AWS video, but it just doesn't work like it did in the video. I know I am missing something.

chrisbonifacio commented 8 months ago

@magic-fruits can you try simply removing the owner field from your schema? I think having it there, also as optional, is causing DataStore to expect it to be set manually.

You should only need the owner auth rule.

After removing the owner field and redeploying your app using amplify push, try saving a new record as a logged in user and check the network activity for the graphic create mutation. Please confirm that the owner field was automatically sent in the request body, and that the request headers include an Authorization header.

This is to confirm that DataStore is attempting to authorize the request using a Cognito access token.

chrisbonifacio commented 8 months ago

Hi 👋 Closing this as we have not heard back from you. If you are still experiencing this issue and in need of assistance, please feel free to comment and provide us with any information previously requested by our team members so we can re-open this issue and be better able to assist you.

Thank you!

magic-fruits commented 8 months ago

@chrisbonifacio
Hi ! Sorry for late response. I received the GIT notification in a newsletter folder, which I am not used to check.

I originally configurated the ", {allow: owner}" without puting the "owner: String" field in the shema. I also enabled the owner authorization in an untouched type, and didnt worked. So, This is what I have tryend:

    // Create a new post
    const post = new Post({
      content,
      blogID,
      owner: user.username,
    });