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
package/src/rules.ts
ADDED
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import * as Yup from 'yup'
|
|
2
|
+
import {
|
|
3
|
+
IRuleFunction,
|
|
4
|
+
IRule,
|
|
5
|
+
IRuleOptions,
|
|
6
|
+
ICache,
|
|
7
|
+
IFragment,
|
|
8
|
+
ICacheContructorOptions,
|
|
9
|
+
IRuleConstructorOptions,
|
|
10
|
+
ILogicRule,
|
|
11
|
+
ShieldRule,
|
|
12
|
+
IRuleResult,
|
|
13
|
+
IOptions,
|
|
14
|
+
IShieldContext,
|
|
15
|
+
} from './types.js'
|
|
16
|
+
import { isLogicRule } from './utils.js'
|
|
17
|
+
import { GraphQLResolveInfo } from 'graphql'
|
|
18
|
+
|
|
19
|
+
export class Rule implements IRule {
|
|
20
|
+
readonly name: string
|
|
21
|
+
|
|
22
|
+
private cache: ICache
|
|
23
|
+
private fragment?: IFragment
|
|
24
|
+
private func: IRuleFunction
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
name: string,
|
|
28
|
+
func: IRuleFunction,
|
|
29
|
+
constructorOptions: IRuleConstructorOptions,
|
|
30
|
+
) {
|
|
31
|
+
const options = this.normalizeOptions(constructorOptions)
|
|
32
|
+
|
|
33
|
+
this.name = name
|
|
34
|
+
this.func = func
|
|
35
|
+
this.cache = options.cache
|
|
36
|
+
this.fragment = options.fragment
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
* @param parent
|
|
42
|
+
* @param args
|
|
43
|
+
* @param ctx
|
|
44
|
+
* @param info
|
|
45
|
+
*
|
|
46
|
+
* Resolves rule and writes to cache its result.
|
|
47
|
+
*
|
|
48
|
+
*/
|
|
49
|
+
async resolve(
|
|
50
|
+
parent: object,
|
|
51
|
+
args: object,
|
|
52
|
+
ctx: IShieldContext,
|
|
53
|
+
info: GraphQLResolveInfo,
|
|
54
|
+
options: IOptions,
|
|
55
|
+
): Promise<IRuleResult> {
|
|
56
|
+
try {
|
|
57
|
+
/* Resolve */
|
|
58
|
+
const res = await this.executeRule(parent, args, ctx, info, options)
|
|
59
|
+
|
|
60
|
+
if (res instanceof Error) {
|
|
61
|
+
return res
|
|
62
|
+
} else if (typeof res === 'string') {
|
|
63
|
+
return new Error(res)
|
|
64
|
+
} else if (res === true) {
|
|
65
|
+
return true
|
|
66
|
+
} else {
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (options.debug) {
|
|
71
|
+
throw err
|
|
72
|
+
} else {
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
*
|
|
80
|
+
* @param rule
|
|
81
|
+
*
|
|
82
|
+
* Compares a given rule with the current one
|
|
83
|
+
* and checks whether their functions are equal.
|
|
84
|
+
*
|
|
85
|
+
*/
|
|
86
|
+
equals(rule: Rule): boolean {
|
|
87
|
+
return this.func === rule.func
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
*
|
|
92
|
+
* Extracts fragment from the rule.
|
|
93
|
+
*
|
|
94
|
+
*/
|
|
95
|
+
extractFragment(): IFragment | undefined {
|
|
96
|
+
return this.fragment
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
*
|
|
101
|
+
* @param options
|
|
102
|
+
*
|
|
103
|
+
* Sets default values for options.
|
|
104
|
+
*
|
|
105
|
+
*/
|
|
106
|
+
private normalizeOptions(options: IRuleConstructorOptions): IRuleOptions {
|
|
107
|
+
return {
|
|
108
|
+
cache:
|
|
109
|
+
options.cache !== undefined
|
|
110
|
+
? this.normalizeCacheOption(options.cache)
|
|
111
|
+
: 'no_cache',
|
|
112
|
+
fragment: options.fragment !== undefined ? options.fragment : undefined,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
*
|
|
118
|
+
* @param cache
|
|
119
|
+
*
|
|
120
|
+
* This ensures backward capability of shield.
|
|
121
|
+
*
|
|
122
|
+
*/
|
|
123
|
+
private normalizeCacheOption(cache: ICacheContructorOptions): ICache {
|
|
124
|
+
switch (cache) {
|
|
125
|
+
case true: {
|
|
126
|
+
return 'strict'
|
|
127
|
+
}
|
|
128
|
+
case false: {
|
|
129
|
+
return 'no_cache'
|
|
130
|
+
}
|
|
131
|
+
default: {
|
|
132
|
+
return cache
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Executes a rule and writes to cache if needed.
|
|
139
|
+
*
|
|
140
|
+
* @param parent
|
|
141
|
+
* @param args
|
|
142
|
+
* @param ctx
|
|
143
|
+
* @param info
|
|
144
|
+
*/
|
|
145
|
+
private executeRule(
|
|
146
|
+
parent: object,
|
|
147
|
+
args: object,
|
|
148
|
+
ctx: IShieldContext,
|
|
149
|
+
info: GraphQLResolveInfo,
|
|
150
|
+
options: IOptions,
|
|
151
|
+
): string | boolean | Error | Promise<IRuleResult> {
|
|
152
|
+
switch (typeof this.cache) {
|
|
153
|
+
case 'function': {
|
|
154
|
+
/* User defined cache function. */
|
|
155
|
+
const key = `${this.name}-${this.cache(parent, args, ctx, info)}`
|
|
156
|
+
return this.writeToCache(key)(parent, args, ctx, info)
|
|
157
|
+
}
|
|
158
|
+
case 'string': {
|
|
159
|
+
/* Standard cache option. */
|
|
160
|
+
switch (this.cache) {
|
|
161
|
+
case 'strict': {
|
|
162
|
+
const key = options.hashFunction({ parent, args })
|
|
163
|
+
|
|
164
|
+
return this.writeToCache(`${this.name}-${key}`)(
|
|
165
|
+
parent,
|
|
166
|
+
args,
|
|
167
|
+
ctx,
|
|
168
|
+
info,
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
case 'contextual': {
|
|
172
|
+
return this.writeToCache(this.name)(parent, args, ctx, info)
|
|
173
|
+
}
|
|
174
|
+
case 'no_cache': {
|
|
175
|
+
return this.func(parent, args, ctx, info)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/* istanbul ignore next */
|
|
180
|
+
default: {
|
|
181
|
+
throw new Error(`Unsupported cache format: ${typeof this.cache}`)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Writes or reads result from cache.
|
|
188
|
+
*
|
|
189
|
+
* @param key
|
|
190
|
+
*/
|
|
191
|
+
|
|
192
|
+
private writeToCache(
|
|
193
|
+
key: string,
|
|
194
|
+
): (
|
|
195
|
+
parent: object,
|
|
196
|
+
args: object,
|
|
197
|
+
ctx: IShieldContext,
|
|
198
|
+
info: GraphQLResolveInfo,
|
|
199
|
+
) => string | boolean | Error | Promise<IRuleResult> {
|
|
200
|
+
return (parent, args, ctx, info) => {
|
|
201
|
+
if (!ctx._shield.cache[key]) {
|
|
202
|
+
ctx._shield.cache[key] = this.func(parent, args, ctx, info)
|
|
203
|
+
}
|
|
204
|
+
return ctx._shield.cache[key]
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export class InputRule<T> extends Rule {
|
|
210
|
+
constructor(
|
|
211
|
+
name: string,
|
|
212
|
+
schema: (yup: typeof Yup, ctx: IShieldContext) => Yup.BaseSchema<T>,
|
|
213
|
+
options?: Parameters<Yup.BaseSchema<T>['validate']>[1],
|
|
214
|
+
) {
|
|
215
|
+
const validationFunction: IRuleFunction = (
|
|
216
|
+
parent: object,
|
|
217
|
+
args: object,
|
|
218
|
+
ctx: IShieldContext,
|
|
219
|
+
) =>
|
|
220
|
+
schema(Yup, ctx)
|
|
221
|
+
.validate(args, options)
|
|
222
|
+
.then(() => true)
|
|
223
|
+
.catch((err) => err)
|
|
224
|
+
|
|
225
|
+
super(name, validationFunction, { cache: 'strict', fragment: undefined })
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export class LogicRule implements ILogicRule {
|
|
230
|
+
private rules: ShieldRule[]
|
|
231
|
+
|
|
232
|
+
constructor(rules: ShieldRule[]) {
|
|
233
|
+
this.rules = rules
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* By default logic rule resolves to false.
|
|
238
|
+
*/
|
|
239
|
+
async resolve(
|
|
240
|
+
parent: object,
|
|
241
|
+
args: object,
|
|
242
|
+
ctx: IShieldContext,
|
|
243
|
+
info: GraphQLResolveInfo,
|
|
244
|
+
options: IOptions,
|
|
245
|
+
): Promise<IRuleResult> {
|
|
246
|
+
return false
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Evaluates all the rules.
|
|
251
|
+
*/
|
|
252
|
+
async evaluate(
|
|
253
|
+
parent: object,
|
|
254
|
+
args: object,
|
|
255
|
+
ctx: IShieldContext,
|
|
256
|
+
info: GraphQLResolveInfo,
|
|
257
|
+
options: IOptions,
|
|
258
|
+
): Promise<IRuleResult[]> {
|
|
259
|
+
const rules = this.getRules()
|
|
260
|
+
const tasks = rules.map((rule) =>
|
|
261
|
+
rule.resolve(parent, args, ctx, info, options),
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
return Promise.all(tasks)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Returns rules in a logic rule.
|
|
269
|
+
*/
|
|
270
|
+
getRules() {
|
|
271
|
+
return this.rules
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Extracts fragments from the defined rules.
|
|
276
|
+
*/
|
|
277
|
+
extractFragments(): IFragment[] {
|
|
278
|
+
const fragments = this.rules.reduce<IFragment[]>((fragments, rule) => {
|
|
279
|
+
if (isLogicRule(rule)) {
|
|
280
|
+
return fragments.concat(...rule.extractFragments())
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const fragment = rule.extractFragment()
|
|
284
|
+
if (fragment) return fragments.concat(fragment)
|
|
285
|
+
|
|
286
|
+
return fragments
|
|
287
|
+
}, [])
|
|
288
|
+
|
|
289
|
+
return fragments
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Extended Types
|
|
294
|
+
|
|
295
|
+
export class RuleOr extends LogicRule {
|
|
296
|
+
constructor(rules: ShieldRule[]) {
|
|
297
|
+
super(rules)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Makes sure that at least one of them has evaluated to true.
|
|
302
|
+
*/
|
|
303
|
+
async resolve(
|
|
304
|
+
parent: object,
|
|
305
|
+
args: object,
|
|
306
|
+
ctx: IShieldContext,
|
|
307
|
+
info: GraphQLResolveInfo,
|
|
308
|
+
options: IOptions,
|
|
309
|
+
): Promise<IRuleResult> {
|
|
310
|
+
const result = await this.evaluate(parent, args, ctx, info, options)
|
|
311
|
+
|
|
312
|
+
if (result.every((res) => res !== true)) {
|
|
313
|
+
const customError = result.find((res) => res instanceof Error)
|
|
314
|
+
return customError || false
|
|
315
|
+
} else {
|
|
316
|
+
return true
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export class RuleAnd extends LogicRule {
|
|
322
|
+
constructor(rules: ShieldRule[]) {
|
|
323
|
+
super(rules)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Makes sure that all of them have resolved to true.
|
|
328
|
+
*/
|
|
329
|
+
async resolve(
|
|
330
|
+
parent: object,
|
|
331
|
+
args: object,
|
|
332
|
+
ctx: IShieldContext,
|
|
333
|
+
info: GraphQLResolveInfo,
|
|
334
|
+
options: IOptions,
|
|
335
|
+
): Promise<IRuleResult> {
|
|
336
|
+
const result = await this.evaluate(parent, args, ctx, info, options)
|
|
337
|
+
|
|
338
|
+
if (result.some((res) => res !== true)) {
|
|
339
|
+
const customError = result.find((res) => res instanceof Error)
|
|
340
|
+
return customError || false
|
|
341
|
+
} else {
|
|
342
|
+
return true
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export class RuleChain extends LogicRule {
|
|
348
|
+
constructor(rules: ShieldRule[]) {
|
|
349
|
+
super(rules)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Makes sure that all of them have resolved to true.
|
|
354
|
+
*/
|
|
355
|
+
async resolve(
|
|
356
|
+
parent: object,
|
|
357
|
+
args: object,
|
|
358
|
+
ctx: IShieldContext,
|
|
359
|
+
info: GraphQLResolveInfo,
|
|
360
|
+
options: IOptions,
|
|
361
|
+
): Promise<IRuleResult> {
|
|
362
|
+
const result = await this.evaluate(parent, args, ctx, info, options)
|
|
363
|
+
|
|
364
|
+
if (result.some((res) => res !== true)) {
|
|
365
|
+
const customError = result.find((res) => res instanceof Error)
|
|
366
|
+
return customError || false
|
|
367
|
+
} else {
|
|
368
|
+
return true
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Evaluates all the rules.
|
|
374
|
+
*/
|
|
375
|
+
async evaluate(
|
|
376
|
+
parent: object,
|
|
377
|
+
args: object,
|
|
378
|
+
ctx: IShieldContext,
|
|
379
|
+
info: GraphQLResolveInfo,
|
|
380
|
+
options: IOptions,
|
|
381
|
+
): Promise<IRuleResult[]> {
|
|
382
|
+
const rules = this.getRules()
|
|
383
|
+
|
|
384
|
+
return iterate(rules)
|
|
385
|
+
|
|
386
|
+
async function iterate([rule, ...otherRules]: ShieldRule[]): Promise<
|
|
387
|
+
IRuleResult[]
|
|
388
|
+
> {
|
|
389
|
+
if (rule === undefined) return []
|
|
390
|
+
return rule.resolve(parent, args, ctx, info, options).then((res) => {
|
|
391
|
+
if (res !== true) {
|
|
392
|
+
return [res]
|
|
393
|
+
} else {
|
|
394
|
+
return iterate(otherRules).then((ress) => ress.concat(res))
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export class RuleRace extends LogicRule {
|
|
402
|
+
constructor(rules: ShieldRule[]) {
|
|
403
|
+
super(rules)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Makes sure that at least one of them resolved to true.
|
|
408
|
+
*/
|
|
409
|
+
async resolve(
|
|
410
|
+
parent: object,
|
|
411
|
+
args: object,
|
|
412
|
+
ctx: IShieldContext,
|
|
413
|
+
info: GraphQLResolveInfo,
|
|
414
|
+
options: IOptions,
|
|
415
|
+
): Promise<IRuleResult> {
|
|
416
|
+
const result = await this.evaluate(parent, args, ctx, info, options)
|
|
417
|
+
|
|
418
|
+
if (result.some((res) => res === true)) {
|
|
419
|
+
return true
|
|
420
|
+
} else {
|
|
421
|
+
const customError = result.find((res) => res instanceof Error)
|
|
422
|
+
return customError || false
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Evaluates all the rules.
|
|
428
|
+
*/
|
|
429
|
+
async evaluate(
|
|
430
|
+
parent: object,
|
|
431
|
+
args: object,
|
|
432
|
+
ctx: IShieldContext,
|
|
433
|
+
info: GraphQLResolveInfo,
|
|
434
|
+
options: IOptions,
|
|
435
|
+
): Promise<IRuleResult[]> {
|
|
436
|
+
const rules = this.getRules()
|
|
437
|
+
|
|
438
|
+
return iterate(rules)
|
|
439
|
+
|
|
440
|
+
async function iterate([rule, ...otherRules]: ShieldRule[]): Promise<
|
|
441
|
+
IRuleResult[]
|
|
442
|
+
> {
|
|
443
|
+
if (rule === undefined) return []
|
|
444
|
+
return rule.resolve(parent, args, ctx, info, options).then((res) => {
|
|
445
|
+
if (res === true) {
|
|
446
|
+
return [res]
|
|
447
|
+
} else {
|
|
448
|
+
return iterate(otherRules).then((ress) => ress.concat(res))
|
|
449
|
+
}
|
|
450
|
+
})
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export class RuleNot extends LogicRule {
|
|
456
|
+
error?: Error
|
|
457
|
+
|
|
458
|
+
constructor(rule: ShieldRule, error?: Error) {
|
|
459
|
+
super([rule])
|
|
460
|
+
this.error = error
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
*
|
|
465
|
+
* @param parent
|
|
466
|
+
* @param args
|
|
467
|
+
* @param ctx
|
|
468
|
+
* @param info
|
|
469
|
+
*
|
|
470
|
+
* Negates the result.
|
|
471
|
+
*
|
|
472
|
+
*/
|
|
473
|
+
async resolve(
|
|
474
|
+
parent: object,
|
|
475
|
+
args: object,
|
|
476
|
+
ctx: IShieldContext,
|
|
477
|
+
info: GraphQLResolveInfo,
|
|
478
|
+
options: IOptions,
|
|
479
|
+
): Promise<IRuleResult> {
|
|
480
|
+
const [res] = await this.evaluate(parent, args, ctx, info, options)
|
|
481
|
+
|
|
482
|
+
if (res instanceof Error) {
|
|
483
|
+
return true
|
|
484
|
+
} else if (res !== true) {
|
|
485
|
+
return true
|
|
486
|
+
} else {
|
|
487
|
+
if (this.error) return this.error
|
|
488
|
+
return false
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export class RuleTrue extends LogicRule {
|
|
494
|
+
constructor() {
|
|
495
|
+
super([])
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
*
|
|
500
|
+
* Always true.
|
|
501
|
+
*
|
|
502
|
+
*/
|
|
503
|
+
async resolve(): Promise<IRuleResult> {
|
|
504
|
+
return true
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
export class RuleFalse extends LogicRule {
|
|
509
|
+
constructor() {
|
|
510
|
+
super([])
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
*
|
|
515
|
+
* Always false.
|
|
516
|
+
*
|
|
517
|
+
*/
|
|
518
|
+
async resolve(): Promise<IRuleResult> {
|
|
519
|
+
return false
|
|
520
|
+
}
|
|
521
|
+
}
|
package/src/shield.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import hash from 'object-hash'
|
|
2
|
+
import { middleware, IMiddlewareGenerator } from 'graphql-middleware'
|
|
3
|
+
import { ValidationError, validateRuleTree } from './validation.js'
|
|
4
|
+
import { IRules, IOptions, IOptionsConstructor, ShieldRule, IHashFunction, IFallbackErrorType } from './types.js'
|
|
5
|
+
import { generateMiddlewareGeneratorFromRuleTree } from './generator.js'
|
|
6
|
+
import { allow } from './constructors.js'
|
|
7
|
+
import { withDefault } from './utils.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
* @param options
|
|
12
|
+
*
|
|
13
|
+
* Makes sure all of defined rules are in accord with the options
|
|
14
|
+
* shield can process.
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
function normalizeOptions(options: IOptionsConstructor): IOptions {
|
|
18
|
+
if (typeof options.fallbackError === 'string') {
|
|
19
|
+
options.fallbackError = new Error(options.fallbackError)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
debug: options.debug !== undefined ? options.debug : false,
|
|
24
|
+
allowExternalErrors: withDefault(false)(options.allowExternalErrors),
|
|
25
|
+
fallbackRule: withDefault<ShieldRule>(allow)(options.fallbackRule),
|
|
26
|
+
fallbackError: withDefault<IFallbackErrorType>(new Error('Not Authorised!'))(options.fallbackError),
|
|
27
|
+
hashFunction: withDefault<IHashFunction>(hash)(options.hashFunction),
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
*
|
|
33
|
+
* @param ruleTree
|
|
34
|
+
* @param options
|
|
35
|
+
*
|
|
36
|
+
* Validates rules and generates middleware from defined rule tree.
|
|
37
|
+
*
|
|
38
|
+
*/
|
|
39
|
+
export function shield<TSource = any, TContext = any, TArgs = any>(
|
|
40
|
+
ruleTree: IRules,
|
|
41
|
+
options: IOptionsConstructor = {},
|
|
42
|
+
): IMiddlewareGenerator<TSource, TContext, TArgs> {
|
|
43
|
+
const normalizedOptions = normalizeOptions(options)
|
|
44
|
+
const ruleTreeValidity = validateRuleTree(ruleTree)
|
|
45
|
+
|
|
46
|
+
if (ruleTreeValidity.status === 'ok') {
|
|
47
|
+
const generatorFunction = generateMiddlewareGeneratorFromRuleTree<TSource, TContext, TArgs>(ruleTree, normalizedOptions)
|
|
48
|
+
|
|
49
|
+
return middleware(generatorFunction)
|
|
50
|
+
} else {
|
|
51
|
+
throw new ValidationError(ruleTreeValidity.message)
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { GraphQLResolveInfo } from 'graphql'
|
|
2
|
+
import { IMiddlewareGenerator } from 'graphql-middleware'
|
|
3
|
+
|
|
4
|
+
// Rule
|
|
5
|
+
|
|
6
|
+
export type ShieldRule = IRule | ILogicRule
|
|
7
|
+
|
|
8
|
+
export interface IRule {
|
|
9
|
+
readonly name: string
|
|
10
|
+
|
|
11
|
+
equals(rule: IRule): boolean
|
|
12
|
+
extractFragment(): IFragment | undefined
|
|
13
|
+
resolve(parent: object, args: object, ctx: IShieldContext, info: GraphQLResolveInfo, options: IOptions): Promise<IRuleResult>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface IRuleOptions {
|
|
17
|
+
cache: ICache
|
|
18
|
+
fragment?: IFragment
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ILogicRule {
|
|
22
|
+
getRules(): ShieldRule[]
|
|
23
|
+
extractFragments(): IFragment[]
|
|
24
|
+
evaluate(parent: object, args: object, ctx: IShieldContext, info: GraphQLResolveInfo, options: IOptions): Promise<IRuleResult[]>
|
|
25
|
+
resolve(parent: object, args: object, ctx: IShieldContext, info: GraphQLResolveInfo, options: IOptions): Promise<IRuleResult>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type IFragment = string
|
|
29
|
+
export type ICache = 'strict' | 'contextual' | 'no_cache' | ICacheKeyFn
|
|
30
|
+
export type ICacheKeyFn = (parent: any, args: any, ctx: any, info: GraphQLResolveInfo) => string
|
|
31
|
+
export type IRuleResult = boolean | string | Error
|
|
32
|
+
export type IRuleFunction = (parent: any, args: any, ctx: any, info: GraphQLResolveInfo) => IRuleResult | Promise<IRuleResult>
|
|
33
|
+
|
|
34
|
+
// Rule Constructor Options
|
|
35
|
+
|
|
36
|
+
export type ICacheContructorOptions = ICache | boolean
|
|
37
|
+
|
|
38
|
+
export interface IRuleConstructorOptions {
|
|
39
|
+
cache?: ICacheContructorOptions
|
|
40
|
+
fragment?: IFragment
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Rules Definition Tree
|
|
44
|
+
|
|
45
|
+
export interface IRuleTypeMap {
|
|
46
|
+
[key: string]: ShieldRule | IRuleFieldMap
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface IRuleFieldMap {
|
|
50
|
+
[key: string]: ShieldRule
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type IRules = ShieldRule | IRuleTypeMap
|
|
54
|
+
|
|
55
|
+
export type IHashFunction = (arg: { parent: any; args: any }) => string
|
|
56
|
+
|
|
57
|
+
export type IFallbackErrorMapperType = (
|
|
58
|
+
err: unknown,
|
|
59
|
+
parent: object,
|
|
60
|
+
args: object,
|
|
61
|
+
ctx: IShieldContext,
|
|
62
|
+
info: GraphQLResolveInfo,
|
|
63
|
+
) => Promise<Error> | Error
|
|
64
|
+
|
|
65
|
+
export type IFallbackErrorType = Error | IFallbackErrorMapperType
|
|
66
|
+
|
|
67
|
+
// Generator Options
|
|
68
|
+
|
|
69
|
+
export interface IOptions {
|
|
70
|
+
debug: boolean
|
|
71
|
+
allowExternalErrors: boolean
|
|
72
|
+
fallbackRule: ShieldRule
|
|
73
|
+
fallbackError?: IFallbackErrorType
|
|
74
|
+
hashFunction: IHashFunction
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface IOptionsConstructor {
|
|
78
|
+
debug?: boolean
|
|
79
|
+
allowExternalErrors?: boolean
|
|
80
|
+
fallbackRule?: ShieldRule
|
|
81
|
+
fallbackError?: string | IFallbackErrorType
|
|
82
|
+
hashFunction?: IHashFunction
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export declare function shield<TSource = any, TContext = any, TArgs = any>(
|
|
86
|
+
ruleTree: IRules,
|
|
87
|
+
options: IOptions,
|
|
88
|
+
): IMiddlewareGenerator<TSource, TContext, TArgs>
|
|
89
|
+
|
|
90
|
+
export interface IShieldContext {
|
|
91
|
+
_shield: {
|
|
92
|
+
cache: { [key: string]: IRuleResult | Promise<IRuleResult> }
|
|
93
|
+
}
|
|
94
|
+
}
|