@vendure/harden-plugin 1.9.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/README.md +7 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +16 -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 +138 -0
- package/lib/src/harden.plugin.js +178 -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 +26 -0
- package/lib/src/middleware/query-complexity-plugin.js +102 -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 +23 -0
package/README.md
ADDED
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
10
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
__exportStar(require("./src/harden.plugin"), exports);
|
|
14
|
+
__exportStar(require("./src/types"), exports);
|
|
15
|
+
__exportStar(require("./src/middleware/query-complexity-plugin"), exports);
|
|
16
|
+
//# 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,138 @@
|
|
|
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 \@vendure/harden-plugin`
|
|
16
|
+
*
|
|
17
|
+
* or
|
|
18
|
+
*
|
|
19
|
+
* `npm install \@vendure/harden-plugin`
|
|
20
|
+
*
|
|
21
|
+
* Then add the `HardenPlugin`, calling the `.init()` method with {@link HardenPluginOptions}:
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { HardenPlugin } from '\@vendure/harden-plugin';
|
|
26
|
+
*
|
|
27
|
+
* const config: VendureConfig = {
|
|
28
|
+
* // Add an instance of the plugin to the plugins array
|
|
29
|
+
* plugins: [
|
|
30
|
+
* HardenPlugin.init({
|
|
31
|
+
* maxQueryComplexity: 650,
|
|
32
|
+
* apiMode: process.env.NODE_ENV === 'development' ? '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 Vendure 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 defaultVendureComplexityEstimator}, which is tuned specifically to the Vendure Shop API.
|
|
79
|
+
*
|
|
80
|
+
* The optimal max complexity score will vary depending on:
|
|
81
|
+
*
|
|
82
|
+
* - The requirements of your storefront and other clients using the Shop API
|
|
83
|
+
* - The resources available to your server
|
|
84
|
+
*
|
|
85
|
+
* You should aim to set the maximum as low as possible while still being able to service all the requests required. This will take some manual tuning.
|
|
86
|
+
* 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
|
|
87
|
+
* that total score is derived from its child fields:
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* import { HardenPlugin } from '\@vendure/harden-plugin';
|
|
92
|
+
*
|
|
93
|
+
* const config: VendureConfig = {
|
|
94
|
+
* // A detailed summary is logged at the "debug" level
|
|
95
|
+
* logger: new DefaultLogger({ level: LogLevel.Debug }),
|
|
96
|
+
* plugins: [
|
|
97
|
+
* HardenPlugin.init({
|
|
98
|
+
* maxQueryComplexity: 650,
|
|
99
|
+
* logComplexityScore: true,
|
|
100
|
+
* }),
|
|
101
|
+
* ],
|
|
102
|
+
* };
|
|
103
|
+
* ```
|
|
104
|
+
*
|
|
105
|
+
* With logging configured as above, the following query:
|
|
106
|
+
*
|
|
107
|
+
* ```GraphQL
|
|
108
|
+
* query ProductList {
|
|
109
|
+
* products(options: { take: 5 }) {
|
|
110
|
+
* items {
|
|
111
|
+
* id
|
|
112
|
+
* name
|
|
113
|
+
* featuredAsset {
|
|
114
|
+
* preview
|
|
115
|
+
* }
|
|
116
|
+
* }
|
|
117
|
+
* }
|
|
118
|
+
* }
|
|
119
|
+
* ```
|
|
120
|
+
* will log the following breakdown:
|
|
121
|
+
*
|
|
122
|
+
* ```sh
|
|
123
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Calculating complexity of [ProductList]
|
|
124
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Product.id: ID! childComplexity: 0, score: 1
|
|
125
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Product.name: String! childComplexity: 0, score: 1
|
|
126
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Asset.preview: String! childComplexity: 0, score: 1
|
|
127
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Product.featuredAsset: Asset childComplexity: 1, score: 2
|
|
128
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] ProductList.items: [Product!]! childComplexity: 4, score: 20
|
|
129
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Query.products: ProductList! childComplexity: 20, score: 35
|
|
130
|
+
* verbose 16/12/22, 14:12 - [HardenPlugin] Query complexity [ProductList]: 35
|
|
131
|
+
* ```
|
|
132
|
+
*
|
|
133
|
+
* @docsCategory HardenPlugin
|
|
134
|
+
*/
|
|
135
|
+
export declare class HardenPlugin {
|
|
136
|
+
static options: HardenPluginOptions;
|
|
137
|
+
static init(options: HardenPluginOptions): typeof HardenPlugin;
|
|
138
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
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("@vendure/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 \@vendure/harden-plugin`
|
|
29
|
+
*
|
|
30
|
+
* or
|
|
31
|
+
*
|
|
32
|
+
* `npm install \@vendure/harden-plugin`
|
|
33
|
+
*
|
|
34
|
+
* Then add the `HardenPlugin`, calling the `.init()` method with {@link HardenPluginOptions}:
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* import { HardenPlugin } from '\@vendure/harden-plugin';
|
|
39
|
+
*
|
|
40
|
+
* const config: VendureConfig = {
|
|
41
|
+
* // Add an instance of the plugin to the plugins array
|
|
42
|
+
* plugins: [
|
|
43
|
+
* HardenPlugin.init({
|
|
44
|
+
* maxQueryComplexity: 650,
|
|
45
|
+
* apiMode: process.env.NODE_ENV === 'development' ? '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 Vendure 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 defaultVendureComplexityEstimator}, which is tuned specifically to the Vendure Shop API.
|
|
92
|
+
*
|
|
93
|
+
* The optimal max complexity score will vary depending on:
|
|
94
|
+
*
|
|
95
|
+
* - The requirements of your storefront and other clients using the Shop API
|
|
96
|
+
* - The resources available to your server
|
|
97
|
+
*
|
|
98
|
+
* You should aim to set the maximum as low as possible while still being able to service all the requests required. This will take some manual tuning.
|
|
99
|
+
* 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
|
|
100
|
+
* that total score is derived from its child fields:
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* import { HardenPlugin } from '\@vendure/harden-plugin';
|
|
105
|
+
*
|
|
106
|
+
* const config: VendureConfig = {
|
|
107
|
+
* // A detailed summary is logged at the "debug" level
|
|
108
|
+
* logger: new DefaultLogger({ level: LogLevel.Debug }),
|
|
109
|
+
* plugins: [
|
|
110
|
+
* HardenPlugin.init({
|
|
111
|
+
* maxQueryComplexity: 650,
|
|
112
|
+
* logComplexityScore: true,
|
|
113
|
+
* }),
|
|
114
|
+
* ],
|
|
115
|
+
* };
|
|
116
|
+
* ```
|
|
117
|
+
*
|
|
118
|
+
* With logging configured as above, the following query:
|
|
119
|
+
*
|
|
120
|
+
* ```GraphQL
|
|
121
|
+
* query ProductList {
|
|
122
|
+
* products(options: { take: 5 }) {
|
|
123
|
+
* items {
|
|
124
|
+
* id
|
|
125
|
+
* name
|
|
126
|
+
* featuredAsset {
|
|
127
|
+
* preview
|
|
128
|
+
* }
|
|
129
|
+
* }
|
|
130
|
+
* }
|
|
131
|
+
* }
|
|
132
|
+
* ```
|
|
133
|
+
* will log the following breakdown:
|
|
134
|
+
*
|
|
135
|
+
* ```sh
|
|
136
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Calculating complexity of [ProductList]
|
|
137
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Product.id: ID! childComplexity: 0, score: 1
|
|
138
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Product.name: String! childComplexity: 0, score: 1
|
|
139
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Asset.preview: String! childComplexity: 0, score: 1
|
|
140
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Product.featuredAsset: Asset childComplexity: 1, score: 2
|
|
141
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] ProductList.items: [Product!]! childComplexity: 4, score: 20
|
|
142
|
+
* debug 16/12/22, 14:12 - [HardenPlugin] Query.products: ProductList! childComplexity: 20, score: 35
|
|
143
|
+
* verbose 16/12/22, 14:12 - [HardenPlugin] Query complexity [ProductList]: 35
|
|
144
|
+
* ```
|
|
145
|
+
*
|
|
146
|
+
* @docsCategory HardenPlugin
|
|
147
|
+
*/
|
|
148
|
+
let HardenPlugin = HardenPlugin_1 = class HardenPlugin {
|
|
149
|
+
static init(options) {
|
|
150
|
+
this.options = options;
|
|
151
|
+
return HardenPlugin_1;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
HardenPlugin = HardenPlugin_1 = __decorate([
|
|
155
|
+
core_1.VendurePlugin({
|
|
156
|
+
providers: [
|
|
157
|
+
{
|
|
158
|
+
provide: constants_1.HARDEN_PLUGIN_OPTIONS,
|
|
159
|
+
useFactory: () => HardenPlugin_1.options,
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
configuration: config => {
|
|
163
|
+
if (HardenPlugin_1.options.hideFieldSuggestions !== false) {
|
|
164
|
+
core_1.Logger.verbose(`Configuring HideValidationErrorsPlugin`, constants_1.loggerCtx);
|
|
165
|
+
config.apiOptions.apolloServerPlugins.push(new hide_validation_errors_plugin_1.HideValidationErrorsPlugin());
|
|
166
|
+
}
|
|
167
|
+
config.apiOptions.apolloServerPlugins.push(new query_complexity_plugin_1.QueryComplexityPlugin(HardenPlugin_1.options));
|
|
168
|
+
if (HardenPlugin_1.options.apiMode !== 'dev') {
|
|
169
|
+
config.apiOptions.adminApiDebug = false;
|
|
170
|
+
config.apiOptions.shopApiDebug = false;
|
|
171
|
+
config.apiOptions.introspection = false;
|
|
172
|
+
}
|
|
173
|
+
return config;
|
|
174
|
+
},
|
|
175
|
+
})
|
|
176
|
+
], HardenPlugin);
|
|
177
|
+
exports.HardenPlugin = HardenPlugin;
|
|
178
|
+
//# 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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoIG;AAuBH,IAAa,YAAY,oBAAzB,MAAa,YAAY;IAGrB,MAAM,CAAC,IAAI,CAAC,OAA4B;QACpC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,OAAO,cAAY,CAAC;IACxB,CAAC;CACJ,CAAA;AAPY,YAAY;IAtBxB,oBAAa,CAAC;QACX,SAAS,EAAE;YACP;gBACI,OAAO,EAAE,iCAAqB;gBAC9B,UAAU,EAAE,GAAG,EAAE,CAAC,cAAY,CAAC,OAAO;aACzC;SACJ;QACD,aAAa,EAAE,MAAM,CAAC,EAAE;YACpB,IAAI,cAAY,CAAC,OAAO,CAAC,oBAAoB,KAAK,KAAK,EAAE;gBACrD,aAAM,CAAC,OAAO,CAAC,wCAAwC,EAAE,qBAAS,CAAC,CAAC;gBACpE,MAAM,CAAC,UAAU,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,0DAA0B,EAAE,CAAC,CAAC;aAChF;YACD,MAAM,CAAC,UAAU,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,+CAAqB,CAAC,cAAY,CAAC,OAAO,CAAC,CAAC,CAAC;YAC5F,IAAI,cAAY,CAAC,OAAO,CAAC,OAAO,KAAK,KAAK,EAAE;gBACxC,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;aAC3C;YAED,OAAO,MAAM,CAAC;QAClB,CAAC;KACJ,CAAC;GACW,YAAY,CAOxB;AAPY,oCAAY"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ApolloServerPlugin, GraphQLRequestListener } from 'apollo-server-plugin-base';
|
|
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(): GraphQLRequestListener;
|
|
9
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HideValidationErrorsPlugin = void 0;
|
|
4
|
+
const apollo_server_core_1 = require("apollo-server-core");
|
|
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
|
+
requestDidStart() {
|
|
12
|
+
return {
|
|
13
|
+
willSendResponse: async (requestContext) => {
|
|
14
|
+
const { errors, context } = requestContext;
|
|
15
|
+
if (errors) {
|
|
16
|
+
requestContext.response.errors = errors.map(err => {
|
|
17
|
+
if (err.message.includes('Did you mean')) {
|
|
18
|
+
return new apollo_server_core_1.ValidationError('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":";;;AAAA,2DAAqD;AAGrD;;;;GAIG;AACH,MAAa,0BAA0B;IACnC,eAAe;QACX,OAAO;YACH,gBAAgB,EAAE,KAAK,EAAC,cAAc,EAAC,EAAE;gBACrC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,cAAc,CAAC;gBAC3C,IAAI,MAAM,EAAE;oBACP,cAAc,CAAC,QAAgB,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;wBACvD,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE;4BACtC,OAAO,IAAI,oCAAe,CAAC,iBAAiB,CAAC,CAAC;yBACjD;6BAAM;4BACH,OAAO,GAAG,CAAC;yBACd;oBACL,CAAC,CAAC,CAAC;iBACN;YACL,CAAC;SACJ,CAAC;IACN,CAAC;CACJ;AAjBD,gEAiBC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ApolloServerPlugin, GraphQLRequestListener } from 'apollo-server-plugin-base';
|
|
2
|
+
import { GraphQLRequestContext } from 'apollo-server-types';
|
|
3
|
+
import { ComplexityEstimatorArgs } from 'graphql-query-complexity';
|
|
4
|
+
import { HardenPluginOptions } from '../types';
|
|
5
|
+
/**
|
|
6
|
+
* @description
|
|
7
|
+
* Implements query complexity analysis on Shop API requests.
|
|
8
|
+
*/
|
|
9
|
+
export declare class QueryComplexityPlugin implements ApolloServerPlugin {
|
|
10
|
+
private options;
|
|
11
|
+
constructor(options: HardenPluginOptions);
|
|
12
|
+
requestDidStart({ schema }: GraphQLRequestContext): GraphQLRequestListener;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @description
|
|
16
|
+
* A complexity estimator which takes into account List and PaginatedList types and can
|
|
17
|
+
* be further configured by providing a customComplexityFactors object.
|
|
18
|
+
*
|
|
19
|
+
* When selecting PaginatedList types, the "take" argument is used to estimate a complexity
|
|
20
|
+
* factor. If the "take" argument is omitted, a default factor of 1000 is applied.
|
|
21
|
+
*
|
|
22
|
+
* @docsCategory HardenPlugin
|
|
23
|
+
*/
|
|
24
|
+
export declare function defaultVendureComplexityEstimator(customComplexityFactors: {
|
|
25
|
+
[path: string]: number;
|
|
26
|
+
}, logFieldScores: boolean): (options: ComplexityEstimatorArgs) => number | void;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultVendureComplexityEstimator = exports.QueryComplexityPlugin = void 0;
|
|
4
|
+
const core_1 = require("@vendure/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
|
+
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
|
+
? 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 = 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
|
+
defaultVendureComplexityEstimator((_c = this.options.customComplexityFactors) !== null && _c !== void 0 ? _c : {}, (_d = this.options.logComplexityScore) !== null && _d !== void 0 ? _d : false),
|
|
40
|
+
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 HardenPlugin
|
|
71
|
+
*/
|
|
72
|
+
function defaultVendureComplexityEstimator(customComplexityFactors, logFieldScores) {
|
|
73
|
+
return (options) => {
|
|
74
|
+
var _a, _b;
|
|
75
|
+
const { type, args, childComplexity, field } = options;
|
|
76
|
+
const namedType = 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 (graphql_1.isObjectType(namedType)) {
|
|
85
|
+
const isPaginatedList = !!namedType.getInterfaces().find(i => i.name === 'PaginatedList');
|
|
86
|
+
if (isPaginatedList) {
|
|
87
|
+
const take = (_b = (_a = args.options) === null || _a === void 0 ? void 0 : _a.take) !== null && _b !== void 0 ? _b : 1000;
|
|
88
|
+
result = childComplexity + Math.round(Math.log(childComplexity) * take);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (graphql_1.isListType(graphql_1.getNullableType(field.type))) {
|
|
92
|
+
result = childComplexity * 5;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (logFieldScores) {
|
|
96
|
+
core_1.Logger.debug(`${path}: ${field.type.toString()}\tchildComplexity: ${childComplexity}, score: ${result}`, constants_1.loggerCtx);
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
exports.defaultVendureComplexityEstimator = defaultVendureComplexityEstimator;
|
|
102
|
+
//# 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":";;;AAAA,wCAA4D;AAG5D,qCAOiB;AACjB,uEAAmG;AAEnG,4CAAyC;AAGzC;;;GAGG;AACH,MAAa,qBAAqB;IAC9B,YAAoB,OAA4B;QAA5B,YAAO,GAAP,OAAO,CAAqB;IAAG,CAAC;IAEpD,eAAe,CAAC,EAAE,MAAM,EAAyB;;QAC7C,MAAM,kBAAkB,GAAG,MAAA,IAAI,CAAC,OAAO,CAAC,kBAAkB,mCAAI,IAAI,CAAC;QACnE,OAAO;YACH,mBAAmB,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE;;gBACjD,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE;oBACpB,kDAAkD;oBAClD,0DAA0D;oBAC1D,4BAA4B;oBAC5B,OAAO;iBACV;gBACD,MAAM,KAAK,GAAG,OAAO,CAAC,aAAa;oBAC/B,CAAC,CAAC,4BAAkB,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC;oBACrD,CAAC,CAAC,QAAQ,CAAC;gBAEf,IAAI,IAAI,CAAC,OAAO,CAAC,kBAAkB,KAAK,IAAI,EAAE;oBAC1C,aAAM,CAAC,KAAK,CACR,8BAA8B,MAAA,OAAO,CAAC,aAAa,mCAAI,WAAW,GAAG,EACrE,qBAAS,CACZ,CAAC;iBACL;gBACD,MAAM,UAAU,GAAG,wCAAa,CAAC;oBAC7B,MAAM;oBACN,KAAK;oBACL,SAAS,EAAE,OAAO,CAAC,SAAS;oBAC5B,UAAU,EAAE,MAAA,IAAI,CAAC,OAAO,CAAC,yBAAyB,mCAAI;wBAClD,iCAAiC,CAC7B,MAAA,IAAI,CAAC,OAAO,CAAC,uBAAuB,mCAAI,EAAE,EAC1C,MAAA,IAAI,CAAC,OAAO,CAAC,kBAAkB,mCAAI,KAAK,CAC3C;wBACD,0CAAe,CAAC,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC;qBAC5C;iBACJ,CAAC,CAAC;gBAEH,IAAI,IAAI,CAAC,OAAO,CAAC,kBAAkB,KAAK,IAAI,EAAE;oBAC1C,aAAM,CAAC,OAAO,CACV,qBAAqB,MAAA,OAAO,CAAC,aAAa,mCAAI,WAAW,MAAM,UAAU,EAAE,EAC3E,qBAAS,CACZ,CAAC;iBACL;gBACD,IAAI,UAAU,IAAI,kBAAkB,EAAE;oBAClC,aAAM,CAAC,KAAK,CACR,wBACI,MAAA,OAAO,CAAC,aAAa,mCAAI,WAC7B,QAAQ,UAAU,kCAAkC,kBAAkB,EAAE,EACxE,qBAAS,CACZ,CAAC;oBACF,MAAM,IAAI,0BAAmB,CAAC,sBAAsB,CAAC,CAAC;iBACzD;YACL,CAAC;SACJ,CAAC;IACN,CAAC;CACJ;AAtDD,sDAsDC;AAED,SAAS,UAAU,CAAC,MAAqB;IACrC,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,EAAE,CAAC;IACxC,IAAI,SAAS,EAAE;QACX,OAAO,CAAC,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC,cAAc,CAAC;KACjD;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,iCAAiC,CAC7C,uBAAmD,EACnD,cAAuB;IAEvB,OAAO,CAAC,OAAgC,EAAiB,EAAE;;QACvD,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;QACvD,MAAM,SAAS,GAAG,sBAAY,CAAC,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;YACtB,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC,GAAG,YAAY,CAAC;SACxD;aAAM;YACH,IAAI,sBAAY,CAAC,SAAS,CAAC,EAAE;gBACzB,MAAM,eAAe,GAAG,CAAC,CAAC,SAAS,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,eAAe,CAAC,CAAC;gBAC1F,IAAI,eAAe,EAAE;oBACjB,MAAM,IAAI,GAAG,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,IAAI,mCAAI,IAAI,CAAC;oBACxC,MAAM,GAAG,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC,CAAC;iBAC3E;aACJ;YACD,IAAI,oBAAU,CAAC,yBAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE;gBACzC,MAAM,GAAG,eAAe,GAAG,CAAC,CAAC;aAChC;SACJ;QACD,IAAI,cAAc,EAAE;YAChB,aAAM,CAAC,KAAK,CACR,GAAG,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,sBAAsB,eAAe,YAAY,MAAM,EAAE,EAC1F,qBAAS,CACZ,CAAC;SACL;QACD,OAAO,MAAM,CAAC;IAClB,CAAC,CAAC;AACN,CAAC;AAhCD,8EAgCC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { ComplexityEstimator } from 'graphql-query-complexity/dist/cjs/QueryComplexity';
|
|
2
|
+
/**
|
|
3
|
+
* @description
|
|
4
|
+
* Options that can be passed to the `.init()` static method of the HardenPlugin.
|
|
5
|
+
*
|
|
6
|
+
* @docsCategory 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 defaultVendureComplexityEstimator} which is specifically
|
|
24
|
+
* tuned to accurately estimate Vendure 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
|
+
* ```TypeScript
|
|
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,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vendure/harden-plugin",
|
|
3
|
+
"version": "1.9.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib/**/*"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"watch": "tsc -p ./tsconfig.build.json --watch",
|
|
12
|
+
"build": "rimraf lib && tsc -p ./tsconfig.build.json",
|
|
13
|
+
"lint": "tslint --fix --project ./"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://www.vendure.io/",
|
|
16
|
+
"funding": "https://github.com/sponsors/michaelbromley",
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"graphql-query-complexity": "^0.12.0"
|
|
22
|
+
}
|
|
23
|
+
}
|