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.
- package/CHANGELOG.md +31 -0
- package/dist/cjs/constructors.js +134 -0
- package/dist/cjs/generator.js +205 -0
- package/dist/cjs/index.js +15 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/rules.js +402 -0
- package/dist/cjs/shield.js +52 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/utils.js +97 -0
- package/dist/cjs/validation.js +84 -0
- package/dist/esm/constructors.js +124 -0
- package/dist/esm/generator.js +201 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/rules.js +366 -0
- package/dist/esm/shield.js +45 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/utils.js +88 -0
- package/dist/esm/validation.js +79 -0
- package/dist/package.json +47 -0
- package/dist/typings/constructors.d.cts +91 -0
- package/dist/typings/constructors.d.ts +91 -0
- package/dist/typings/generator.d.cts +11 -0
- package/dist/typings/generator.d.ts +11 -0
- package/dist/typings/index.d.cts +3 -0
- package/dist/typings/index.d.ts +3 -0
- package/dist/typings/rules.d.cts +159 -0
- package/dist/typings/rules.d.ts +159 -0
- package/dist/typings/shield.d.cts +11 -0
- package/dist/typings/shield.d.ts +11 -0
- package/dist/typings/types.d.cts +64 -0
- package/dist/typings/types.d.ts +64 -0
- package/dist/typings/utils.d.cts +52 -0
- package/dist/typings/utils.d.ts +52 -0
- package/dist/typings/validation.d.cts +19 -0
- package/dist/typings/validation.d.ts +19 -0
- package/package.json +67 -0
- package/src/constructors.ts +157 -0
- package/src/generator.ts +294 -0
- package/src/index.ts +13 -0
- package/src/rules.ts +521 -0
- package/src/shield.ts +53 -0
- package/src/types.ts +94 -0
- package/src/utils.ts +101 -0
- package/src/validation.ts +90 -0
- package/tests/__snapshots__/input.test.ts.snap +7 -0
- package/tests/cache.test.ts +545 -0
- package/tests/constructors.test.ts +136 -0
- package/tests/fallback.test.ts +618 -0
- package/tests/fragments.test.ts +113 -0
- package/tests/generator.test.ts +356 -0
- package/tests/input.test.ts +63 -0
- package/tests/integration.test.ts +65 -0
- package/tests/logic.test.ts +530 -0
- package/tests/utils.test.ts +55 -0
- package/tests/validation.test.ts +139 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { makeExecutableSchema } from '@graphql-tools/schema'
|
|
2
|
+
import { applyMiddleware } from 'graphql-middleware'
|
|
3
|
+
import { shield, rule } from '../src'
|
|
4
|
+
import { graphql } from 'graphql'
|
|
5
|
+
|
|
6
|
+
describe('generates correct middleware', () => {
|
|
7
|
+
test('correctly applies schema rule to schema', async () => {
|
|
8
|
+
/* Schema */
|
|
9
|
+
|
|
10
|
+
const typeDefs = `
|
|
11
|
+
type Query {
|
|
12
|
+
a: String
|
|
13
|
+
type: Type
|
|
14
|
+
}
|
|
15
|
+
type Type {
|
|
16
|
+
a: String
|
|
17
|
+
}
|
|
18
|
+
`
|
|
19
|
+
|
|
20
|
+
const resolvers = {
|
|
21
|
+
Query: {
|
|
22
|
+
a: () => 'a',
|
|
23
|
+
type: () => ({}),
|
|
24
|
+
},
|
|
25
|
+
Type: {
|
|
26
|
+
a: () => 'a',
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const schema = makeExecutableSchema({ typeDefs, resolvers })
|
|
31
|
+
|
|
32
|
+
/* Permissions */
|
|
33
|
+
|
|
34
|
+
const allowMock = jest.fn().mockResolvedValue(true)
|
|
35
|
+
const permissions = shield(rule({ cache: 'no_cache' })(allowMock))
|
|
36
|
+
|
|
37
|
+
const schemaWithPermissions = applyMiddleware(schema, permissions)
|
|
38
|
+
|
|
39
|
+
/* Execution */
|
|
40
|
+
const query = `
|
|
41
|
+
query {
|
|
42
|
+
a
|
|
43
|
+
type {
|
|
44
|
+
a
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
`
|
|
48
|
+
|
|
49
|
+
const res = await graphql({
|
|
50
|
+
schema: schemaWithPermissions,
|
|
51
|
+
source: query,
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
/* Tests */
|
|
55
|
+
|
|
56
|
+
expect(res).toEqual({
|
|
57
|
+
data: {
|
|
58
|
+
a: 'a',
|
|
59
|
+
type: {
|
|
60
|
+
a: 'a',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
expect(allowMock).toBeCalledTimes(3)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('correctly applies type rule to type', async () => {
|
|
68
|
+
/* Schema */
|
|
69
|
+
|
|
70
|
+
const typeDefs = `
|
|
71
|
+
type Query {
|
|
72
|
+
a: String
|
|
73
|
+
type: Type
|
|
74
|
+
}
|
|
75
|
+
type Type {
|
|
76
|
+
a: String
|
|
77
|
+
}
|
|
78
|
+
`
|
|
79
|
+
|
|
80
|
+
const resolvers = {
|
|
81
|
+
Query: {
|
|
82
|
+
a: () => 'a',
|
|
83
|
+
type: () => ({}),
|
|
84
|
+
},
|
|
85
|
+
Type: {
|
|
86
|
+
a: () => 'a',
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const schema = makeExecutableSchema({ typeDefs, resolvers })
|
|
91
|
+
|
|
92
|
+
/* Permissions */
|
|
93
|
+
|
|
94
|
+
const allowMock = jest.fn().mockResolvedValue(true)
|
|
95
|
+
const permissions = shield({
|
|
96
|
+
Query: rule({ cache: 'no_cache' })(allowMock),
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const schemaWithPermissions = applyMiddleware(schema, permissions)
|
|
100
|
+
|
|
101
|
+
/* Execution */
|
|
102
|
+
const query = `
|
|
103
|
+
query {
|
|
104
|
+
a
|
|
105
|
+
type {
|
|
106
|
+
a
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
`
|
|
110
|
+
|
|
111
|
+
const res = await graphql({
|
|
112
|
+
schema: schemaWithPermissions,
|
|
113
|
+
source: query,
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
/* Tests */
|
|
117
|
+
|
|
118
|
+
expect(res).toEqual({
|
|
119
|
+
data: {
|
|
120
|
+
a: 'a',
|
|
121
|
+
type: {
|
|
122
|
+
a: 'a',
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
})
|
|
126
|
+
expect(allowMock).toBeCalledTimes(2)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('correctly applies field rule to field', async () => {
|
|
130
|
+
/* Schema */
|
|
131
|
+
|
|
132
|
+
const typeDefs = `
|
|
133
|
+
type Query {
|
|
134
|
+
a: String
|
|
135
|
+
type: Type
|
|
136
|
+
}
|
|
137
|
+
type Type {
|
|
138
|
+
a: String
|
|
139
|
+
}
|
|
140
|
+
`
|
|
141
|
+
|
|
142
|
+
const resolvers = {
|
|
143
|
+
Query: {
|
|
144
|
+
a: () => 'a',
|
|
145
|
+
type: () => ({}),
|
|
146
|
+
},
|
|
147
|
+
Type: {
|
|
148
|
+
a: () => 'a',
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const schema = makeExecutableSchema({ typeDefs, resolvers })
|
|
153
|
+
|
|
154
|
+
/* Permissions */
|
|
155
|
+
|
|
156
|
+
const allowMock = jest.fn().mockResolvedValue(true)
|
|
157
|
+
const permissions = shield({
|
|
158
|
+
Query: { a: rule({ cache: 'no_cache' })(allowMock) },
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
const schemaWithPermissions = applyMiddleware(schema, permissions)
|
|
162
|
+
|
|
163
|
+
/* Execution */
|
|
164
|
+
const query = `
|
|
165
|
+
query {
|
|
166
|
+
a
|
|
167
|
+
type {
|
|
168
|
+
a
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
`
|
|
172
|
+
|
|
173
|
+
const res = await graphql({
|
|
174
|
+
schema: schemaWithPermissions,
|
|
175
|
+
source: query,
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
/* Tests */
|
|
179
|
+
|
|
180
|
+
expect(res).toEqual({
|
|
181
|
+
data: {
|
|
182
|
+
a: 'a',
|
|
183
|
+
type: {
|
|
184
|
+
a: 'a',
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
})
|
|
188
|
+
expect(allowMock).toBeCalledTimes(1)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test('correctly applies wildcard rule to type', async () => {
|
|
192
|
+
/* Schema */
|
|
193
|
+
|
|
194
|
+
const typeDefs = `
|
|
195
|
+
type Query {
|
|
196
|
+
a: String
|
|
197
|
+
b: String
|
|
198
|
+
type: Type
|
|
199
|
+
}
|
|
200
|
+
type Type {
|
|
201
|
+
field1: String
|
|
202
|
+
field2: String
|
|
203
|
+
}
|
|
204
|
+
`
|
|
205
|
+
|
|
206
|
+
const resolvers = {
|
|
207
|
+
Query: {
|
|
208
|
+
a: () => 'a',
|
|
209
|
+
b: () => 'b',
|
|
210
|
+
type: () => ({
|
|
211
|
+
field1: 'field1',
|
|
212
|
+
field2: 'field2',
|
|
213
|
+
}),
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const schema = makeExecutableSchema({ typeDefs, resolvers })
|
|
218
|
+
|
|
219
|
+
/* Permissions */
|
|
220
|
+
|
|
221
|
+
const allowMock = jest.fn().mockResolvedValue(true)
|
|
222
|
+
const defaultQueryMock = jest.fn().mockResolvedValue(true)
|
|
223
|
+
const defaultTypeMock = jest.fn().mockResolvedValue(true)
|
|
224
|
+
|
|
225
|
+
const permissions = shield({
|
|
226
|
+
Query: {
|
|
227
|
+
a: rule({ cache: 'no_cache' })(allowMock),
|
|
228
|
+
type: rule({ cache: 'no_cache' })(jest.fn().mockResolvedValue(true)),
|
|
229
|
+
'*': rule({ cache: 'no_cache' })(defaultQueryMock),
|
|
230
|
+
},
|
|
231
|
+
Type: {
|
|
232
|
+
'*': rule({ cache: 'no_cache' })(defaultTypeMock),
|
|
233
|
+
},
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
const schemaWithPermissions = applyMiddleware(schema, permissions)
|
|
237
|
+
|
|
238
|
+
/* Execution */
|
|
239
|
+
const query = `
|
|
240
|
+
query {
|
|
241
|
+
a
|
|
242
|
+
b
|
|
243
|
+
type {
|
|
244
|
+
field1
|
|
245
|
+
field2
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
`
|
|
249
|
+
|
|
250
|
+
const res = await graphql({
|
|
251
|
+
schema: schemaWithPermissions,
|
|
252
|
+
source: query,
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
/* Tests */
|
|
256
|
+
|
|
257
|
+
expect(res).toEqual({
|
|
258
|
+
data: {
|
|
259
|
+
a: 'a',
|
|
260
|
+
b: 'b',
|
|
261
|
+
type: {
|
|
262
|
+
field1: 'field1',
|
|
263
|
+
field2: 'field2',
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
})
|
|
267
|
+
expect(allowMock).toBeCalledTimes(1)
|
|
268
|
+
expect(defaultQueryMock).toBeCalledTimes(1)
|
|
269
|
+
expect(defaultTypeMock).toBeCalledTimes(2)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
test('correctly allows multiple uses of the same wildcard rule', async () => {
|
|
273
|
+
/* Schema */
|
|
274
|
+
|
|
275
|
+
const typeDefs = `
|
|
276
|
+
type Query {
|
|
277
|
+
a: String
|
|
278
|
+
b: String
|
|
279
|
+
type: Type
|
|
280
|
+
}
|
|
281
|
+
type Type {
|
|
282
|
+
field1: String
|
|
283
|
+
field2: String
|
|
284
|
+
}
|
|
285
|
+
`
|
|
286
|
+
|
|
287
|
+
const resolvers = {
|
|
288
|
+
Query: {
|
|
289
|
+
a: () => 'a',
|
|
290
|
+
b: () => 'b',
|
|
291
|
+
type: () => ({
|
|
292
|
+
field1: 'field1',
|
|
293
|
+
field2: 'field2',
|
|
294
|
+
}),
|
|
295
|
+
},
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const schema = makeExecutableSchema({ typeDefs, resolvers })
|
|
299
|
+
|
|
300
|
+
/* Permissions */
|
|
301
|
+
|
|
302
|
+
const allowMock = jest.fn().mockResolvedValue(true)
|
|
303
|
+
const defaultQueryMock = jest.fn().mockResolvedValue(true)
|
|
304
|
+
const defaultTypeMock = jest.fn().mockResolvedValue(true)
|
|
305
|
+
|
|
306
|
+
const permissions = shield({
|
|
307
|
+
Query: {
|
|
308
|
+
a: rule({ cache: 'no_cache' })(allowMock),
|
|
309
|
+
type: rule({ cache: 'no_cache' })(jest.fn().mockResolvedValue(true)),
|
|
310
|
+
'*': rule({ cache: 'no_cache' })(defaultQueryMock),
|
|
311
|
+
},
|
|
312
|
+
Type: {
|
|
313
|
+
'*': rule({ cache: 'no_cache' })(defaultTypeMock),
|
|
314
|
+
},
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
/* First usage */
|
|
318
|
+
applyMiddleware(schema, permissions)
|
|
319
|
+
|
|
320
|
+
/* Second usage */
|
|
321
|
+
const schemaWithPermissions = applyMiddleware(schema, permissions)
|
|
322
|
+
|
|
323
|
+
/* Execution */
|
|
324
|
+
const query = `
|
|
325
|
+
query {
|
|
326
|
+
a
|
|
327
|
+
b
|
|
328
|
+
type {
|
|
329
|
+
field1
|
|
330
|
+
field2
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
`
|
|
334
|
+
|
|
335
|
+
const res = await graphql({
|
|
336
|
+
schema: schemaWithPermissions,
|
|
337
|
+
source: query,
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
/* Tests */
|
|
341
|
+
|
|
342
|
+
expect(res).toEqual({
|
|
343
|
+
data: {
|
|
344
|
+
a: 'a',
|
|
345
|
+
b: 'b',
|
|
346
|
+
type: {
|
|
347
|
+
field1: 'field1',
|
|
348
|
+
field2: 'field2',
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
})
|
|
352
|
+
expect(allowMock).toBeCalledTimes(1)
|
|
353
|
+
expect(defaultQueryMock).toBeCalledTimes(1)
|
|
354
|
+
expect(defaultTypeMock).toBeCalledTimes(2)
|
|
355
|
+
})
|
|
356
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { graphql } from 'graphql'
|
|
2
|
+
import { applyMiddleware } from 'graphql-middleware'
|
|
3
|
+
import { makeExecutableSchema } from '@graphql-tools/schema'
|
|
4
|
+
import { shield, inputRule } from '../src'
|
|
5
|
+
|
|
6
|
+
describe('input rule', () => {
|
|
7
|
+
test('schema validation works as expected', async () => {
|
|
8
|
+
const typeDefs = `
|
|
9
|
+
type Query {
|
|
10
|
+
hello: String!
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type Mutation {
|
|
14
|
+
login(email: String!): String
|
|
15
|
+
}
|
|
16
|
+
`
|
|
17
|
+
|
|
18
|
+
const resolvers = {
|
|
19
|
+
Query: {
|
|
20
|
+
hello: () => 'world',
|
|
21
|
+
},
|
|
22
|
+
Mutation: {
|
|
23
|
+
login: () => 'pass',
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const schema = makeExecutableSchema({ typeDefs, resolvers })
|
|
28
|
+
|
|
29
|
+
// Permissions
|
|
30
|
+
const permissions = shield({
|
|
31
|
+
Mutation: {
|
|
32
|
+
login: inputRule()((yup) =>
|
|
33
|
+
yup.object({
|
|
34
|
+
email: yup.string().email('It has to be an email!').required(),
|
|
35
|
+
}),
|
|
36
|
+
),
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const schemaWithPermissions = applyMiddleware(schema, permissions)
|
|
41
|
+
|
|
42
|
+
/* Execution */
|
|
43
|
+
|
|
44
|
+
const query = `
|
|
45
|
+
mutation {
|
|
46
|
+
success: login(email: "shield@graphql.com")
|
|
47
|
+
failure: login(email: "notemail")
|
|
48
|
+
}
|
|
49
|
+
`
|
|
50
|
+
const res = await graphql({
|
|
51
|
+
schema: schemaWithPermissions,
|
|
52
|
+
source: query,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
/* Tests */
|
|
56
|
+
|
|
57
|
+
expect(res.data).toEqual({
|
|
58
|
+
success: 'pass',
|
|
59
|
+
failure: null,
|
|
60
|
+
})
|
|
61
|
+
expect(res.errors).toMatchSnapshot()
|
|
62
|
+
})
|
|
63
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { makeExecutableSchema } from '@graphql-tools/schema'
|
|
2
|
+
import { gql, ApolloServer } from 'apollo-server'
|
|
3
|
+
import fetch from 'node-fetch'
|
|
4
|
+
import { applyMiddleware } from 'graphql-middleware'
|
|
5
|
+
|
|
6
|
+
import { shield, allow, deny } from '../src'
|
|
7
|
+
|
|
8
|
+
describe('integration tests', () => {
|
|
9
|
+
test('works with ApolloServer', async () => {
|
|
10
|
+
/* Schema */
|
|
11
|
+
|
|
12
|
+
const typeDefs = gql`
|
|
13
|
+
type Query {
|
|
14
|
+
allow: String
|
|
15
|
+
deny: String
|
|
16
|
+
}
|
|
17
|
+
`
|
|
18
|
+
|
|
19
|
+
const resolvers = {
|
|
20
|
+
Query: {
|
|
21
|
+
allow: () => 'allow',
|
|
22
|
+
deny: () => 'deny',
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* Permissions */
|
|
27
|
+
|
|
28
|
+
const permissions = shield({
|
|
29
|
+
Query: {
|
|
30
|
+
allow: allow,
|
|
31
|
+
deny: deny,
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const server = new ApolloServer({
|
|
36
|
+
schema: applyMiddleware(makeExecutableSchema({ typeDefs, resolvers }), permissions),
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
await server.listen({ port: 8008 })
|
|
40
|
+
const uri = `http://localhost:8008/`
|
|
41
|
+
|
|
42
|
+
/* Tests */
|
|
43
|
+
|
|
44
|
+
const query = `
|
|
45
|
+
query {
|
|
46
|
+
allow
|
|
47
|
+
deny
|
|
48
|
+
}
|
|
49
|
+
`
|
|
50
|
+
|
|
51
|
+
const res = await fetch(uri, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
body: JSON.stringify({ query }),
|
|
54
|
+
headers: { 'Content-Type': 'application/json' },
|
|
55
|
+
}).then((res) => res.json())
|
|
56
|
+
|
|
57
|
+
expect(res.data).toEqual({
|
|
58
|
+
allow: 'allow',
|
|
59
|
+
deny: null,
|
|
60
|
+
})
|
|
61
|
+
expect(res.errors.length).toBe(1)
|
|
62
|
+
|
|
63
|
+
await server.stop()
|
|
64
|
+
})
|
|
65
|
+
})
|