Skip to content

Commit f8098b6

Browse files
committed
fix(server): Audits safely handle unparsable JSON in response body
1 parent 2b21b08 commit f8098b6

File tree

2 files changed

+47
-9
lines changed

2 files changed

+47
-9
lines changed

src/audits/server.ts

+30-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66

77
import type { ExecutionResult } from 'graphql';
88
import { Audit, AuditResult } from './common';
9-
import { assert, audit, extendedTypeof } from './utils';
9+
import {
10+
assert,
11+
assertBodyAsExecutionResult,
12+
audit,
13+
extendedTypeof,
14+
} from './utils';
1015

1116
/**
1217
* Options for server audits required to check GraphQL over HTTP spec conformance.
@@ -158,8 +163,10 @@ export function serverAudits(opts: ServerAuditOptions): Audit[] {
158163
if (contentType.includes('application/json')) {
159164
assert(`Content-Type ${contentType} status code`, res.status).toBe(200);
160165

161-
const body = await res.json();
162-
assert('Body data errors', body.errors).toBeDefined();
166+
assert(
167+
'Body data errors',
168+
(await assertBodyAsExecutionResult(res)).errors,
169+
).toBeDefined();
163170
return;
164171
}
165172

@@ -345,7 +352,9 @@ export function serverAudits(opts: ServerAuditOptions): Audit[] {
345352
}),
346353
});
347354
assert('Status code', res.status).toBe(200);
348-
const result = (await res.json()) as ExecutionResult;
355+
const result = (await assertBodyAsExecutionResult(
356+
res,
357+
)) as ExecutionResult;
349358
assert('Execution result', result).notToHaveProperty('errors');
350359
}),
351360
audit(
@@ -361,7 +370,7 @@ export function serverAudits(opts: ServerAuditOptions): Audit[] {
361370
method: 'GET',
362371
});
363372
assert('Status code', res.status).toBe(200);
364-
const result = (await res.json()) as ExecutionResult;
373+
const result = await assertBodyAsExecutionResult(res);
365374
assert('Execution result', result).notToHaveProperty('errors');
366375
},
367376
),
@@ -493,7 +502,10 @@ export function serverAudits(opts: ServerAuditOptions): Audit[] {
493502
},
494503
body: '{ "not a JSON',
495504
});
496-
assert('Data entry', (await res.json()).data).toBe(undefined);
505+
assert(
506+
'Data entry',
507+
(await assertBodyAsExecutionResult(res)).data,
508+
).toBe(undefined);
497509
},
498510
),
499511
audit(
@@ -530,7 +542,10 @@ export function serverAudits(opts: ServerAuditOptions): Audit[] {
530542
method: 'GET',
531543
headers: { accept: 'application/graphql-response+json' },
532544
});
533-
assert('Data entry', (await res.json()).data).toBe(undefined);
545+
assert(
546+
'Data entry',
547+
(await assertBodyAsExecutionResult(res)).data,
548+
).toBe(undefined);
534549
},
535550
),
536551
audit(
@@ -567,7 +582,10 @@ export function serverAudits(opts: ServerAuditOptions): Audit[] {
567582
method: 'GET',
568583
headers: { accept: 'application/graphql-response+json' },
569584
});
570-
assert('Data entry', (await res.json()).data).toBe(undefined);
585+
assert(
586+
'Data entry',
587+
(await assertBodyAsExecutionResult(res)).data,
588+
).toBe(undefined);
571589
},
572590
),
573591
audit(
@@ -604,7 +622,10 @@ export function serverAudits(opts: ServerAuditOptions): Audit[] {
604622
method: 'GET',
605623
headers: { accept: 'application/graphql-response+json' },
606624
});
607-
assert('Data entry', (await res.json()).data).toBe(undefined);
625+
assert(
626+
'Data entry',
627+
(await assertBodyAsExecutionResult(res)).data,
628+
).toBe(undefined);
608629
},
609630
),
610631
// TODO: how to fail and have the data entry?

src/audits/utils.ts

+17
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*
55
*/
66

7+
import { ExecutionResult } from 'graphql';
78
import { Audit, AuditName } from './common';
89

910
export * from '../utils';
@@ -106,3 +107,19 @@ export function assert<T = unknown>(name: string, actual: T) {
106107
},
107108
};
108109
}
110+
111+
/**
112+
* Parses the string as JSON and safely reports parsing issues for audits.
113+
*
114+
* Assumes the parsed JSON will be an `ExecutionResult`.
115+
*
116+
* @private
117+
* */
118+
export async function assertBodyAsExecutionResult(res: Response) {
119+
const str = await res.text();
120+
try {
121+
return JSON.parse(str) as ExecutionResult;
122+
} catch (err) {
123+
throw `Response body is not valid JSON. Got ${JSON.stringify(str)}`;
124+
}
125+
}

0 commit comments

Comments
 (0)