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,618 @@
1
+ import { graphql } from 'graphql'
2
+ import { applyMiddleware } from 'graphql-middleware'
3
+ import { makeExecutableSchema } from '@graphql-tools/schema'
4
+ import { shield, rule, allow } from '../src/index'
5
+
6
+ describe('fallbackError correctly handles errors', () => {
7
+ test('error in resolver returns fallback error.', async () => {
8
+ /* Schema */
9
+
10
+ const typeDefs = `
11
+ type Query {
12
+ test: String!
13
+ }
14
+ `
15
+ const resolvers = {
16
+ Query: {
17
+ test: async () => {
18
+ throw new Error()
19
+ },
20
+ },
21
+ }
22
+
23
+ const schema = makeExecutableSchema({
24
+ typeDefs,
25
+ resolvers,
26
+ })
27
+
28
+ /* Permissions */
29
+
30
+ const fallbackError = new Error('fallback')
31
+
32
+ const permissions = shield(
33
+ {
34
+ Query: allow,
35
+ },
36
+ {
37
+ fallbackError,
38
+ },
39
+ )
40
+
41
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
42
+
43
+ /* Execution */
44
+
45
+ const query = `
46
+ query {
47
+ test
48
+ }
49
+ `
50
+ const res = await graphql({
51
+ schema: schemaWithPermissions,
52
+ source: query,
53
+ })
54
+
55
+ /* Tests */
56
+
57
+ expect(res.data).toBeNull()
58
+ expect(res.errors?.[0]?.message).toBe(fallbackError.message)
59
+ })
60
+
61
+ test('error in rule returns fallback error.', async () => {
62
+ /* Schema */
63
+
64
+ const typeDefs = `
65
+ type Query {
66
+ test: String!
67
+ }
68
+ `
69
+
70
+ const schema = makeExecutableSchema({ typeDefs, resolvers: {} })
71
+
72
+ /* Permissions */
73
+
74
+ const fallbackError = new Error('fallback')
75
+
76
+ const allow = rule()(() => {
77
+ throw new Error()
78
+ })
79
+
80
+ const permissions = shield(
81
+ {
82
+ Query: allow,
83
+ },
84
+ {
85
+ fallbackError,
86
+ },
87
+ )
88
+
89
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
90
+
91
+ /* Execution */
92
+
93
+ const query = `
94
+ query {
95
+ test
96
+ }
97
+ `
98
+ const res = await graphql({
99
+ schema: schemaWithPermissions,
100
+ source: query,
101
+ })
102
+
103
+ /* Tests */
104
+
105
+ expect(res.data).toBeNull()
106
+ expect(res.errors?.[0]?.message).toBe(fallbackError.message)
107
+ })
108
+
109
+ test('correctly converts string fallbackError to error fallbackError', async () => {
110
+ /* Schema */
111
+
112
+ const typeDefs = `
113
+ type Query {
114
+ test: String!
115
+ }
116
+ `
117
+
118
+ const resolvers = {
119
+ Query: {
120
+ test: () => {
121
+ throw new Error()
122
+ },
123
+ },
124
+ }
125
+
126
+ const schema = makeExecutableSchema({ typeDefs, resolvers })
127
+
128
+ /* Permissions */
129
+
130
+ const fallbackMessage = Math.random().toString()
131
+ const permissions = shield(
132
+ {
133
+ Query: allow,
134
+ },
135
+ {
136
+ fallbackError: fallbackMessage,
137
+ },
138
+ )
139
+
140
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
141
+
142
+ /* Execution */
143
+ const query = `
144
+ query {
145
+ test
146
+ }
147
+ `
148
+ const res = await graphql({
149
+ schema: schemaWithPermissions,
150
+ source: query,
151
+ })
152
+
153
+ /* Tests */
154
+
155
+ expect(res.data).toBeNull()
156
+ expect(res.errors?.[0]?.message).toBe(fallbackMessage)
157
+ })
158
+
159
+ test('error in rule can be mapped.', async () => {
160
+ /* Schema */
161
+
162
+ const typeDefs = `
163
+ type Query {
164
+ test: String!
165
+ }
166
+ `
167
+
168
+ const schema = makeExecutableSchema({ typeDefs, resolvers: {} })
169
+
170
+ /* Permissions */
171
+
172
+ const fallbackError = () => new Error('fallback')
173
+
174
+ const allow = rule()(() => {
175
+ throw new Error()
176
+ })
177
+
178
+ const permissions = shield(
179
+ {
180
+ Query: allow,
181
+ },
182
+ {
183
+ fallbackError,
184
+ },
185
+ )
186
+
187
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
188
+
189
+ /* Execution */
190
+
191
+ const query = `
192
+ query {
193
+ test
194
+ }
195
+ `
196
+ const res = await graphql({
197
+ schema: schemaWithPermissions,
198
+ source: query,
199
+ })
200
+
201
+ /* Tests */
202
+
203
+ expect(res.data).toBeNull()
204
+ expect(res.errors?.[0]?.message).toBe(fallbackError().message)
205
+ })
206
+
207
+ test('error in resolver can be mapped.', async () => {
208
+ /* Schema */
209
+
210
+ const typeDefs = `
211
+ type Query {
212
+ test: String!
213
+ }
214
+ `
215
+ const resolvers = {
216
+ Query: {
217
+ test: async () => {
218
+ throw new Error()
219
+ },
220
+ },
221
+ }
222
+
223
+ const schema = makeExecutableSchema({
224
+ typeDefs,
225
+ resolvers,
226
+ })
227
+
228
+ /* Permissions */
229
+
230
+ const fallbackError = () => new Error('fallback')
231
+
232
+ const permissions = shield(
233
+ {
234
+ Query: allow,
235
+ },
236
+ {
237
+ fallbackError,
238
+ },
239
+ )
240
+
241
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
242
+
243
+ /* Execution */
244
+
245
+ const query = `
246
+ query {
247
+ test
248
+ }
249
+ `
250
+ const res = await graphql({
251
+ schema: schemaWithPermissions,
252
+ source: query,
253
+ })
254
+
255
+ /* Tests */
256
+
257
+ expect(res.data).toBeNull()
258
+ expect(res.errors?.[0]?.message).toBe(fallbackError().message)
259
+ })
260
+ })
261
+
262
+ describe('external errors can be controled correctly', () => {
263
+ test('error in resolver with allowExternalErrors returns external error.', async () => {
264
+ /* Schema */
265
+
266
+ const typeDefs = `
267
+ type Query {
268
+ test: String!
269
+ }
270
+ `
271
+
272
+ const resolvers = {
273
+ Query: {
274
+ test: () => {
275
+ throw new Error('external')
276
+ },
277
+ },
278
+ }
279
+
280
+ const schema = makeExecutableSchema({ typeDefs, resolvers })
281
+
282
+ /* Permissions */
283
+
284
+ const permissions = shield(
285
+ {
286
+ Query: allow,
287
+ },
288
+ {
289
+ allowExternalErrors: true,
290
+ },
291
+ )
292
+
293
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
294
+
295
+ /* Execution */
296
+
297
+ const query = `
298
+ query {
299
+ test
300
+ }
301
+ `
302
+ const res = await graphql({
303
+ schema: schemaWithPermissions,
304
+ source: query,
305
+ })
306
+
307
+ /* Tests */
308
+
309
+ expect(res.data).toBeNull()
310
+ expect(res.errors?.[0]?.message).toBe('external')
311
+ })
312
+
313
+ test('error in rule with allowExternalErrors returns fallback.', async () => {
314
+ /* Schema */
315
+ const typeDefs = `
316
+ type Query {
317
+ test: String!
318
+ }
319
+ `
320
+
321
+ const schema = makeExecutableSchema({ typeDefs, resolvers: {} })
322
+
323
+ /* Permissions */
324
+
325
+ const allow = rule()(() => {
326
+ throw new Error('external')
327
+ })
328
+
329
+ const permissions = shield(
330
+ {
331
+ Query: allow,
332
+ },
333
+ {
334
+ allowExternalErrors: true,
335
+ },
336
+ )
337
+
338
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
339
+
340
+ /* Execution */
341
+
342
+ const query = `
343
+ query {
344
+ test
345
+ }
346
+ `
347
+ const res = await graphql({
348
+ schema: schemaWithPermissions,
349
+ source: query,
350
+ })
351
+
352
+ /* Tests */
353
+
354
+ expect(res.data).toBeNull()
355
+ expect(res.errors?.[0]?.message).toBe('Not Authorised!')
356
+ })
357
+ })
358
+
359
+ describe('debug mode works as expected', () => {
360
+ test('returns original error in debug mode when rule error occurs', async () => {
361
+ /* Schema */
362
+ const typeDefs = `
363
+ type Query {
364
+ test: String!
365
+ }
366
+ `
367
+
368
+ const schema = makeExecutableSchema({ typeDefs, resolvers: {} })
369
+
370
+ /* Permissions */
371
+
372
+ const allow = rule()(() => {
373
+ throw new Error('debug')
374
+ })
375
+
376
+ const permissions = shield(
377
+ {
378
+ Query: allow,
379
+ },
380
+ {
381
+ debug: true,
382
+ },
383
+ )
384
+
385
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
386
+
387
+ /* Execution */
388
+
389
+ const query = `
390
+ query {
391
+ test
392
+ }
393
+ `
394
+ const res = await graphql({
395
+ schema: schemaWithPermissions,
396
+ source: query,
397
+ })
398
+
399
+ /* Tests */
400
+
401
+ expect(res.data).toBeNull()
402
+ expect(res.errors?.[0]?.message).toBe('debug')
403
+ })
404
+
405
+ test('returns original error in debug mode when resolver error occurs.', async () => {
406
+ /* Schema */
407
+
408
+ const typeDefs = `
409
+ type Query {
410
+ test: String!
411
+ }
412
+ `
413
+
414
+ const resolvers = {
415
+ Query: {
416
+ test: () => {
417
+ throw new Error('debug')
418
+ },
419
+ },
420
+ }
421
+
422
+ const schema = makeExecutableSchema({ typeDefs, resolvers })
423
+
424
+ /* Permissions */
425
+
426
+ const permissions = shield(
427
+ {
428
+ Query: allow,
429
+ },
430
+ {
431
+ debug: true,
432
+ },
433
+ )
434
+
435
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
436
+
437
+ /* Execution */
438
+
439
+ const query = `
440
+ query {
441
+ test
442
+ }
443
+ `
444
+ const res = await graphql({
445
+ schema: schemaWithPermissions,
446
+ source: query,
447
+ })
448
+
449
+ /* Tests */
450
+
451
+ expect(res.data).toBeNull()
452
+ expect(res.errors?.[0]?.message).toBe('debug')
453
+ })
454
+ })
455
+
456
+ describe('custom errors work as expected', () => {
457
+ test('custom error in rule returns custom error.', async () => {
458
+ /* Schema */
459
+
460
+ const typeDefs = `
461
+ type Query {
462
+ test: String!
463
+ }
464
+ `
465
+
466
+ const schema = makeExecutableSchema({ typeDefs, resolvers: {} })
467
+
468
+ /* Permissions */
469
+
470
+ const error = new Error(`${Math.random()}`)
471
+ const permissions = shield({
472
+ Query: rule()(() => {
473
+ return error
474
+ }),
475
+ })
476
+
477
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
478
+
479
+ /* Execution */
480
+ const query = `
481
+ query {
482
+ test
483
+ }
484
+ `
485
+ const res = await graphql({
486
+ schema: schemaWithPermissions,
487
+ source: query,
488
+ })
489
+
490
+ /* Tests */
491
+
492
+ expect(res.data).toBeNull()
493
+ expect(res.errors?.[0]?.message).toBe(error.message)
494
+ })
495
+
496
+ test('custom error message in rule returns custom error.', async () => {
497
+ /* Schema */
498
+
499
+ const typeDefs = `
500
+ type Query {
501
+ test: String!
502
+ }
503
+ `
504
+
505
+ const schema = makeExecutableSchema({ typeDefs, resolvers: {} })
506
+
507
+ /* Permissions */
508
+
509
+ const error = `${Math.random()}`
510
+
511
+ const permissions = shield({
512
+ Query: rule()(() => {
513
+ return error
514
+ }),
515
+ })
516
+
517
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
518
+
519
+ /* Execution */
520
+ const query = `
521
+ query {
522
+ test
523
+ }
524
+ `
525
+ const res = await graphql({
526
+ schema: schemaWithPermissions,
527
+ source: query,
528
+ })
529
+
530
+ /* Tests */
531
+
532
+ expect(res.data).toBeNull()
533
+ expect(res.errors?.[0]?.message).toBe(error)
534
+ })
535
+ })
536
+
537
+ describe('fallbackRule correctly applies fallback rule', () => {
538
+ test('correctly applies fallback rule on undefined fields', async () => {
539
+ /* Schema */
540
+
541
+ const typeDefs = `
542
+ type Query {
543
+ a: String
544
+ b: String
545
+ type: Type
546
+ }
547
+
548
+ type Type {
549
+ a: String
550
+ b: String
551
+ }
552
+ `
553
+
554
+ const resolvers = {
555
+ Query: {
556
+ a: () => 'a',
557
+ b: () => 'b',
558
+ type: () => ({}),
559
+ },
560
+ Type: {
561
+ a: () => 'a',
562
+ b: () => 'b',
563
+ },
564
+ }
565
+
566
+ const schema = makeExecutableSchema({ typeDefs, resolvers })
567
+
568
+ /* Permissions */
569
+
570
+ const fallbackRuleMock = jest.fn().mockResolvedValue(true)
571
+ const fallbackRule = rule({ cache: 'no_cache' })(fallbackRuleMock)
572
+
573
+ const permissions = shield(
574
+ {
575
+ Query: {
576
+ a: allow,
577
+ type: allow,
578
+ },
579
+ },
580
+ {
581
+ fallbackRule: fallbackRule,
582
+ },
583
+ )
584
+
585
+ const schemaWithPermissions = applyMiddleware(schema, permissions)
586
+
587
+ /* Execution */
588
+
589
+ const query = `
590
+ query {
591
+ a
592
+ b
593
+ type {
594
+ a
595
+ b
596
+ }
597
+ }
598
+ `
599
+ const res = await graphql({
600
+ schema: schemaWithPermissions,
601
+ source: query,
602
+ })
603
+
604
+ /* Tests */
605
+
606
+ expect(res).toEqual({
607
+ data: {
608
+ a: 'a',
609
+ b: 'b',
610
+ type: {
611
+ a: 'a',
612
+ b: 'b',
613
+ },
614
+ },
615
+ })
616
+ expect(fallbackRuleMock).toBeCalledTimes(3)
617
+ })
618
+ })
@@ -0,0 +1,113 @@
1
+ import { applyMiddleware } from 'graphql-middleware'
2
+ import { makeExecutableSchema } from '@graphql-tools/schema'
3
+ import { shield, rule, and, not, or } from '../src/index'
4
+ import { allow } from '../src/constructors'
5
+
6
+ describe('Fragment extraction', () => {
7
+ test('Extracts fragment from rule correctly.', async () => {
8
+ const ruleWithFragment = rule({ fragment: 'pass' })(() => true)
9
+ expect(ruleWithFragment.extractFragment()).toBe('pass')
10
+ })
11
+
12
+ test('Extracts fragment from logic rule correctly.', async () => {
13
+ const ruleWithNoFragment = rule()(() => true)
14
+ const ruleWithFragmentA = rule({ fragment: 'pass-A' })(() => true)
15
+ const ruleWithFragmentB = rule({ fragment: 'pass-B' })(() => true)
16
+ const ruleWithFragmentC = rule({ fragment: 'pass-C' })(() => true)
17
+
18
+ const logicRuleAND = and(
19
+ ruleWithNoFragment,
20
+ ruleWithFragmentA,
21
+ ruleWithFragmentB,
22
+ )
23
+ const logicRuleNOT = not(logicRuleAND)
24
+ const logicRuleOR = or(ruleWithFragmentB, ruleWithFragmentC, logicRuleNOT)
25
+
26
+ expect(logicRuleOR.extractFragments()).toEqual([
27
+ 'pass-B',
28
+ 'pass-C',
29
+ 'pass-A',
30
+ 'pass-B',
31
+ ])
32
+ })
33
+ })
34
+
35
+ describe('Fragment application', () => {
36
+ test('Applies rule-fragment correctly.', async () => {
37
+ /* Schema */
38
+ const typeDefs = `
39
+ type Query {
40
+ user: User
41
+ events: [Event!]
42
+ }
43
+
44
+ type User {
45
+ id: ID!
46
+ name: String!
47
+ }
48
+
49
+ type Event {
50
+ id: ID!
51
+ location: String!
52
+ published: Boolean
53
+ }
54
+ `
55
+
56
+ /* Permissions */
57
+
58
+ const isUserSelf = rule({
59
+ fragment: 'fragment UserId on User { id }',
60
+ })(async (parent, args, ctx, info) => {
61
+ return true
62
+ })
63
+
64
+ const isProfilePublic = rule({
65
+ fragment: 'fragment UserPublic on User { public }',
66
+ })(async (parent, args, ctx, info) => {
67
+ return true
68
+ })
69
+
70
+ const isEventPublished = rule({
71
+ fragment: '... on Event { published }',
72
+ })(async () => {
73
+ return true
74
+ })
75
+
76
+ const permissions = shield({
77
+ Query: {
78
+ user: allow,
79
+ events: allow,
80
+ },
81
+ User: or(isUserSelf, isProfilePublic),
82
+ Event: isEventPublished,
83
+ })
84
+
85
+ const { fragmentReplacements } = applyMiddleware(
86
+ makeExecutableSchema({ typeDefs, resolvers: {} }),
87
+ permissions,
88
+ )
89
+
90
+ expect(fragmentReplacements).toEqual([
91
+ {
92
+ field: 'id',
93
+ fragment: '... on User {\n public\n}',
94
+ },
95
+ {
96
+ field: 'name',
97
+ fragment: '... on User {\n id\n}',
98
+ },
99
+ {
100
+ field: 'name',
101
+ fragment: '... on User {\n public\n}',
102
+ },
103
+ {
104
+ field: 'id',
105
+ fragment: '... on Event {\n published\n}',
106
+ },
107
+ {
108
+ field: 'location',
109
+ fragment: '... on Event {\n published\n}',
110
+ },
111
+ ])
112
+ })
113
+ })