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

Datastore sync is not working properly with @auth #10377

Closed laurentlouk closed 1 year ago

laurentlouk commented 1 year ago

Before opening, please confirm:

JavaScript Framework

React Native

Amplify APIs

DataStore

Amplify Categories

auth, storage, function, api

Environment information

``` # Put output below this line System: OS: macOS 12.6 CPU: (10) arm64 Apple M1 Max Memory: 7.45 GB / 64.00 GB Shell: 5.8.1 - /bin/zsh Binaries: Node: 16.15.1 - /usr/local/bin/node Yarn: 1.22.19 - /opt/homebrew/bin/yarn npm: 8.19.2 - /opt/homebrew/bin/npm Watchman: 2022.07.04.00 - /opt/homebrew/bin/watchman Browsers: Chrome: 105.0.5195.125 Firefox: 101.0.1 Safari: 16.0 npmPackages: @aws-amplify/datastore-storage-adapter: ^1.3.5 => 1.3.14 @azure/core-asynciterator-polyfill: ^1.0.2 => 1.0.2 @babel/core: ^7.18.6 => 7.19.1 @babel/runtime: ^7.18.6 => 7.19.0 @react-native-async-storage/async-storage: ^1.17.7 => 1.17.10 @react-native-clipboard/clipboard: ^1.8.5 => 1.11.1 @react-native-community/cameraroll: ^4.1.2 => 4.1.2 @react-native-community/eslint-config: ^3.0.3 => 3.1.0 @react-native-community/masked-view: ^0.1.11 => 0.1.11 @react-native-community/netinfo: ^9.3.2 => 9.3.2 @react-native-community/slider: ^4.2.4 => 4.3.1 @react-navigation/native: ^6.0.11 => 6.0.13 @react-navigation/stack: ^6.2.2 => 6.3.1 @types/jest: ^27.4.0 => 27.5.2 HelloWorld: 0.0.1 amazon-cognito-identity-js: ^5.2.10 => 5.2.10 aws-amplify: ^4.3.27 => 4.3.36 aws-amplify-react-native: ^6.0.2 => 6.0.5 babel-jest: ^28.1.3 => 28.1.3 (27.5.1) esbuild: ^0.14.49 => 0.14.54 eslint: 8.20.0 => 8.20.0 fbjs: ^3.0.4 => 3.0.4 fuse.js: ^6.5.3 => 6.6.2 hermes-inspector-msggen: 1.0.0 ini: ^3.0.1 => 3.0.1 inquirer: ^9.0.1 => 9.1.2 jest: ^27.4.7 => 27.5.1 metro-react-native-babel-preset: ^0.72.1 => 0.72.3 (0.72.1) moment: ^2.29.4 => 2.29.4 react: 18.1.0 => 18.1.0 react-native: 0.70.1 => 0.70.1 react-native-geolocation-service: ^5.3.0 => 5.3.0 react-native-gesture-handler: ^2.6.1 => 2.6.1 react-native-image-resizer: ^1.4.5 => 1.4.5 react-native-linear-gradient: ^2.6.2 => 2.6.2 react-native-safe-area-context: ^4.3.4 => 4.3.4 react-native-screens: ^3.15.0 => 3.17.0 react-native-spinkit: ^1.5.1 => 1.5.1 react-native-sqlite-storage: ^6.0.1 => 6.0.1 react-native-svg: ^12.4.0 => 12.4.4 react-native-vector-icons: ^9.2.0 => 9.2.0 react-test-renderer: 18.1.0 => 18.1.0 npmGlobalPackages: corepack: 0.10.0 npm: 8.11.0 ```

Describe the bug

When I auth with a user(A) save information and observe it's working good When I auth with a user(B) on a second iPhone emulator (or physical iPhone) and replicate the above line the observe method doesn't return anything from the user(A).

I tried this with different types of my GQL.

Meanwhile I keep having these alerts : [WARN] 17:20.125 DataStore - queryError User is unauthorized to query syncLabs, some items could not be returned. [WARN] 17:20.134 DataStore - queryError User is unauthorized to query syncUsers, some items could not be returned. [WARN] 17:20.139 DataStore - queryError User is unauthorized to query syncParticipants, some items could not be returned. [WARN] 17:20.142 DataStore - queryError User is unauthorized to query syncRooms, some items could not be returned.

type User
  @model
  @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }]) {

  id: ID! @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])
  username: String @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])
  description: String @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])
  photo: S3Object @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])
  showImage: Boolean! @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])
  colorLogo: String! @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])

  owner: String @auth(rules: [{ allow: owner, operations: [read, create, delete] }])

  walletID: ID! @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])
}

Expected behavior

When DataStore.observe I want to be able to have both information from user(A) or user(B) independently from who has the owner of the data.

with the following auth rules:

  ...
  @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }]) {
  ...
  owner: String
  ...
}

Reproduction steps

Yarn packages

    "react": "18.1.0",
    "react-native": "0.70.1",
    "@aws-amplify/datastore-storage-adapter": "^1.3.5",
    "@azure/core-asynciterator-polyfill": "^1.0.2",,
    "@react-native-community/netinfo": "^9.3.2",
    "amazon-cognito-identity-js": "^5.2.10",
    "aws-amplify": "^4.3.27",
    "aws-amplify-react-native": "^6.0.2",

Amplify add API : with Amazon Cognito User Pool and Datastore Auto Merge (no override)

Code Snippet

// Put your code below this line.
import { useState, useEffect } from 'react'
import { DataStore } from 'aws-amplify'

import { User, SystemUser } from '../models'

const useProfileUser = (userID) => {
  const [userProfile, setUserProfile] = useState({})
  const [init, setInit] = useState(false)

  const changeSystemUser = async ({ user, password }) => {
    const systemUserData = await DataStore.query(SystemUser, user)

    await DataStore.save(
      SystemUser.copyOf(systemUserData, (updated) => {
        updated.password = password
      }),
    )
  }

  useEffect(() => {
    if (init) {
      const subscription = DataStore.observe(User, userProfile.id).subscribe(
        () => getUserProfile(userProfile.id),
      )
      return () => subscription.unsubscribe()
    }
  }, [userProfile, init])

  const getUserProfile = async (user) => {
    setInit(true)

    const userData = await DataStore.query(User, user)
    userData && setUserProfile(userData)
  }

  const updateUserProfile = async (updatedUser) => {
    await DataStore.save(
      User.copyOf(userProfile, (updated) => {
        updated.username = updatedUser?.username
        updated.description = updatedUser?.description
        updated.showImage = updatedUser?.showImage
        updated.photo = updatedUser?.photo
      }),
    )
  }

  return {
    userProfile,
    changeSystemUser,
    getUserProfile,
    updateUserProfile,
  }
}

export { useProfileUser }

Log output

``` // Put your logs below this line Amplify.Logger.LOG_LEVEL = 'DEBUG' ```

aws-exports.js

/* eslint-disable */
// WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten.

const awsmobile = {
    "aws_project_region": "eu-west-1",
    "aws_cognito_identity_pool_id": "eu-west-1:c9251472-XXXXXXXXXXXX",
    "aws_cognito_region": "eu-west-1",
    "aws_user_pools_id": "eu-west-1_XXXXXXXXX",
    "aws_user_pools_web_client_id": "XXXXXXXXXX",
    "oauth": {},
    "aws_cognito_username_attributes": [
        "PHONE_NUMBER"
    ],
    "aws_cognito_social_providers": [],
    "aws_cognito_signup_attributes": [
        "PHONE_NUMBER"
    ],
    "aws_cognito_mfa_configuration": "OFF",
    "aws_cognito_mfa_types": [
        "SMS"
    ],
    "aws_cognito_password_protection_settings": {
        "passwordPolicyMinLength": 8,
        "passwordPolicyCharacters": [
            "REQUIRES_LOWERCASE",
            "REQUIRES_NUMBERS",
            "REQUIRES_SYMBOLS",
            "REQUIRES_UPPERCASE"
        ]
    },
    "aws_cognito_verification_mechanisms": [
        "PHONE_NUMBER"
    ],
    "aws_appsync_graphqlEndpoint": "https://XXXXXXXXXXX.appsync-api.eu-west-1.amazonaws.com/graphql",
    "aws_appsync_region": "eu-west-1",
    "aws_appsync_authenticationType": "AMAZON_COGNITO_USER_POOLS",
    "aws_user_files_s3_bucket": "awesome-lab-XXXXXX-dev",
    "aws_user_files_s3_bucket_region": "eu-west-1"
};

export default awsmobile;

Manual configuration

No response

Additional configuration

No response

Mobile Device

iPhone 13 emulators and physical iPhone 12

Mobile Operating System

16

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

full package.json

{
  "name": "awesomelab",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "test": "jest",
    "lint": "eslint .",
    "amplify:AwesomeLabPostConfirmation": "cd packages/AwesomeLabPostConfirmation && rm -rf dist/* && tsc -p ./tsconfig.json && esbuild dist/index.js --bundle --resolve-extensions=.ts,.js --platform=node --tree-shaking=true --outfile=../../amplify/backend/function/AwesomeLabPostConfirmation/src/index.js --minify",
    "amplify:AwesomeLabPostMutationLab": "cd packages/AwesomeLabPostMutationLab && rm -rf dist/* && tsc -p ./tsconfig.json && esbuild dist/index.js --bundle --resolve-extensions=.ts,.js --platform=node --tree-shaking=true --outfile=../../amplify/backend/function/AwesomeLabPostMutationLab/src/index.js --minify",
    "amplify:AwesomeLabMutationTransaction": "cd packages/AwesomeLabMutationTransaction && rm -rf dist/* && tsc -p ./tsconfig.json && esbuild dist/index.js --bundle --resolve-extensions=.ts,.js --platform=node --tree-shaking=true --outfile=../../amplify/backend/function/AwesomeLabMutationTransaction/src/index.js --minify",
    "amplify:AwesomeLabMutationWallet": "cd packages/AwesomeLabMutationWallet && rm -rf dist/* && tsc -p ./tsconfig.json && esbuild dist/index.js --bundle --resolve-extensions=.ts,.js --platform=node --tree-shaking=true --outfile=../../amplify/backend/function/AwesomeLabMutationWallet/src/index.js --minify"
  },
  "dependencies": {
    "react": "18.1.0",
    "react-native": "0.70.1",
    "@aws-amplify/datastore-storage-adapter": "^1.3.5",
    "@azure/core-asynciterator-polyfill": "^1.0.2",
    "@react-native-async-storage/async-storage": "^1.17.7",
    "@react-native-clipboard/clipboard": "^1.8.5",
    "@react-native-community/cameraroll": "^4.1.2",
    "@react-native-community/masked-view": "^0.1.11",
    "@react-native-community/netinfo": "^9.3.2",
    "@react-native-community/slider": "^4.2.4",
    "@react-navigation/native": "^6.0.11",
    "@react-navigation/stack": "^6.2.2",
    "amazon-cognito-identity-js": "^5.2.10",
    "aws-amplify": "^4.3.27",
    "aws-amplify-react-native": "^6.0.2",
    "fbjs": "^3.0.4",
    "fuse.js": "^6.5.3",
    "moment": "^2.29.4",
    "react-native-geolocation-service": "^5.3.0",
    "react-native-gesture-handler": "^2.6.1",
    "react-native-image-resizer": "^1.4.5",
    "react-native-linear-gradient": "^2.6.2",
    "react-native-safe-area-context": "^4.3.4",
    "react-native-screens": "^3.15.0",
    "react-native-spinkit": "^1.5.1",
    "react-native-sqlite-storage": "^6.0.1",
    "react-native-svg": "^12.4.0",
    "react-native-vector-icons": "^9.2.0"
  },
  "devDependencies": {
    "@babel/core": "^7.18.6",
    "@babel/runtime": "^7.18.6",
    "@react-native-community/eslint-config": "^3.0.3",
    "@types/jest": "^27.4.0",
    "babel-jest": "^28.1.3",
    "esbuild": "^0.14.49",
    "eslint": "8.20.0",
    "ini": "^3.0.1",
    "inquirer": "^9.0.1",
    "jest": "^27.4.7",
    "metro-react-native-babel-preset": "^0.72.1",
    "react-test-renderer": "18.1.0"
  },
  "jest": {
    "preset": "react-native",
    "testEnvironment": "jsdom",
    "setupFiles": [
      "<rootDir>/jestSetupFile.js"
    ]
  }
}
chrisbonifacio commented 1 year ago

Hi @laurentlouk 👋 thanks for raising this issue! At a glance it seems like you might be running into this scenario described in the documentation because you have field level auth enabled on each model listed by the console warnings.

To prevent sensitive data from being sent over subscriptions, the GraphQL Transformer needs to alter the response of mutations for those fields by setting them to null. Therefore, to facilitate field-level authorization with subscriptions, you need to either apply field-level authorization rules to all required fields, make the other fields nullable, or disable subscriptions by setting it to public or off.

So, you can try adding field level auth to all the other fields to match the model level auth and keep them non-nullable.

Let me know if that helps.

Otherwise, I'm currently deploying the schema you've shared to test the behavior as well.

laurentlouk commented 1 year ago

@chrisbonifacio thank you for your fast feedback ✨ I'm not sure to understand here what you mean by apply field-level authorization rules to all required fields

Do you mean something like that?

type User
  @model
  @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }]) {

  id: ID! @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])
  username: String @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])
  description: String @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])
  photo: S3Object @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])
  showImage: Boolean! @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])
  colorLogo: String! @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])

  owner: String @auth(rules: [{ allow: owner, operations: [read, create, delete] }])

  walletID: ID! @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])
}
laurentlouk commented 1 year ago

I tried with the following schema it didn't work 😣

I removed all the owner: String @auth(rules: [{ allow: owner, operations: [read, create, delete] }]) to owner: String

laurentlouk commented 1 year ago

@chrisbonifacio I tested all the GQL schema you recommended, but nothing seemed to work. I'm saving the new types with owner: `${sub::owner}` that's the only change I've made recently, but according to the documentation it should work the same way as before.

chrisbonifacio commented 1 year ago

oh, you're manually setting the value of the owner field? I would check those values in DynamoDB and make sure that they are correct.

laurentlouk commented 1 year ago

@chrisbonifacio I spent the whole day running some extra tests, I have done this from scratch (new amplify + new RN) with a smaller GQL schema (see below). No matter if I add manually the owner field or not I have the same bug.

I have also tested on the console appsync : listening to a subscription to "onCreateUser" and I still have the same problem.

have you tried to reproduce it ?

type User
  @model
  @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }]) {
  id: ID!
    @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])
  username: String
    @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])
  description: String
    @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])
  showImage: Boolean!
    @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])
  colorLogo: String!
    @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])
  owner: String
    @auth(
      rules: [
        { allow: owner, operations: [read, create, delete] }
        { allow: private, operations: [read] }
      ]
    )
  walletID: ID!
    @auth(rules: [{ allow: owner }, { allow: private, operations: [read] }])
}
chrisbonifacio commented 1 year ago

Hi @laurentlouk 👋 can you check in the Network tab of the browser, and share the logs from the websocket connection?

We're curious to see how the subscription queries are being generated, particularly whether there is an owner argument included. Please share for both devices/users.

laurentlouk commented 1 year ago

hi @chrisbonifacio unfortunately I had to restructure my code and remove the datastore, as it is not suitable anymore due to hazard bugs. I needed to get on with my product and stop spending time on datastore 😣 I'm sorry I don't have more time to check with you (hard times 😅) and thank you again for your time trying to help me 🙏🏻.

I'll let you decide if you need to close this issue or not.

ianfmc commented 1 year ago

hi @laurentlouk: i am having the same issue. what are you using instead of datastore? i have both a web and mobile front end, so i chose the datastore to be able to sync (when on mobile). i'm not sure that calling the API directly would work either, as i can't get anything from the AppSync query.

ianfmc commented 1 year ago

Cross-reference #10652

laurentlouk commented 1 year ago

hi @laurentlouk: i am having the same issue. what are you using instead of datastore? i have both a web and mobile front end, so i chose the datastore to be able to sync (when on mobile). i'm not sure that calling the API directly would work either, as i can't get anything from the AppSync query.

Hey I stopped using datastore and did my own caching with subscriptions and persisting caching similar to useQuery. It's more cording but way more stable.

ianfmc commented 1 year ago

thanks for the info! This is the only thing holding up a launch, and my customer is getting anxious. i'll have to give that a think.

alharris-at commented 1 year ago

See related item https://github.com/aws-amplify/amplify-category-api/issues/1018 for information on resolution.