Giter VIP home page Giter VIP logo

Comments (19)

bwoodlt avatar bwoodlt commented on May 25, 2024 1

Took me a while to pull these together...

  • the query text expected by the server

Using the requestDidStart and didEncounterErrors plugins on the server, the client sends this for matches:

api_1-1  | [07/02/2024 16:01:48.946] [LOG]    errors_here: provided sha does not match query
api_1-1  | [07/02/2024 16:01:48.970] [LOG]    {
   'query getMatches($userId: Int!) {\n' +
     '  matches(user_id: $userId) {\n' +
     '    matchesId\n' +
    '    swipedByUser {\n' +
    '      user_id\n' +
    '      name\n' +
     '      image_url\n' +
     '      description\n' +
     '      city\n' +
    '      country\n' +
    '      __typename\n' +
     '    }\n' +
    '    swipedUser {\n' +
    '      user_id\n' +
    '      __typename\n' +
    '    }\n' +
    '    createdAt\n' +
    '    operationType\n' +
     '    __typename\n' +
    '  }\n' +
  '}'
 }
  • the hash for that query expected on the server
api_1-1  |     http: {
api_1-1  |       method: 'GET',
api_1-1  |       headers: [HeaderMap [Map]],
_api_1-1  |       search: '?operationName=getMatches&variables=%7B%22userId%22%3A002%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%2245c8cf69b6f71af947bdbf11370c4e303daaf848d7c83bf3049cf031bf308665%22%7D%7D',_
api_1-1  |       body: {}
api_1-1  |     }
  • the query text the client would send out (console.log it when calculating the hash)

The client doesnt actually show the query text, when you breakdown the query param, this is what you get, this is even with ApolloLink logging outgoing requests:

 INFO  request name getMatches
 INFO  request query [{"directives": [], "kind": "OperationDefinition", "name": {"kind": "Name", "value": "getMatches"}, "operation": "query", "selectionSet": {"kind": "SelectionSet", "selections": [Array]}, "variableDefinitions": [[Object]]}]
  • the hash that the client is sending out
LOG  {"query_name": "getMatches", "sha256Hash": "45c8cf69b6f71af947bdbf11370c4e303daaf848d7c83bf3049cf031bf308665"}

I noticed the hash expected with getMatches 2245c8cf69b6f71af947bdbf11370c4e303daaf848d7c83bf3049cf031bf308665 on the server is different from what client is sending i.e 45c8cf69b6f71af947bdbf11370c4e303daaf848d7c83bf3049cf031bf308665. The question is, how does it attach a different hash value and how do I obtain this if the client is expecting a hash function to be provided?

from apollo-client.

phryneas avatar phryneas commented on May 25, 2024 1

@bwoodlt Great to hear that you found it! I'm sorry I couldn't really help more here!

from apollo-client.

phryneas avatar phryneas commented on May 25, 2024

I think your problem here is client.query as opposed to client.watchQuery - client.query will only ever wait for the first part of the data to stream in and not receive any updates beyond that - that means you'll never be able to access the deferred parts of the query.

Could it be that your error here is caused by userland code that tries to access node.subscriptions.length?

PS: usually you should not copy data from Apollo Client over into Redux - Apollo Client is already a fully fledged cache, and copying that data into Redux would mean that you write code for something that has already been handled for you by the library. I'd recommend to use Apollo Client for your GraphQL data and Redux for the application state beyond that.

from apollo-client.

bwoodlt avatar bwoodlt commented on May 25, 2024

Thanks @phryneas makes sense!

from apollo-client.

alessbell avatar alessbell commented on May 25, 2024

@bwoodlt I also want to call your attention to this section in the docs on the polyfills needed to consume multipart responses in React Native, in case you haven't come across it yet.

from apollo-client.

bwoodlt avatar bwoodlt commented on May 25, 2024

@alessbell yeah I've got that setup in place. I've now been able to get the data via watchQuery.. Though we wouldnt be able to use this yet as it may cause additional load on the server, will also mean some refactoring so will revisit another time. thanks both!

I'm happy to create another ticket, but wanted to highlight another issue i came across with persisted queries having followed instructions here https://www.apollographql.com/docs/apollo-server/performance/apq/

I have this code:

const generateSHA256Hash = query => {
  return sha256(query).toString();
};
const httpLink = new HttpLink({
  uri: API_LINK_URL,
  fetch: customFetch
});

const persistedQueryLink = createPersistedQueryLink({
  useGETForHashedQueries: true,
  generateHash: generateSHA256Hash
}).concat(httpLink);

And inside the client initialization:

const client = new ApolloClient({
  link: from([
    onError(error => {
      let { networkError = {}, graphQLErrors = [] } = error || {};
      try {
        if (graphQLErrors) {
          Array.isArray(graphQLErrors) &&
            graphQLErrors.forEach(({ extensions, message, locations, path }) => {
              console.log(
                `[GraphQL error]: Message: ${message}, Location: ${JSON.stringify(locations)}, Path: ${path}, Type: ${extensions.code}`
              );
              console.log(extensions?.exception);
            });
        }
        if (networkError && typeof networkError === 'object' && Object.keys(networkError).length) {
          console.log({ networkError });
        }
      } catch (e) {
        networkError = error.bodyText;
      }
    }),
    split(
      ({ query }) => {
        const definition = getMainDefinition(query);
        return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
      },
      wsLink,
      authLink.concat(persistedQueryLink) // using the persisted query link here
    )
  ]),
...other_props,
})
// this is returning `provided sha does not match query,` error because there is a check at line no 56 in node_modules/apollo-server-core/dist/requestPipeline.js.

const computedQueryHash = computeQueryHash(query);
if (queryHash !== computedQueryHash) {
return await sendErrorResponse(new graphql_1.GraphQLError(‘provided sha does not match query’));
}

Any thoughts on what might be wrong?

from apollo-client.

phryneas avatar phryneas commented on May 25, 2024

Though we wouldnt be able to use this yet as it may cause additional load on the server, will also mean some refactoring so will revisit another time. thanks both!

There might be a bit of confusion about what watchQuery does here: client.watchQuery won't cause any more strain on the server than client.query.
From the server perspective, both are exactly the same - the difference is on the client. query will only give you the first sufficient part of a result, while watchQuery will give you all results of a request, and future cache updates.

this is returning provided sha does not match query, error because there is a check at line no 56 in node_modules/apollo-server-core/dist/requestPipeline.js.

Are you 100% sure it is the same query? Apollo Client will do things like add __typename to the outgoing query, and that will also be reflected in your hash.

from apollo-client.

bwoodlt avatar bwoodlt commented on May 25, 2024

There might be a bit of confusion about what watchQuery does here: client.watchQuery won't cause any more strain on the server than client.query. From the server perspective, both are exactly the same - the difference is on the client. query will only give you the first sufficient part of a result, while watchQuery will give you all results of a request, and future cache updates.

Right, that makes sense. Assumption around watchQuery was this uses a polling mechanism internally, and that will impact. So will test abit more later today. Thanks for clarifying!

Are you 100% sure it is the same query? Apollo Client will do things like add __typename to the outgoing query, and that will also be reflected in your hash.

This is the first time the query will be hashed (sent to the server with the hash value), the hash generated is something like so 4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e and i wonder whether the algorithm that creates the expected hash in ApolloServer is different from what we use on the client side.

We're using crypto-js/sha256 to generate hash, ApolloServer is computing its hash using the crypto module like so:

function computeQueryHash(query: string) {
  return createHash('sha256').update(query).digest('hex');
}

and checking like so:

      // The provided hash must exactly match the SHA-256 hash of
      // the query string. This prevents hash hijacking, where a
      // new and potentially malicious query is associated with
      // an existing hash.
      if (queryHash !== computedQueryHash) {
        return await sendErrorResponse([
          new GraphQLError('provided sha does not match query', {
            extensions: { http: newHTTPGraphQLHead(400) },
          }),
        ]);
      }

So if its expecting the provided hash by the client to be the same as the one computeQueryHash generate, the algo might be different?

Sorry for lots of code, i'll rather understand implementation with confidence than just follow any random guide online :)

from apollo-client.

phryneas avatar phryneas commented on May 25, 2024

My point is not that the hash algorithm is different, but that the query that is being hashed is different.

Apollo Client will change your query before sending it to the server/hashing it:

const GET_USERS = gql`
  query users($first: Int, $last: int, $id: int) { // im using relay connection args in BE for pagination
    users(first: $first, last: $last, id: $id) {
+       __typename
       pageInfo {
+         __typename
         endCursor
       }
       edges {
+         __typename
         node {
+           __typename
           name
           image_url 
           ... @defer {
               subscriptions {
+                  __typename
                  status
               }
            }
         }
       }
    }
  }
 `

So a hash of that would be different from a hash of that without these changes.

from apollo-client.

bwoodlt avatar bwoodlt commented on May 25, 2024

My point is not that the hash algorithm is different, but that the query that is being hashed is different.

The query remains the same. Given your example above, the same users(id: !int) { ... } is fetched at all times. Nothing changes on that front. Although, whilst debugging the logic in persisted-queries.cjs.native, I noticed the generated query and hashes are the same for all queries which doesnt look normal to me...

 LOG  {"operation": undefined, "query_name": "UserPostsFragment", "sha256Hash": "4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e"}
 LOG  {"operation": undefined, "query_name": "PostCommentsFragment", "sha256Hash": "4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e"}
 LOG  {"operation": "query", "query_name": "notifications", "sha256Hash": "4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e"}
 LOG  {"operation": undefined, "query_name": "PostCommentsFragment", "sha256Hash": "4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e"}
 LOG  {"operation": undefined, "query_name": "UserPostsFragment", "sha256Hash": "4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e"}
 LOG  {"operation": undefined, "query_name": "PostLikesFragment", "sha256Hash": "4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e"}
 LOG  {"operation": "query", "query_name": "getPartners", "sha256Hash": "4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e"}
 LOG  {"operation": "query", "query_name": "users", "sha256Hash": "4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e"}

Do you have a sample project you can point me to where I could see this in action?

from apollo-client.

phryneas avatar phryneas commented on May 25, 2024

Curiously that exact hash turns up on google in an example where a hash function is used incorrectly: https://stackoverflow.com/questions/59990295/why-is-the-hash-of-this-file-staying-the-same-when-i-change-its-data

Are these hash values calculated on the server or in the browser?

from apollo-client.

bwoodlt avatar bwoodlt commented on May 25, 2024

Are these hash values calculated on the server or in the browser?

we generate them on the client.

So by modifying the generateHash fn to stringify the query sha256(JSON.stringify(query)).toString();, this returns unique hash values. Although, still getting provided sha does not match query error

 LOG  {"operation": "query", "query_name": "getMatches", "sha256Hash": "17389ea0cc05abb508005fa9d24bda3b0c71a266a299c24e867bb3c491337aea"}
 LOG  {"operation": "query", "query_name": "users", "sha256Hash": "7a38186d4be8632c7432aa8cf3389747fc50532e62bb5ea079ce00d98263a0d7"}

from apollo-client.

phryneas avatar phryneas commented on May 25, 2024

I still think that your server might not account for modification of these queries before they go out.

Could you please provide these for one query:

  • the query text expected by the server
  • the hash for that query expected on the server
  • the query text the client would send out (console.log it when calculating the hash)
  • the hash that the client is sending out

from apollo-client.

phryneas avatar phryneas commented on May 25, 2024

INFO request name getMatches
INFO request query [{"directives": [], "kind": "OperationDefinition", "name": {"kind": "Name", "value": "getMatches"}, "operation": "query", "selectionSet": {"kind": "SelectionSet", "selections": [Array]}, "variableDefinitions": [[Object]]}]

Could you send that through print (exported from @apollo/client/utilities) before logging it out?

from apollo-client.

bwoodlt avatar bwoodlt commented on May 25, 2024

2245c8cf69b6f71af947bdbf11370c4e303daaf848d7c83bf3049cf031bf308665

didnt know about print until now. Nice one!

// client request
 LOG  {"query": "query getMatches($userId: Int!) {
  matches(user_id: $userId) {
    matchesId
    swipedByUser {
      user_id
      name
      image_url
      description
      city
      country
      __typename
    }
    swipedUser {
      user_id
      __typename
    }
    createdAt
    operationType
    __typename
  }
}"}

from apollo-client.

bwoodlt avatar bwoodlt commented on May 25, 2024

@phryneas have you had a moment to take a look at this further? Would you know why there are discrepancies between hash values received on the server vs what was sent from the client?

from apollo-client.

phryneas avatar phryneas commented on May 25, 2024

We're really not doing anything different from sha256(print(query)) so at this point I honestly don't know.

You might have a weird hash function in one of both environments - I'd recommend hashing known strings in both environments and comparing them to an online hash calculator.

from apollo-client.

bwoodlt avatar bwoodlt commented on May 25, 2024

I've manage to sort out this issue. For anyone coming across this perhaps from google, just ensure your client doesnt stringfy the query before hashing the value. Ensure you are hashing just like @phryneas stated above.

Thanks all!

from apollo-client.

github-actions avatar github-actions commented on May 25, 2024

Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Client usage and allow us to serve you better.

from apollo-client.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.