graphql-http 1.6.1 → 1.7.1
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/LICENSE.md +1 -1
- package/README.md +124 -114
- package/lib/audits/server.js +160 -106
- package/lib/audits/server.mjs +160 -106
- package/lib/audits/utils.d.mts +2 -3
- package/lib/audits/utils.d.ts +2 -3
- package/lib/audits/utils.js +3 -8
- package/lib/audits/utils.mjs +3 -8
- package/lib/handler.d.mts +26 -24
- package/lib/handler.d.ts +26 -24
- package/lib/handler.js +17 -7
- package/lib/handler.mjs +17 -7
- package/lib/use/express.d.mts +21 -0
- package/lib/use/express.d.ts +21 -0
- package/lib/use/express.js +73 -0
- package/lib/use/express.mjs +69 -0
- package/lib/use/fastify.d.mts +21 -0
- package/lib/use/fastify.d.ts +21 -0
- package/lib/use/fastify.js +69 -0
- package/lib/use/fastify.mjs +65 -0
- package/lib/use/fetch.d.mts +40 -0
- package/lib/use/fetch.d.ts +40 -0
- package/lib/use/fetch.js +81 -0
- package/lib/use/fetch.mjs +77 -0
- package/lib/use/node.d.mts +21 -0
- package/lib/use/node.d.ts +21 -0
- package/lib/use/node.js +72 -0
- package/lib/use/node.mjs +68 -0
- package/package.json +45 -21
- package/umd/graphql-http.js +0 -2
- package/umd/graphql-http.min.js +1 -1
- package/umd/graphql-http.min.js.gz +0 -0
package/lib/audits/server.js
CHANGED
|
@@ -19,23 +19,25 @@ function serverAudits(opts) {
|
|
|
19
19
|
(0, utils_1.audit)(
|
|
20
20
|
// TODO: convert to MUST after watershed
|
|
21
21
|
'SHOULD accept application/graphql-response+json and match the content-type', async () => {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
const res = await fetchFn(url.toString(), {
|
|
22
|
+
const res = await fetchFn(opts.url, {
|
|
23
|
+
method: 'POST',
|
|
25
24
|
headers: {
|
|
25
|
+
'content-type': 'application/json',
|
|
26
26
|
accept: 'application/graphql-response+json',
|
|
27
27
|
},
|
|
28
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
28
29
|
});
|
|
29
30
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
30
31
|
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('application/graphql-response+json');
|
|
31
32
|
}),
|
|
32
33
|
(0, utils_1.audit)('MUST accept application/json and match the content-type', async () => {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
const res = await fetchFn(url.toString(), {
|
|
34
|
+
const res = await fetchFn(opts.url, {
|
|
35
|
+
method: 'POST',
|
|
36
36
|
headers: {
|
|
37
|
+
'content-type': 'application/json',
|
|
37
38
|
accept: 'application/json',
|
|
38
39
|
},
|
|
40
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
39
41
|
});
|
|
40
42
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
41
43
|
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('application/json');
|
|
@@ -43,12 +45,13 @@ function serverAudits(opts) {
|
|
|
43
45
|
(0, utils_1.audit)(
|
|
44
46
|
// TODO: convert to MUST after watershed
|
|
45
47
|
'SHOULD accept */* and use application/graphql-response+json for the content-type', async () => {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
const res = await fetchFn(url.toString(), {
|
|
48
|
+
const res = await fetchFn(opts.url, {
|
|
49
|
+
method: 'POST',
|
|
49
50
|
headers: {
|
|
51
|
+
'content-type': 'application/json',
|
|
50
52
|
accept: '*/*',
|
|
51
53
|
},
|
|
54
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
52
55
|
});
|
|
53
56
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
54
57
|
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('application/graphql-response+json');
|
|
@@ -56,16 +59,24 @@ function serverAudits(opts) {
|
|
|
56
59
|
(0, utils_1.audit)(
|
|
57
60
|
// TODO: convert to MUST after watershed
|
|
58
61
|
'SHOULD assume application/graphql-response+json content-type when accept is missing', async () => {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
const res = await fetchFn(opts.url, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: {
|
|
65
|
+
'content-type': 'application/json',
|
|
66
|
+
},
|
|
67
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
68
|
+
});
|
|
62
69
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
63
70
|
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('application/graphql-response+json');
|
|
64
71
|
}),
|
|
65
72
|
(0, utils_1.audit)('MUST use utf-8 encoding when responding', async () => {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
const res = await fetchFn(opts.url, {
|
|
74
|
+
method: 'POST',
|
|
75
|
+
headers: {
|
|
76
|
+
'content-type': 'application/json',
|
|
77
|
+
},
|
|
78
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
79
|
+
});
|
|
69
80
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
70
81
|
// has charset set to utf-8
|
|
71
82
|
try {
|
|
@@ -87,24 +98,27 @@ function serverAudits(opts) {
|
|
|
87
98
|
throw 'Body is not UTF-8 encoded';
|
|
88
99
|
}
|
|
89
100
|
}),
|
|
90
|
-
(0, utils_1.audit)('MUST accept
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
const res = await fetchFn(url.toString(), {
|
|
101
|
+
(0, utils_1.audit)('MUST accept utf-8 encoding', async () => {
|
|
102
|
+
const res = await fetchFn(opts.url, {
|
|
103
|
+
method: 'POST',
|
|
94
104
|
headers: {
|
|
95
|
-
|
|
105
|
+
'content-type': 'application/json; charset=utf-8',
|
|
96
106
|
},
|
|
107
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
97
108
|
});
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
110
|
+
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('utf-8');
|
|
111
|
+
}),
|
|
112
|
+
(0, utils_1.audit)('MUST assume utf-8 if encoding is unspecified', async () => {
|
|
113
|
+
const res = await fetchFn(opts.url, {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers: {
|
|
116
|
+
'content-type': 'application/json',
|
|
117
|
+
},
|
|
118
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
119
|
+
});
|
|
120
|
+
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
121
|
+
(0, utils_1.assert)('Content-Type header', res.headers.get('content-type')).toContain('utf-8');
|
|
108
122
|
}),
|
|
109
123
|
// Request
|
|
110
124
|
(0, utils_1.audit)('MUST accept POST requests', async () => {
|
|
@@ -122,7 +136,8 @@ function serverAudits(opts) {
|
|
|
122
136
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
123
137
|
}),
|
|
124
138
|
// Request GET
|
|
125
|
-
|
|
139
|
+
// TODO: this is a MUST if the server supports GET requests
|
|
140
|
+
(0, utils_1.audit)('MAY NOT allow executing mutations on GET requests', async () => {
|
|
126
141
|
const url = new URL(opts.url);
|
|
127
142
|
url.searchParams.set('query', 'mutation { __typename }');
|
|
128
143
|
const res = await fetchFn(url.toString(), {
|
|
@@ -130,15 +145,14 @@ function serverAudits(opts) {
|
|
|
130
145
|
accept: 'application/graphql-response+json',
|
|
131
146
|
},
|
|
132
147
|
});
|
|
133
|
-
(0, utils_1.assert)('Status code', res.status).
|
|
148
|
+
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 499);
|
|
134
149
|
}),
|
|
135
150
|
// Request POST
|
|
136
151
|
(0, utils_1.audit)('SHOULD respond with 4xx status code if content-type is not supplied on POST requests', async () => {
|
|
137
152
|
const res = await fetchFn(opts.url, {
|
|
138
153
|
method: 'POST',
|
|
139
154
|
});
|
|
140
|
-
(0, utils_1.assert)('Status code', res.status).
|
|
141
|
-
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(499);
|
|
155
|
+
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 499);
|
|
142
156
|
}),
|
|
143
157
|
(0, utils_1.audit)('MUST accept application/json POST requests', async () => {
|
|
144
158
|
const res = await fetchFn(opts.url, {
|
|
@@ -363,9 +377,7 @@ function serverAudits(opts) {
|
|
|
363
377
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
364
378
|
(0, utils_1.assert)('Execution result', await (0, utils_1.assertBodyAsExecutionResult)(res)).notToHaveProperty('errors');
|
|
365
379
|
}),
|
|
366
|
-
(0, utils_1.audit)(
|
|
367
|
-
// TODO: convert to MUST after watershed
|
|
368
|
-
'SHOULD allow URL-encoded JSON string {variables} parameter in GETs when accepting application/graphql-response+json', async () => {
|
|
380
|
+
(0, utils_1.audit)('MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/graphql-response+json', async () => {
|
|
369
381
|
const url = new URL(opts.url);
|
|
370
382
|
url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
|
|
371
383
|
url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
|
|
@@ -377,7 +389,7 @@ function serverAudits(opts) {
|
|
|
377
389
|
});
|
|
378
390
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
379
391
|
}),
|
|
380
|
-
(0, utils_1.audit)('
|
|
392
|
+
(0, utils_1.audit)('MAY allow URL-encoded JSON string {variables} parameter in GETs when accepting application/json', async () => {
|
|
381
393
|
const url = new URL(opts.url);
|
|
382
394
|
url.searchParams.set('query', 'query Type($name: String!) { __type(name: $name) { name } }');
|
|
383
395
|
url.searchParams.set('variables', JSON.stringify({ name: 'sometype' }));
|
|
@@ -466,29 +478,39 @@ function serverAudits(opts) {
|
|
|
466
478
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
467
479
|
}),
|
|
468
480
|
(0, utils_1.audit)('SHOULD use 200 status code if parameters are invalid when accepting application/json', async () => {
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
481
|
+
const res = await fetchFn(opts.url, {
|
|
482
|
+
method: 'POST',
|
|
483
|
+
headers: {
|
|
484
|
+
'content-type': 'application/json',
|
|
485
|
+
accept: 'application/json',
|
|
486
|
+
},
|
|
487
|
+
body: JSON.stringify({
|
|
488
|
+
qeury: /* typo */ '{ __typename }',
|
|
489
|
+
}),
|
|
474
490
|
});
|
|
475
491
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
476
492
|
}),
|
|
477
493
|
(0, utils_1.audit)('SHOULD use 200 status code on document parsing failure when accepting application/json', async () => {
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
494
|
+
const res = await fetchFn(opts.url, {
|
|
495
|
+
method: 'POST',
|
|
496
|
+
headers: {
|
|
497
|
+
'content-type': 'application/json',
|
|
498
|
+
accept: 'application/json',
|
|
499
|
+
},
|
|
500
|
+
body: JSON.stringify({ query: '{' }),
|
|
483
501
|
});
|
|
484
502
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
485
503
|
}),
|
|
486
504
|
(0, utils_1.audit)('SHOULD use 200 status code on document validation failure when accepting application/json', async () => {
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
505
|
+
const res = await fetchFn(opts.url, {
|
|
506
|
+
method: 'POST',
|
|
507
|
+
headers: {
|
|
508
|
+
'content-type': 'application/json',
|
|
509
|
+
accept: 'application/json',
|
|
510
|
+
},
|
|
511
|
+
body: JSON.stringify({
|
|
512
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
513
|
+
}),
|
|
492
514
|
});
|
|
493
515
|
(0, utils_1.assert)('Status code', res.status).toBe(200);
|
|
494
516
|
}),
|
|
@@ -504,8 +526,7 @@ function serverAudits(opts) {
|
|
|
504
526
|
},
|
|
505
527
|
body: '{ "not a JSON',
|
|
506
528
|
});
|
|
507
|
-
(0, utils_1.assert)('Status code', res.status).
|
|
508
|
-
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(599);
|
|
529
|
+
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 499);
|
|
509
530
|
}),
|
|
510
531
|
(0, utils_1.audit)('SHOULD use 400 status code on JSON parsing failure when accepting application/graphql-response+json', async () => {
|
|
511
532
|
const res = await fetchFn(opts.url, {
|
|
@@ -532,90 +553,123 @@ function serverAudits(opts) {
|
|
|
532
553
|
(0, utils_1.audit)(
|
|
533
554
|
// TODO: convert to MUST after watershed
|
|
534
555
|
'SHOULD use 4xx or 5xx status codes if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
556
|
+
const res = await fetchFn(opts.url, {
|
|
557
|
+
method: 'POST',
|
|
558
|
+
headers: {
|
|
559
|
+
'content-type': 'application/json',
|
|
560
|
+
accept: 'application/graphql-response+json',
|
|
561
|
+
},
|
|
562
|
+
body: JSON.stringify({
|
|
563
|
+
qeury /* typo */: '{ __typename }',
|
|
564
|
+
}),
|
|
540
565
|
});
|
|
541
|
-
(0, utils_1.assert)('Status code', res.status).
|
|
542
|
-
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(599);
|
|
566
|
+
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 599);
|
|
543
567
|
}),
|
|
544
568
|
(0, utils_1.audit)('SHOULD use 400 status code if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
569
|
+
const res = await fetchFn(opts.url, {
|
|
570
|
+
method: 'POST',
|
|
571
|
+
headers: {
|
|
572
|
+
'content-type': 'application/json',
|
|
573
|
+
accept: 'application/graphql-response+json',
|
|
574
|
+
},
|
|
575
|
+
body: JSON.stringify({
|
|
576
|
+
qeury: /* typo */ '{ __typename }',
|
|
577
|
+
}),
|
|
550
578
|
});
|
|
551
579
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
552
580
|
}),
|
|
553
581
|
(0, utils_1.audit)('SHOULD not contain the data entry if parameters are invalid when accepting application/graphql-response+json', async () => {
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
582
|
+
const res = await fetchFn(opts.url, {
|
|
583
|
+
method: 'POST',
|
|
584
|
+
headers: {
|
|
585
|
+
'content-type': 'application/json',
|
|
586
|
+
accept: 'application/graphql-response+json',
|
|
587
|
+
},
|
|
588
|
+
body: JSON.stringify({
|
|
589
|
+
qeury: /* typo */ '{ __typename }',
|
|
590
|
+
}),
|
|
559
591
|
});
|
|
560
592
|
(0, utils_1.assert)('Data entry', (await (0, utils_1.assertBodyAsExecutionResult)(res)).data).toBe(undefined);
|
|
561
593
|
}),
|
|
562
594
|
(0, utils_1.audit)(
|
|
563
595
|
// TODO: convert to MUST after watershed
|
|
564
596
|
'SHOULD use 4xx or 5xx status codes on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
597
|
+
const res = await fetchFn(opts.url, {
|
|
598
|
+
method: 'POST',
|
|
599
|
+
headers: {
|
|
600
|
+
'content-type': 'application/json',
|
|
601
|
+
accept: 'application/graphql-response+json',
|
|
602
|
+
},
|
|
603
|
+
body: JSON.stringify({
|
|
604
|
+
query: '{',
|
|
605
|
+
}),
|
|
570
606
|
});
|
|
571
|
-
(0, utils_1.assert)('Status code', res.status).
|
|
572
|
-
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(599);
|
|
607
|
+
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 599);
|
|
573
608
|
}),
|
|
574
609
|
(0, utils_1.audit)('SHOULD use 400 status code on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
610
|
+
const res = await fetchFn(opts.url, {
|
|
611
|
+
method: 'POST',
|
|
612
|
+
headers: {
|
|
613
|
+
'content-type': 'application/json',
|
|
614
|
+
accept: 'application/graphql-response+json',
|
|
615
|
+
},
|
|
616
|
+
body: JSON.stringify({
|
|
617
|
+
query: '{',
|
|
618
|
+
}),
|
|
580
619
|
});
|
|
581
620
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
582
621
|
}),
|
|
583
622
|
(0, utils_1.audit)('SHOULD not contain the data entry on document parsing failure when accepting application/graphql-response+json', async () => {
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
623
|
+
const res = await fetchFn(opts.url, {
|
|
624
|
+
method: 'POST',
|
|
625
|
+
headers: {
|
|
626
|
+
'content-type': 'application/json',
|
|
627
|
+
accept: 'application/graphql-response+json',
|
|
628
|
+
},
|
|
629
|
+
body: JSON.stringify({
|
|
630
|
+
query: '{',
|
|
631
|
+
}),
|
|
589
632
|
});
|
|
590
633
|
(0, utils_1.assert)('Data entry', (await (0, utils_1.assertBodyAsExecutionResult)(res)).data).toBe(undefined);
|
|
591
634
|
}),
|
|
592
635
|
(0, utils_1.audit)(
|
|
593
636
|
// TODO: convert to MUST after watershed
|
|
594
637
|
'SHOULD use 4xx or 5xx status codes on document validation failure when accepting application/graphql-response+json', async () => {
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
638
|
+
const res = await fetchFn(opts.url, {
|
|
639
|
+
method: 'POST',
|
|
640
|
+
headers: {
|
|
641
|
+
'content-type': 'application/json',
|
|
642
|
+
accept: 'application/graphql-response+json',
|
|
643
|
+
},
|
|
644
|
+
body: JSON.stringify({
|
|
645
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
646
|
+
}),
|
|
600
647
|
});
|
|
601
|
-
(0, utils_1.assert)('Status code', res.status).
|
|
602
|
-
(0, utils_1.assert)('Status code', res.status).toBeLessThanOrEqual(599);
|
|
648
|
+
(0, utils_1.assert)('Status code', res.status).toBeBetween(400, 599);
|
|
603
649
|
}),
|
|
604
650
|
(0, utils_1.audit)('SHOULD use 400 status code on document validation failure when accepting application/graphql-response+json', async () => {
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
651
|
+
const res = await fetchFn(opts.url, {
|
|
652
|
+
method: 'POST',
|
|
653
|
+
headers: {
|
|
654
|
+
'content-type': 'application/json',
|
|
655
|
+
accept: 'application/graphql-response+json',
|
|
656
|
+
},
|
|
657
|
+
body: JSON.stringify({
|
|
658
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
659
|
+
}),
|
|
610
660
|
});
|
|
611
661
|
(0, utils_1.assert)('Status code', res.status).toBe(400);
|
|
612
662
|
}),
|
|
613
663
|
(0, utils_1.audit)('SHOULD not contain the data entry on document validation failure when accepting application/graphql-response+json', async () => {
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
664
|
+
const res = await fetchFn(opts.url, {
|
|
665
|
+
method: 'POST',
|
|
666
|
+
headers: {
|
|
667
|
+
'content-type': 'application/json',
|
|
668
|
+
accept: 'application/graphql-response+json',
|
|
669
|
+
},
|
|
670
|
+
body: JSON.stringify({
|
|
671
|
+
query: '{ 8f31403dfe404bccbb0e835f2629c6a7 }', // making sure the field doesnt exist
|
|
672
|
+
}),
|
|
619
673
|
});
|
|
620
674
|
(0, utils_1.assert)('Data entry', (await (0, utils_1.assertBodyAsExecutionResult)(res)).data).toBe(undefined);
|
|
621
675
|
}),
|