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,530 @@
1
+ import { graphql, GraphQLResolveInfo } from 'graphql'
2
+ import { applyMiddleware } from 'graphql-middleware'
3
+ import { makeExecutableSchema } from '@graphql-tools/schema'
4
+
5
+ import { shield, rule, allow, deny, and, or, not } from '../src'
6
+ import { LogicRule } from '../src/rules'
7
+ import { chain, race } from '../src/constructors'
8
+
9
+ describe('logic rules', () => {
10
+ test('allow, deny work as expeted', async () => {
11
+ const typeDefs = `
12
+ type Query {
13
+ allow: String
14
+ deny: String
15
+ }
16
+ `
17
+
18
+ const resolvers = {
19
+ Query: {
20
+ allow: () => 'allow',
21
+ deny: () => 'deny',
22
+ },
23
+ }
24
+
25
+ const schema = makeExecutableSchema({ typeDefs, resolvers })
26
+
27
+ // Permissions
28
+ const permissions = shield({
29
+ Query: {
30
+ allow: allow,
31
+ deny: deny,
32
+ },
33
+ })
34
+
35
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
36
+
37
+ /* Execution */
38
+
39
+ const query = `
40
+ query {
41
+ allow
42
+ deny
43
+ }
44
+ `
45
+ const res = await graphql({
46
+ schema: schemaWithPermissions,
47
+ source: query,
48
+ })
49
+
50
+ /* Tests */
51
+
52
+ expect(res.data).toEqual({
53
+ allow: 'allow',
54
+ deny: null,
55
+ })
56
+ expect(res.errors?.length).toBe(1)
57
+ })
58
+
59
+ test('and works as expected', async () => {
60
+ const typeDefs = `
61
+ type Query {
62
+ allow: String
63
+ deny: String
64
+ ruleError: String
65
+ }
66
+ `
67
+
68
+ const resolvers = {
69
+ Query: {
70
+ allow: () => 'allow',
71
+ deny: () => 'deny',
72
+ ruleError: () => 'ruleError',
73
+ },
74
+ }
75
+
76
+ const schema = makeExecutableSchema({ typeDefs, resolvers })
77
+
78
+ /* Permissions */
79
+
80
+ const ruleWithError = rule()(async () => {
81
+ throw new Error()
82
+ })
83
+
84
+ const permissions = shield({
85
+ Query: {
86
+ allow: and(allow, allow),
87
+ deny: and(allow, deny),
88
+ ruleError: and(allow, ruleWithError),
89
+ },
90
+ })
91
+
92
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
93
+
94
+ /* Execution */
95
+
96
+ const query = `
97
+ query {
98
+ allow
99
+ deny
100
+ ruleError
101
+ }
102
+ `
103
+ const res = await graphql({
104
+ schema: schemaWithPermissions,
105
+ source: query,
106
+ })
107
+
108
+ /* Tests */
109
+
110
+ expect(res.data).toEqual({
111
+ allow: 'allow',
112
+ deny: null,
113
+ ruleError: null,
114
+ })
115
+ expect(res.errors?.length).toBe(2)
116
+ })
117
+
118
+ test('chain works as expected', async () => {
119
+ const typeDefs = `
120
+ type Query {
121
+ allow: String
122
+ deny: String
123
+ ruleError: String
124
+ }
125
+ `
126
+
127
+ const resolvers = {
128
+ Query: {
129
+ allow: () => 'allow',
130
+ deny: () => 'deny',
131
+ ruleError: () => 'error',
132
+ },
133
+ }
134
+
135
+ const schema = makeExecutableSchema({ typeDefs, resolvers })
136
+
137
+ /* Permissions */
138
+
139
+ let allowRuleSequence: string[] = []
140
+ const allowRuleA = rule()(() => {
141
+ allowRuleSequence.push('A')
142
+ return true
143
+ })
144
+ const allowRuleB = rule()(() => {
145
+ allowRuleSequence.push('B')
146
+ return true
147
+ })
148
+ const allowRuleC = rule()(() => {
149
+ allowRuleSequence.push('C')
150
+ return true
151
+ })
152
+ let denyRuleCount = 0
153
+ const denyRule = rule({})(() => {
154
+ denyRuleCount += 1
155
+ return false
156
+ })
157
+ let ruleWithErrorCount = 0
158
+ const ruleWithError = rule()(() => {
159
+ ruleWithErrorCount += 1
160
+ throw new Error('error')
161
+ })
162
+
163
+ const permissions = shield({
164
+ Query: {
165
+ allow: chain(allowRuleA, allowRuleB, allowRuleC),
166
+ deny: chain(denyRule, denyRule, denyRule),
167
+ ruleError: chain(ruleWithError, ruleWithError, ruleWithError),
168
+ },
169
+ })
170
+
171
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
172
+
173
+ /* Execution */
174
+
175
+ const query = `
176
+ query {
177
+ allow
178
+ deny
179
+ ruleError
180
+ }
181
+ `
182
+ const res = await graphql({
183
+ schema: schemaWithPermissions,
184
+ source: query,
185
+ })
186
+
187
+ /* Tests */
188
+
189
+ expect(res.data).toEqual({
190
+ allow: 'allow',
191
+ deny: null,
192
+ ruleError: null,
193
+ })
194
+ expect(allowRuleSequence.toString()).toEqual(['A', 'B', 'C'].toString())
195
+ expect(denyRuleCount).toEqual(1)
196
+ expect(ruleWithErrorCount).toEqual(1)
197
+ expect(res.errors?.length).toBe(2)
198
+ })
199
+
200
+ test('race chain works as expected', async () => {
201
+ const typeDefs = `
202
+ type Query {
203
+ allow: String
204
+ deny: String
205
+ ruleError: String
206
+ }
207
+ `
208
+
209
+ const resolvers = {
210
+ Query: {
211
+ allow: () => 'allow',
212
+ deny: () => 'deny',
213
+ ruleError: () => 'error',
214
+ },
215
+ }
216
+
217
+ const schema = makeExecutableSchema({ typeDefs, resolvers })
218
+
219
+ /* Permissions */
220
+
221
+ let allowRuleSequence: string[] = []
222
+ const denyRuleA = rule()(() => {
223
+ allowRuleSequence.push('A')
224
+ return false
225
+ })
226
+ const allowRuleB = rule()(() => {
227
+ allowRuleSequence.push('B')
228
+ return true
229
+ })
230
+ const allowRuleC = rule()(() => {
231
+ allowRuleSequence.push('C')
232
+ return true
233
+ })
234
+ let denyRuleCount = 0
235
+ const denyRule = rule({})(() => {
236
+ denyRuleCount += 1
237
+ return false
238
+ })
239
+ let ruleWithErrorCount = 0
240
+ const ruleWithError = rule()(() => {
241
+ ruleWithErrorCount += 1
242
+ throw new Error('error')
243
+ })
244
+
245
+ const permissions = shield({
246
+ Query: {
247
+ allow: race(denyRuleA, allowRuleB, allowRuleC),
248
+ deny: race(denyRule, denyRule, denyRule),
249
+ ruleError: race(ruleWithError, ruleWithError, ruleWithError),
250
+ },
251
+ })
252
+
253
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
254
+
255
+ /* Execution */
256
+
257
+ const query = `
258
+ query {
259
+ allow
260
+ deny
261
+ ruleError
262
+ }
263
+ `
264
+ const res = await graphql({
265
+ schema: schemaWithPermissions,
266
+ source: query,
267
+ })
268
+
269
+ /* Tests */
270
+
271
+ expect(res.data).toEqual({
272
+ allow: 'allow',
273
+ deny: null,
274
+ ruleError: null,
275
+ })
276
+ expect(allowRuleSequence.toString()).toEqual(['A', 'B'].toString())
277
+ expect(denyRuleCount).toEqual(3)
278
+ expect(ruleWithErrorCount).toEqual(3)
279
+ expect(res.errors?.length).toBe(2)
280
+ })
281
+
282
+ test('or works as expected', async () => {
283
+ const typeDefs = `
284
+ type Query {
285
+ allow: String
286
+ deny: String
287
+ }
288
+ `
289
+
290
+ const resolvers = {
291
+ Query: {
292
+ allow: () => 'allow',
293
+ deny: () => 'deny',
294
+ },
295
+ }
296
+
297
+ const schema = makeExecutableSchema({ typeDefs, resolvers })
298
+
299
+ /* Permissions */
300
+
301
+ const permissions = shield({
302
+ Query: {
303
+ allow: or(allow, deny),
304
+ deny: or(deny, deny),
305
+ },
306
+ })
307
+
308
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
309
+
310
+ /* Execution */
311
+
312
+ const query = `
313
+ query {
314
+ allow
315
+ deny
316
+ }
317
+ `
318
+ const res = await graphql({
319
+ schema: schemaWithPermissions,
320
+ source: query,
321
+ })
322
+
323
+ /* Tests */
324
+
325
+ expect(res.data).toEqual({
326
+ allow: 'allow',
327
+ deny: null,
328
+ })
329
+ expect(res.errors?.length).toBe(1)
330
+ })
331
+
332
+ test('not works as expected', async () => {
333
+ const typeDefs = `
334
+ type Query {
335
+ allow: String
336
+ deny: String
337
+ ruleError: String
338
+ resolverError: String
339
+ customRuleError: String
340
+ customRuleErrorString: String
341
+ }
342
+ `
343
+
344
+ const resolvers = {
345
+ Query: {
346
+ allow: () => 'allow',
347
+ deny: () => 'deny',
348
+ ruleError: () => 'ruleError',
349
+ resolverError: () => {
350
+ throw new Error()
351
+ },
352
+ customRuleError: () => 'customRuleError',
353
+ customRuleErrorString: () => 'customRuleErrorString',
354
+ },
355
+ }
356
+
357
+ const schema = makeExecutableSchema({ typeDefs, resolvers })
358
+
359
+ /* Permissions */
360
+
361
+ const ruleWithError = rule()(async () => {
362
+ throw new Error()
363
+ })
364
+
365
+ const ruleWithCustomError = rule()(async () => {
366
+ return new Error('error_pass')
367
+ })
368
+
369
+ const ruleWithCustomErrorString = rule()(async () => {
370
+ return 'error_string_pass'
371
+ })
372
+
373
+ const permissions = shield({
374
+ Query: {
375
+ allow: not(deny),
376
+ deny: not(allow),
377
+ ruleError: not(ruleWithError),
378
+ resolverError: not(allow),
379
+ customRuleError: not(ruleWithCustomError),
380
+ customRuleErrorString: not(ruleWithCustomErrorString),
381
+ },
382
+ })
383
+
384
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
385
+
386
+ /* Execution */
387
+
388
+ const query = `
389
+ query {
390
+ allow
391
+ deny
392
+ ruleError
393
+ resolverError
394
+ customRuleError
395
+ customRuleErrorString
396
+ }
397
+ `
398
+ const res = await graphql({
399
+ schema: schemaWithPermissions,
400
+ source: query,
401
+ })
402
+
403
+ expect(res.data).toEqual({
404
+ allow: 'allow',
405
+ deny: null,
406
+ ruleError: 'ruleError',
407
+ resolverError: null,
408
+ customRuleError: 'customRuleError',
409
+ customRuleErrorString: 'customRuleErrorString',
410
+ })
411
+ expect(res.errors?.map((err) => err.message)).toEqual([
412
+ 'Not Authorised!',
413
+ 'Not Authorised!',
414
+ ])
415
+ })
416
+
417
+ test('not returns custom error', async () => {
418
+ const typeDefs = `
419
+ type Query {
420
+ not: String
421
+ }
422
+ `
423
+
424
+ const resolvers = {
425
+ Query: {
426
+ not: () => 'not',
427
+ },
428
+ }
429
+
430
+ const schema = makeExecutableSchema({ typeDefs, resolvers })
431
+
432
+ /* Permissions */
433
+
434
+ const permissions = shield({
435
+ Query: {
436
+ not: not(allow, 'This is a custom not message.'),
437
+ },
438
+ })
439
+
440
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
441
+
442
+ /* Execution */
443
+
444
+ const query = `
445
+ query {
446
+ not
447
+ }
448
+ `
449
+ const res = await graphql({
450
+ schema: schemaWithPermissions,
451
+ source: query,
452
+ })
453
+
454
+ expect(res.data).toEqual({
455
+ not: null,
456
+ })
457
+ expect(res.errors?.map((err) => err.message)).toEqual([
458
+ 'This is a custom not message.',
459
+ ])
460
+ })
461
+ })
462
+
463
+ describe('internal execution', () => {
464
+ test('logic rule by default resolves to false', async () => {
465
+ const rule = new LogicRule([])
466
+
467
+ const res = await rule.resolve(
468
+ {},
469
+ {},
470
+ { _shield: { cache: {} } },
471
+ {} as GraphQLResolveInfo,
472
+ {
473
+ allowExternalErrors: false,
474
+ debug: false,
475
+ fallbackRule: allow,
476
+ fallbackError: new Error(),
477
+ hashFunction: () => `${Math.random()}`,
478
+ },
479
+ )
480
+
481
+ expect(res).toBeFalsy()
482
+ })
483
+
484
+ test('rule prevents access when access not permited', async () => {
485
+ const typeDefs = `
486
+ type Query {
487
+ deny: String
488
+ }
489
+ `
490
+
491
+ const resolvers = {
492
+ Query: {
493
+ deny: () => 'deny',
494
+ },
495
+ }
496
+
497
+ const schema = makeExecutableSchema({ typeDefs, resolvers })
498
+
499
+ /* Permissions */
500
+
501
+ const ruleDeny = rule()(() => false)
502
+
503
+ const permissions = shield({
504
+ Query: {
505
+ deny: ruleDeny,
506
+ },
507
+ })
508
+
509
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
510
+
511
+ /* Execution */
512
+
513
+ const query = `
514
+ query {
515
+ deny
516
+ }
517
+ `
518
+ const res = await graphql({
519
+ schema: schemaWithPermissions,
520
+ source: query,
521
+ })
522
+
523
+ /* Tests */
524
+
525
+ expect(res.data).toEqual({
526
+ deny: null,
527
+ })
528
+ expect(res.errors?.length).toBe(1)
529
+ })
530
+ })
@@ -0,0 +1,55 @@
1
+ import {
2
+ isRule,
3
+ isLogicRule,
4
+ isRuleFieldMap,
5
+ isRuleFunction,
6
+ withDefault,
7
+ } from '../src/utils'
8
+ import { rule, and } from '../src/constructors'
9
+
10
+ import { testSimpleRule, testLogicRule } from 'graphql-shield-rules'
11
+
12
+ describe('type identifiers', () => {
13
+ test('isRuleFunction finds rule function.', async () => {
14
+ expect(isRuleFunction(rule()(() => true))).toBeTruthy()
15
+ expect(isRuleFunction(and())).toBeTruthy()
16
+ expect(isRuleFunction(false)).toBeFalsy()
17
+ })
18
+
19
+ test('isRule finds rule.', async () => {
20
+ expect(isRule(rule()(() => true))).toBeTruthy()
21
+ expect(isRule(and())).toBeFalsy()
22
+ expect(isRule(false)).toBeFalsy()
23
+ expect(isRule(testSimpleRule)).toBeTruthy()
24
+ })
25
+
26
+ test('isLogicRule finds logic rule.', async () => {
27
+ expect(isLogicRule(and())).toBeTruthy()
28
+ expect(isLogicRule(rule()(() => true))).toBeFalsy()
29
+ expect(isLogicRule(false)).toBeFalsy()
30
+ expect(isLogicRule(testLogicRule)).toBeTruthy()
31
+ })
32
+
33
+ test('isRuleFieldMap finds rule field map.', async () => {
34
+ expect(
35
+ isRuleFieldMap({
36
+ foo: rule()(() => true),
37
+ bar: and(),
38
+ }),
39
+ ).toBeTruthy()
40
+
41
+ expect(
42
+ isRuleFieldMap({
43
+ foo: rule()(() => true),
44
+ bar: false,
45
+ }),
46
+ ).toBeFalsy()
47
+ })
48
+ })
49
+
50
+ describe('helper functions', () => {
51
+ test('withDefault returns correct value', async () => {
52
+ expect(withDefault('pass')(undefined)).toBe('pass')
53
+ expect(withDefault('fail')('pass')).toBe('pass')
54
+ })
55
+ })
@@ -0,0 +1,139 @@
1
+ import { applyMiddleware } from 'graphql-middleware'
2
+ import { makeExecutableSchema } from '@graphql-tools/schema'
3
+ import { validateRuleTree } from '../src/validation'
4
+ import { shield, rule, allow } from '../src/'
5
+ import { and } from '../src/constructors'
6
+
7
+ describe('correctly helps developer', () => {
8
+ test('Finds a type missing in schema and warns developer.', async () => {
9
+ /* Schema */
10
+
11
+ const typeDefs = `
12
+ type Query {
13
+ a: String!
14
+ }
15
+ `
16
+
17
+ const schema = makeExecutableSchema({
18
+ typeDefs,
19
+ resolvers: {},
20
+ })
21
+
22
+ // Permissions
23
+
24
+ const permissions = shield({
25
+ Query: allow,
26
+ Fail1: allow,
27
+ Fail2: allow,
28
+ })
29
+
30
+ expect(() => {
31
+ applyMiddleware(schema, permissions)
32
+ }).toThrow(
33
+ `It seems like you have applied rules to Fail1, Fail2 types but Shield cannot find them in your schema.`,
34
+ )
35
+ })
36
+
37
+ test('Finds the fields missing in schema and warns developer.', async () => {
38
+ // Schema
39
+ const typeDefs = `
40
+ type Query {
41
+ a: String!
42
+ }
43
+ `
44
+
45
+ const schema = makeExecutableSchema({
46
+ typeDefs,
47
+ resolvers: {},
48
+ })
49
+
50
+ // Permissions
51
+
52
+ const permissions = shield({
53
+ Query: {
54
+ a: allow,
55
+ b: allow,
56
+ c: allow,
57
+ },
58
+ })
59
+
60
+ expect(() => {
61
+ applyMiddleware(schema, permissions)
62
+ }).toThrow(
63
+ 'It seems like you have applied rules to Query.b, Query.c fields but Shield cannot find them in your schema.',
64
+ )
65
+ })
66
+ })
67
+
68
+ describe('rule tree validation', () => {
69
+ test('validates rules correctly', async () => {
70
+ /* Rules */
71
+
72
+ const rule1 = rule('one')(() => true)
73
+ const rule12 = rule('one')(() => true)
74
+ const rule2 = rule('two')(() => true)
75
+ const rule22 = rule('two')(() => true)
76
+ const rule3 = rule()(() => true)
77
+ const rule4 = rule()(() => true)
78
+
79
+ const correctRuleTree = {
80
+ Query: {
81
+ foo: rule1,
82
+ bar: rule2,
83
+ },
84
+ Mutation: rule3,
85
+ Bar: rule4,
86
+ }
87
+
88
+ const incorrectRuleTree = {
89
+ Query: {
90
+ foo: rule1,
91
+ bar: rule12,
92
+ qux: rule2,
93
+ foobarqux: rule22,
94
+ quxbarfoo: and(rule1, rule12),
95
+ },
96
+ Mutation: rule3,
97
+ Bar: rule4,
98
+ }
99
+
100
+ /* Tests */
101
+
102
+ expect(validateRuleTree(correctRuleTree)).toEqual({ status: 'ok' })
103
+ expect(validateRuleTree(incorrectRuleTree)).toEqual({
104
+ status: 'err',
105
+ message: `There seem to be multiple definitions of these rules: one, two`,
106
+ })
107
+ })
108
+ })
109
+
110
+ describe('shield works as expected', () => {
111
+ test('throws an error on invalid schema', async () => {
112
+ /* Rules */
113
+
114
+ const rule1 = rule('one')(() => true)
115
+ const rule12 = rule('one')(() => true)
116
+ const rule2 = rule('two')(() => true)
117
+ const rule22 = rule('two')(() => true)
118
+ const rule3 = rule()(() => true)
119
+ const rule4 = rule()(() => true)
120
+
121
+ const incorrectRuleTree = {
122
+ Query: {
123
+ foo: rule1,
124
+ bar: rule12,
125
+ qux: rule2,
126
+ foobarqux: rule22,
127
+ quxbarfoo: and(rule1, rule12),
128
+ },
129
+ Mutation: rule3,
130
+ Bar: rule4,
131
+ }
132
+
133
+ /* Tests */
134
+
135
+ expect(() => {
136
+ shield(incorrectRuleTree)
137
+ }).toThrow(`There seem to be multiple definitions of these rules: one, two`)
138
+ })
139
+ })