@deenruv/harden-plugin 1.0.0
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 +23 -0
- package/README.md +52 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +20 -0
- package/lib/index.js.map +1 -0
- package/lib/src/constants.d.ts +2 -0
- package/lib/src/constants.js +6 -0
- package/lib/src/constants.js.map +1 -0
- package/lib/src/harden.plugin.d.ts +143 -0
- package/lib/src/harden.plugin.js +184 -0
- package/lib/src/harden.plugin.js.map +1 -0
- package/lib/src/middleware/hide-validation-errors-plugin.d.ts +9 -0
- package/lib/src/middleware/hide-validation-errors-plugin.js +30 -0
- package/lib/src/middleware/hide-validation-errors-plugin.js.map +1 -0
- package/lib/src/middleware/query-complexity-plugin.d.ts +25 -0
- package/lib/src/middleware/query-complexity-plugin.js +105 -0
- package/lib/src/middleware/query-complexity-plugin.js.map +1 -0
- package/lib/src/types.d.ts +79 -0
- package/lib/src/types.js +3 -0
- package/lib/src/types.js.map +1 -0
- package/package.json +32 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# License 1
|
|
2
|
+
|
|
3
|
+
The MIT License
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2025-present Aexol
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
8
|
+
|
|
9
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
10
|
+
|
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
12
|
+
|
|
13
|
+
# License 2
|
|
14
|
+
|
|
15
|
+
The MIT License
|
|
16
|
+
|
|
17
|
+
Copyright (c) 2018-2025 Michael Bromley
|
|
18
|
+
|
|
19
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# @deenruv/harden-plugin
|
|
2
|
+
|
|
3
|
+
Hardens your Deenruv GraphQL APIs against abuse and attacks. Recommended for all production deployments.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @deenruv/harden-plugin
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { HardenPlugin } from '@deenruv/harden-plugin';
|
|
15
|
+
|
|
16
|
+
const config = {
|
|
17
|
+
plugins: [
|
|
18
|
+
HardenPlugin.init({
|
|
19
|
+
maxQueryComplexity: 650,
|
|
20
|
+
apiMode: process.env.APP_ENV === 'dev' ? 'dev' : 'prod',
|
|
21
|
+
}),
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Options:**
|
|
27
|
+
|
|
28
|
+
| Option | Type | Default | Description |
|
|
29
|
+
|--------|------|---------|-------------|
|
|
30
|
+
| `maxQueryComplexity` | `number` | `1000` | Maximum permitted query complexity score |
|
|
31
|
+
| `apiMode` | `'dev' \| 'prod'` | `'prod'` | In `prod` mode, disables introspection and GraphQL playground |
|
|
32
|
+
| `hideFieldSuggestions` | `boolean` | `true` | Prevents field name suggestions in error messages (blocks schema sniffing) |
|
|
33
|
+
| `logComplexityScore` | `boolean` | `false` | Logs complexity score breakdown for each query (useful for tuning) |
|
|
34
|
+
| `customComplexityFactors` | `{ [path: string]: number }` | - | Custom complexity weights for specific fields |
|
|
35
|
+
| `queryComplexityEstimators` | `ComplexityEstimator[]` | Deenruv default | Custom estimator functions for complexity calculation |
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- **Query complexity analysis** - Rejects overly complex queries that could overload server resources (powered by [graphql-query-complexity](https://www.npmjs.com/package/graphql-query-complexity))
|
|
40
|
+
- **Introspection control** - Disables introspection and GraphQL playground in production mode
|
|
41
|
+
- **Field suggestion hiding** - Removes field name suggestions from validation errors, preventing trial-and-error schema discovery
|
|
42
|
+
- **Complexity logging** - Optional detailed logging of query complexity scores for tuning the `maxQueryComplexity` threshold
|
|
43
|
+
- **Custom complexity weights** - Per-field complexity factors for expensive custom operations
|
|
44
|
+
- **Deenruv-optimized estimator** - Default complexity estimator tuned specifically for the Deenruv API (applies a factor of 1000 for list queries without a `take` argument)
|
|
45
|
+
|
|
46
|
+
## Admin UI
|
|
47
|
+
|
|
48
|
+
Server-only plugin. No Admin UI extensions.
|
|
49
|
+
|
|
50
|
+
## API Extensions
|
|
51
|
+
|
|
52
|
+
No GraphQL API extensions. This plugin configures Apollo Server plugins for query analysis and validation.
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./src/harden.plugin"), exports);
|
|
18
|
+
__exportStar(require("./src/types"), exports);
|
|
19
|
+
__exportStar(require("./src/middleware/query-complexity-plugin"), exports);
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,sDAAoC;AACpC,8CAA4B;AAC5B,2EAAyD"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HARDEN_PLUGIN_OPTIONS = exports.loggerCtx = void 0;
|
|
4
|
+
exports.loggerCtx = "HardenPlugin";
|
|
5
|
+
exports.HARDEN_PLUGIN_OPTIONS = Symbol("HARDEN_PLUGIN_OPTIONS");
|
|
6
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,SAAS,GAAG,cAAc,CAAC;AAC3B,QAAA,qBAAqB,GAAG,MAAM,CAAC,uBAAuB,CAAC,CAAC"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { HardenPluginOptions } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* @description
|
|
4
|
+
* The HardenPlugin hardens the Shop and Admin GraphQL APIs against attacks and abuse.
|
|
5
|
+
*
|
|
6
|
+
* - It analyzes the complexity on incoming graphql queries and rejects queries that are too complex and
|
|
7
|
+
* could be used to overload the resources of the server.
|
|
8
|
+
* - It disables dev-mode API features such as introspection and the GraphQL playground app.
|
|
9
|
+
* - It removes field name suggestions to prevent trial-and-error schema sniffing.
|
|
10
|
+
*
|
|
11
|
+
* It is a recommended plugin for all production configurations.
|
|
12
|
+
*
|
|
13
|
+
* ## Installation
|
|
14
|
+
*
|
|
15
|
+
* `yarn add \@deenruv/harden-plugin`
|
|
16
|
+
*
|
|
17
|
+
* or
|
|
18
|
+
*
|
|
19
|
+
* `npm install \@deenruv/harden-plugin`
|
|
20
|
+
*
|
|
21
|
+
* Then add the `HardenPlugin`, calling the `.init()` method with {@link HardenPluginOptions}:
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { HardenPlugin } from '\@deenruv/harden-plugin';
|
|
26
|
+
*
|
|
27
|
+
* const config: DeenruvConfig = {
|
|
28
|
+
* // Add an instance of the plugin to the plugins array
|
|
29
|
+
* plugins: [
|
|
30
|
+
* HardenPlugin.init({
|
|
31
|
+
* maxQueryComplexity: 650,
|
|
32
|
+
* apiMode: process.env.APP_ENV === 'dev' ? 'dev' : 'prod',
|
|
33
|
+
* }),
|
|
34
|
+
* ],
|
|
35
|
+
* };
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* ## Setting the max query complexity
|
|
39
|
+
*
|
|
40
|
+
* The `maxQueryComplexity` option determines how complex a query can be. The complexity of a query relates to how many, and how
|
|
41
|
+
* deeply-nested are the fields being selected, and is intended to roughly correspond to the amount of server resources that would
|
|
42
|
+
* be required to resolve that query.
|
|
43
|
+
*
|
|
44
|
+
* The goal of this setting is to prevent attacks in which a malicious actor crafts a very complex query in order to overwhelm your
|
|
45
|
+
* server resources. Here's an example of a request which would likely overwhelm a Deenruv server:
|
|
46
|
+
*
|
|
47
|
+
* ```GraphQL
|
|
48
|
+
* query EvilQuery {
|
|
49
|
+
* products {
|
|
50
|
+
* items {
|
|
51
|
+
* collections {
|
|
52
|
+
* productVariants {
|
|
53
|
+
* items {
|
|
54
|
+
* product {
|
|
55
|
+
* collections {
|
|
56
|
+
* productVariants {
|
|
57
|
+
* items {
|
|
58
|
+
* product {
|
|
59
|
+
* variants {
|
|
60
|
+
* name
|
|
61
|
+
* }
|
|
62
|
+
* }
|
|
63
|
+
* }
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
* }
|
|
67
|
+
* }
|
|
68
|
+
* }
|
|
69
|
+
* }
|
|
70
|
+
* }
|
|
71
|
+
* }
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* This evil query has a complexity score of 2,443,203 - much greater than the default of 1,000!
|
|
76
|
+
*
|
|
77
|
+
* The complexity score is calculated by the [graphql-query-complexity library](https://www.npmjs.com/package/graphql-query-complexity),
|
|
78
|
+
* and by default uses the {@link defaultDeenruvComplexityEstimator}, which is tuned specifically to the Deenruv Shop API.
|
|
79
|
+
*
|
|
80
|
+
* :::caution
|
|
81
|
+
* Note: By default, if the "take" argument is omitted from a list query (e.g. the `products` or `collections` query), a default factor of 1000 is applied.
|
|
82
|
+
* :::
|
|
83
|
+
*
|
|
84
|
+
* The optimal max complexity score will vary depending on:
|
|
85
|
+
*
|
|
86
|
+
* - The requirements of your storefront and other clients using the Shop API
|
|
87
|
+
* - The resources available to your server
|
|
88
|
+
*
|
|
89
|
+
* You should aim to set the maximum as low as possible while still being able to service all the requests required.
|
|
90
|
+
* This will take some manual tuning.
|
|
91
|
+
* While tuning the max, you can turn on the `logComplexityScore` to get a detailed breakdown of the complexity of each query, as well as how
|
|
92
|
+
* that total score is derived from its child fields:
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* import { HardenPlugin } from '\@deenruv/harden-plugin';
|
|
97
|
+
*
|
|
98
|
+
* const config: DeenruvConfig = {
|
|
99
|
+
* // A detailed summary is logged at the "debug" level
|
|
100
|
+
* logger: new DefaultLogger({ level: LogLevel.Debug }),
|
|
101
|
+
* plugins: [
|
|
102
|
+
* HardenPlugin.init({
|
|
103
|
+
* maxQueryComplexity: 650,
|
|
104
|
+
* logComplexityScore: true,
|
|
105
|
+
* }),
|
|
106
|
+
* ],
|
|
107
|
+
* };
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* With logging configured as above, the following query:
|
|
111
|
+
*
|
|
112
|
+
* ```GraphQL
|
|
113
|
+
* query ProductList {
|
|
114
|
+
* products(options: { take: 5 }) {
|
|
115
|
+
* items {
|
|
116
|
+
* id
|
|
117
|
+
* name
|
|
118
|
+
* featuredAsset {
|
|
119
|
+
* preview
|
|
120
|
+
* }
|
|
121
|
+
* }
|
|
122
|
+
* }
|
|
123
|
+
* }
|
|
124
|
+
* ```
|
|
125
|
+
* will log the following breakdown:
|
|
126
|
+
*
|
|
127
|
+
* ```sh
|
|
128
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Calculating complexity of [ProductList]
|
|
129
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Product.id: ID! childComplexity: 0, score: 1
|
|
130
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Product.name: String! childComplexity: 0, score: 1
|
|
131
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Asset.preview: String! childComplexity: 0, score: 1
|
|
132
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Product.featuredAsset: Asset childComplexity: 1, score: 2
|
|
133
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] ProductList.items: [Product!]! childComplexity: 4, score: 20
|
|
134
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Query.products: ProductList! childComplexity: 20, score: 35
|
|
135
|
+
* verbose 16/12/22, 14:12 - [HardenPlugin] Query complexity [ProductList]: 35
|
|
136
|
+
* ```
|
|
137
|
+
*
|
|
138
|
+
* @docsCategory core plugins/HardenPlugin
|
|
139
|
+
*/
|
|
140
|
+
export declare class HardenPlugin {
|
|
141
|
+
static options: HardenPluginOptions;
|
|
142
|
+
static init(options: HardenPluginOptions): typeof HardenPlugin;
|
|
143
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var HardenPlugin_1;
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.HardenPlugin = void 0;
|
|
11
|
+
const core_1 = require("@deenruv/core");
|
|
12
|
+
const constants_1 = require("./constants");
|
|
13
|
+
const hide_validation_errors_plugin_1 = require("./middleware/hide-validation-errors-plugin");
|
|
14
|
+
const query_complexity_plugin_1 = require("./middleware/query-complexity-plugin");
|
|
15
|
+
/**
|
|
16
|
+
* @description
|
|
17
|
+
* The HardenPlugin hardens the Shop and Admin GraphQL APIs against attacks and abuse.
|
|
18
|
+
*
|
|
19
|
+
* - It analyzes the complexity on incoming graphql queries and rejects queries that are too complex and
|
|
20
|
+
* could be used to overload the resources of the server.
|
|
21
|
+
* - It disables dev-mode API features such as introspection and the GraphQL playground app.
|
|
22
|
+
* - It removes field name suggestions to prevent trial-and-error schema sniffing.
|
|
23
|
+
*
|
|
24
|
+
* It is a recommended plugin for all production configurations.
|
|
25
|
+
*
|
|
26
|
+
* ## Installation
|
|
27
|
+
*
|
|
28
|
+
* `yarn add \@deenruv/harden-plugin`
|
|
29
|
+
*
|
|
30
|
+
* or
|
|
31
|
+
*
|
|
32
|
+
* `npm install \@deenruv/harden-plugin`
|
|
33
|
+
*
|
|
34
|
+
* Then add the `HardenPlugin`, calling the `.init()` method with {@link HardenPluginOptions}:
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* import { HardenPlugin } from '\@deenruv/harden-plugin';
|
|
39
|
+
*
|
|
40
|
+
* const config: DeenruvConfig = {
|
|
41
|
+
* // Add an instance of the plugin to the plugins array
|
|
42
|
+
* plugins: [
|
|
43
|
+
* HardenPlugin.init({
|
|
44
|
+
* maxQueryComplexity: 650,
|
|
45
|
+
* apiMode: process.env.APP_ENV === 'dev' ? 'dev' : 'prod',
|
|
46
|
+
* }),
|
|
47
|
+
* ],
|
|
48
|
+
* };
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* ## Setting the max query complexity
|
|
52
|
+
*
|
|
53
|
+
* The `maxQueryComplexity` option determines how complex a query can be. The complexity of a query relates to how many, and how
|
|
54
|
+
* deeply-nested are the fields being selected, and is intended to roughly correspond to the amount of server resources that would
|
|
55
|
+
* be required to resolve that query.
|
|
56
|
+
*
|
|
57
|
+
* The goal of this setting is to prevent attacks in which a malicious actor crafts a very complex query in order to overwhelm your
|
|
58
|
+
* server resources. Here's an example of a request which would likely overwhelm a Deenruv server:
|
|
59
|
+
*
|
|
60
|
+
* ```GraphQL
|
|
61
|
+
* query EvilQuery {
|
|
62
|
+
* products {
|
|
63
|
+
* items {
|
|
64
|
+
* collections {
|
|
65
|
+
* productVariants {
|
|
66
|
+
* items {
|
|
67
|
+
* product {
|
|
68
|
+
* collections {
|
|
69
|
+
* productVariants {
|
|
70
|
+
* items {
|
|
71
|
+
* product {
|
|
72
|
+
* variants {
|
|
73
|
+
* name
|
|
74
|
+
* }
|
|
75
|
+
* }
|
|
76
|
+
* }
|
|
77
|
+
* }
|
|
78
|
+
* }
|
|
79
|
+
* }
|
|
80
|
+
* }
|
|
81
|
+
* }
|
|
82
|
+
* }
|
|
83
|
+
* }
|
|
84
|
+
* }
|
|
85
|
+
* }
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* This evil query has a complexity score of 2,443,203 - much greater than the default of 1,000!
|
|
89
|
+
*
|
|
90
|
+
* The complexity score is calculated by the [graphql-query-complexity library](https://www.npmjs.com/package/graphql-query-complexity),
|
|
91
|
+
* and by default uses the {@link defaultDeenruvComplexityEstimator}, which is tuned specifically to the Deenruv Shop API.
|
|
92
|
+
*
|
|
93
|
+
* :::caution
|
|
94
|
+
* Note: By default, if the "take" argument is omitted from a list query (e.g. the `products` or `collections` query), a default factor of 1000 is applied.
|
|
95
|
+
* :::
|
|
96
|
+
*
|
|
97
|
+
* The optimal max complexity score will vary depending on:
|
|
98
|
+
*
|
|
99
|
+
* - The requirements of your storefront and other clients using the Shop API
|
|
100
|
+
* - The resources available to your server
|
|
101
|
+
*
|
|
102
|
+
* You should aim to set the maximum as low as possible while still being able to service all the requests required.
|
|
103
|
+
* This will take some manual tuning.
|
|
104
|
+
* While tuning the max, you can turn on the `logComplexityScore` to get a detailed breakdown of the complexity of each query, as well as how
|
|
105
|
+
* that total score is derived from its child fields:
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* import { HardenPlugin } from '\@deenruv/harden-plugin';
|
|
110
|
+
*
|
|
111
|
+
* const config: DeenruvConfig = {
|
|
112
|
+
* // A detailed summary is logged at the "debug" level
|
|
113
|
+
* logger: new DefaultLogger({ level: LogLevel.Debug }),
|
|
114
|
+
* plugins: [
|
|
115
|
+
* HardenPlugin.init({
|
|
116
|
+
* maxQueryComplexity: 650,
|
|
117
|
+
* logComplexityScore: true,
|
|
118
|
+
* }),
|
|
119
|
+
* ],
|
|
120
|
+
* };
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* With logging configured as above, the following query:
|
|
124
|
+
*
|
|
125
|
+
* ```GraphQL
|
|
126
|
+
* query ProductList {
|
|
127
|
+
* products(options: { take: 5 }) {
|
|
128
|
+
* items {
|
|
129
|
+
* id
|
|
130
|
+
* name
|
|
131
|
+
* featuredAsset {
|
|
132
|
+
* preview
|
|
133
|
+
* }
|
|
134
|
+
* }
|
|
135
|
+
* }
|
|
136
|
+
* }
|
|
137
|
+
* ```
|
|
138
|
+
* will log the following breakdown:
|
|
139
|
+
*
|
|
140
|
+
* ```sh
|
|
141
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Calculating complexity of [ProductList]
|
|
142
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Product.id: ID! childComplexity: 0, score: 1
|
|
143
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Product.name: String! childComplexity: 0, score: 1
|
|
144
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Asset.preview: String! childComplexity: 0, score: 1
|
|
145
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Product.featuredAsset: Asset childComplexity: 1, score: 2
|
|
146
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] ProductList.items: [Product!]! childComplexity: 4, score: 20
|
|
147
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Query.products: ProductList! childComplexity: 20, score: 35
|
|
148
|
+
* verbose 16/12/22, 14:12 - [HardenPlugin] Query complexity [ProductList]: 35
|
|
149
|
+
* ```
|
|
150
|
+
*
|
|
151
|
+
* @docsCategory core plugins/HardenPlugin
|
|
152
|
+
*/
|
|
153
|
+
let HardenPlugin = HardenPlugin_1 = class HardenPlugin {
|
|
154
|
+
static init(options) {
|
|
155
|
+
this.options = options;
|
|
156
|
+
return HardenPlugin_1;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
exports.HardenPlugin = HardenPlugin;
|
|
160
|
+
exports.HardenPlugin = HardenPlugin = HardenPlugin_1 = __decorate([
|
|
161
|
+
(0, core_1.DeenruvPlugin)({
|
|
162
|
+
providers: [
|
|
163
|
+
{
|
|
164
|
+
provide: constants_1.HARDEN_PLUGIN_OPTIONS,
|
|
165
|
+
useFactory: () => HardenPlugin.options,
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
configuration: (config) => {
|
|
169
|
+
if (HardenPlugin.options.hideFieldSuggestions !== false) {
|
|
170
|
+
core_1.Logger.verbose("Configuring HideValidationErrorsPlugin", constants_1.loggerCtx);
|
|
171
|
+
config.apiOptions.apolloServerPlugins.push(new hide_validation_errors_plugin_1.HideValidationErrorsPlugin());
|
|
172
|
+
}
|
|
173
|
+
config.apiOptions.apolloServerPlugins.push(new query_complexity_plugin_1.QueryComplexityPlugin(HardenPlugin.options));
|
|
174
|
+
if (HardenPlugin.options.apiMode !== "dev") {
|
|
175
|
+
config.apiOptions.adminApiDebug = false;
|
|
176
|
+
config.apiOptions.shopApiDebug = false;
|
|
177
|
+
config.apiOptions.introspection = false;
|
|
178
|
+
}
|
|
179
|
+
return config;
|
|
180
|
+
},
|
|
181
|
+
compatibility: "^0.0.0",
|
|
182
|
+
})
|
|
183
|
+
], HardenPlugin);
|
|
184
|
+
//# sourceMappingURL=harden.plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harden.plugin.js","sourceRoot":"","sources":["../../src/harden.plugin.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,wCAAsD;AAEtD,2CAA+D;AAC/D,8FAAwF;AACxF,kFAA6E;AAG7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyIG;AA4BI,IAAM,YAAY,oBAAlB,MAAM,YAAY;IAGvB,MAAM,CAAC,IAAI,CAAC,OAA4B;QACtC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,OAAO,cAAY,CAAC;IACtB,CAAC;CACF,CAAA;AAPY,oCAAY;uBAAZ,YAAY;IA3BxB,IAAA,oBAAa,EAAC;QACb,SAAS,EAAE;YACT;gBACE,OAAO,EAAE,iCAAqB;gBAC9B,UAAU,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO;aACvC;SACF;QACD,aAAa,EAAE,CAAC,MAAM,EAAE,EAAE;YACxB,IAAI,YAAY,CAAC,OAAO,CAAC,oBAAoB,KAAK,KAAK,EAAE,CAAC;gBACxD,aAAM,CAAC,OAAO,CAAC,wCAAwC,EAAE,qBAAS,CAAC,CAAC;gBACpE,MAAM,CAAC,UAAU,CAAC,mBAAmB,CAAC,IAAI,CACxC,IAAI,0DAA0B,EAAE,CACjC,CAAC;YACJ,CAAC;YACD,MAAM,CAAC,UAAU,CAAC,mBAAmB,CAAC,IAAI,CACxC,IAAI,+CAAqB,CAAC,YAAY,CAAC,OAAO,CAAC,CAChD,CAAC;YACF,IAAI,YAAY,CAAC,OAAO,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;gBAC3C,MAAM,CAAC,UAAU,CAAC,aAAa,GAAG,KAAK,CAAC;gBACxC,MAAM,CAAC,UAAU,CAAC,YAAY,GAAG,KAAK,CAAC;gBACvC,MAAM,CAAC,UAAU,CAAC,aAAa,GAAG,KAAK,CAAC;YAC1C,CAAC;YAED,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,aAAa,EAAE,QAAQ;KACxB,CAAC;GACW,YAAY,CAOxB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ApolloServerPlugin, GraphQLRequestListener } from "@apollo/server";
|
|
2
|
+
/**
|
|
3
|
+
* @description
|
|
4
|
+
* Hides graphql-js suggestions when invalid field names are given.
|
|
5
|
+
* Based on ideas discussed in https://github.com/apollographql/apollo-server/issues/3919
|
|
6
|
+
*/
|
|
7
|
+
export declare class HideValidationErrorsPlugin implements ApolloServerPlugin {
|
|
8
|
+
requestDidStart(): Promise<GraphQLRequestListener<any>>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HideValidationErrorsPlugin = void 0;
|
|
4
|
+
const index_1 = require("graphql/error/index");
|
|
5
|
+
/**
|
|
6
|
+
* @description
|
|
7
|
+
* Hides graphql-js suggestions when invalid field names are given.
|
|
8
|
+
* Based on ideas discussed in https://github.com/apollographql/apollo-server/issues/3919
|
|
9
|
+
*/
|
|
10
|
+
class HideValidationErrorsPlugin {
|
|
11
|
+
async requestDidStart() {
|
|
12
|
+
return {
|
|
13
|
+
willSendResponse: async (requestContext) => {
|
|
14
|
+
const { errors } = requestContext;
|
|
15
|
+
if (errors) {
|
|
16
|
+
requestContext.response.errors = errors.map((err) => {
|
|
17
|
+
if (err.message.includes("Did you mean")) {
|
|
18
|
+
return new index_1.GraphQLError("Invalid request");
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
return err;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.HideValidationErrorsPlugin = HideValidationErrorsPlugin;
|
|
30
|
+
//# sourceMappingURL=hide-validation-errors-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hide-validation-errors-plugin.js","sourceRoot":"","sources":["../../../src/middleware/hide-validation-errors-plugin.ts"],"names":[],"mappings":";;;AACA,+CAAmD;AAEnD;;;;GAIG;AACH,MAAa,0BAA0B;IACrC,KAAK,CAAC,eAAe;QACnB,OAAO;YACL,gBAAgB,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE;gBACzC,MAAM,EAAE,MAAM,EAAE,GAAG,cAAc,CAAC;gBAClC,IAAI,MAAM,EAAE,CAAC;oBACV,cAAc,CAAC,QAAgB,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;wBAC3D,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;4BACzC,OAAO,IAAI,oBAAY,CAAC,iBAAiB,CAAC,CAAC;wBAC7C,CAAC;6BAAM,CAAC;4BACN,OAAO,GAAG,CAAC;wBACb,CAAC;oBACH,CAAC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF;AAjBD,gEAiBC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ApolloServerPlugin, GraphQLRequestListener, GraphQLRequestContext } from "@apollo/server";
|
|
2
|
+
import { ComplexityEstimatorArgs } from "graphql-query-complexity";
|
|
3
|
+
import { HardenPluginOptions } from "../types";
|
|
4
|
+
/**
|
|
5
|
+
* @description
|
|
6
|
+
* Implements query complexity analysis on Shop API requests.
|
|
7
|
+
*/
|
|
8
|
+
export declare class QueryComplexityPlugin implements ApolloServerPlugin {
|
|
9
|
+
private options;
|
|
10
|
+
constructor(options: HardenPluginOptions);
|
|
11
|
+
requestDidStart({ schema, }: GraphQLRequestContext<any>): Promise<GraphQLRequestListener<any>>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* @description
|
|
15
|
+
* A complexity estimator which takes into account List and PaginatedList types and can
|
|
16
|
+
* be further configured by providing a customComplexityFactors object.
|
|
17
|
+
*
|
|
18
|
+
* When selecting PaginatedList types, the "take" argument is used to estimate a complexity
|
|
19
|
+
* factor. If the "take" argument is omitted, a default factor of 1000 is applied.
|
|
20
|
+
*
|
|
21
|
+
* @docsCategory core plugins/HardenPlugin
|
|
22
|
+
*/
|
|
23
|
+
export declare function defaultDeenruvComplexityEstimator(customComplexityFactors: {
|
|
24
|
+
[path: string]: number;
|
|
25
|
+
}, logFieldScores: boolean): (options: ComplexityEstimatorArgs) => number | void;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultDeenruvComplexityEstimator = exports.QueryComplexityPlugin = void 0;
|
|
4
|
+
const core_1 = require("@deenruv/core");
|
|
5
|
+
const graphql_1 = require("graphql");
|
|
6
|
+
const graphql_query_complexity_1 = require("graphql-query-complexity");
|
|
7
|
+
const constants_1 = require("../constants");
|
|
8
|
+
/**
|
|
9
|
+
* @description
|
|
10
|
+
* Implements query complexity analysis on Shop API requests.
|
|
11
|
+
*/
|
|
12
|
+
class QueryComplexityPlugin {
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.options = options;
|
|
15
|
+
}
|
|
16
|
+
async requestDidStart({ schema, }) {
|
|
17
|
+
var _a;
|
|
18
|
+
const maxQueryComplexity = (_a = this.options.maxQueryComplexity) !== null && _a !== void 0 ? _a : 1000;
|
|
19
|
+
return {
|
|
20
|
+
didResolveOperation: async ({ request, document }) => {
|
|
21
|
+
var _a, _b, _c, _d, _e, _f;
|
|
22
|
+
if (isAdminApi(schema)) {
|
|
23
|
+
// We don't want to apply the cost analysis on the
|
|
24
|
+
// Admin API, since any expensive operations would require
|
|
25
|
+
// an authenticated session.
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const query = request.operationName
|
|
29
|
+
? (0, graphql_1.separateOperations)(document)[request.operationName]
|
|
30
|
+
: document;
|
|
31
|
+
if (this.options.logComplexityScore === true) {
|
|
32
|
+
core_1.Logger.debug(`Calculating complexity of "${(_a = request.operationName) !== null && _a !== void 0 ? _a : "anonymous"}"`, constants_1.loggerCtx);
|
|
33
|
+
}
|
|
34
|
+
const complexity = (0, graphql_query_complexity_1.getComplexity)({
|
|
35
|
+
schema,
|
|
36
|
+
query,
|
|
37
|
+
variables: request.variables,
|
|
38
|
+
estimators: (_b = this.options.queryComplexityEstimators) !== null && _b !== void 0 ? _b : [
|
|
39
|
+
defaultDeenruvComplexityEstimator((_c = this.options.customComplexityFactors) !== null && _c !== void 0 ? _c : {}, (_d = this.options.logComplexityScore) !== null && _d !== void 0 ? _d : false),
|
|
40
|
+
(0, graphql_query_complexity_1.simpleEstimator)({ defaultComplexity: 1 }),
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
if (this.options.logComplexityScore === true) {
|
|
44
|
+
core_1.Logger.verbose(`Query complexity "${(_e = request.operationName) !== null && _e !== void 0 ? _e : "anonymous"}": ${complexity}`, constants_1.loggerCtx);
|
|
45
|
+
}
|
|
46
|
+
if (complexity >= maxQueryComplexity) {
|
|
47
|
+
core_1.Logger.error(`Query complexity of "${(_f = request.operationName) !== null && _f !== void 0 ? _f : "anonymous"}" is ${complexity}, which exceeds the maximum of ${maxQueryComplexity}`, constants_1.loggerCtx);
|
|
48
|
+
throw new core_1.InternalServerError("Query is too complex");
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.QueryComplexityPlugin = QueryComplexityPlugin;
|
|
55
|
+
function isAdminApi(schema) {
|
|
56
|
+
const queryType = schema.getQueryType();
|
|
57
|
+
if (queryType) {
|
|
58
|
+
return !!queryType.getFields().administrators;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* @description
|
|
64
|
+
* A complexity estimator which takes into account List and PaginatedList types and can
|
|
65
|
+
* be further configured by providing a customComplexityFactors object.
|
|
66
|
+
*
|
|
67
|
+
* When selecting PaginatedList types, the "take" argument is used to estimate a complexity
|
|
68
|
+
* factor. If the "take" argument is omitted, a default factor of 1000 is applied.
|
|
69
|
+
*
|
|
70
|
+
* @docsCategory core plugins/HardenPlugin
|
|
71
|
+
*/
|
|
72
|
+
function defaultDeenruvComplexityEstimator(customComplexityFactors, logFieldScores) {
|
|
73
|
+
return (options) => {
|
|
74
|
+
var _a, _b;
|
|
75
|
+
const { type, args, childComplexity, field } = options;
|
|
76
|
+
const namedType = (0, graphql_1.getNamedType)(field.type);
|
|
77
|
+
const path = `${type.name}.${field.name}`;
|
|
78
|
+
let result = childComplexity + 1;
|
|
79
|
+
const customFactor = customComplexityFactors[path];
|
|
80
|
+
if (customFactor != null) {
|
|
81
|
+
result = Math.max(childComplexity, 1) * customFactor;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
if ((0, graphql_1.isObjectType)(namedType)) {
|
|
85
|
+
const isPaginatedList = !!namedType
|
|
86
|
+
.getInterfaces()
|
|
87
|
+
.find((i) => i.name === "PaginatedList");
|
|
88
|
+
if (isPaginatedList) {
|
|
89
|
+
const take = (_b = (_a = args.options) === null || _a === void 0 ? void 0 : _a.take) !== null && _b !== void 0 ? _b : 1000;
|
|
90
|
+
result =
|
|
91
|
+
childComplexity + Math.round(Math.log(childComplexity) * take);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if ((0, graphql_1.isListType)((0, graphql_1.getNullableType)(field.type))) {
|
|
95
|
+
result = childComplexity * 5;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (logFieldScores) {
|
|
99
|
+
core_1.Logger.debug(`${path}: ${field.type.toString()}\tchildComplexity: ${childComplexity}, score: ${result}`, constants_1.loggerCtx);
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
exports.defaultDeenruvComplexityEstimator = defaultDeenruvComplexityEstimator;
|
|
105
|
+
//# sourceMappingURL=query-complexity-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-complexity-plugin.js","sourceRoot":"","sources":["../../../src/middleware/query-complexity-plugin.ts"],"names":[],"mappings":";;;AAKA,wCAA4D;AAC5D,qCAOiB;AACjB,uEAIkC;AAElC,4CAAyC;AAGzC;;;GAGG;AACH,MAAa,qBAAqB;IAChC,YAAoB,OAA4B;QAA5B,YAAO,GAAP,OAAO,CAAqB;IAAG,CAAC;IAEpD,KAAK,CAAC,eAAe,CAAC,EACpB,MAAM,GACqB;;QAC3B,MAAM,kBAAkB,GAAG,MAAA,IAAI,CAAC,OAAO,CAAC,kBAAkB,mCAAI,IAAI,CAAC;QACnE,OAAO;YACL,mBAAmB,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE;;gBACnD,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;oBACvB,kDAAkD;oBAClD,0DAA0D;oBAC1D,4BAA4B;oBAC5B,OAAO;gBACT,CAAC;gBACD,MAAM,KAAK,GAAG,OAAO,CAAC,aAAa;oBACjC,CAAC,CAAC,IAAA,4BAAkB,EAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC;oBACrD,CAAC,CAAC,QAAQ,CAAC;gBAEb,IAAI,IAAI,CAAC,OAAO,CAAC,kBAAkB,KAAK,IAAI,EAAE,CAAC;oBAC7C,aAAM,CAAC,KAAK,CACV,8BAA8B,MAAA,OAAO,CAAC,aAAa,mCAAI,WAAW,GAAG,EACrE,qBAAS,CACV,CAAC;gBACJ,CAAC;gBACD,MAAM,UAAU,GAAG,IAAA,wCAAa,EAAC;oBAC/B,MAAM;oBACN,KAAK;oBACL,SAAS,EAAE,OAAO,CAAC,SAAS;oBAC5B,UAAU,EAAE,MAAA,IAAI,CAAC,OAAO,CAAC,yBAAyB,mCAAI;wBACpD,iCAAiC,CAC/B,MAAA,IAAI,CAAC,OAAO,CAAC,uBAAuB,mCAAI,EAAE,EAC1C,MAAA,IAAI,CAAC,OAAO,CAAC,kBAAkB,mCAAI,KAAK,CACzC;wBACD,IAAA,0CAAe,EAAC,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC;qBAC1C;iBACF,CAAC,CAAC;gBAEH,IAAI,IAAI,CAAC,OAAO,CAAC,kBAAkB,KAAK,IAAI,EAAE,CAAC;oBAC7C,aAAM,CAAC,OAAO,CACZ,qBAAqB,MAAA,OAAO,CAAC,aAAa,mCAAI,WAAW,MAAM,UAAU,EAAE,EAC3E,qBAAS,CACV,CAAC;gBACJ,CAAC;gBACD,IAAI,UAAU,IAAI,kBAAkB,EAAE,CAAC;oBACrC,aAAM,CAAC,KAAK,CACV,wBACE,MAAA,OAAO,CAAC,aAAa,mCAAI,WAC3B,QAAQ,UAAU,kCAAkC,kBAAkB,EAAE,EACxE,qBAAS,CACV,CAAC;oBACF,MAAM,IAAI,0BAAmB,CAAC,sBAAsB,CAAC,CAAC;gBACxD,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF;AAxDD,sDAwDC;AAED,SAAS,UAAU,CAAC,MAAqB;IACvC,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,EAAE,CAAC;IACxC,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,CAAC,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC,cAAc,CAAC;IAChD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,iCAAiC,CAC/C,uBAAmD,EACnD,cAAuB;IAEvB,OAAO,CAAC,OAAgC,EAAiB,EAAE;;QACzD,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;QACvD,MAAM,SAAS,GAAG,IAAA,sBAAY,EAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QAC1C,IAAI,MAAM,GAAG,eAAe,GAAG,CAAC,CAAC;QACjC,MAAM,YAAY,GAAG,uBAAuB,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,YAAY,IAAI,IAAI,EAAE,CAAC;YACzB,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC,GAAG,YAAY,CAAC;QACvD,CAAC;aAAM,CAAC;YACN,IAAI,IAAA,sBAAY,EAAC,SAAS,CAAC,EAAE,CAAC;gBAC5B,MAAM,eAAe,GAAG,CAAC,CAAC,SAAS;qBAChC,aAAa,EAAE;qBACf,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,eAAe,CAAC,CAAC;gBAC3C,IAAI,eAAe,EAAE,CAAC;oBACpB,MAAM,IAAI,GAAG,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,IAAI,mCAAI,IAAI,CAAC;oBACxC,MAAM;wBACJ,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC,CAAC;gBACnE,CAAC;YACH,CAAC;YACD,IAAI,IAAA,oBAAU,EAAC,IAAA,yBAAe,EAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;gBAC5C,MAAM,GAAG,eAAe,GAAG,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;QACD,IAAI,cAAc,EAAE,CAAC;YACnB,aAAM,CAAC,KAAK,CACV,GAAG,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,sBAAsB,eAAe,YAAY,MAAM,EAAE,EAC1F,qBAAS,CACV,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC;AAnCD,8EAmCC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { ComplexityEstimator } from "graphql-query-complexity";
|
|
2
|
+
/**
|
|
3
|
+
* @description
|
|
4
|
+
* Options that can be passed to the `.init()` static method of the HardenPlugin.
|
|
5
|
+
*
|
|
6
|
+
* @docsCategory core plugins/HardenPlugin
|
|
7
|
+
*/
|
|
8
|
+
export interface HardenPluginOptions {
|
|
9
|
+
/**
|
|
10
|
+
* @description
|
|
11
|
+
* Defines the maximum permitted complexity score of a query. The complexity score is based
|
|
12
|
+
* on the number of fields being selected as well as other factors like whether there are nested
|
|
13
|
+
* lists.
|
|
14
|
+
*
|
|
15
|
+
* A query which exceeds the maximum score will result in an error.
|
|
16
|
+
*
|
|
17
|
+
* @default 1000
|
|
18
|
+
*/
|
|
19
|
+
maxQueryComplexity?: number;
|
|
20
|
+
/**
|
|
21
|
+
* @description
|
|
22
|
+
* An array of custom estimator functions for calculating the complexity of a query. By default,
|
|
23
|
+
* the plugin will use the {@link defaultDeenruvComplexityEstimator} which is specifically
|
|
24
|
+
* tuned to accurately estimate Deenruv queries.
|
|
25
|
+
*/
|
|
26
|
+
queryComplexityEstimators?: ComplexityEstimator[];
|
|
27
|
+
/**
|
|
28
|
+
* @description
|
|
29
|
+
* When set to `true`, the complexity score of each query will be logged at the Verbose
|
|
30
|
+
* log level, and a breakdown of the calculation for each field will be logged at the Debug level.
|
|
31
|
+
*
|
|
32
|
+
* This is very useful for tuning your complexity scores.
|
|
33
|
+
*
|
|
34
|
+
* @default false
|
|
35
|
+
*/
|
|
36
|
+
logComplexityScore?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* @description
|
|
39
|
+
* This object allows you to tune the complexity weight of specific fields. For example,
|
|
40
|
+
* if you have a custom `stockLocations` field defined on the `ProductVariant` type, and
|
|
41
|
+
* you know that it is a particularly expensive operation to execute, you can increase
|
|
42
|
+
* its complexity like this:
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* HardenPlugin.init({
|
|
47
|
+
* maxQueryComplexity: 650,
|
|
48
|
+
* customComplexityFactors: {
|
|
49
|
+
* 'ProductVariant.stockLocations': 10
|
|
50
|
+
* }
|
|
51
|
+
* }),
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
customComplexityFactors?: {
|
|
55
|
+
[path: string]: number;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* @description
|
|
59
|
+
* Graphql-js will make suggestions about the names of fields if an invalid field name is provided.
|
|
60
|
+
* This would allow an attacker to find out the available fields by brute force even if introspection
|
|
61
|
+
* is disabled.
|
|
62
|
+
*
|
|
63
|
+
* Setting this option to `true` will prevent these suggestion error messages from being returned,
|
|
64
|
+
* instead replacing the message with a generic "Invalid request" message.
|
|
65
|
+
*
|
|
66
|
+
* @default true
|
|
67
|
+
*/
|
|
68
|
+
hideFieldSuggestions?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* @description
|
|
71
|
+
* When set to `'prod'`, the plugin will disable dev-mode features of the GraphQL APIs:
|
|
72
|
+
*
|
|
73
|
+
* - introspection
|
|
74
|
+
* - GraphQL playground
|
|
75
|
+
*
|
|
76
|
+
* @default 'prod'
|
|
77
|
+
*/
|
|
78
|
+
apiMode?: "dev" | "prod";
|
|
79
|
+
}
|
package/lib/src/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deenruv/harden-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib/**/*"
|
|
9
|
+
],
|
|
10
|
+
"homepage": "https://deenruv.com/",
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"@deenruv/core": "^0.1.0"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@apollo/server": "^4.10.4",
|
|
19
|
+
"graphql-query-complexity": "^0.12.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"rimraf": "^5.0.5",
|
|
23
|
+
"@deenruv/core": "^1.0.0",
|
|
24
|
+
"@deenruv/common": "^1.0.0"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"watch": "tsc -p ./tsconfig.build.json --watch",
|
|
28
|
+
"build": "rimraf lib && tsc -p ./tsconfig.build.json",
|
|
29
|
+
"lint": "eslint .",
|
|
30
|
+
"lint:fix": "eslint --fix ."
|
|
31
|
+
}
|
|
32
|
+
}
|