UNPKG

3.32 kBJavaScriptView Raw
1const httpError = require('http-errors')
2const objectDiff = require('../utils/object-diff')
3
4class ACLInterface {
5 constructor(acl, args, defaultAction) {
6 if (!acl) throw new Error('ACLInterface: missing input `acl`')
7 if (!args) throw new Error('ACLInterface: missing input `args`')
8 if (!defaultAction)
9 throw new Error('ACLInterface: missing input `defaultAction`')
10
11 let { items, inputItems, relation, context: queryContext } = args
12 const {
13 _user: user,
14 _opts: opts,
15 _action,
16 _resource: resource,
17 _authorize: authorize,
18 _class: ModelClass,
19 _diffInputFromResource: diffInputFromResource
20 } = queryContext
21
22 if (!authorize) return
23
24 // relation support, ho!
25 const InputClass = relation ? relation.relatedModelClass : ModelClass
26
27 // wrap the resource in model class for consistency;
28 // note that the base [items] are already wrapped in the appropriate model class
29 if (resource) {
30 const resourceList = Array.isArray(resource) ? resource : [resource]
31 items = resourceList.map(resource =>
32 resource instanceof ModelClass
33 ? resource
34 : ModelClass.fromJson(resource, { skipValidation: true })
35 )
36 } else if (!items.length) items = [new ModelClass()]
37
38 Object.assign(this, {
39 acl,
40 items,
41 inputItems: inputItems.length ? inputItems : [new InputClass()],
42 user,
43 action: _action || defaultAction,
44 opts,
45 relation,
46 authorize,
47 ModelClass,
48 InputClass,
49 diffInputFromResource
50 })
51 }
52
53 // Yes, I'm still enforcing *synchronous* ACL checks!!
54 checkAccess() {
55 if (!this.authorize) return
56 this.items.forEach(item => {
57 this.inputItems.forEach(inputItem => {
58 // the base inputItems passed by the ORM are already wrapped in model class;
59 // however, performing this diff operation causes class information to be lost,
60 // so we need to regenerate it by wrapping the diff (which is a plain object) in class
61 if (this.diffInputFromResource) {
62 inputItem = objectDiff(item, inputItem)
63 if (this.opts.castDiffToModelClass)
64 inputItem = this.InputClass.fromJson(inputItem, {
65 skipValidation: true
66 })
67 }
68
69 if (!this._checkIndividualAccess(item, inputItem))
70 throw httpError(
71 this.user.role === this.opts.defaultRole
72 ? this.opts.unauthenticatedErrorCode
73 : this.opts.unauthorizedErrorCode
74 )
75 })
76 })
77 }
78
79 /**
80 * This function should be overridden to check a particular item/inputItem pair,
81 * and return true/false depending on whether the check succeeded or not.
82 * @param {Object} item
83 * @param {Object} inputItem
84 * @returns {Boolean}
85 */
86 _checkIndividualAccess(item, inputItem) {
87 throw new Error('Override this method before use!')
88 }
89
90 /**
91 * This function should be overridden to return "allowed" fields for a read action,
92 * where we're only checking the model instance (this.items[0]) with the ACL.
93 * NOTE: the returning fields should be in dot notation (e.g. 'field.subfield')
94 * @returns {Array}
95 */
96 get allowedFields() {
97 throw new Error('Override this method before use!')
98 }
99}
100
101module.exports = ACLInterface