@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 ADDED
@@ -0,0 +1,7 @@
1
+ # Vendure Harden Plugin
2
+
3
+ Hardens your Vendure GraphQL APIs against attacks.
4
+
5
+ `npm install @vendure/harden-plugin`
6
+
7
+ For documentation, see [www.vendure.io/docs/typescript-api/harden-plugin/](https://www.vendure.io/docs/typescript-api/harden-plugin/)
package/lib/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './src/harden.plugin';
2
+ export * from './src/types';
3
+ export * from './src/middleware/query-complexity-plugin';
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,sDAAoC;AACpC,8CAA4B;AAC5B,2EAAyD"}
@@ -0,0 +1,2 @@
1
+ export declare const loggerCtx = "HardenPlugin";
2
+ export declare const HARDEN_PLUGIN_OPTIONS: unique symbol;
@@ -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":"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"}
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -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
+ }