Skip to content

Commit 745a4ca

Browse files
committed
fix: verify APQ hash before executing mutation in automatic mode
Fixes #1227
1 parent 11f60f6 commit 745a4ca

File tree

2 files changed

+64
-6
lines changed

2 files changed

+64
-6
lines changed

lib/routes.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -281,24 +281,29 @@ module.exports = async function (app, opts) {
281281
}
282282
}
283283

284-
// Execute the query
285-
const result = await executeQuery(query, variables, operationName, request, reply)
286-
287-
// Only save queries which are not yet persisted and if this is a persisted query retry
284+
// Verify hash before executing (if this is a persisted query retry)
288285
if (isPersistedQueryRetry && isPersistedQueryRetry(body) && query) {
289286
// Extract the hash from the request
290287
const hash = getHash && getHash(body)
291-
292288
const hashForQuery = getHashForQuery && getHashForQuery(query)
289+
293290
if (hash && hashForQuery !== hash) {
294291
// The calculated hash does not match the provided one, tell the client
295292
throw new MER_ERR_GQL_PERSISTED_QUERY_MISMATCH(mismatchError)
296293
}
294+
}
295+
296+
// Execute the query
297+
const result = await executeQuery(query, variables, operationName, request, reply)
298+
299+
// Only save queries which are not yet persisted and if this is a persisted query retry
300+
if (isPersistedQueryRetry && isPersistedQueryRetry(body) && query) {
301+
const hashForQuery = getHashForQuery && getHashForQuery(query)
297302

298303
try {
299304
await saveQuery(hashForQuery, query)
300305
} catch (err) {
301-
request.log.warn({ err, hash, query }, 'Failed to persist query')
306+
request.log.warn({ err, hashForQuery, query }, 'Failed to persist query')
302307
}
303308
}
304309

test/persisted.test.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,59 @@ test('avoid persisting query if hashes mismatch', async (t) => {
497497
t.assert.deepEqual(JSON.parse(res.body), { data: null, errors: [{ message: 'provided sha does not match query' }] })
498498
})
499499

500+
test('avoid executing mutations if hashes mismatch in automatic mode', async (t) => {
501+
const app = Fastify()
502+
503+
const schema = `
504+
type Query {
505+
add(x: Int, y: Int): Int
506+
}
507+
type Mutation {
508+
setFlag(value: Boolean): Boolean
509+
}
510+
`
511+
512+
const sideEffects = []
513+
514+
const resolvers = {
515+
add: async ({ x, y }) => x + y,
516+
Mutation: {
517+
setFlag: async (_, { value }) => {
518+
sideEffects.push(value)
519+
return value
520+
}
521+
}
522+
}
523+
524+
app.register(GQL, {
525+
schema,
526+
resolvers,
527+
persistedQueryProvider: GQL.persistedQueryDefaults.automatic()
528+
})
529+
530+
const res = await app.inject({
531+
method: 'POST',
532+
url: '/graphql',
533+
body: {
534+
operationName: 'SetFlagMutation',
535+
variables: { value: true },
536+
query: `
537+
mutation SetFlagMutation ($value: Boolean!) {
538+
setFlag(value: $value)
539+
}`,
540+
extensions: {
541+
persistedQuery: {
542+
version: 1,
543+
sha256Hash: 'foobar'
544+
}
545+
}
546+
}
547+
})
548+
549+
t.assert.deepEqual(JSON.parse(res.body), { data: null, errors: [{ message: 'provided sha does not match query' }] })
550+
t.assert.strictEqual(sideEffects.length, 0, 'Mutation should not execute when hash mismatches')
551+
})
552+
500553
// persistedQueryProvider
501554

502555
test('GET route with query, variables & persisted', async (t) => {

0 commit comments

Comments
 (0)