1 | const pick = require('lodash/pick')
|
2 | const merge = require('lodash/merge')
|
3 |
|
4 |
|
5 | async function fillResourceContext(args) {
|
6 | if (!args.context._fetchResourceContextFromDB) return
|
7 |
|
8 | const OGValue = args.context._authorize
|
9 | args.context._authorize = false
|
10 | args.context._resource = await args.asFindQuery()
|
11 | args.context._authorize = OGValue
|
12 | }
|
13 |
|
14 | module.exports = (acl, library = 'role-acl', opts) => {
|
15 | if (!acl || typeof library === 'object') {
|
16 | throw new Error(
|
17 | "usage: require('objection-authorize')(acl, library: String[, opts: Object])(Model)"
|
18 | )
|
19 | }
|
20 |
|
21 | const defaultOpts = {
|
22 | defaultRole: 'anonymous',
|
23 | unauthenticatedErrorCode: 401,
|
24 | unauthorizedErrorCode: 403,
|
25 | castDiffToModelClass: true,
|
26 | casl: {
|
27 | useInputItemAsResourceForRelation: false
|
28 | }
|
29 | }
|
30 | opts = merge({}, defaultOpts, opts)
|
31 |
|
32 | const Adapter = require(`./adapters/${library}`)
|
33 |
|
34 | return Model => {
|
35 | class AuthZQueryBuilder extends Model.QueryBuilder {
|
36 | action(_action) {
|
37 | return this.context({ _action })
|
38 | }
|
39 |
|
40 | inputItem(_resource) {
|
41 | return this.context({ _resource })
|
42 | }
|
43 |
|
44 | authorize(user, resource, optOverride) {
|
45 | const _opts = merge({}, opts, optOverride)
|
46 |
|
47 | return this.context({
|
48 | _user: merge({ role: _opts.defaultRole }, user),
|
49 | _opts,
|
50 | _resource: resource,
|
51 | _class: this.modelClass(),
|
52 | _authorize: true
|
53 | })
|
54 | }
|
55 |
|
56 | fetchResourceContextFromDB() {
|
57 | return this.context({
|
58 | _fetchResourceContextFromDB: true
|
59 | })
|
60 | }
|
61 |
|
62 | diffInputFromResource() {
|
63 | return this.context({
|
64 | _diffInputFromResource: true
|
65 | })
|
66 | }
|
67 | }
|
68 |
|
69 | return class extends Model {
|
70 | static get QueryBuilder() {
|
71 | return AuthZQueryBuilder
|
72 | }
|
73 |
|
74 | static async beforeInsert(args) {
|
75 | await super.beforeInsert(args)
|
76 | new Adapter(acl, args, 'create').checkAccess()
|
77 | }
|
78 |
|
79 | static async beforeFind(args) {
|
80 | await super.beforeFind(args)
|
81 | new Adapter(acl, args, 'read').checkAccess()
|
82 | }
|
83 |
|
84 | static async beforeUpdate(args) {
|
85 | await super.beforeUpdate(args)
|
86 | await fillResourceContext(args)
|
87 | new Adapter(acl, args, 'update').checkAccess()
|
88 | }
|
89 |
|
90 | static async beforeDelete(args) {
|
91 | await super.beforeDelete(args)
|
92 | await fillResourceContext(args)
|
93 | new Adapter(acl, args, 'delete').checkAccess()
|
94 | }
|
95 |
|
96 | authorizeRead(user, action = 'read', optOverride) {
|
97 | const args = {
|
98 | items: [],
|
99 | inputItems: [],
|
100 | relation: '',
|
101 | context: {
|
102 | _user: merge({ role: opts.defaultRole }, user),
|
103 | _opts: merge({}, opts, optOverride),
|
104 | _action: action,
|
105 | _resource: this,
|
106 | _class: this.constructor,
|
107 | _authorize: true
|
108 | }
|
109 | }
|
110 |
|
111 | const fields = new Adapter(acl, args, action).allowedFields
|
112 |
|
113 |
|
114 |
|
115 |
|
116 | return pick(this.toJSON(), fields)
|
117 | }
|
118 | }
|
119 | }
|
120 | }
|