graphql-shield-node23 7.6.5

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/cjs/constructors.js +134 -0
  3. package/dist/cjs/generator.js +205 -0
  4. package/dist/cjs/index.js +15 -0
  5. package/dist/cjs/package.json +1 -0
  6. package/dist/cjs/rules.js +402 -0
  7. package/dist/cjs/shield.js +52 -0
  8. package/dist/cjs/types.js +2 -0
  9. package/dist/cjs/utils.js +97 -0
  10. package/dist/cjs/validation.js +84 -0
  11. package/dist/esm/constructors.js +124 -0
  12. package/dist/esm/generator.js +201 -0
  13. package/dist/esm/index.js +2 -0
  14. package/dist/esm/rules.js +366 -0
  15. package/dist/esm/shield.js +45 -0
  16. package/dist/esm/types.js +1 -0
  17. package/dist/esm/utils.js +88 -0
  18. package/dist/esm/validation.js +79 -0
  19. package/dist/package.json +47 -0
  20. package/dist/typings/constructors.d.cts +91 -0
  21. package/dist/typings/constructors.d.ts +91 -0
  22. package/dist/typings/generator.d.cts +11 -0
  23. package/dist/typings/generator.d.ts +11 -0
  24. package/dist/typings/index.d.cts +3 -0
  25. package/dist/typings/index.d.ts +3 -0
  26. package/dist/typings/rules.d.cts +159 -0
  27. package/dist/typings/rules.d.ts +159 -0
  28. package/dist/typings/shield.d.cts +11 -0
  29. package/dist/typings/shield.d.ts +11 -0
  30. package/dist/typings/types.d.cts +64 -0
  31. package/dist/typings/types.d.ts +64 -0
  32. package/dist/typings/utils.d.cts +52 -0
  33. package/dist/typings/utils.d.ts +52 -0
  34. package/dist/typings/validation.d.cts +19 -0
  35. package/dist/typings/validation.d.ts +19 -0
  36. package/package.json +67 -0
  37. package/src/constructors.ts +157 -0
  38. package/src/generator.ts +294 -0
  39. package/src/index.ts +13 -0
  40. package/src/rules.ts +521 -0
  41. package/src/shield.ts +53 -0
  42. package/src/types.ts +94 -0
  43. package/src/utils.ts +101 -0
  44. package/src/validation.ts +90 -0
  45. package/tests/__snapshots__/input.test.ts.snap +7 -0
  46. package/tests/cache.test.ts +545 -0
  47. package/tests/constructors.test.ts +136 -0
  48. package/tests/fallback.test.ts +618 -0
  49. package/tests/fragments.test.ts +113 -0
  50. package/tests/generator.test.ts +356 -0
  51. package/tests/input.test.ts +63 -0
  52. package/tests/integration.test.ts +65 -0
  53. package/tests/logic.test.ts +530 -0
  54. package/tests/utils.test.ts +55 -0
  55. package/tests/validation.test.ts +139 -0
  56. package/tsconfig.json +10 -0
@@ -0,0 +1,124 @@
1
+ import { Rule, RuleAnd, RuleOr, RuleNot, RuleTrue, RuleFalse, InputRule, RuleChain, RuleRace, } from './rules.js';
2
+ /**
3
+ *
4
+ * @param name
5
+ * @param options
6
+ *
7
+ * Wraps a function into a Rule class. This way we can identify rules
8
+ * once we start generating middleware from our ruleTree.
9
+ *
10
+ * 1.
11
+ * const auth = rule()(async (parent, args, ctx, info) => {
12
+ * return true
13
+ * })
14
+ *
15
+ * 2.
16
+ * const auth = rule('name')(async (parent, args, ctx, info) => {
17
+ * return true
18
+ * })
19
+ *
20
+ * 3.
21
+ * const auth = rule({
22
+ * name: 'name',
23
+ * fragment: 'string',
24
+ * cache: 'cache',
25
+ * })(async (parent, args, ctx, info) => {
26
+ * return true
27
+ * })
28
+ *
29
+ */
30
+ export const rule = (name, options) => (func) => {
31
+ if (typeof name === 'object') {
32
+ options = name;
33
+ name = Math.random().toString();
34
+ }
35
+ else if (typeof name === 'string') {
36
+ options = options || {};
37
+ }
38
+ else {
39
+ name = Math.random().toString();
40
+ options = {};
41
+ }
42
+ return new Rule(name, func, {
43
+ fragment: options.fragment,
44
+ cache: options.cache,
45
+ });
46
+ };
47
+ /**
48
+ *
49
+ * Constructs a new InputRule based on the schema.
50
+ *
51
+ * @param schema
52
+ */
53
+ export const inputRule = (name) => (schema, options) => {
54
+ if (typeof name === 'string') {
55
+ return new InputRule(name, schema, options);
56
+ }
57
+ else {
58
+ return new InputRule(Math.random().toString(), schema, options);
59
+ }
60
+ };
61
+ /**
62
+ *
63
+ * @param rules
64
+ *
65
+ * Logical operator and serves as a wrapper for and operation.
66
+ *
67
+ */
68
+ export const and = (...rules) => {
69
+ return new RuleAnd(rules);
70
+ };
71
+ /**
72
+ *
73
+ * @param rules
74
+ *
75
+ * Logical operator and serves as a wrapper for and operation.
76
+ *
77
+ */
78
+ export const chain = (...rules) => {
79
+ return new RuleChain(rules);
80
+ };
81
+ /**
82
+ *
83
+ * @param rules
84
+ *
85
+ * Logical operator and serves as a wrapper for and operation.
86
+ *
87
+ */
88
+ export const race = (...rules) => {
89
+ return new RuleRace(rules);
90
+ };
91
+ /**
92
+ *
93
+ * @param rules
94
+ *
95
+ * Logical operator or serves as a wrapper for or operation.
96
+ *
97
+ */
98
+ export const or = (...rules) => {
99
+ return new RuleOr(rules);
100
+ };
101
+ /**
102
+ *
103
+ * @param rule
104
+ *
105
+ * Logical operator not serves as a wrapper for not operation.
106
+ *
107
+ */
108
+ export const not = (rule, error) => {
109
+ if (typeof error === 'string')
110
+ return new RuleNot(rule, new Error(error));
111
+ return new RuleNot(rule, error);
112
+ };
113
+ /**
114
+ *
115
+ * Allow queries.
116
+ *
117
+ */
118
+ export const allow = new RuleTrue();
119
+ /**
120
+ *
121
+ * Deny queries.
122
+ *
123
+ */
124
+ export const deny = new RuleFalse();
@@ -0,0 +1,201 @@
1
+ import { isObjectType, isIntrospectionType, } from 'graphql';
2
+ import { isRuleFunction, isRuleFieldMap, isRule, isLogicRule, withDefault, } from './utils.js';
3
+ import { ValidationError } from './validation.js';
4
+ /**
5
+ *
6
+ * @param options
7
+ *
8
+ * Generates a middleware function from a given rule and
9
+ * initializes the cache object in context.
10
+ *
11
+ */
12
+ function generateFieldMiddlewareFromRule(rule, options) {
13
+ async function middleware(resolve, parent, args, ctx, info) {
14
+ // Cache
15
+ if (!ctx) {
16
+ ctx = {};
17
+ }
18
+ if (!ctx._shield) {
19
+ ctx._shield = {
20
+ cache: {},
21
+ };
22
+ }
23
+ // Execution
24
+ try {
25
+ const res = await rule.resolve(parent, args, ctx, info, options);
26
+ if (res === true) {
27
+ return await resolve(parent, args, ctx, info);
28
+ }
29
+ else if (res === false) {
30
+ if (typeof options.fallbackError === 'function') {
31
+ return await options.fallbackError(null, parent, args, ctx, info);
32
+ }
33
+ return options.fallbackError;
34
+ }
35
+ else {
36
+ return res;
37
+ }
38
+ }
39
+ catch (err) {
40
+ if (options.debug) {
41
+ throw err;
42
+ }
43
+ else if (options.allowExternalErrors) {
44
+ return err;
45
+ }
46
+ else {
47
+ if (typeof options.fallbackError === 'function') {
48
+ return await options.fallbackError(err, parent, args, ctx, info);
49
+ }
50
+ return options.fallbackError;
51
+ }
52
+ }
53
+ }
54
+ if (isRule(rule) && rule.extractFragment()) {
55
+ return {
56
+ fragment: rule.extractFragment(),
57
+ resolve: middleware,
58
+ };
59
+ }
60
+ if (isLogicRule(rule)) {
61
+ return {
62
+ fragments: rule.extractFragments(),
63
+ resolve: middleware,
64
+ };
65
+ }
66
+ return middleware;
67
+ }
68
+ /**
69
+ *
70
+ * @param type
71
+ * @param rules
72
+ * @param options
73
+ *
74
+ * Generates middleware from rule for a particular type.
75
+ *
76
+ */
77
+ function applyRuleToType(type, rules, options) {
78
+ if (isRuleFunction(rules)) {
79
+ /* Apply defined rule function to every field */
80
+ const fieldMap = type.getFields();
81
+ const middleware = Object.keys(fieldMap).reduce((middleware, field) => {
82
+ return {
83
+ ...middleware,
84
+ [field]: generateFieldMiddlewareFromRule(rules, options),
85
+ };
86
+ }, {});
87
+ return middleware;
88
+ }
89
+ else if (isRuleFieldMap(rules)) {
90
+ /* Apply rules assigned to each field to each field */
91
+ const fieldMap = type.getFields();
92
+ /* Extract default type wildcard if any and remove it for validation */
93
+ const defaultTypeRule = rules['*'];
94
+ const { '*': _, ...rulesWithoutWildcard } = rules;
95
+ /* Validation */
96
+ const fieldErrors = Object.keys(rulesWithoutWildcard)
97
+ .filter((type) => !Object.prototype.hasOwnProperty.call(fieldMap, type))
98
+ .map((field) => `${type.name}.${field}`)
99
+ .join(', ');
100
+ if (fieldErrors.length > 0) {
101
+ throw new ValidationError(`It seems like you have applied rules to ${fieldErrors} fields but Shield cannot find them in your schema.`);
102
+ }
103
+ /* Generation */
104
+ const middleware = Object.keys(fieldMap).reduce((middleware, field) => ({
105
+ ...middleware,
106
+ [field]: generateFieldMiddlewareFromRule(withDefault(defaultTypeRule || options.fallbackRule)(rules[field]), options),
107
+ }), {});
108
+ return middleware;
109
+ }
110
+ else {
111
+ /* Apply fallbackRule to type with no defined rule */
112
+ const fieldMap = type.getFields();
113
+ const middleware = Object.keys(fieldMap).reduce((middleware, field) => ({
114
+ ...middleware,
115
+ [field]: generateFieldMiddlewareFromRule(options.fallbackRule, options),
116
+ }), {});
117
+ return middleware;
118
+ }
119
+ }
120
+ /**
121
+ *
122
+ * @param schema
123
+ * @param rule
124
+ * @param options
125
+ *
126
+ * Applies the same rule over entire schema.
127
+ *
128
+ */
129
+ function applyRuleToSchema(schema, rule, options) {
130
+ const typeMap = schema.getTypeMap();
131
+ const middleware = Object.keys(typeMap)
132
+ .filter((type) => !isIntrospectionType(typeMap[type]))
133
+ .reduce((middleware, typeName) => {
134
+ const type = typeMap[typeName];
135
+ if (isObjectType(type)) {
136
+ return {
137
+ ...middleware,
138
+ [typeName]: applyRuleToType(type, rule, options),
139
+ };
140
+ }
141
+ else {
142
+ return middleware;
143
+ }
144
+ }, {});
145
+ return middleware;
146
+ }
147
+ /**
148
+ *
149
+ * @param rules
150
+ * @param wrapper
151
+ *
152
+ * Converts rule tree to middleware.
153
+ *
154
+ */
155
+ function generateMiddlewareFromSchemaAndRuleTree(schema, rules, options) {
156
+ if (isRuleFunction(rules)) {
157
+ /* Applies rule to entire schema. */
158
+ return applyRuleToSchema(schema, rules, options);
159
+ }
160
+ else {
161
+ /**
162
+ * Checks type map and field map and applies rules
163
+ * to particular fields.
164
+ */
165
+ const typeMap = schema.getTypeMap();
166
+ /* Validation */
167
+ const typeErrors = Object.keys(rules)
168
+ .filter((type) => !Object.prototype.hasOwnProperty.call(typeMap, type))
169
+ .join(', ');
170
+ if (typeErrors.length > 0) {
171
+ throw new ValidationError(`It seems like you have applied rules to ${typeErrors} types but Shield cannot find them in your schema.`);
172
+ }
173
+ // Generation
174
+ const middleware = Object.keys(typeMap)
175
+ .filter((type) => !isIntrospectionType(typeMap[type]))
176
+ .reduce((middleware, typeName) => {
177
+ const type = typeMap[typeName];
178
+ if (isObjectType(type)) {
179
+ return {
180
+ ...middleware,
181
+ [typeName]: applyRuleToType(type, rules[typeName], options),
182
+ };
183
+ }
184
+ else {
185
+ return middleware;
186
+ }
187
+ }, {});
188
+ return middleware;
189
+ }
190
+ }
191
+ /**
192
+ *
193
+ * @param ruleTree
194
+ * @param options
195
+ *
196
+ * Generates middleware from given rules.
197
+ *
198
+ */
199
+ export function generateMiddlewareGeneratorFromRuleTree(ruleTree, options) {
200
+ return (schema) => generateMiddlewareFromSchemaAndRuleTree(schema, ruleTree, options);
201
+ }
@@ -0,0 +1,2 @@
1
+ export { shield } from './shield.js';
2
+ export { rule, inputRule, allow, deny, and, chain, race, or, not, } from './constructors.js';
@@ -0,0 +1,366 @@
1
+ import * as Yup from 'yup';
2
+ import { isLogicRule } from './utils.js';
3
+ export class Rule {
4
+ constructor(name, func, constructorOptions) {
5
+ const options = this.normalizeOptions(constructorOptions);
6
+ this.name = name;
7
+ this.func = func;
8
+ this.cache = options.cache;
9
+ this.fragment = options.fragment;
10
+ }
11
+ /**
12
+ *
13
+ * @param parent
14
+ * @param args
15
+ * @param ctx
16
+ * @param info
17
+ *
18
+ * Resolves rule and writes to cache its result.
19
+ *
20
+ */
21
+ async resolve(parent, args, ctx, info, options) {
22
+ try {
23
+ /* Resolve */
24
+ const res = await this.executeRule(parent, args, ctx, info, options);
25
+ if (res instanceof Error) {
26
+ return res;
27
+ }
28
+ else if (typeof res === 'string') {
29
+ return new Error(res);
30
+ }
31
+ else if (res === true) {
32
+ return true;
33
+ }
34
+ else {
35
+ return false;
36
+ }
37
+ }
38
+ catch (err) {
39
+ if (options.debug) {
40
+ throw err;
41
+ }
42
+ else {
43
+ return false;
44
+ }
45
+ }
46
+ }
47
+ /**
48
+ *
49
+ * @param rule
50
+ *
51
+ * Compares a given rule with the current one
52
+ * and checks whether their functions are equal.
53
+ *
54
+ */
55
+ equals(rule) {
56
+ return this.func === rule.func;
57
+ }
58
+ /**
59
+ *
60
+ * Extracts fragment from the rule.
61
+ *
62
+ */
63
+ extractFragment() {
64
+ return this.fragment;
65
+ }
66
+ /**
67
+ *
68
+ * @param options
69
+ *
70
+ * Sets default values for options.
71
+ *
72
+ */
73
+ normalizeOptions(options) {
74
+ return {
75
+ cache: options.cache !== undefined
76
+ ? this.normalizeCacheOption(options.cache)
77
+ : 'no_cache',
78
+ fragment: options.fragment !== undefined ? options.fragment : undefined,
79
+ };
80
+ }
81
+ /**
82
+ *
83
+ * @param cache
84
+ *
85
+ * This ensures backward capability of shield.
86
+ *
87
+ */
88
+ normalizeCacheOption(cache) {
89
+ switch (cache) {
90
+ case true: {
91
+ return 'strict';
92
+ }
93
+ case false: {
94
+ return 'no_cache';
95
+ }
96
+ default: {
97
+ return cache;
98
+ }
99
+ }
100
+ }
101
+ /**
102
+ * Executes a rule and writes to cache if needed.
103
+ *
104
+ * @param parent
105
+ * @param args
106
+ * @param ctx
107
+ * @param info
108
+ */
109
+ executeRule(parent, args, ctx, info, options) {
110
+ switch (typeof this.cache) {
111
+ case 'function': {
112
+ /* User defined cache function. */
113
+ const key = `${this.name}-${this.cache(parent, args, ctx, info)}`;
114
+ return this.writeToCache(key)(parent, args, ctx, info);
115
+ }
116
+ case 'string': {
117
+ /* Standard cache option. */
118
+ switch (this.cache) {
119
+ case 'strict': {
120
+ const key = options.hashFunction({ parent, args });
121
+ return this.writeToCache(`${this.name}-${key}`)(parent, args, ctx, info);
122
+ }
123
+ case 'contextual': {
124
+ return this.writeToCache(this.name)(parent, args, ctx, info);
125
+ }
126
+ case 'no_cache': {
127
+ return this.func(parent, args, ctx, info);
128
+ }
129
+ }
130
+ }
131
+ /* istanbul ignore next */
132
+ default: {
133
+ throw new Error(`Unsupported cache format: ${typeof this.cache}`);
134
+ }
135
+ }
136
+ }
137
+ /**
138
+ * Writes or reads result from cache.
139
+ *
140
+ * @param key
141
+ */
142
+ writeToCache(key) {
143
+ return (parent, args, ctx, info) => {
144
+ if (!ctx._shield.cache[key]) {
145
+ ctx._shield.cache[key] = this.func(parent, args, ctx, info);
146
+ }
147
+ return ctx._shield.cache[key];
148
+ };
149
+ }
150
+ }
151
+ export class InputRule extends Rule {
152
+ constructor(name, schema, options) {
153
+ const validationFunction = (parent, args, ctx) => schema(Yup, ctx)
154
+ .validate(args, options)
155
+ .then(() => true)
156
+ .catch((err) => err);
157
+ super(name, validationFunction, { cache: 'strict', fragment: undefined });
158
+ }
159
+ }
160
+ export class LogicRule {
161
+ constructor(rules) {
162
+ this.rules = rules;
163
+ }
164
+ /**
165
+ * By default logic rule resolves to false.
166
+ */
167
+ async resolve(parent, args, ctx, info, options) {
168
+ return false;
169
+ }
170
+ /**
171
+ * Evaluates all the rules.
172
+ */
173
+ async evaluate(parent, args, ctx, info, options) {
174
+ const rules = this.getRules();
175
+ const tasks = rules.map((rule) => rule.resolve(parent, args, ctx, info, options));
176
+ return Promise.all(tasks);
177
+ }
178
+ /**
179
+ * Returns rules in a logic rule.
180
+ */
181
+ getRules() {
182
+ return this.rules;
183
+ }
184
+ /**
185
+ * Extracts fragments from the defined rules.
186
+ */
187
+ extractFragments() {
188
+ const fragments = this.rules.reduce((fragments, rule) => {
189
+ if (isLogicRule(rule)) {
190
+ return fragments.concat(...rule.extractFragments());
191
+ }
192
+ const fragment = rule.extractFragment();
193
+ if (fragment)
194
+ return fragments.concat(fragment);
195
+ return fragments;
196
+ }, []);
197
+ return fragments;
198
+ }
199
+ }
200
+ // Extended Types
201
+ export class RuleOr extends LogicRule {
202
+ constructor(rules) {
203
+ super(rules);
204
+ }
205
+ /**
206
+ * Makes sure that at least one of them has evaluated to true.
207
+ */
208
+ async resolve(parent, args, ctx, info, options) {
209
+ const result = await this.evaluate(parent, args, ctx, info, options);
210
+ if (result.every((res) => res !== true)) {
211
+ const customError = result.find((res) => res instanceof Error);
212
+ return customError || false;
213
+ }
214
+ else {
215
+ return true;
216
+ }
217
+ }
218
+ }
219
+ export class RuleAnd extends LogicRule {
220
+ constructor(rules) {
221
+ super(rules);
222
+ }
223
+ /**
224
+ * Makes sure that all of them have resolved to true.
225
+ */
226
+ async resolve(parent, args, ctx, info, options) {
227
+ const result = await this.evaluate(parent, args, ctx, info, options);
228
+ if (result.some((res) => res !== true)) {
229
+ const customError = result.find((res) => res instanceof Error);
230
+ return customError || false;
231
+ }
232
+ else {
233
+ return true;
234
+ }
235
+ }
236
+ }
237
+ export class RuleChain extends LogicRule {
238
+ constructor(rules) {
239
+ super(rules);
240
+ }
241
+ /**
242
+ * Makes sure that all of them have resolved to true.
243
+ */
244
+ async resolve(parent, args, ctx, info, options) {
245
+ const result = await this.evaluate(parent, args, ctx, info, options);
246
+ if (result.some((res) => res !== true)) {
247
+ const customError = result.find((res) => res instanceof Error);
248
+ return customError || false;
249
+ }
250
+ else {
251
+ return true;
252
+ }
253
+ }
254
+ /**
255
+ * Evaluates all the rules.
256
+ */
257
+ async evaluate(parent, args, ctx, info, options) {
258
+ const rules = this.getRules();
259
+ return iterate(rules);
260
+ async function iterate([rule, ...otherRules]) {
261
+ if (rule === undefined)
262
+ return [];
263
+ return rule.resolve(parent, args, ctx, info, options).then((res) => {
264
+ if (res !== true) {
265
+ return [res];
266
+ }
267
+ else {
268
+ return iterate(otherRules).then((ress) => ress.concat(res));
269
+ }
270
+ });
271
+ }
272
+ }
273
+ }
274
+ export class RuleRace extends LogicRule {
275
+ constructor(rules) {
276
+ super(rules);
277
+ }
278
+ /**
279
+ * Makes sure that at least one of them resolved to true.
280
+ */
281
+ async resolve(parent, args, ctx, info, options) {
282
+ const result = await this.evaluate(parent, args, ctx, info, options);
283
+ if (result.some((res) => res === true)) {
284
+ return true;
285
+ }
286
+ else {
287
+ const customError = result.find((res) => res instanceof Error);
288
+ return customError || false;
289
+ }
290
+ }
291
+ /**
292
+ * Evaluates all the rules.
293
+ */
294
+ async evaluate(parent, args, ctx, info, options) {
295
+ const rules = this.getRules();
296
+ return iterate(rules);
297
+ async function iterate([rule, ...otherRules]) {
298
+ if (rule === undefined)
299
+ return [];
300
+ return rule.resolve(parent, args, ctx, info, options).then((res) => {
301
+ if (res === true) {
302
+ return [res];
303
+ }
304
+ else {
305
+ return iterate(otherRules).then((ress) => ress.concat(res));
306
+ }
307
+ });
308
+ }
309
+ }
310
+ }
311
+ export class RuleNot extends LogicRule {
312
+ constructor(rule, error) {
313
+ super([rule]);
314
+ this.error = error;
315
+ }
316
+ /**
317
+ *
318
+ * @param parent
319
+ * @param args
320
+ * @param ctx
321
+ * @param info
322
+ *
323
+ * Negates the result.
324
+ *
325
+ */
326
+ async resolve(parent, args, ctx, info, options) {
327
+ const [res] = await this.evaluate(parent, args, ctx, info, options);
328
+ if (res instanceof Error) {
329
+ return true;
330
+ }
331
+ else if (res !== true) {
332
+ return true;
333
+ }
334
+ else {
335
+ if (this.error)
336
+ return this.error;
337
+ return false;
338
+ }
339
+ }
340
+ }
341
+ export class RuleTrue extends LogicRule {
342
+ constructor() {
343
+ super([]);
344
+ }
345
+ /**
346
+ *
347
+ * Always true.
348
+ *
349
+ */
350
+ async resolve() {
351
+ return true;
352
+ }
353
+ }
354
+ export class RuleFalse extends LogicRule {
355
+ constructor() {
356
+ super([]);
357
+ }
358
+ /**
359
+ *
360
+ * Always false.
361
+ *
362
+ */
363
+ async resolve() {
364
+ return false;
365
+ }
366
+ }