-
Notifications
You must be signed in to change notification settings - Fork 15
Document limitRequired and complexityEstimators flags #291
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
a-alle
wants to merge
8
commits into
7.x
Choose a base branch
from
complexity-estimation
base: 7.x
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 3 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
8b086c0
add description of limitRequired and complexityEstimators flags
a-alle 0145e95
update formula
a-alle 5d61b94
Merge branch '7.x' into complexity-estimation
a-alle e994eb7
Apply suggestions from code review
a-alle 7b48aec
update text
a-alle 2904a44
remove irrelevant calculations
a-alle e8c2300
adds space
a-alle 8322d1a
Apply suggestions from code review
angrykoala File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
184 changes: 184 additions & 0 deletions
184
modules/ROOT/pages/security/avoid-unbounded-queries.adoc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,184 @@ | ||
| = Avoid unbounded queries | ||
| :description: This section describes how to prevent unbounded queries from reaching the server that can lead to performance issues. | ||
|
|
||
| Unbounded queries can cause performance issues and potentially crash your application by consuming excessive resources. The Neo4j GraphQL Library provides several mechanisms to help you avoid these problems. | ||
a-alle marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| == Require `limit` argument | ||
a-alle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| Setting `limitRequired` to `true` in the feature settings makes the `limit` argument non-nullable in the generated schema, forcing all read operations to include a `limit` argument on all fields that return a list of items. | ||
|
|
||
| This protects against queries that return massive datasets across nested relationships. | ||
a-alle marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| [source, javascript, indent=0] | ||
| ---- | ||
| const neoSchema = new Neo4jGraphQL({ | ||
| typeDefs, | ||
| features: { | ||
| limitRequired: true | ||
| } | ||
| }); | ||
| ---- | ||
|
|
||
| When this feature is enabled: | ||
|
|
||
| * All queries that return arrays must include a `limit` argument | ||
| * Queries without a `limit` will not be compliant with the GraphQL schema | ||
a-alle marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * This applies to both top-level queries and nested relationship queries | ||
|
|
||
| Assuming the following type definitions: | ||
| [source, graphql, indent=0] | ||
| ---- | ||
| type Movie @node { | ||
| title: String | ||
| actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) | ||
| } | ||
|
|
||
| type Actor @node { | ||
| name: String | ||
| } | ||
| ---- | ||
|
|
||
| * Valid query: | ||
| [source, graphql, indent=0] | ||
| ---- | ||
| query { | ||
| movies(limit: 1) { | ||
| title | ||
| actors(limit: 1) { | ||
| name | ||
| } | ||
| } | ||
| } | ||
| ---- | ||
|
|
||
| * Invalid query, `limit` argument not provided on `movies` field: | ||
| [source, graphql, indent=0] | ||
| ---- | ||
| query { | ||
| # Error: `Field "movies" argument "limit" of type "Int!" is required, but it was not provided` | ||
| movies { | ||
| title | ||
| actors(limit: 1) { | ||
| name | ||
| } | ||
| } | ||
| } | ||
| ---- | ||
|
|
||
| * Invalid query, `limit` argument not provided on `actors` field: | ||
| [source, graphql, indent=0] | ||
| ---- | ||
| query { | ||
| movies(limit: 1) { | ||
| title | ||
| # Error: `Field "actors" argument "limit" of type "Int!" is required, but it was not provided` | ||
| actors { | ||
| name | ||
| } | ||
| } | ||
| } | ||
| ---- | ||
|
|
||
|
|
||
| == Complexity estimators | ||
|
|
||
| Setting `complexityEstimators` to `true` enables the assignment of a complexity "score" to each field in your schema. This feature creates complexity estimators that are compatible with the link:https://www.npmjs.com/package/graphql-query-complexity[graphql-query-complexity] library. | ||
a-alle marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| You can then use the complexity "score" to automatically reject queries that exceed your defined complexity limits before reaching the server, protecting your system from resource-intensive operations. | ||
|
|
||
| [source, javascript, indent=0] | ||
| ---- | ||
| const neoSchema = new Neo4jGraphQL({ | ||
| typeDefs, | ||
| features: { | ||
| complexityEstimators: true | ||
| } | ||
| }); | ||
| ---- | ||
|
|
||
| When this feature is enabled: | ||
|
|
||
| * Each field in your schema is assigned a complexity score | ||
| * You can use the `getComplexity` function from `graphql-query-complexity` to calculate the total complexity of incoming queries | ||
| * You can set maximum complexity thresholds to reject overly complex queries | ||
|
|
||
| === Example usage | ||
|
|
||
| To use the complexity estimators, include this logic to run when receiving a query in your GraphQL server implementation. | ||
| For example, if you are using Apollo Server, you can use the `requestDidStart` function of an `ApolloServerPlugin`. | ||
|
|
||
| [source, javascript, indent=0] | ||
| ---- | ||
| const complexity = getComplexity({ | ||
| schema, // result of Neo4jGraphQL.getSchema() | ||
| query, // the GraphQL query document sent to server for execution | ||
| estimators: DefaultComplexityEstimators, // exported from the neo4j/graphql package | ||
| }); | ||
|
|
||
| // Define your maximum complexity threshold | ||
| const complexityThreshold = 1000; | ||
| if (complexity > complexityThreshold) { | ||
| throw new Error(`Query is too complex: ${complexity}. Maximum allowed complexity is ${complexityThreshold}.`); | ||
| } | ||
| ---- | ||
|
|
||
| === Computing the complexity | ||
|
|
||
| The provided complexity estimators compute the complexity of a query using the following formula: | ||
| - Each field has a base complexity of 1 | ||
a-alle marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| - If a field returns a list, the complexity of its children is multiplied by the `limit` argument provided on that field to which the field's complexity is added | ||
a-alle marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| - If no `limit` argument is provided, a default multiplier of 1 is used | ||
a-alle marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| [NOTE] | ||
| ==== | ||
| c_query = c_child * lvl_limit + c_field | ||
|
|
||
| where c_field = 1, lvl_limit defaults to 1 | ||
| ==== | ||
a-alle marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| The queries below demonstrate the usage of the formula. | ||
a-alle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| [source, graphql, indent=0] | ||
| ---- | ||
| query { | ||
| movies(limit: 10) { # c = 10 * 102 + 1 = 1021 | ||
| title # c = 1 | ||
| actors(limit: 50) { # c = 50 * 2 + 1 = 101 | ||
| name # c = 1 | ||
| age # c = 1 | ||
| } | ||
| } | ||
| } | ||
| # Total complexity = 1 + 10 * (1 + 1 + 50 * (1 + 1)) = 1021 | ||
| ---- | ||
|
|
||
| [source, graphql, indent=0] | ||
| ---- | ||
| query { | ||
| productionsConnection(first: 10, sort: [{ title: ASC }]) { # (12 + 2x) * 10 + 1 = 121 + 20x | ||
| edges { # 12 + 2x | ||
| node { # 9 + 2x + 1 + 1 = 11 + 2x | ||
| title # 1 | ||
| actorsConnection(first: 2, sort: { node: { name: ASC } }) { # (4 + x) * 2 + 1 = 9 + 2x | ||
| edges { # 3 + x + 1 = 4 + x | ||
| node { # 2 | ||
| name # 1 | ||
| } | ||
| properties { # 1 + x; x = max nr of properties in below fragments | ||
| ... on ActedIn { | ||
| roles | ||
| } | ||
| ... on ActedInSeries { | ||
| roles | ||
| episodes | ||
| year | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| # Total complexity = 1 + 10 * (1 + 1 + 1 + 1 + 2 * (1 + 1 + 1 + 1 + 3)) = 181 | ||
| ---- | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.