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 - Sync processor retry error: {"data": {}, "errors": [[GraphQLError: Request failed with status code 401]]} #12951

Open elkee2003 opened 7 months ago

elkee2003 commented 7 months ago

Before opening, please confirm:

App Id

arn:aws:amplify:us-east-1:115372163617:apps/dnxdny8iom5jq

Region

us-east-1

Environment name

VS code

Figma File Version (if applicable)

No response

Amplify CLI Version

12.8.2

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

v20.11.0

What operating system are you using?

Windows

Browser type?

Google Chrome

Describe the bug

I keep getting this bug: [ERROR] 05:24.121 DataStore - Sync processor retry error: {"data": {}, "errors": [[GraphQLError: Request failed with status code 401]]} I don't know why. Theses are where Datastore is used:


import '@azure/core-asynciterator-polyfill'
import { View, Text, TextInput, Button, Pressable, Alert } from 'react-native'
import React, {useEffect, useState, } from 'react'
import { GooglePlacesAutocomplete } from 'react-native-google-places-autocomplete';
import { Auth, DataStore } from 'aws-amplify'
import { User } from '../../models'
import styles from './styles'
import { useAuthContext } from '../../contexts/AuthContext'
import { useNavigation } from '@react-navigation/native'

const ProfileScreen = () => {

  const {sub, dbUser, setDbUser} = useAuthContext()

    const [name, setName] = useState(dbUser?.name || "")
    const [address, setAddress] = useState(dbUser?.address || "")
    const [phoneNumber, setPhoneNumber]= useState(dbUser?.phoneNumber || "")
    const [lat, setLat] = useState(dbUser?.lat.toString() || "0")
    const [lng, setLng] = useState (dbUser?.lng.toString() || "0") 

    const [isFocused, setIsFocused] = useState(false);

    const navigation = useNavigation()

    // Start of Function to Create and Update User
    const createUser = async ()=>{
      try{
        const user = await DataStore.save(new User({
         name, 
         address,
         phoneNumber,
         lat:parseFloat(lat), 
         lng:parseFloat(lng), 
         sub
       })
       );
       console.log("I am User:",user)
       setDbUser(user)
     }catch(e){
       Alert.alert("Error", e.message)
     }
    }

    const updateUser= async ()=>{
      const user = await DataStore.save(User.copyOf(dbUser, (updated)=>{
        updated.name = name;
        updated.address = address;
        updated.phoneNumber = phoneNumber
        updated.lat = parseFloat(lat);
        updated.lng = parseFloat(lng);
      }))
      setDbUser(user)
    }
    // End Of Function to Create and Update User

    // Function to Save Data
    const onSave= async()=>{
      if(dbUser){
        await updateUser()
        navigation.goBack()
      }else{
        await createUser()
        navigation.navigate('HomeScreen')
      }
      // navigation.goBack()
    }

    // function to handle focus
    const handleFocusChange = (focused) => {
      setIsFocused(focused);
    };

    // Start Of GooglePlacesAutoComplete
    const handlePlaceSelect = (data, details = null) => {
      // Extract the address from the selected place
      const selectedAddress = details?.formatted_address || data.description;

      console.log( "Show the Lat, and Lng data and details:",data,details)

      // Update the address state
      setAddress(selectedAddress);

    };
    // End Of GooglePlacesAutoComplete

    return (
    <View style={styles.container}>

      <Text style={styles.title}>Profile</Text>

      <TextInput 
      value={name}
      onChangeText={setName}
      placeholder='Name'
      style={styles.input}
      />

      <TextInput 
      value={address}
      placeholder='Address'
      style={{...styles.input, color: '#04df04'}}
      />

      <View style={isFocused ? styles.gContainerFocused : styles.gContainer}>
        <GooglePlacesAutocomplete
        placeholder='Select Address From Here'
        onPress={handlePlaceSelect}
        textInputProps={{
          onFocus:() => handleFocusChange(true),
          onBlur:() => handleFocusChange(false)
        }} 
        styles={{
          textInput:styles.gTextInput,
          textInputContainer:styles.gTextInputContainer,
          listView:styles.glistView,
          poweredContainer:styles.gPoweredContainer
        }}
        query={{
          key: 'AIzaSyAY',
          language: 'en',
        }}
        />
      </View>

    {/* TextInputs that will be below GooglePlacesAutocomplete */}

      <TextInput
      value={phoneNumber}
      onChangeText={setPhoneNumber}
      placeholder='Phone Number'
      style={styles.input}
      />

      <TextInput 
      value={lat}
      onChangeText={setLat}
      placeholder='Latitude'
      keyboardType='numeric'
      style={styles.input}
      />

      <TextInput 
      value={lng}
      onChangeText={setLng}
      placeholder='Longitude'
      keyboardType='numeric'
      style={styles.input}
      />

      <View style={styles.scrnBtn}>
        {/* Save */}
        <Pressable onPress={onSave
        } style={styles.saveBackground}>
          <Text style={styles.save}>
            Save
          </Text>
        </Pressable>

        {/* SignOut */}
        <Pressable onPress={()=>{Auth.signOut
        ()}}>
          <Text style={styles.signOut}>
            Sign out
          </Text>
        </Pressable>
      </View>

    </View>
  )
}

export default ProfileScreen

import { createContext, useState,useEffect, useContext } from "react";
import {Auth, DataStore, Amplify, Predicates} from 'aws-amplify';
import { User } from "../models";

const AuthContext = createContext({})

const AuthContextProvider = ({children})=>{

    const [authUser, setAuthUser] = useState(null)
    const [dbUser, setDbUser] = useState(null)
    const sub = authUser?.attributes?.sub;

    useEffect(()=>{
        Auth.currentAuthenticatedUser({bypassCache: true}).then(setAuthUser)
    },[]);

    useEffect(()=>{
        DataStore.query(User, (user)=>user.sub.eq(sub)).then((users)=>setDbUser(users[0]))
        console.log(dbUser)

    }, [sub])

    return(
        <AuthContext.Provider value={{authUser, dbUser, sub, setDbUser, }}>
            {children}
        </AuthContext.Provider>
    )
}

export default AuthContextProvider;

export const useAuthContext = ()=> useContext(AuthContext)

Expected behavior

It used to work, all of a sudden it stopped

Reproduction steps

  1. run the code I pasted
  2. it will then give you this error [ERROR] 05:24.121 DataStore - Sync processor retry error: {"data": {}, "errors": [[GraphQLError: Request failed with status code 401]]}

Project Identifier

No response

Additional information

No response

elkee2003 commented 7 months ago

Screenshot (82) Screenshot (83) These are screenshots of the error

ykethan commented 7 months ago

Hey,πŸ‘‹ thanks for raising this! I'm going to transfer this over to our Amplify JS repository for better assistance πŸ™‚

chrisbonifacio commented 7 months ago

Hi @elkee2003 πŸ‘‹ it looks like your error logs mention UnuathorizedExceptions for create and update User operations, and the sync is failing with auth mode API_KEY. You might have to enable MULTI_AUTH on your DataStore configuration so that it uses your other auth modes like COGNITO_USER_POOLS.

Please refer to the docs to configure MULTI_AUTH: https://docs.amplify.aws/javascript/build-a-backend/more-features/datastore/authz-rules-setup/#configure-multiple-authorization-types

cwomack commented 7 months ago

@elkee2003, let us know if you had a chance to review @chrisbonifacio's comment above and if it unblocked you.

elkee2003 commented 7 months ago

@elkee2003, let us know if you had a chance to review @chrisbonifacio's comment above and if it unblocked you.

No I haven't had the chance to try it out, once I try it out I'll let him know

elkee2003 commented 7 months ago

It does not work. Just to be clear after I run this: `import { DataStore, AuthModeStrategyType } from 'aws-amplify/datastore';

DataStore.configure({ authModeStrategyType: AuthModeStrategyType.MULTI_AUTH });` is it meant to solve the problem?

chrisbonifacio commented 7 months ago

Hi @elkee2003 I thought it might resolve the problem because the error says that the user is not authorized perform the query using API_KEY as an auth mode. Can you share your schema so we can confirm this is expected behavior?

By default DataStore will attempt to authorize qraphql calls to AppSync only using the default auth mode mentioned in the amplifyconfiguration.json file. MULTI_AUTH allows DataStore to use additional auth modes, of which it will re-attempt calls with. If you look at the hierarchy of auith modes it retries with, API_KEY is the last one because it goes from least to most permissive auth rules.

If you are still getting unauthorized errors with multi auth enabled, then we need to take a look at the schema to figure out how the auth rules are configured on the model and what auth mode DataStore should be able to authorize a user with.

elkee2003 commented 7 months ago

Hi @elkee2003 I thought it might resolve the problem because the error says that the user is not authorized perform the query using API_KEY as an auth mode. Can you share your schema so we can confirm this is expected behavior?

By default DataStore will attempt to authorize qraphql calls to AppSync only using the default auth mode mentioned in the amplifyconfiguration.json file. MULTI_AUTH allows DataStore to use additional auth modes, of which it will re-attempt calls with. If you look at the hierarchy of auith modes it retries with, API_KEY is the last one because it goes from least to most permissive auth rules.

If you are still getting unauthorized errors with multi auth enabled, then we need to take a look at the schema to figure out how the auth rules are configured on the model and what auth mode DataStore should be able to authorize a user with.

enum OrderStatus { READY_FOR_PICKUP ACCEPTED PICKEDUP DELIVERED }

type Order @model @auth(rules: [{allow: public}]) { id: ID! recipientName: String! recipientNumber: String! orderDetails: String total: Float status: OrderStatus! userID: ID! @index(name: "byUser") originPlace: String destinationPlace: String }

type User @model @auth(rules: [{allow: public}]) { id: ID! sub: String! name: String! phoneNumber: String! lng: Float! Orders: [Order] @hasMany(indexName: "byUser", fields: ["id"]) lat: Float! address: String! }