graphql-http 1.22.0 → 1.22.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -3
- package/lib/audits/render.js +9 -5
- package/lib/audits/render.mjs +9 -5
- package/lib/handler.js +23 -10
- package/lib/handler.mjs +23 -10
- package/lib/use/@netlify/functions.d.mts +1 -1
- package/lib/use/express.js +1 -0
- package/lib/use/express.mjs +1 -0
- package/lib/use/http.js +1 -0
- package/lib/use/http.mjs +1 -0
- package/lib/use/http2.js +1 -0
- package/lib/use/http2.mjs +1 -0
- package/lib/use/koa.js +3 -2
- package/lib/use/koa.mjs +3 -2
- package/package.json +32 -32
- package/umd/graphql-http-audits.js +9 -5
- package/umd/graphql-http-audits.min.js +1 -1
- package/umd/graphql-http-audits.min.js.gz +0 -0
- package/umd/graphql-http.min.js.gz +0 -0
package/README.md
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
[](https://graphql.org/conf/)
|
|
2
|
-
|
|
3
1
|
<div align="center">
|
|
4
2
|
<br />
|
|
5
3
|
|
|
@@ -120,7 +118,7 @@ import express from 'express'; // yarn add express
|
|
|
120
118
|
import { createHandler } from 'graphql-http/lib/use/express';
|
|
121
119
|
import { schema } from './previous-step';
|
|
122
120
|
|
|
123
|
-
// Create
|
|
121
|
+
// Create an express instance serving all methods on `/graphql`
|
|
124
122
|
// where the GraphQL over HTTP express request handler is
|
|
125
123
|
const app = express();
|
|
126
124
|
app.all('/graphql', createHandler({ schema }));
|
|
@@ -266,6 +264,16 @@ const client = createClient({
|
|
|
266
264
|
})();
|
|
267
265
|
```
|
|
268
266
|
|
|
267
|
+
#### Serve [GraphiQL](https://github.com/graphql/graphiql)
|
|
268
|
+
|
|
269
|
+
Thanks to [`ruru`](https://github.com/graphile/crystal/tree/main/grafast/ruru), serving GraphiQL is as easy as running:
|
|
270
|
+
|
|
271
|
+
```sh
|
|
272
|
+
npx ruru -SP -p 4001 -e http://localhost:4000/graphql
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Open [http://localhost:4001](http://localhost:4001) in the browser to use it.
|
|
276
|
+
|
|
269
277
|
## Recipes
|
|
270
278
|
|
|
271
279
|
<details id="promise">
|
package/lib/audits/render.js
CHANGED
|
@@ -38,8 +38,7 @@ async function renderAuditResultsToHTML(results) {
|
|
|
38
38
|
report += `<li><span style="font-family: monospace">💡</span> <b>${grouped.notice.length}</b> notices (suggestions)</li>\n`;
|
|
39
39
|
}
|
|
40
40
|
if (grouped.warn.length) {
|
|
41
|
-
|
|
42
|
-
report += `<li><span style="font-family: monospace">⚠️</span> <b>${grouped.warn.length}</b> warnings (optional)</li>\n`;
|
|
41
|
+
report += `<li><span style="font-family: monospace">❗️</span> <b>${grouped.warn.length}</b> warnings (optional)</li>\n`;
|
|
43
42
|
}
|
|
44
43
|
if (grouped.error.length) {
|
|
45
44
|
report += `<li><span style="font-family: monospace">❌</span> <b>${grouped.error.length}</b> errors (required)</li>\n`;
|
|
@@ -89,6 +88,7 @@ async function renderAuditResultsToHTML(results) {
|
|
|
89
88
|
}
|
|
90
89
|
exports.renderAuditResultsToHTML = renderAuditResultsToHTML;
|
|
91
90
|
async function printAuditFail(result) {
|
|
91
|
+
var _a;
|
|
92
92
|
let report = '';
|
|
93
93
|
report += `<li><code>${result.id}</code> ${result.name}\n`;
|
|
94
94
|
report += '<details>\n';
|
|
@@ -101,7 +101,7 @@ async function printAuditFail(result) {
|
|
|
101
101
|
if (key === 'date') {
|
|
102
102
|
headers[key] = '<timestamp>';
|
|
103
103
|
}
|
|
104
|
-
else if (['cf-ray', 'server-timing'].includes(key)) {
|
|
104
|
+
else if (['cf-ray', 'server-timing', 'set-cookie'].includes(key)) {
|
|
105
105
|
headers[key] = '<omitted>';
|
|
106
106
|
}
|
|
107
107
|
else {
|
|
@@ -112,9 +112,13 @@ async function printAuditFail(result) {
|
|
|
112
112
|
try {
|
|
113
113
|
text = await res.text();
|
|
114
114
|
json = JSON.parse(text);
|
|
115
|
+
// is json, there shouldnt be nothing to sanitize (hopefully)
|
|
115
116
|
}
|
|
116
|
-
catch (
|
|
117
|
-
//
|
|
117
|
+
catch (_b) {
|
|
118
|
+
// is not json, avoid rendering html (rest is allowed)
|
|
119
|
+
if ((_a = res.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.includes('text/html')) {
|
|
120
|
+
text = '<html omitted>';
|
|
121
|
+
}
|
|
118
122
|
}
|
|
119
123
|
const stringified = JSON.stringify({
|
|
120
124
|
status: res.status,
|
package/lib/audits/render.mjs
CHANGED
|
@@ -35,8 +35,7 @@ export async function renderAuditResultsToHTML(results) {
|
|
|
35
35
|
report += `<li><span style="font-family: monospace">💡</span> <b>${grouped.notice.length}</b> notices (suggestions)</li>\n`;
|
|
36
36
|
}
|
|
37
37
|
if (grouped.warn.length) {
|
|
38
|
-
|
|
39
|
-
report += `<li><span style="font-family: monospace">⚠️</span> <b>${grouped.warn.length}</b> warnings (optional)</li>\n`;
|
|
38
|
+
report += `<li><span style="font-family: monospace">❗️</span> <b>${grouped.warn.length}</b> warnings (optional)</li>\n`;
|
|
40
39
|
}
|
|
41
40
|
if (grouped.error.length) {
|
|
42
41
|
report += `<li><span style="font-family: monospace">❌</span> <b>${grouped.error.length}</b> errors (required)</li>\n`;
|
|
@@ -85,6 +84,7 @@ export async function renderAuditResultsToHTML(results) {
|
|
|
85
84
|
return report;
|
|
86
85
|
}
|
|
87
86
|
async function printAuditFail(result) {
|
|
87
|
+
var _a;
|
|
88
88
|
let report = '';
|
|
89
89
|
report += `<li><code>${result.id}</code> ${result.name}\n`;
|
|
90
90
|
report += '<details>\n';
|
|
@@ -97,7 +97,7 @@ async function printAuditFail(result) {
|
|
|
97
97
|
if (key === 'date') {
|
|
98
98
|
headers[key] = '<timestamp>';
|
|
99
99
|
}
|
|
100
|
-
else if (['cf-ray', 'server-timing'].includes(key)) {
|
|
100
|
+
else if (['cf-ray', 'server-timing', 'set-cookie'].includes(key)) {
|
|
101
101
|
headers[key] = '<omitted>';
|
|
102
102
|
}
|
|
103
103
|
else {
|
|
@@ -108,9 +108,13 @@ async function printAuditFail(result) {
|
|
|
108
108
|
try {
|
|
109
109
|
text = await res.text();
|
|
110
110
|
json = JSON.parse(text);
|
|
111
|
+
// is json, there shouldnt be nothing to sanitize (hopefully)
|
|
111
112
|
}
|
|
112
|
-
catch (
|
|
113
|
-
//
|
|
113
|
+
catch (_b) {
|
|
114
|
+
// is not json, avoid rendering html (rest is allowed)
|
|
115
|
+
if ((_a = res.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.includes('text/html')) {
|
|
116
|
+
text = '<html omitted>';
|
|
117
|
+
}
|
|
114
118
|
}
|
|
115
119
|
const stringified = JSON.stringify({
|
|
116
120
|
status: res.status,
|
package/lib/handler.js
CHANGED
|
@@ -10,10 +10,22 @@ const graphql_1 = require("graphql");
|
|
|
10
10
|
const utils_1 = require("./utils");
|
|
11
11
|
/** Checks whether the passed value is the `graphql-http` server agnostic response. */
|
|
12
12
|
function isResponse(val) {
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
// Make sure the contents of body match string | null
|
|
14
|
+
if (!Array.isArray(val))
|
|
15
|
+
return false;
|
|
16
|
+
if (typeof val[0] !== 'string' && val[0] !== null)
|
|
17
|
+
return false;
|
|
18
|
+
if (!(0, utils_1.isObject)(val[1]))
|
|
19
|
+
return false;
|
|
20
|
+
// Make sure the contents of init match ResponseInit
|
|
21
|
+
const init = val[1];
|
|
22
|
+
if (typeof init.status !== 'number')
|
|
23
|
+
return false;
|
|
24
|
+
if (typeof init.statusText !== 'string')
|
|
25
|
+
return false;
|
|
26
|
+
if (init.headers && !(0, utils_1.isObject)(init.headers))
|
|
27
|
+
return false;
|
|
28
|
+
return true;
|
|
17
29
|
}
|
|
18
30
|
/**
|
|
19
31
|
* The GraphQL over HTTP spec compliant request parser for an incoming GraphQL request.
|
|
@@ -193,9 +205,9 @@ function createHandler(options) {
|
|
|
193
205
|
// accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
|
|
194
206
|
// TODO: handle the weight parameter "q"
|
|
195
207
|
const [mediaType, ...params] = accept.split(';');
|
|
196
|
-
const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=
|
|
208
|
+
const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf-8'; // utf-8 is assumed when not specified;
|
|
197
209
|
if (mediaType === 'application/graphql-response+json' &&
|
|
198
|
-
charset === 'charset=
|
|
210
|
+
charset === 'charset=utf-8') {
|
|
199
211
|
acceptedMediaType = 'application/graphql-response+json';
|
|
200
212
|
break;
|
|
201
213
|
}
|
|
@@ -203,7 +215,7 @@ function createHandler(options) {
|
|
|
203
215
|
if ((mediaType === 'application/json' ||
|
|
204
216
|
mediaType === 'application/*' ||
|
|
205
217
|
mediaType === '*/*') &&
|
|
206
|
-
charset === 'charset=utf8') {
|
|
218
|
+
(charset === 'charset=utf-8' || charset === 'charset=utf8')) {
|
|
207
219
|
acceptedMediaType = 'application/json';
|
|
208
220
|
break;
|
|
209
221
|
}
|
|
@@ -417,10 +429,11 @@ val) {
|
|
|
417
429
|
if (val instanceof Error &&
|
|
418
430
|
// GraphQL errors implement their own stringer
|
|
419
431
|
!isGraphQLError(val)) {
|
|
432
|
+
const error = val;
|
|
420
433
|
return {
|
|
421
|
-
// name:
|
|
422
|
-
message:
|
|
423
|
-
// stack:
|
|
434
|
+
// name: error.name, name is included in message
|
|
435
|
+
message: error.message,
|
|
436
|
+
// stack: error.stack, can leak sensitive details
|
|
424
437
|
};
|
|
425
438
|
}
|
|
426
439
|
return val;
|
package/lib/handler.mjs
CHANGED
|
@@ -7,10 +7,22 @@ import { validate as graphqlValidate, specifiedRules, execute as graphqlExecute,
|
|
|
7
7
|
import { isAsyncIterable, isExecutionResult, isObject } from './utils.mjs';
|
|
8
8
|
/** Checks whether the passed value is the `graphql-http` server agnostic response. */
|
|
9
9
|
function isResponse(val) {
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
// Make sure the contents of body match string | null
|
|
11
|
+
if (!Array.isArray(val))
|
|
12
|
+
return false;
|
|
13
|
+
if (typeof val[0] !== 'string' && val[0] !== null)
|
|
14
|
+
return false;
|
|
15
|
+
if (!isObject(val[1]))
|
|
16
|
+
return false;
|
|
17
|
+
// Make sure the contents of init match ResponseInit
|
|
18
|
+
const init = val[1];
|
|
19
|
+
if (typeof init.status !== 'number')
|
|
20
|
+
return false;
|
|
21
|
+
if (typeof init.statusText !== 'string')
|
|
22
|
+
return false;
|
|
23
|
+
if (init.headers && !isObject(init.headers))
|
|
24
|
+
return false;
|
|
25
|
+
return true;
|
|
14
26
|
}
|
|
15
27
|
/**
|
|
16
28
|
* The GraphQL over HTTP spec compliant request parser for an incoming GraphQL request.
|
|
@@ -189,9 +201,9 @@ export function createHandler(options) {
|
|
|
189
201
|
// accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
|
|
190
202
|
// TODO: handle the weight parameter "q"
|
|
191
203
|
const [mediaType, ...params] = accept.split(';');
|
|
192
|
-
const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=
|
|
204
|
+
const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf-8'; // utf-8 is assumed when not specified;
|
|
193
205
|
if (mediaType === 'application/graphql-response+json' &&
|
|
194
|
-
charset === 'charset=
|
|
206
|
+
charset === 'charset=utf-8') {
|
|
195
207
|
acceptedMediaType = 'application/graphql-response+json';
|
|
196
208
|
break;
|
|
197
209
|
}
|
|
@@ -199,7 +211,7 @@ export function createHandler(options) {
|
|
|
199
211
|
if ((mediaType === 'application/json' ||
|
|
200
212
|
mediaType === 'application/*' ||
|
|
201
213
|
mediaType === '*/*') &&
|
|
202
|
-
charset === 'charset=utf8') {
|
|
214
|
+
(charset === 'charset=utf-8' || charset === 'charset=utf8')) {
|
|
203
215
|
acceptedMediaType = 'application/json';
|
|
204
216
|
break;
|
|
205
217
|
}
|
|
@@ -412,10 +424,11 @@ val) {
|
|
|
412
424
|
if (val instanceof Error &&
|
|
413
425
|
// GraphQL errors implement their own stringer
|
|
414
426
|
!isGraphQLError(val)) {
|
|
427
|
+
const error = val;
|
|
415
428
|
return {
|
|
416
|
-
// name:
|
|
417
|
-
message:
|
|
418
|
-
// stack:
|
|
429
|
+
// name: error.name, name is included in message
|
|
430
|
+
message: error.message,
|
|
431
|
+
// stack: error.stack, can leak sensitive details
|
|
419
432
|
};
|
|
420
433
|
}
|
|
421
434
|
return val;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../../handler
|
|
1
|
+
import { HandlerOptions as RawHandlerOptions, OperationContext } from '../../handler.mjs';
|
|
2
2
|
import type { Handler as NetlifyHandler, HandlerEvent as NetlifyHandlerEvent, HandlerContext as NetlifyHandlerContext } from '@netlify/functions';
|
|
3
3
|
/**
|
|
4
4
|
* Handler options when using the netlify adapter
|
package/lib/use/express.js
CHANGED
package/lib/use/express.mjs
CHANGED
package/lib/use/http.js
CHANGED
package/lib/use/http.mjs
CHANGED
package/lib/use/http2.js
CHANGED
package/lib/use/http2.mjs
CHANGED
package/lib/use/koa.js
CHANGED
|
@@ -92,12 +92,13 @@ function createHandler(options) {
|
|
|
92
92
|
method: ctx.method,
|
|
93
93
|
headers: ctx.headers,
|
|
94
94
|
body: () => {
|
|
95
|
-
if (ctx.
|
|
95
|
+
if ('body' in ctx.request) {
|
|
96
96
|
// in case koa has a body parser
|
|
97
|
-
return ctx.body;
|
|
97
|
+
return ctx.request.body;
|
|
98
98
|
}
|
|
99
99
|
return new Promise((resolve) => {
|
|
100
100
|
let body = '';
|
|
101
|
+
ctx.req.setEncoding('utf-8');
|
|
101
102
|
ctx.req.on('data', (chunk) => (body += chunk));
|
|
102
103
|
ctx.req.on('end', () => resolve(body));
|
|
103
104
|
});
|
package/lib/use/koa.mjs
CHANGED
|
@@ -88,12 +88,13 @@ export function createHandler(options) {
|
|
|
88
88
|
method: ctx.method,
|
|
89
89
|
headers: ctx.headers,
|
|
90
90
|
body: () => {
|
|
91
|
-
if (ctx.
|
|
91
|
+
if ('body' in ctx.request) {
|
|
92
92
|
// in case koa has a body parser
|
|
93
|
-
return ctx.body;
|
|
93
|
+
return ctx.request.body;
|
|
94
94
|
}
|
|
95
95
|
return new Promise((resolve) => {
|
|
96
96
|
let body = '';
|
|
97
|
+
ctx.req.setEncoding('utf-8');
|
|
97
98
|
ctx.req.on('data', (chunk) => (body += chunk));
|
|
98
99
|
ctx.req.on('end', () => resolve(body));
|
|
99
100
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphql-http",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.2",
|
|
4
4
|
"description": "Simple, pluggable, zero-dependency, GraphQL over HTTP spec compliant server, client and audit suite.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphql",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"engines": {
|
|
25
25
|
"node": ">=12"
|
|
26
26
|
},
|
|
27
|
-
"packageManager": "yarn@
|
|
27
|
+
"packageManager": "yarn@4.0.2",
|
|
28
28
|
"main": "lib/index.js",
|
|
29
29
|
"module": "lib/index.mjs",
|
|
30
30
|
"browser": "umd/graphql-http.js",
|
|
@@ -116,43 +116,43 @@
|
|
|
116
116
|
"graphql": ">=0.11 <=16"
|
|
117
117
|
},
|
|
118
118
|
"devDependencies": {
|
|
119
|
-
"@cspell/cspell-types": "^
|
|
120
|
-
"@netlify/functions": "^
|
|
121
|
-
"@rollup/plugin-terser": "^0.4.
|
|
122
|
-
"@rollup/plugin-typescript": "^11.1.
|
|
119
|
+
"@cspell/cspell-types": "^8.1.2",
|
|
120
|
+
"@netlify/functions": "^2.4.0",
|
|
121
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
122
|
+
"@rollup/plugin-typescript": "^11.1.5",
|
|
123
123
|
"@semantic-release/changelog": "^6.0.3",
|
|
124
124
|
"@semantic-release/git": "^10.0.1",
|
|
125
|
-
"@types/eslint": "^8.44.
|
|
126
|
-
"@types/express": "^4.17.
|
|
125
|
+
"@types/eslint": "^8.44.8",
|
|
126
|
+
"@types/express": "^4.17.21",
|
|
127
127
|
"@types/glob": "^8.1.0",
|
|
128
|
-
"@types/html-validator": "^5.0.
|
|
129
|
-
"@types/k6": "^0.
|
|
130
|
-
"@types/koa": "^2.
|
|
131
|
-
"@types/koa-mount": "^4.0.
|
|
132
|
-
"@typescript-eslint/eslint-plugin": "^6.
|
|
133
|
-
"@typescript-eslint/parser": "^6.
|
|
134
|
-
"@whatwg-node/fetch": "^0.9.
|
|
135
|
-
"cspell": "^
|
|
136
|
-
"eslint": "^8.
|
|
137
|
-
"eslint-config-prettier": "^9.
|
|
128
|
+
"@types/html-validator": "^5.0.6",
|
|
129
|
+
"@types/k6": "^0.47.3",
|
|
130
|
+
"@types/koa": "^2.15.0",
|
|
131
|
+
"@types/koa-mount": "^4.0.5",
|
|
132
|
+
"@typescript-eslint/eslint-plugin": "^6.13.2",
|
|
133
|
+
"@typescript-eslint/parser": "^6.13.2",
|
|
134
|
+
"@whatwg-node/fetch": "^0.9.14",
|
|
135
|
+
"cspell": "^8.1.2",
|
|
136
|
+
"eslint": "^8.55.0",
|
|
137
|
+
"eslint-config-prettier": "^9.1.0",
|
|
138
138
|
"express": "^4.18.2",
|
|
139
|
-
"fastify": "^4.
|
|
140
|
-
"glob": "^10.3.
|
|
141
|
-
"graphql": "^16.8.
|
|
139
|
+
"fastify": "^4.24.3",
|
|
140
|
+
"glob": "^10.3.10",
|
|
141
|
+
"graphql": "^16.8.1",
|
|
142
142
|
"html-validator": "^6.0.1",
|
|
143
|
-
"koa": "^2.
|
|
143
|
+
"koa": "^2.15.3",
|
|
144
144
|
"koa-mount": "^4.0.0",
|
|
145
145
|
"node-fetch": "^3.3.2",
|
|
146
|
-
"prettier": "^3.0
|
|
147
|
-
"rollup": "^
|
|
146
|
+
"prettier": "^3.1.0",
|
|
147
|
+
"rollup": "^4.6.1",
|
|
148
148
|
"rollup-plugin-gzip": "^3.1.0",
|
|
149
|
-
"semantic-release": "^
|
|
150
|
-
"tslib": "^2.6.
|
|
151
|
-
"tsx": "^
|
|
152
|
-
"typedoc": "^0.
|
|
153
|
-
"typedoc-plugin-markdown": "^3.
|
|
154
|
-
"typescript": "^5.
|
|
155
|
-
"uWebSockets.js": "uNetworking/uWebSockets.js#v20.
|
|
156
|
-
"vitest": "^0.
|
|
149
|
+
"semantic-release": "^22.0.9",
|
|
150
|
+
"tslib": "^2.6.2",
|
|
151
|
+
"tsx": "^4.6.2",
|
|
152
|
+
"typedoc": "^0.25.4",
|
|
153
|
+
"typedoc-plugin-markdown": "^3.17.1",
|
|
154
|
+
"typescript": "^5.3.2",
|
|
155
|
+
"uWebSockets.js": "uNetworking/uWebSockets.js#v20.34.0",
|
|
156
|
+
"vitest": "^1.0.1"
|
|
157
157
|
}
|
|
158
158
|
}
|
|
@@ -808,8 +808,7 @@
|
|
|
808
808
|
report += `<li><span style="font-family: monospace">💡</span> <b>${grouped.notice.length}</b> notices (suggestions)</li>\n`;
|
|
809
809
|
}
|
|
810
810
|
if (grouped.warn.length) {
|
|
811
|
-
|
|
812
|
-
report += `<li><span style="font-family: monospace">⚠️</span> <b>${grouped.warn.length}</b> warnings (optional)</li>\n`;
|
|
811
|
+
report += `<li><span style="font-family: monospace">❗️</span> <b>${grouped.warn.length}</b> warnings (optional)</li>\n`;
|
|
813
812
|
}
|
|
814
813
|
if (grouped.error.length) {
|
|
815
814
|
report += `<li><span style="font-family: monospace">❌</span> <b>${grouped.error.length}</b> errors (required)</li>\n`;
|
|
@@ -858,6 +857,7 @@
|
|
|
858
857
|
return report;
|
|
859
858
|
}
|
|
860
859
|
async function printAuditFail(result) {
|
|
860
|
+
var _a;
|
|
861
861
|
let report = '';
|
|
862
862
|
report += `<li><code>${result.id}</code> ${result.name}\n`;
|
|
863
863
|
report += '<details>\n';
|
|
@@ -870,7 +870,7 @@
|
|
|
870
870
|
if (key === 'date') {
|
|
871
871
|
headers[key] = '<timestamp>';
|
|
872
872
|
}
|
|
873
|
-
else if (['cf-ray', 'server-timing'].includes(key)) {
|
|
873
|
+
else if (['cf-ray', 'server-timing', 'set-cookie'].includes(key)) {
|
|
874
874
|
headers[key] = '<omitted>';
|
|
875
875
|
}
|
|
876
876
|
else {
|
|
@@ -881,9 +881,13 @@
|
|
|
881
881
|
try {
|
|
882
882
|
text = await res.text();
|
|
883
883
|
json = JSON.parse(text);
|
|
884
|
+
// is json, there shouldnt be nothing to sanitize (hopefully)
|
|
884
885
|
}
|
|
885
|
-
catch (
|
|
886
|
-
//
|
|
886
|
+
catch (_b) {
|
|
887
|
+
// is not json, avoid rendering html (rest is allowed)
|
|
888
|
+
if ((_a = res.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.includes('text/html')) {
|
|
889
|
+
text = '<html omitted>';
|
|
890
|
+
}
|
|
887
891
|
}
|
|
888
892
|
const stringified = JSON.stringify({
|
|
889
893
|
status: res.status,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).graphqlHttpAudits={})}(this,(function(e){"use strict";function t(e){return null===e?"null":Array.isArray(e)?"array":typeof e}function a(e,t,a){return{id:e,name:t,fn:async()=>{try{return await a(),{id:e,name:t,status:"ok"}}catch(a){if(!(a instanceof n))throw a;return{id:e,name:t,status:t.startsWith("MUST")?"error":t.startsWith("SHOULD")?"warn":"notice",reason:a.reason,response:a.response}}}}}class n{constructor(e,t){this.response=e,this.reason=t}}function o(e){return{status:{toBe(t){if(e.status!==t)throw new n(e,`Response status code is not ${t}`)},toBeBetween:(t,a)=>{if(!(t<=e.status&&e.status<=a))throw new n(e,`Response status is not between ${t} and ${a}`)}},header:t=>({toContain(a){var o;if(!(null===(o=e.headers.get(t))||void 0===o?void 0:o.includes(a)))throw new n(e,`Response header ${t} does not contain ${a}`)},notToContain(a){var o;if(null===(o=e.headers.get(t))||void 0===o?void 0:o.includes(a))throw new n(e,`Response header ${t} contains ${a}`)}}),bodyAsExecutionResult:{data:{async toBe(t){const a=e.clone();if((await s(e)).data!==t)throw new n(a,`Response body execution result data is not "${t}"`)}},async toHaveProperty(t){const a=e.clone();if(!(t in await s(e)))throw new n(a,`Response body execution result does not have a property "${t}"`)},async notToHaveProperty(t){const a=e.clone();if(t in await s(e))throw new n(a,`Response body execution result has a property "${t}"`)}}}}async function s(e){let t,a;try{const a=new TextDecoder("utf-8"),n=await e.arrayBuffer();t=a.decode(n)}catch(t){throw new n(e,"Response body is not UTF-8 encoded")}try{a=JSON.parse(t)}catch(t){throw new n(e,"Response body is not valid JSON")}return a}function i(e){const s=e.fetchFn||fetch;return[a("22EB","SHOULD accept application/graphql-response+json and match the content-type",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),o(t).header("content-type").toContain("application/graphql-response+json")})),a("4655","MUST accept application/json and match the content-type",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),o(t).header("content-type").toContain("application/json")})),a("47DE","SHOULD accept */* and use application/json for the content-type",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"*/*"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),o(t).header("content-type").toContain("application/json")})),a("80D8","SHOULD assume application/json content-type when accept is missing",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),o(t).header("content-type").toContain("application/json")})),a("82A3","MUST use utf-8 encoding when responding",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200);try{new TextDecoder("utf-8").decode(await t.arrayBuffer())}catch(e){throw new n(t,"Response body is not UTF-8 encoded")}})),a("BF61","MUST accept utf-8 encoded request",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json; charset=utf-8"},body:JSON.stringify({query:'{ __type(name: "Run🏃Swim🏊") { name } }'})})).status.toBe(200)})),a("78D5","MUST assume utf-8 in request if encoding is unspecified",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})})).status.toBe(200)})),a("2C94","MUST accept POST requests",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})})).status.toBe(200)})),a("5A70","MAY accept application/x-www-form-urlencoded formatted GET requests",(async()=>{const t=new URL(await r(e.url));t.searchParams.set("query","{ __typename }");o(await s(t.toString())).status.toBe(200)})),a("9C48","MAY NOT allow executing mutations on GET requests",(async()=>{const t=new URL(await r(e.url));t.searchParams.set("query","mutation { __typename }");o(await s(t.toString(),{headers:{accept:"application/graphql-response+json"}})).status.toBeBetween(400,499)})),a("9ABE","MAY respond with 4xx status code if content-type is not supplied on POST requests",(async()=>{o(await s(await r(e.url),{method:"POST"})).status.toBeBetween(400,499)})),a("03D4","MUST accept application/json POST requests",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})})).status.toBe(200)})),a("A5BF","MAY use 400 status code when request body is missing on POST",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"}})).status.toBe(400)})),a("423L","MAY use 400 status code on missing {query} parameter",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({notquery:"{ __typename }"})})).status.toBe(400)})),...[{obj:"ect"},0,!1,["array"]].map(((n,i)=>a(`LKJ${i}`,`MAY use 400 status code on ${t(n)} {query} parameter`,(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:n})})).status.toBe(400)})))),a("34A2","SHOULD allow string {query} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }"})})).status.toBe(200)})),a("13EE","MUST allow string {query} parameter when accepting application/json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.notToHaveProperty("errors")})),...[{obj:"ect"},0,!1,["array"]].map(((n,i)=>a(`6C0${i}`,`MAY use 400 status code on ${t(n)} {operationName} parameter`,(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({operationName:n,query:"{ __typename }"})})).status.toBe(400)})))),a("8161","SHOULD allow string {operationName} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({operationName:"Query",query:"query Query { __typename }"})})).status.toBe(200)})),a("B8B3","MUST allow string {operationName} parameter when accepting application/json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({operationName:"Query",query:"query Query { __typename }"})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.notToHaveProperty("errors")})),...["variables","operationName","extensions"].flatMap(((t,n)=>[a(`94B${n}`,`SHOULD allow null {${t}} parameter when accepting application/graphql-response+json`,(async()=>{const a=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }",[t]:null})});o(a).status.toBe(200),await o(a).bodyAsExecutionResult.notToHaveProperty("errors")})),a(`022${n}`,`MUST allow null {${t}} parameter when accepting application/json`,(async()=>{const a=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }",[t]:null})});o(a).status.toBe(200),await o(a).bodyAsExecutionResult.notToHaveProperty("errors")}))])),...["string",0,!1,["array"]].map(((n,i)=>a(`476${i}`,`MAY use 400 status code on ${t(n)} {variables} parameter`,(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }",variables:n})})).status.toBe(400)})))),a("2EA1","SHOULD allow map {variables} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"query Type($name: String!) { __type(name: $name) { name } }",variables:{name:"sometype"}})})).status.toBe(200)})),a("28B9","MUST allow map {variables} parameter when accepting application/json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"query Type($name: String!) { __type(name: $name) { name } }",variables:{name:"sometype"}})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.notToHaveProperty("errors")})),a("D6D5","MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/graphql-response+json",(async()=>{const t=new URL(await r(e.url));t.searchParams.set("query","query Type($name: String!) { __type(name: $name) { name } }"),t.searchParams.set("variables",JSON.stringify({name:"sometype"}));o(await s(t.toString(),{method:"GET",headers:{accept:"application/graphql-response+json"}})).status.toBe(200)})),a("6A70","MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/json",(async()=>{const t=new URL(await r(e.url));t.searchParams.set("query","query Type($name: String!) { __type(name: $name) { name } }"),t.searchParams.set("variables",JSON.stringify({name:"sometype"}));const a=await s(t.toString(),{method:"GET",headers:{accept:"application/json"}});o(a).status.toBe(200),await o(a).bodyAsExecutionResult.notToHaveProperty("errors")})),...["string",0,!1,["array"]].map(((n,i)=>a(`58B${i}`,`MAY use 400 status code on ${t(n)} {extensions} parameter`,(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }",extensions:n})})).status.toBe(400)})))),a("428F","SHOULD allow map {extensions} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }",extensions:{some:"value"}})})).status.toBe(200)})),a("1B7A","MUST allow map {extensions} parameter when accepting application/json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }",extensions:{some:"value"}})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.notToHaveProperty("errors")})),a("B6DC","MAY use 4xx or 5xx status codes on JSON parsing failure",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:'{ "not a JSON'})).status.toBeBetween(400,499)})),a("BCF8","MAY use 400 status code on JSON parsing failure",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:'{ "not a JSON'})).status.toBe(400)})),a("8764","MAY use 4xx or 5xx status codes if parameters are invalid",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({qeury:"{ __typename }"})})).status.toBeBetween(400,599)})),a("3E3A","MAY use 400 status code if parameters are invalid",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({qeury:"{ __typename }"})})).status.toBe(400)})),a("572B","SHOULD use 200 status code on document parsing failure when accepting application/json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{"})})).status.toBe(200)})),a("FDE2","SHOULD use 200 status code on document validation failure when accepting application/json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ 8f31403dfe404bccbb0e835f2629c6a7 }"})})).status.toBe(200)})),a("7B9B","SHOULD use a status code of 200 on variable coercion failure when accepting application/json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"query CoerceFailure($id: ID!){ __typename }",variables:{id:null}})})).status.toBe(200)})),a("865D","SHOULD use 4xx or 5xx status codes on document parsing failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{"})})).status.toBeBetween(400,599)})),a("556A","SHOULD use 400 status code on document parsing failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{"})})).status.toBe(400)})),a("D586","SHOULD not contain the data entry on document parsing failure when accepting application/graphql-response+json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{"})});await o(t).bodyAsExecutionResult.data.toBe(void 0)})),a("51FE","SHOULD use 4xx or 5xx status codes on document validation failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ 8f31403dfe404bccbb0e835f2629c6a7 }"})})).status.toBeBetween(400,599)})),a("74FF","SHOULD use 400 status code on document validation failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ 8f31403dfe404bccbb0e835f2629c6a7 }"})})).status.toBe(400)})),a("5E5B","SHOULD not contain the data entry on document validation failure when accepting application/graphql-response+json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ 8f31403dfe404bccbb0e835f2629c6a7 }"})});await o(t).bodyAsExecutionResult.data.toBe(void 0)})),a("86EE","SHOULD use a status code of 400 on variable coercion failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"query CoerceFailure($id: ID!){ __typename }",variables:{id:null}})})).status.toBe(400)}))]}async function r(e){return"function"==typeof e?await e():e}async function p(e){let t="";t+=`<li><code>${e.id}</code> ${e.name}\n`,t+="<details>\n",t+=`<summary>${function(e,t=1024){if(e.length>t)return e.substring(0,t)+"...";return e}(e.reason)}</summary>\n`,t+='<pre><code class="lang-json">';const a=e.response,n={};for(const[e,t]of a.headers.entries())"date"===e?n[e]="<timestamp>":["cf-ray","server-timing"].includes(e)?n[e]="<omitted>":n[e]=t;let o,s="";try{s=await a.text(),o=JSON.parse(s)}catch(e){}return t+=JSON.stringify({status:a.status,statusText:a.statusText,headers:n,body:o||((null==s?void 0:s.length)>5120?"<body is too long>":s)||null},((e,t)=>{if(null!=t&&"object"==typeof t&&!Array.isArray(t)){const e={};return Object.keys(t).sort().reverse().reduce(((e,a)=>(e[a]=t[a],e)),e)}return t}),2)+"\n",t+="</code></pre>\n",t+="</details>\n",t+="</li>\n",t}e.auditServer=async function(e){const t=i(e);return await Promise.all(t.map((({fn:e})=>e())))},e.renderAuditResultsToHTML=async function(e){const t={total:0,ok:[],notice:[],warn:[],error:[]};for(const a of e)t.total++,a.status,t[a.status].push(a);let a="<i>* This report was auto-generated by graphql-http</i>\n";if(a+="\n",a+="<h1>GraphQL over HTTP audit report</h1>\n",a+="\n",a+="<ul>\n",a+=`<li><b>${t.total}</b> audits in total</li>\n`,t.ok.length&&(a+=`<li><span style="font-family: monospace">✅</span> <b>${t.ok.length}</b> pass</li>\n`),t.notice.length&&(a+=`<li><span style="font-family: monospace">💡</span> <b>${t.notice.length}</b> notices (suggestions)</li>\n`),t.warn.length&&(a+=`<li><span style="font-family: monospace">⚠️</span> <b>${t.warn.length}</b> warnings (optional)</li>\n`),t.error.length&&(a+=`<li><span style="font-family: monospace">❌</span> <b>${t.error.length}</b> errors (required)</li>\n`),a+="</ul>\n",a+="\n",t.ok.length){a+="<h2>Passing</h2>\n",a+="<ol>\n";for(const[,e]of t.ok.entries())a+=`<li><code>${e.id}</code> ${e.name}</li>\n`;a+="</ol>\n",a+="\n"}if(t.notice.length){a+="<h2>Notices</h2>\n",a+="The server <i>MAY</i> support these, but are truly optional. These are suggestions following recommended conventions.\n",a+="<ol>\n";for(const[,e]of t.notice.entries())a+=await p(e);a+="</ol>\n",a+="\n"}if(t.warn.length){a+="<h2>Warnings</h2>\n",a+="The server <i>SHOULD</i> support these, but is not required.\n",a+="<ol>\n";for(const[,e]of t.warn.entries())a+=await p(e);a+="</ol>\n",a+="\n"}if(t.error.length){a+="<h2>Errors</h2>\n",a+="The server <b>MUST</b> support these.\n",a+="<ol>\n";for(const[,e]of t.error.entries())a+=await p(e);a+="</ol>\n"}return a},e.serverAudits=i}));
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).graphqlHttpAudits={})}(this,(function(e){"use strict";function t(e){return null===e?"null":Array.isArray(e)?"array":typeof e}function a(e,t,a){return{id:e,name:t,fn:async()=>{try{return await a(),{id:e,name:t,status:"ok"}}catch(a){if(!(a instanceof n))throw a;return{id:e,name:t,status:t.startsWith("MUST")?"error":t.startsWith("SHOULD")?"warn":"notice",reason:a.reason,response:a.response}}}}}class n{constructor(e,t){this.response=e,this.reason=t}}function o(e){return{status:{toBe(t){if(e.status!==t)throw new n(e,`Response status code is not ${t}`)},toBeBetween:(t,a)=>{if(!(t<=e.status&&e.status<=a))throw new n(e,`Response status is not between ${t} and ${a}`)}},header:t=>({toContain(a){var o;if(!(null===(o=e.headers.get(t))||void 0===o?void 0:o.includes(a)))throw new n(e,`Response header ${t} does not contain ${a}`)},notToContain(a){var o;if(null===(o=e.headers.get(t))||void 0===o?void 0:o.includes(a))throw new n(e,`Response header ${t} contains ${a}`)}}),bodyAsExecutionResult:{data:{async toBe(t){const a=e.clone();if((await s(e)).data!==t)throw new n(a,`Response body execution result data is not "${t}"`)}},async toHaveProperty(t){const a=e.clone();if(!(t in await s(e)))throw new n(a,`Response body execution result does not have a property "${t}"`)},async notToHaveProperty(t){const a=e.clone();if(t in await s(e))throw new n(a,`Response body execution result has a property "${t}"`)}}}}async function s(e){let t,a;try{const a=new TextDecoder("utf-8"),n=await e.arrayBuffer();t=a.decode(n)}catch(t){throw new n(e,"Response body is not UTF-8 encoded")}try{a=JSON.parse(t)}catch(t){throw new n(e,"Response body is not valid JSON")}return a}function i(e){const s=e.fetchFn||fetch;return[a("22EB","SHOULD accept application/graphql-response+json and match the content-type",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),o(t).header("content-type").toContain("application/graphql-response+json")})),a("4655","MUST accept application/json and match the content-type",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),o(t).header("content-type").toContain("application/json")})),a("47DE","SHOULD accept */* and use application/json for the content-type",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"*/*"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),o(t).header("content-type").toContain("application/json")})),a("80D8","SHOULD assume application/json content-type when accept is missing",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),o(t).header("content-type").toContain("application/json")})),a("82A3","MUST use utf-8 encoding when responding",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200);try{new TextDecoder("utf-8").decode(await t.arrayBuffer())}catch(e){throw new n(t,"Response body is not UTF-8 encoded")}})),a("BF61","MUST accept utf-8 encoded request",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json; charset=utf-8"},body:JSON.stringify({query:'{ __type(name: "Run🏃Swim🏊") { name } }'})})).status.toBe(200)})),a("78D5","MUST assume utf-8 in request if encoding is unspecified",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})})).status.toBe(200)})),a("2C94","MUST accept POST requests",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})})).status.toBe(200)})),a("5A70","MAY accept application/x-www-form-urlencoded formatted GET requests",(async()=>{const t=new URL(await r(e.url));t.searchParams.set("query","{ __typename }");o(await s(t.toString())).status.toBe(200)})),a("9C48","MAY NOT allow executing mutations on GET requests",(async()=>{const t=new URL(await r(e.url));t.searchParams.set("query","mutation { __typename }");o(await s(t.toString(),{headers:{accept:"application/graphql-response+json"}})).status.toBeBetween(400,499)})),a("9ABE","MAY respond with 4xx status code if content-type is not supplied on POST requests",(async()=>{o(await s(await r(e.url),{method:"POST"})).status.toBeBetween(400,499)})),a("03D4","MUST accept application/json POST requests",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }"})})).status.toBe(200)})),a("A5BF","MAY use 400 status code when request body is missing on POST",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"}})).status.toBe(400)})),a("423L","MAY use 400 status code on missing {query} parameter",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({notquery:"{ __typename }"})})).status.toBe(400)})),...[{obj:"ect"},0,!1,["array"]].map(((n,i)=>a(`LKJ${i}`,`MAY use 400 status code on ${t(n)} {query} parameter`,(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:n})})).status.toBe(400)})))),a("34A2","SHOULD allow string {query} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }"})})).status.toBe(200)})),a("13EE","MUST allow string {query} parameter when accepting application/json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }"})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.notToHaveProperty("errors")})),...[{obj:"ect"},0,!1,["array"]].map(((n,i)=>a(`6C0${i}`,`MAY use 400 status code on ${t(n)} {operationName} parameter`,(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({operationName:n,query:"{ __typename }"})})).status.toBe(400)})))),a("8161","SHOULD allow string {operationName} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({operationName:"Query",query:"query Query { __typename }"})})).status.toBe(200)})),a("B8B3","MUST allow string {operationName} parameter when accepting application/json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({operationName:"Query",query:"query Query { __typename }"})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.notToHaveProperty("errors")})),...["variables","operationName","extensions"].flatMap(((t,n)=>[a(`94B${n}`,`SHOULD allow null {${t}} parameter when accepting application/graphql-response+json`,(async()=>{const a=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }",[t]:null})});o(a).status.toBe(200),await o(a).bodyAsExecutionResult.notToHaveProperty("errors")})),a(`022${n}`,`MUST allow null {${t}} parameter when accepting application/json`,(async()=>{const a=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }",[t]:null})});o(a).status.toBe(200),await o(a).bodyAsExecutionResult.notToHaveProperty("errors")}))])),...["string",0,!1,["array"]].map(((n,i)=>a(`476${i}`,`MAY use 400 status code on ${t(n)} {variables} parameter`,(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }",variables:n})})).status.toBe(400)})))),a("2EA1","SHOULD allow map {variables} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"query Type($name: String!) { __type(name: $name) { name } }",variables:{name:"sometype"}})})).status.toBe(200)})),a("28B9","MUST allow map {variables} parameter when accepting application/json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"query Type($name: String!) { __type(name: $name) { name } }",variables:{name:"sometype"}})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.notToHaveProperty("errors")})),a("D6D5","MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/graphql-response+json",(async()=>{const t=new URL(await r(e.url));t.searchParams.set("query","query Type($name: String!) { __type(name: $name) { name } }"),t.searchParams.set("variables",JSON.stringify({name:"sometype"}));o(await s(t.toString(),{method:"GET",headers:{accept:"application/graphql-response+json"}})).status.toBe(200)})),a("6A70","MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/json",(async()=>{const t=new URL(await r(e.url));t.searchParams.set("query","query Type($name: String!) { __type(name: $name) { name } }"),t.searchParams.set("variables",JSON.stringify({name:"sometype"}));const a=await s(t.toString(),{method:"GET",headers:{accept:"application/json"}});o(a).status.toBe(200),await o(a).bodyAsExecutionResult.notToHaveProperty("errors")})),...["string",0,!1,["array"]].map(((n,i)=>a(`58B${i}`,`MAY use 400 status code on ${t(n)} {extensions} parameter`,(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({query:"{ __typename }",extensions:n})})).status.toBe(400)})))),a("428F","SHOULD allow map {extensions} parameter when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ __typename }",extensions:{some:"value"}})})).status.toBe(200)})),a("1B7A","MUST allow map {extensions} parameter when accepting application/json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ __typename }",extensions:{some:"value"}})});o(t).status.toBe(200),await o(t).bodyAsExecutionResult.notToHaveProperty("errors")})),a("B6DC","MAY use 4xx or 5xx status codes on JSON parsing failure",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:'{ "not a JSON'})).status.toBeBetween(400,499)})),a("BCF8","MAY use 400 status code on JSON parsing failure",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:'{ "not a JSON'})).status.toBe(400)})),a("8764","MAY use 4xx or 5xx status codes if parameters are invalid",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({qeury:"{ __typename }"})})).status.toBeBetween(400,599)})),a("3E3A","MAY use 400 status code if parameters are invalid",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({qeury:"{ __typename }"})})).status.toBe(400)})),a("572B","SHOULD use 200 status code on document parsing failure when accepting application/json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{"})})).status.toBe(200)})),a("FDE2","SHOULD use 200 status code on document validation failure when accepting application/json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"{ 8f31403dfe404bccbb0e835f2629c6a7 }"})})).status.toBe(200)})),a("7B9B","SHOULD use a status code of 200 on variable coercion failure when accepting application/json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:"query CoerceFailure($id: ID!){ __typename }",variables:{id:null}})})).status.toBe(200)})),a("865D","SHOULD use 4xx or 5xx status codes on document parsing failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{"})})).status.toBeBetween(400,599)})),a("556A","SHOULD use 400 status code on document parsing failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{"})})).status.toBe(400)})),a("D586","SHOULD not contain the data entry on document parsing failure when accepting application/graphql-response+json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{"})});await o(t).bodyAsExecutionResult.data.toBe(void 0)})),a("51FE","SHOULD use 4xx or 5xx status codes on document validation failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ 8f31403dfe404bccbb0e835f2629c6a7 }"})})).status.toBeBetween(400,599)})),a("74FF","SHOULD use 400 status code on document validation failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ 8f31403dfe404bccbb0e835f2629c6a7 }"})})).status.toBe(400)})),a("5E5B","SHOULD not contain the data entry on document validation failure when accepting application/graphql-response+json",(async()=>{const t=await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"{ 8f31403dfe404bccbb0e835f2629c6a7 }"})});await o(t).bodyAsExecutionResult.data.toBe(void 0)})),a("86EE","SHOULD use a status code of 400 on variable coercion failure when accepting application/graphql-response+json",(async()=>{o(await s(await r(e.url),{method:"POST",headers:{"content-type":"application/json",accept:"application/graphql-response+json"},body:JSON.stringify({query:"query CoerceFailure($id: ID!){ __typename }",variables:{id:null}})})).status.toBe(400)}))]}async function r(e){return"function"==typeof e?await e():e}async function p(e){var t;let a="";a+=`<li><code>${e.id}</code> ${e.name}\n`,a+="<details>\n",a+=`<summary>${function(e,t=1024){if(e.length>t)return e.substring(0,t)+"...";return e}(e.reason)}</summary>\n`,a+='<pre><code class="lang-json">';const n=e.response,o={};for(const[e,t]of n.headers.entries())"date"===e?o[e]="<timestamp>":["cf-ray","server-timing","set-cookie"].includes(e)?o[e]="<omitted>":o[e]=t;let s,i="";try{i=await n.text(),s=JSON.parse(i)}catch(e){(null===(t=n.headers.get("content-type"))||void 0===t?void 0:t.includes("text/html"))&&(i="<html omitted>")}return a+=JSON.stringify({status:n.status,statusText:n.statusText,headers:o,body:s||((null==i?void 0:i.length)>5120?"<body is too long>":i)||null},((e,t)=>{if(null!=t&&"object"==typeof t&&!Array.isArray(t)){const e={};return Object.keys(t).sort().reverse().reduce(((e,a)=>(e[a]=t[a],e)),e)}return t}),2)+"\n",a+="</code></pre>\n",a+="</details>\n",a+="</li>\n",a}e.auditServer=async function(e){const t=i(e);return await Promise.all(t.map((({fn:e})=>e())))},e.renderAuditResultsToHTML=async function(e){const t={total:0,ok:[],notice:[],warn:[],error:[]};for(const a of e)t.total++,a.status,t[a.status].push(a);let a="<i>* This report was auto-generated by graphql-http</i>\n";if(a+="\n",a+="<h1>GraphQL over HTTP audit report</h1>\n",a+="\n",a+="<ul>\n",a+=`<li><b>${t.total}</b> audits in total</li>\n`,t.ok.length&&(a+=`<li><span style="font-family: monospace">✅</span> <b>${t.ok.length}</b> pass</li>\n`),t.notice.length&&(a+=`<li><span style="font-family: monospace">💡</span> <b>${t.notice.length}</b> notices (suggestions)</li>\n`),t.warn.length&&(a+=`<li><span style="font-family: monospace">❗️</span> <b>${t.warn.length}</b> warnings (optional)</li>\n`),t.error.length&&(a+=`<li><span style="font-family: monospace">❌</span> <b>${t.error.length}</b> errors (required)</li>\n`),a+="</ul>\n",a+="\n",t.ok.length){a+="<h2>Passing</h2>\n",a+="<ol>\n";for(const[,e]of t.ok.entries())a+=`<li><code>${e.id}</code> ${e.name}</li>\n`;a+="</ol>\n",a+="\n"}if(t.notice.length){a+="<h2>Notices</h2>\n",a+="The server <i>MAY</i> support these, but are truly optional. These are suggestions following recommended conventions.\n",a+="<ol>\n";for(const[,e]of t.notice.entries())a+=await p(e);a+="</ol>\n",a+="\n"}if(t.warn.length){a+="<h2>Warnings</h2>\n",a+="The server <i>SHOULD</i> support these, but is not required.\n",a+="<ol>\n";for(const[,e]of t.warn.entries())a+=await p(e);a+="</ol>\n",a+="\n"}if(t.error.length){a+="<h2>Errors</h2>\n",a+="The server <b>MUST</b> support these.\n",a+="<ol>\n";for(const[,e]of t.error.entries())a+=await p(e);a+="</ol>\n"}return a},e.serverAudits=i}));
|
|
Binary file
|
|
Binary file
|