1 | const pick = require('lodash/pick')
|
2 |
|
3 |
|
4 | async 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 |
|
13 | module.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 |
|
32 | action(_action) {
|
33 | return this.context({ _action })
|
34 | }
|
35 |
|
36 |
|
37 |
|
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 |
|
55 |
|
56 | diffInputFromResource() {
|
57 | return this.context({
|
58 | _diffInputFromResource: true
|
59 | })
|
60 | }
|
61 | }
|
62 |
|
63 |
|
64 |
|
65 |
|
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 |
|
120 |
|
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 |
|
139 |
|
140 |
|
141 | return pick(this.toJSON(), fields)
|
142 | }
|
143 | }
|
144 | }
|
145 | }
|