@live-change/access-control-service 0.2.41 → 0.2.42
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/access.js +33 -12
- package/accessControl.js +110 -0
- package/index.js +1 -0
- package/package.json +3 -3
- package/limitedAccess.js +0 -107
package/access.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { parents,
|
|
1
|
+
const { parents, parentsSources } = require('./accessControlParents.js')
|
|
2
2
|
const App = require('@live-change/framework')
|
|
3
3
|
const app = App.app()
|
|
4
4
|
|
|
@@ -16,6 +16,16 @@ module.exports = (definition) => {
|
|
|
16
16
|
canRequest = (roles, client, { objectType, object }) => false
|
|
17
17
|
} = config
|
|
18
18
|
|
|
19
|
+
function testRoles(requiredRoles, clientRoles) {
|
|
20
|
+
for(const requiredRolesOption of requiredRoles) {
|
|
21
|
+
if(
|
|
22
|
+
(Array.isArray(requiredRolesOption) ? requiredRolesOption : [requiredRolesOption])
|
|
23
|
+
.every(role => clientRoles.includes(role))
|
|
24
|
+
) return true
|
|
25
|
+
}
|
|
26
|
+
return false
|
|
27
|
+
}
|
|
28
|
+
|
|
19
29
|
async function clientHasAnyAccess(client, { objectType, object, objects }) {
|
|
20
30
|
return checkRoles(client, { objectType, object, objects }, hasAny)
|
|
21
31
|
}
|
|
@@ -38,7 +48,7 @@ module.exports = (definition) => {
|
|
|
38
48
|
|
|
39
49
|
function clientHasAccessRoles(client, { objectType, object, objects }, roles) {
|
|
40
50
|
return checkRoles(client, { objectType, object, objects },
|
|
41
|
-
(clientRoles) =>
|
|
51
|
+
(clientRoles) => testRoles(requiredRoles, roles)
|
|
42
52
|
)
|
|
43
53
|
}
|
|
44
54
|
|
|
@@ -98,7 +108,10 @@ module.exports = (definition) => {
|
|
|
98
108
|
|
|
99
109
|
/// QUERIES:
|
|
100
110
|
|
|
101
|
-
function dbAccessFunctions({
|
|
111
|
+
function dbAccessFunctions({
|
|
112
|
+
input, publicAccessTable, accessTable, updateRoles, isLoaded,
|
|
113
|
+
client, parentsSourcesMap, output
|
|
114
|
+
}) {
|
|
102
115
|
async function treeNode(objectType, object) {
|
|
103
116
|
const node = {
|
|
104
117
|
objectType, object,
|
|
@@ -119,7 +132,7 @@ module.exports = (definition) => {
|
|
|
119
132
|
})
|
|
120
133
|
|
|
121
134
|
const sessionAccessObject = accessTable.object(
|
|
122
|
-
`session_Session:${JSON.stringify(client.session)}:${JSON.stringify(objectType)}:${JSON.stringify(object)}`
|
|
135
|
+
`"session_Session":${JSON.stringify(client.session)}:${JSON.stringify(objectType)}:${JSON.stringify(object)}`
|
|
123
136
|
)
|
|
124
137
|
sessionAccessObserver = sessionAccessObject && sessionAccessObject.onChange((accessData, oldAccessData) => {
|
|
125
138
|
node.sessionRoles = accessData?.roles ?? []
|
|
@@ -127,10 +140,10 @@ module.exports = (definition) => {
|
|
|
127
140
|
})
|
|
128
141
|
|
|
129
142
|
const userAccessObject = client.user && accessTable.object(
|
|
130
|
-
`user_User:${JSON.stringify(client.user)}:${JSON.stringify(objectType)}:${JSON.stringify(object)}`
|
|
143
|
+
`"user_User":${JSON.stringify(client.user)}:${JSON.stringify(objectType)}:${JSON.stringify(object)}`
|
|
131
144
|
)
|
|
132
145
|
userAccessObserver = userAccessObject && userAccessObject.onChange((accessData, oldAccessData) => {
|
|
133
|
-
node.
|
|
146
|
+
node.userRoles = accessData?.roles ?? []
|
|
134
147
|
if(isLoaded()) updateRoles()
|
|
135
148
|
})
|
|
136
149
|
|
|
@@ -187,9 +200,12 @@ module.exports = (definition) => {
|
|
|
187
200
|
let loaded = false
|
|
188
201
|
|
|
189
202
|
const { treeNode, computeNodeRoles } =
|
|
190
|
-
eval(dbAccessFunctions)({
|
|
203
|
+
eval(dbAccessFunctions)({
|
|
204
|
+
input, publicAccessTable, accessTable, updateRoles, isLoaded: () => loaded,
|
|
205
|
+
client, parentsSourcesMap, output
|
|
206
|
+
})
|
|
191
207
|
|
|
192
|
-
let rolesTreesRoots = objects.map(({ object, objectType }) => treeNode(objectType, object))
|
|
208
|
+
let rolesTreesRoots = objects.map(({ object, objectType }) => treeNode(objectType, object, client))
|
|
193
209
|
|
|
194
210
|
const outputObjectId = `${JSON.stringify(client.session)}:${JSON.stringify(client.user)}:` +
|
|
195
211
|
objects.map( obj => `${JSON.stringify(objectType)}:${JSON.stringify(object)}`)
|
|
@@ -216,7 +232,7 @@ module.exports = (definition) => {
|
|
|
216
232
|
await updateRoles()
|
|
217
233
|
}
|
|
218
234
|
})`, {
|
|
219
|
-
objectType, object, parentsSourcesMap, client,
|
|
235
|
+
objectType, object, parentsSourcesMap: parentsSources, client,
|
|
220
236
|
accessTableName: Access.tableName, publicAccessTableName: PublicAccess.tableName,
|
|
221
237
|
dbAccessFunctions: `(${dbAccessFunctions})`
|
|
222
238
|
}]
|
|
@@ -233,9 +249,12 @@ module.exports = (definition) => {
|
|
|
233
249
|
let loaded = false
|
|
234
250
|
|
|
235
251
|
const { treeNode, computeNodeRoles } =
|
|
236
|
-
eval(dbAccessFunctions)({
|
|
252
|
+
eval(dbAccessFunctions)({
|
|
253
|
+
input, publicAccessTable, accessTable, updateRoles, isLoaded: () => loaded,
|
|
254
|
+
client, parentsSourcesMap, output,
|
|
255
|
+
})
|
|
237
256
|
|
|
238
|
-
let rolesTreesRoots = objects.map(({ object, objectType }) => treeNode(objectType, object))
|
|
257
|
+
let rolesTreesRoots = objects.map(({ object, objectType }) => treeNode(objectType, object, client))
|
|
239
258
|
const accesses = []
|
|
240
259
|
async function updateRoles() {
|
|
241
260
|
const roots = await Promise.all(rolesTreesRoots)
|
|
@@ -264,8 +283,9 @@ module.exports = (definition) => {
|
|
|
264
283
|
await updateRoles()
|
|
265
284
|
}
|
|
266
285
|
})`, {
|
|
267
|
-
objects, parentsSourcesMap, client,
|
|
286
|
+
objects, parentsSourcesMap: parentsSources, client,
|
|
268
287
|
accessTableName: Access.tableName, publicAccessTableName: PublicAccess.tableName,
|
|
288
|
+
dbAccessFunctions: `(${dbAccessFunctions})`
|
|
269
289
|
}]
|
|
270
290
|
}
|
|
271
291
|
|
|
@@ -282,6 +302,7 @@ module.exports = (definition) => {
|
|
|
282
302
|
}
|
|
283
303
|
|
|
284
304
|
return {
|
|
305
|
+
testRoles,
|
|
285
306
|
clientHasAnyAccess, clientHasAdminAccess,
|
|
286
307
|
clientCanInvite,
|
|
287
308
|
clientCanRequest,
|
package/accessControl.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const definition = require('./definition.js')
|
|
2
|
+
const App = require("@live-change/framework")
|
|
3
|
+
const { ObservableValue, ObservableList, ObservableProxy } = require("@live-change/dao")
|
|
4
|
+
const app = App.app()
|
|
5
|
+
const access = require('./access.js')(definition)
|
|
6
|
+
|
|
7
|
+
definition.processor({
|
|
8
|
+
priority: -1,
|
|
9
|
+
process(service, app) {
|
|
10
|
+
|
|
11
|
+
for(const actionName in service.actions) {
|
|
12
|
+
const action = service.actions[actionName]
|
|
13
|
+
if(!action.accessControl) continue
|
|
14
|
+
const config = action.accessControl
|
|
15
|
+
|
|
16
|
+
console.log("ACCESS CONTROL", service.name, "ACTION", action.name)
|
|
17
|
+
|
|
18
|
+
const oldExec = action.execute
|
|
19
|
+
action.execute = async (...args) => {
|
|
20
|
+
const [ properties, context, emit ] = args
|
|
21
|
+
const { client } = context
|
|
22
|
+
|
|
23
|
+
const objects = [].concat(
|
|
24
|
+
config.objects ? config.objects(properties) : [],
|
|
25
|
+
(objectType && object) ? [{ objectType, object }] : []
|
|
26
|
+
)
|
|
27
|
+
if(objects.length == 0) {
|
|
28
|
+
throw new Error('no objects for access control to work')
|
|
29
|
+
}
|
|
30
|
+
const accessible = access.clientHasAccessRoles(client, { objects }, config.roles)
|
|
31
|
+
if(!accessible) throw 'notAuthorized'
|
|
32
|
+
|
|
33
|
+
return oldExec.apply(action, args)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for(const viewName in service.views) {
|
|
38
|
+
const view = service.views[viewName]
|
|
39
|
+
if(!view.accessControl) continue
|
|
40
|
+
const config = view.accessControl
|
|
41
|
+
|
|
42
|
+
console.log("ACCESS CONTROL", service.name, "VIEW", view.name)
|
|
43
|
+
|
|
44
|
+
const oldGet = view.get
|
|
45
|
+
const oldObservable = view.observable
|
|
46
|
+
view.get = async (...args) => {
|
|
47
|
+
const [ properties, context ] = args
|
|
48
|
+
const { client } = context
|
|
49
|
+
const { objectType, object } = properties
|
|
50
|
+
const objects = [].concat(
|
|
51
|
+
config.objects ? config.objects(properties) : [],
|
|
52
|
+
(objectType && object) ? [{ objectType, object }] : []
|
|
53
|
+
)
|
|
54
|
+
if(objects.length == 0) {
|
|
55
|
+
throw new Error('no objects for access control to work')
|
|
56
|
+
}
|
|
57
|
+
const accessible = access.clientHasAccessRoles(client, { objects }, config.roles)
|
|
58
|
+
if(!accessible) throw 'notAuthorized'
|
|
59
|
+
return oldGet.apply(view, args)
|
|
60
|
+
}
|
|
61
|
+
view.observable = (...args) => {
|
|
62
|
+
const [ properties, context ] = args
|
|
63
|
+
const { client } = context
|
|
64
|
+
const { objectType, object } = properties
|
|
65
|
+
const objects = [].concat(
|
|
66
|
+
config.objects ? config.objects(properties) : [],
|
|
67
|
+
(objectType && object) ? [{ objectType, object }] : []
|
|
68
|
+
)
|
|
69
|
+
if(objects.length == 0) {
|
|
70
|
+
throw new Error('no objects for access control to work')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const rolesPath = access.accessPath(client, objects)
|
|
74
|
+
|
|
75
|
+
const errorObservable = new ObservableValue()
|
|
76
|
+
errorObservable.handleError('notAuthorized')
|
|
77
|
+
|
|
78
|
+
const observableProxy = new ObservableProxy(null)
|
|
79
|
+
|
|
80
|
+
let valueObservable
|
|
81
|
+
|
|
82
|
+
let accessible
|
|
83
|
+
const rolesObservable = app.dao.observable(rolesPath)
|
|
84
|
+
const rolesObserver = (signal, value) => {
|
|
85
|
+
const accessObject = rolesObservable.getValue()
|
|
86
|
+
const newAccessible = access.testRoles(config.roles, accessObject.roles)
|
|
87
|
+
if(newAccessible !== accessible) {
|
|
88
|
+
if(newAccessible === true /*&& !valueObservable*/) {
|
|
89
|
+
valueObservable = oldObservable.apply(view, args)
|
|
90
|
+
}
|
|
91
|
+
observableProxy.setTarget(accessible ? valueObservable : errorObservable)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
rolesObservable.observe(rolesObserver)
|
|
95
|
+
|
|
96
|
+
const oldDispose = observableProxy.dispose
|
|
97
|
+
const oldRespawn = observableProxy.respawn
|
|
98
|
+
observableProxy.dispose = () => {
|
|
99
|
+
rolesObservable.unobserve(rolesObserver)
|
|
100
|
+
oldDispose.apply(observableProxy)
|
|
101
|
+
}
|
|
102
|
+
observableProxy.respawn = () => {
|
|
103
|
+
rolesObservable.observe(rolesObserver)
|
|
104
|
+
oldRespawn.apply(observableProxy)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
})
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/access-control-service",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.42",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"url": "https://www.viamage.com/"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@live-change/framework": "0.6.
|
|
24
|
+
"@live-change/framework": "0.6.9"
|
|
25
25
|
},
|
|
26
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "ad7e746f87b5c7be7d040885ca228aab2ec2f23e"
|
|
27
27
|
}
|
package/limitedAccess.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
const definition = require('./definition.js')
|
|
2
|
-
const App = require("@live-change/framework")
|
|
3
|
-
const { ObservableValue, ObservableList, ObservableProxy } = require("@live-change/dao")
|
|
4
|
-
const app = App.app()
|
|
5
|
-
const access = require('./access.js')(definition)
|
|
6
|
-
|
|
7
|
-
definition.processor(function(service, app) {
|
|
8
|
-
|
|
9
|
-
for(const actionName in service.actions) {
|
|
10
|
-
const action = service.actions[actionName]
|
|
11
|
-
if(!action.limitedAccess) continue
|
|
12
|
-
const config = action.limitedAccess
|
|
13
|
-
|
|
14
|
-
console.log("LIMITED ACCESS", service.name, "ACTION", action.name)
|
|
15
|
-
|
|
16
|
-
const oldExec = action.execute
|
|
17
|
-
action.execute = async (...args) => {
|
|
18
|
-
const [ properties, context, emit ] = args
|
|
19
|
-
const { client } = context
|
|
20
|
-
|
|
21
|
-
const objects = [].concat(
|
|
22
|
-
config.objects ? config.objects(properties) : [],
|
|
23
|
-
(objectType && object) ? [{ objectType, object }] : []
|
|
24
|
-
)
|
|
25
|
-
if(objects.length == 0) {
|
|
26
|
-
throw new Error('no objects for access control to work')
|
|
27
|
-
}
|
|
28
|
-
const accessible = access.clientHasAccessRoles(client, { objects }, config.roles)
|
|
29
|
-
if(!accessible) throw 'notAuthorized'
|
|
30
|
-
|
|
31
|
-
return oldExec.apply(action, args)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
for(const viewName in service.views) {
|
|
36
|
-
const view = service.view[viewName]
|
|
37
|
-
if(!view.limitedAccess) continue
|
|
38
|
-
const config = view.limitedAccess
|
|
39
|
-
|
|
40
|
-
console.log("LIMITED ACCESS", service.name, "VIEW", view.name)
|
|
41
|
-
|
|
42
|
-
const oldGet = view.get
|
|
43
|
-
const oldObservable = view.observable
|
|
44
|
-
view.get = async (...args) => {
|
|
45
|
-
const [ properties, context ] = args
|
|
46
|
-
const { client } = context
|
|
47
|
-
const { objectType, object } = properties
|
|
48
|
-
const objects = [].concat(
|
|
49
|
-
config.objects ? config.objects(properties) : [],
|
|
50
|
-
(objectType && object) ? [{ objectType, object }] : []
|
|
51
|
-
)
|
|
52
|
-
if(objects.length == 0) {
|
|
53
|
-
throw new Error('no objects for access control to work')
|
|
54
|
-
}
|
|
55
|
-
const accessible = access.clientHasAccessRoles(client, { objects }, config.roles)
|
|
56
|
-
if(!accessible) throw 'notAuthorized'
|
|
57
|
-
return oldGet.apply(view, args)
|
|
58
|
-
}
|
|
59
|
-
view.observable = (...args) => {
|
|
60
|
-
const [ properties, context ] = args
|
|
61
|
-
const { client } = context
|
|
62
|
-
const { objectType, object } = properties
|
|
63
|
-
const objects = [].concat(
|
|
64
|
-
config.objects ? config.objects(properties) : [],
|
|
65
|
-
(objectType && object) ? [{ objectType, object }] : []
|
|
66
|
-
)
|
|
67
|
-
if(objects.length == 0) {
|
|
68
|
-
throw new Error('no objects for access control to work')
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const rolesPath = access.accessPath(client, objects)
|
|
72
|
-
|
|
73
|
-
const errorObservable = new ObservableValue()
|
|
74
|
-
errorObservable.handleError('notAuthorized')
|
|
75
|
-
|
|
76
|
-
const observableProxy = new ObservableProxy(null)
|
|
77
|
-
|
|
78
|
-
let valueObservable
|
|
79
|
-
|
|
80
|
-
let accessible
|
|
81
|
-
const rolesObservable = app.dao.observable(rolesPath)
|
|
82
|
-
const rolesObserver = (signal, value) => {
|
|
83
|
-
const accessObject = rolesObservable.getValue()
|
|
84
|
-
const newAccessible = config.roles.every(role => accessObject.roles.includes(role))
|
|
85
|
-
if(newAccessible !== accessible) {
|
|
86
|
-
if(newAccessible === true /*&& !valueObservable*/) {
|
|
87
|
-
valueObservable = oldObservable.apply(view, args)
|
|
88
|
-
}
|
|
89
|
-
observableProxy.setTarget(accessible ? valueObservable : errorObservable)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
rolesObservable.observe(rolesObserver)
|
|
93
|
-
|
|
94
|
-
const oldDispose = observableProxy.dispose
|
|
95
|
-
const oldRespawn = observableProxy.respawn
|
|
96
|
-
observableProxy.dispose = () => {
|
|
97
|
-
rolesObservable.unobserve(rolesObserver)
|
|
98
|
-
oldDispose.apply(observableProxy)
|
|
99
|
-
}
|
|
100
|
-
observableProxy.respawn = () => {
|
|
101
|
-
rolesObservable.observe(rolesObserver)
|
|
102
|
-
oldRespawn.apply(observableProxy)
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
})
|