Skip to content

Commit 799477f

Browse files
committed
Initial commit
1 parent bbf8da5 commit 799477f

33 files changed

+9174
-0
lines changed

.babelrc

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"sourceRoot": "./packages/core/src",
3+
"parserOpts": {
4+
"allowReturnOutsideFunction": true
5+
},
6+
"presets": ["es2015", "stage-0"],
7+
"plugins": [
8+
"syntax-async-functions",
9+
"transform-class-properties",
10+
"transform-flow-strip-types",
11+
"transform-object-rest-spread",
12+
"transform-regenerator",
13+
"transform-runtime",
14+
["babel-plugin-transform-builtin-extend", {
15+
"globals": ["Error"]
16+
}]
17+
]
18+
}

.eslintrc

+232
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
{
2+
"parser": "babel-eslint",
3+
4+
"plugins": [
5+
"babel",
6+
"flowtype",
7+
"flowtype-errors"
8+
],
9+
10+
"extends": [
11+
"eslint:recommended",
12+
"plugin:flowtype/recommended"
13+
],
14+
15+
"env": {
16+
"browser": true,
17+
"es6": true,
18+
"node": true
19+
},
20+
21+
"globals": {
22+
"describe": true,
23+
"expect": true,
24+
"it": true,
25+
"before": true,
26+
"after": true
27+
},
28+
29+
"parserOptions": {
30+
"ecmaVersion": 7,
31+
"sourceType": "module",
32+
"ecmaFeatures": {
33+
"arrowFunctions": true,
34+
"binaryLiterals": true,
35+
"blockBindings": true,
36+
"classes": true,
37+
"defaultParams": true,
38+
"destructuring": true,
39+
"experimentalObjectRestSpread": true,
40+
"forOf": true,
41+
"generators": true,
42+
"globalReturn": true,
43+
"jsx": true,
44+
"modules": true,
45+
"objectLiteralComputedProperties": true,
46+
"objectLiteralDuplicateProperties": true,
47+
"objectLiteralShorthandMethods": true,
48+
"objectLiteralShorthandProperties": true,
49+
"octalLiterals": true,
50+
"regexUFlag": true,
51+
"regexYFlag": true,
52+
"restParams": true,
53+
"spread": true,
54+
"superInFunctions": true,
55+
"templateStrings": true,
56+
"unicodeCodePointEscapes": true
57+
}
58+
},
59+
60+
"rules": {
61+
"flowtype-errors/show-errors": 2,
62+
"arrow-parens": [2, "as-needed"],
63+
"array-bracket-spacing": [2, "always"],
64+
"generator-star-spacing": [2, {"before": true, "after": false}],
65+
66+
"arrow-spacing": 2,
67+
"block-scoped-var": 0,
68+
"brace-style": [2, "1tbs", {"allowSingleLine": true}],
69+
"callback-return": 2,
70+
"camelcase": [2, {"properties": "always"}],
71+
"comma-dangle": 0,
72+
"comma-spacing": 0,
73+
"comma-style": [2, "last"],
74+
"complexity": 0,
75+
"computed-property-spacing": [2, "never"],
76+
"consistent-return": 0,
77+
"consistent-this": 0,
78+
"curly": [2, "all"],
79+
"default-case": 0,
80+
"dot-location": [2, "property"],
81+
"dot-notation": 0,
82+
"eol-last": 2,
83+
"eqeqeq": 2,
84+
"func-names": 0,
85+
"func-style": 0,
86+
"guard-for-in": 2,
87+
"handle-callback-err": [2, "error"],
88+
"id-length": 0,
89+
"id-match": [2, "^(?:_?[a-zA-Z0-9]*)|[_A-Z0-9]+$"],
90+
"indent": [2, 2, {"SwitchCase": 1}],
91+
"init-declarations": 0,
92+
"key-spacing": [2, {"beforeColon": false, "afterColon": true}],
93+
"keyword-spacing": 2,
94+
"linebreak-style": 2,
95+
"lines-around-comment": 0,
96+
"max-depth": 0,
97+
"max-len": [2, 120, 4, {"ignoreStrings": true}],
98+
"max-nested-callbacks": 0,
99+
"max-params": 0,
100+
"max-statements": 0,
101+
"new-cap": 0,
102+
"new-parens": 2,
103+
"newline-after-var": 0,
104+
"no-alert": 2,
105+
"no-array-constructor": 2,
106+
"no-bitwise": 0,
107+
"no-caller": 2,
108+
"no-catch-shadow": 0,
109+
"no-class-assign": 2,
110+
"no-cond-assign": 2,
111+
"no-console": 1,
112+
"no-const-assign": 2,
113+
"no-constant-condition": 2,
114+
"no-continue": 0,
115+
"no-control-regex": 0,
116+
"no-debugger": 1,
117+
"no-delete-var": 2,
118+
"no-div-regex": 2,
119+
"no-dupe-args": 2,
120+
"no-dupe-keys": 2,
121+
"no-duplicate-case": 2,
122+
"no-else-return": 2,
123+
"no-empty": 2,
124+
"no-empty-character-class": 2,
125+
"no-eq-null": 0,
126+
"no-eval": 2,
127+
"no-ex-assign": 2,
128+
"no-extend-native": 2,
129+
"no-extra-bind": 2,
130+
"no-extra-boolean-cast": 2,
131+
"no-extra-parens": 0,
132+
"no-extra-semi": 2,
133+
"no-fallthrough": 2,
134+
"no-floating-decimal": 2,
135+
"no-func-assign": 2,
136+
"no-implicit-coercion": 2,
137+
"no-implied-eval": 2,
138+
"no-inline-comments": 0,
139+
"no-inner-declarations": [2, "functions"],
140+
"no-invalid-regexp": 2,
141+
"no-invalid-this": 0,
142+
"no-irregular-whitespace": 2,
143+
"no-iterator": 2,
144+
"no-label-var": 2,
145+
"no-labels": [2, {"allowLoop": true}],
146+
"no-lone-blocks": 2,
147+
"no-lonely-if": 2,
148+
"no-loop-func": 0,
149+
"no-mixed-requires": [2, true],
150+
"no-mixed-spaces-and-tabs": 2,
151+
"no-multi-spaces": 2,
152+
"no-multi-str": 2,
153+
"no-multiple-empty-lines": 0,
154+
"no-native-reassign": 0,
155+
"no-negated-in-lhs": 2,
156+
"no-nested-ternary": 0,
157+
"no-new": 2,
158+
"no-new-func": 0,
159+
"no-new-object": 2,
160+
"no-new-require": 2,
161+
"no-new-wrappers": 2,
162+
"no-obj-calls": 2,
163+
"no-octal": 2,
164+
"no-octal-escape": 2,
165+
"no-param-reassign": 2,
166+
"no-path-concat": 2,
167+
"no-plusplus": 0,
168+
"no-process-env": 0,
169+
"no-process-exit": 0,
170+
"no-proto": 2,
171+
"no-redeclare": 2,
172+
"no-regex-spaces": 2,
173+
"no-restricted-modules": 0,
174+
"no-return-assign": 2,
175+
"no-script-url": 2,
176+
"no-self-compare": 0,
177+
"no-sequences": 2,
178+
"no-shadow": 2,
179+
"no-shadow-restricted-names": 2,
180+
"no-spaced-func": 2,
181+
"no-sparse-arrays": 2,
182+
"no-sync": 2,
183+
"no-ternary": 0,
184+
"no-this-before-super": 2,
185+
"no-throw-literal": 2,
186+
"no-undef": 2,
187+
"no-undef-init": 2,
188+
"no-undefined": 0,
189+
"no-underscore-dangle": 0,
190+
"no-unexpected-multiline": 2,
191+
"no-unneeded-ternary": 2,
192+
"no-unreachable": 2,
193+
"no-unused-expressions": 2,
194+
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
195+
"no-use-before-define": 0,
196+
"no-useless-call": 2,
197+
"no-var": 2,
198+
"no-void": 2,
199+
"no-warning-comments": 0,
200+
"no-with": 2,
201+
"object-curly-spacing": [0, "always"],
202+
"object-shorthand": [2, "always"],
203+
"one-var": [2, "never"],
204+
"operator-assignment": [2, "always"],
205+
"operator-linebreak": [2, "after"],
206+
"padded-blocks": 0,
207+
"prefer-const": 2,
208+
"prefer-reflect": 0,
209+
"prefer-spread": 0,
210+
"quote-props": [2, "as-needed"],
211+
"quotes": [2, "single"],
212+
"radix": 2,
213+
"require-yield": 2,
214+
"semi": [2, "always"],
215+
"semi-spacing": [2, {"before": false, "after": true}],
216+
"sort-vars": 0,
217+
"space-before-blocks": [2, "always"],
218+
"space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
219+
"space-in-parens": 0,
220+
"space-infix-ops": [2, {"int32Hint": false}],
221+
"space-unary-ops": [2, {"words": true, "nonwords": false}],
222+
"spaced-comment": [2, "always"],
223+
"strict": 0,
224+
"use-isnan": 2,
225+
"valid-jsdoc": 0,
226+
"valid-typeof": 2,
227+
"vars-on-top": 0,
228+
"wrap-iife": 2,
229+
"wrap-regex": 0,
230+
"yoda": [2, "never", {"exceptRange": true}]
231+
}
232+
}

.flowconfig

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[include]
2+
./src
3+
4+
[ignore]
5+
6+
[libs]
7+
./flow-typed/npm
8+
9+
[options]
10+
module.file_ext=.js
11+
esproposal.class_static_fields=enable
12+
esproposal.class_instance_fields=enable

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/dist
2+
node_modules
3+
.DS_Store
4+
.idea

README.md

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# GraphQL Query Complexity Analysis for graphql-js
2+
3+
This library provides GraphQL query analysis to reject complex queries to your GraphQL server.
4+
This can be used to protect your GraphQL servers against resource exhaustion and DoS attacks.
5+
6+
Works with [graphql-js](https://github.com/graphql/graphql-js) reference implementation.
7+
8+
9+
## Installation
10+
11+
Install the package via npm
12+
13+
```bash
14+
npm install -S graphql-query-complexity
15+
```
16+
17+
## Usage
18+
19+
Create the rule with a maximum query complexity:
20+
21+
```javascript
22+
const rule = queryComplexity({
23+
// The maximum allowed query complexity, queries above this threshold will be rejected
24+
maximumComplexity: 1000,
25+
26+
// The query variables. This is needed because the variables are not available
27+
// in the visitor of the graphql-js library
28+
variables: {},
29+
30+
// Optional callback function to retrieve the determined query complexity
31+
// Will be invoked weather the query is rejected or not
32+
// This can be used for logging or to implement rate limiting
33+
onComplete: (complexity: number) => {console.log('Determined query complexity: ', complexity)},
34+
35+
// Optional function to create a custom error
36+
createError: (max: number, actual: number) => {
37+
return new GraphQLError(`Query is too complex: ${actual}. Maximum allowed complexity: ${max}`);
38+
}
39+
});
40+
```
41+
42+
## Customizing complexity calculation
43+
44+
By default, every field gets a complexity of 1. Let's look at the following example query:
45+
46+
```graphql
47+
query {
48+
posts(count: 10) {
49+
title
50+
text
51+
}
52+
}
53+
```
54+
55+
This would result in a complexity of 3. The fields `posts`, `title` and `text` each add a complexity of 1.
56+
If we assume that the posts field returns a list of 10 posts, the complexity estimation is pretty inaccurate.
57+
58+
When defining your fields, you have a two options to customize the calculation.
59+
60+
You can set a custom complexity in the field config:
61+
62+
```javascript
63+
const Post = new GraphQLObjectType({
64+
name: 'Post',
65+
fields: () => ({
66+
title: { type: GraphQLString },
67+
text: { type: GraphQLString, complexity: 5 },
68+
}),
69+
});
70+
```
71+
The same query would now result in a complexity of 7.
72+
5 for the `text` field and 1 for each of the other fields.
73+
74+
You can also pass a calculation function in the field config to determine a custom complexity.
75+
This function will provide the complexity of the child nodes as well as the field input arguments.
76+
77+
That way you can make a more realistic estimation of individual field complexity values:
78+
79+
```
80+
const Query = new GraphQLObjectType({
81+
name: 'Query',
82+
fields: () => ({
83+
posts: {
84+
type: new GraphQLList(Post),
85+
complexity: (args, childComplexity) => childComplexity * args.count,
86+
args: {
87+
count: {
88+
type: GraphQLInt,
89+
defaultValue: 10
90+
}
91+
}
92+
},
93+
}),
94+
});
95+
```
96+
97+
This would result in a complexity of 60 since the `childComplexity` of posts (`text` 5, `title` 1) is multiplied by the
98+
number of posts (`args.count`).
99+
100+
## Usage with express-graphql
101+
102+
To use the query complexity analysis validation rule with express-graphql, use something like the
103+
following:
104+
105+
```javascript
106+
import queryComplexity from 'graphql-query-complexity';
107+
import express from 'express';
108+
import graphqlHTTP from 'express-graphql';
109+
import schema from './schema';
110+
111+
const app = express();
112+
app.use('/api', graphqlHTTP(async (request, response, {variables}) => ({
113+
schema,
114+
validationRules: [ queryComplexity({
115+
maximumComplexity: 1000,
116+
variables,
117+
onComplete: (complexity: number) => {console.log('Query Complexity:', complexity);},
118+
}) ]
119+
})));
120+
```
121+
122+
## Credits
123+
124+
This project is heavily inspired by the query complexity analysis in the
125+
[Sangria GraphQL](http://sangria-graphql.org/) implementation.

0 commit comments

Comments
 (0)