UNPKG

4.74 kBJavaScriptView Raw
1const pick = require('lodash/pick')
2
3// TODO: @sssss465 I mean like this kind of shit
4async function fillResourceContext(args) {
5 if (!args.context._fetchResourceContextFromDB) return
6
7 const OGValue = args.context._authorize
8 args.context._authorize = false
9 args.context._resource = await args.asFindQuery()
10 args.context._authorize = OGValue
11}
12
13module.exports = (acl, library = 'role-acl', opts) => {
14 if (!acl || typeof library === 'object') {
15 throw new Error(
16 "usage: require('objection-authorize')(acl, library: String[, opts: Object])(Model)"
17 )
18 }
19
20 const defaultOpts = {
21 defaultRole: 'anonymous',
22 unauthenticatedErrorCode: 401,
23 unauthorizedErrorCode: 403
24 }
25 opts = Object.assign(defaultOpts, opts)
26
27 const Adapter = require(`./adapters/${library}`)
28
29 return Model => {
30 class AuthZQueryBuilder extends Model.QueryBuilder {
31 // specify a custom action, which takes precedence over the "default" action.
32 action(_action) {
33 return this.context({ _action })
34 }
35
36 // THE magic method that schedules the actual authorization logic to be called
37 // later down the line when the query is built and is ready to be executed.
38 authorize(user, resource, optOverride) {
39 return this.context({
40 _user: Object.assign({ role: opts.defaultRole }, user),
41 _opts: Object.assign({}, opts, optOverride),
42 _resource: resource,
43 _class: this.modelClass(),
44 _authorize: true
45 })
46 }
47
48 fetchResourceContextFromDB() {
49 return this.context({
50 _fetchResourceContextFromDB: true
51 })
52 }
53
54 // used for UPDATE queries where you're passing in the whole object -
55 // so clearly we want to ONLY check ACL for ONLY the parts that changed
56 diffInputFromResource() {
57 return this.context({
58 _diffInputFromResource: true
59 })
60 }
61 }
62
63 // Need to monkeypatch toFindQuery because it is fucking broken
64 // and koskimas is too high up on his horse and is busy shitting on contributors:
65 // https://github.com/Vincit/objection.js/issues/1855
66 function getOperationClass(modify) {
67 const query = Model.query()
68 let constructor = null
69 // Locally override QueryBuilder#addOperation() in order to extract the
70 // private operation constructor / class:
71 query.addOperation = operation => {
72 constructor = operation.constructor
73 }
74 modify(query)
75 return constructor
76 }
77
78 const InsertOperation = getOperationClass(query => query.insert())
79 const UpdateOperation = getOperationClass(query => query.update())
80 const DeleteOperation = getOperationClass(query => query.delete())
81
82 AuthZQueryBuilder.prototype.toFindQuery = function () {
83 const builder = this.clone()
84 const selector = op =>
85 op.is(InsertOperation) ||
86 op.is(UpdateOperation) ||
87 op.is(DeleteOperation)
88 builder.forEachOperation(selector, op => op.onBuild(builder))
89 return builder.clear(selector)
90 }
91
92 return class extends Model {
93 static get QueryBuilder() {
94 return AuthZQueryBuilder
95 }
96
97 static async beforeInsert(args) {
98 await super.beforeInsert(args)
99 new Adapter(acl, args, 'create').checkAccess()
100 }
101
102 static async beforeFind(args) {
103 await super.beforeFind(args)
104 new Adapter(acl, args, 'read').checkAccess()
105 }
106
107 static async beforeUpdate(args) {
108 await super.beforeUpdate(args)
109 await fillResourceContext(args)
110 new Adapter(acl, args, 'update').checkAccess()
111 }
112
113 static async beforeDelete(args) {
114 await super.beforeDelete(args)
115 await fillResourceContext(args)
116 new Adapter(acl, args, 'delete').checkAccess()
117 }
118
119 // Explicit model instance call to check read access before serializing
120 // instance.authorizeRead(req.user[, action = 'read']).toJSON()
121 authorizeRead(user, action = 'read', optOverride) {
122 const args = {
123 items: [],
124 inputItems: [],
125 relation: '',
126 context: {
127 _user: Object.assign({ role: opts.defaultRole }, user),
128 _opts: Object.assign({}, opts, optOverride),
129 _action: action,
130 _resource: this,
131 _class: this.constructor,
132 _authorize: true
133 }
134 }
135
136 const fields = new Adapter(acl, args, action).allowedFields
137
138 // using lodash's implementation of pick instead of "internal" Objection.js one
139 // because the ORM's implementation is absolute shit and buggy as fuck:
140 // https://git.io/JLMsm
141 return pick(this.toJSON(), fields)
142 }
143 }
144 }
145}