Skip to content

Commit 95b1827

Browse files
committed
[feat] add policy configuration
1 parent 2c1793f commit 95b1827

File tree

13 files changed

+231
-27
lines changed

13 files changed

+231
-27
lines changed

lib/RouterSpool.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ export class RouterSpool extends SystemSpool {
5555
Validator.validateRouter(this.app.config.get('router')),
5656
Promise.all(
5757
Object.values(this.app.config.get('routes') || {}).map(Validator.validateRoute)
58+
),
59+
Promise.all(
60+
Object.values(this.app.config.get('policies') || {}).map(Validator.validatePolicy)
5861
)
5962
])
6063
}
@@ -79,8 +82,8 @@ export class RouterSpool extends SystemSpool {
7982
}
8083

8184
sanity () {
82-
if (!isObject(this.app.routes)) {
83-
throw new Error('Sanity Failed: app.routes is not an array!')
85+
if (!(this.app.routes instanceof Map)) {
86+
throw new Error('Sanity Failed: app.routes is not a Map!')
8487
}
8588
}
8689
}

lib/config/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
export { policies } from './policies'
12
export { router } from './router'
23
export { routes } from './routes'
34
export { spool } from './spool'
5+

lib/config/policies.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const policies = {
2+
}

lib/schemas/policy.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import * as joi from 'joi'
2+
3+
export const policySchema = joi.object().keys({
4+
'*': joi.alternatives().try(
5+
joi.func(),
6+
joi.string(),
7+
joi.object(),
8+
joi.array().items(joi.string())
9+
),
10+
GET: joi.alternatives().try(
11+
joi.func(),
12+
joi.string(),
13+
joi.object(),
14+
joi.array().items(joi.string())
15+
),
16+
HEAD: joi.alternatives().try(
17+
joi.func(),
18+
joi.string(),
19+
joi.object(),
20+
joi.array().items(joi.string())
21+
),
22+
POST: joi.alternatives().try(
23+
joi.func(),
24+
joi.string(),
25+
joi.object(),
26+
joi.array().items(joi.string())
27+
),
28+
PUT: joi.alternatives().try(
29+
joi.func(),
30+
joi.string(),
31+
joi.object(),
32+
joi.array().items(joi.string())
33+
),
34+
DELETE: joi.alternatives().try(
35+
joi.func(),
36+
joi.string(),
37+
joi.object(),
38+
joi.array().items(joi.string())
39+
),
40+
CONNECT: joi.alternatives().try(
41+
joi.func(),
42+
joi.string(),
43+
joi.object(),
44+
joi.array().items(joi.string())
45+
),
46+
OPTIONS: joi.alternatives().try(
47+
joi.func(),
48+
joi.string(),
49+
joi.object(),
50+
joi.array().items(joi.string())
51+
),
52+
TRACE: joi.alternatives().try(
53+
joi.func(),
54+
joi.string(),
55+
joi.object(),
56+
joi.array().items(joi.string())
57+
),
58+
PATCH: joi.alternatives().try(
59+
joi.func(),
60+
joi.string(),
61+
joi.object(),
62+
joi.array().items(joi.string())
63+
)
64+
}).unknown()

lib/utils.ts

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { FabrixApp } from '@fabrix/fabrix'
2-
import { get, omit } from 'lodash'
2+
import { get, omit, isString } from 'lodash'
33
import { Router } from 'call'
44
import { IRoute } from './interfaces/IRoute'
55

@@ -16,6 +16,13 @@ export const Utils = {
1616
'PATCH'
1717
],
1818

19+
/**
20+
*
21+
*/
22+
stringToArray(strOrArray): string[] {
23+
return isString(strOrArray) ? [ strOrArray ] : strOrArray
24+
},
25+
1926
/**
2027
* Build a complete route, with bound handler and attached preconditions
2128
*/
@@ -37,13 +44,14 @@ export const Utils = {
3744
Utils.getHandlerFromString(app, orgRoute)
3845

3946
orgRoute.config.pre = orgRoute.config.pre
40-
.map(pre => Utils.getHandlerFromPrerequisite(app, pre))
47+
.map(pre => Utils.getPolicyFromPrerequisite(app, pre))
4148
.filter(handler => !!handler)
4249

43-
const orgRouteHandlers = Object.keys(orgRoute).filter(value => -1 !== Utils.methods.indexOf(value))
50+
const orgRouteHandlers = Object.keys(orgRoute)
51+
.filter(value => -1 !== Utils.methods.indexOf(value))
4452

4553
if (!orgRouteHandlers.some(v => Utils.methods.indexOf(v) >= 0 || !!orgRoute[v])) {
46-
app.log.error('spool-orgRouter: orgRoute ', path, ' handler [', orgRouteHandlers.join(', '), ']',
54+
app.log.error('spool-router: route ', path, ' handler [', orgRouteHandlers.join(', '), ']',
4755
'does not correspond to any defined Controller handler')
4856
return {}
4957
}
@@ -53,7 +61,8 @@ export const Utils = {
5361
orgRoute[method].config = orgRoute[method].config || orgRoute.config
5462
orgRoute[method].config.pre = orgRoute[method].config.pre || orgRoute.config.pre
5563
orgRoute[method].config.pre = orgRoute[method].config.pre
56-
.map(pre => Utils.getHandlerFromPrerequisite(app, pre))
64+
.map(pre => Utils.getPolicyFromPrerequisite(app, pre))
65+
// .map(pre => Utils.getPolicyFromPrerequisite(app, pre))
5766
.filter(handler => !!handler)
5867
}
5968
})
@@ -116,14 +125,33 @@ export const Utils = {
116125
return get(app.policies, handler)
117126
},
118127

128+
/**
129+
* Get a Controller's method's policies
130+
*/
131+
getControllerPolicy(app: FabrixApp, handler, routeMethod, pre = [ ]) {
132+
if (app.config.get('policies.*.*')) {
133+
pre = [...new Set([...pre, ...Utils.stringToArray(app.config.get('policies.*.*'))])]
134+
}
135+
if (app.config.get(`policies.*.${routeMethod}`)) {
136+
pre = [...new Set([...pre, ...Utils.stringToArray(app.config.get(`policies.*.${routeMethod}`))])]
137+
}
138+
if (handler && app.config.get(`policies.${handler}.${routeMethod}`)) {
139+
pre = [...new Set([...pre, ...Utils.stringToArray(app.config.get(`policies.${handler}.${routeMethod}`))])]
140+
}
141+
return pre
142+
},
143+
119144
/**
120145
* Get handler method from a "hapi/hapi-like" prerequisite object/string
121146
*/
122-
getHandlerFromPrerequisite (app: FabrixApp, pre) {
147+
getPolicyFromPrerequisite (app: FabrixApp, pre) {
123148
let handler
124149
if (pre && typeof pre === 'string') {
125150
handler = Utils.getPolicyFromString(app, pre)
126151
}
152+
else if (pre && Array.isArray(pre)) {
153+
handler = pre.map(p => Utils.getPolicyFromString(app, p)).filter(p => p)
154+
}
127155
else if (pre && typeof pre.method === 'string') {
128156
handler = Utils.getPolicyFromString(app, pre.method)
129157
}
@@ -158,15 +186,32 @@ export const Utils = {
158186

159187
Utils.methods.forEach(method => {
160188
if (route[method]) {
189+
route.config = route.config || { }
190+
route.config.pre = Utils.getControllerPolicy(app, null, method, route.config.pre)
191+
161192
if (typeof route[method] === 'string') {
162-
return route[method] = { handler: Utils.getControllerFromString(app, route[method]) }
193+
route.config.pre = Utils.getControllerPolicy(app, route[method], method, route.config.pre)
194+
return route[method] = {
195+
handler: Utils.getControllerFromString(app, route[method]),
196+
config: route.config
197+
}
163198
}
164199
else if (route[method] instanceof Object && route[method].hasOwnProperty('handler')) {
200+
route[method].config = route[method].config || route.config
201+
route[method].config.pre = route[method].config.pre || route.config.pre
202+
165203
if (typeof route[method].handler === 'string') {
166-
return route[method].handler = Utils.getControllerFromString(app, route[method].handler)
204+
route.config.pre = Utils.getControllerPolicy(app, route[method].handler, method, route.config.pre)
205+
return route[method] = {
206+
...route[method],
207+
handler: Utils.getControllerFromString(app, route[method].handler)
208+
}
167209
}
168210
else {
169-
return route[method].handler
211+
return route[method] = {
212+
...route[method],
213+
handler: route[method].handler
214+
}
170215
}
171216
}
172217
else {
@@ -176,6 +221,20 @@ export const Utils = {
176221
})
177222
},
178223

224+
// /**
225+
// *
226+
// */
227+
// getControllerPolicyFromString(app: FabrixApp, handlerString: string) {
228+
// let pre = []
229+
// if (app.config.get('policies.*')) {
230+
// pre.push(Utils.policyStringToArray(app.config.get('policies.*')))
231+
// }
232+
// if (app.config.get(`policies.${handlerString}`)) {
233+
// pre = [...pre, ...Utils.policyStringToArray(app.config.get(`policies.${handlerString}`))]
234+
// }
235+
// return { pre: pre }
236+
// },
237+
179238
/**
180239
* Build a route collection
181240
*/

lib/validator.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import * as joi from 'joi'
22
import { Utils } from './utils'
3+
4+
import { policySchema } from './schemas/policy'
35
import { routeSchema } from './schemas/route'
46
import { routerSchema } from './schemas/router'
57

8+
69
export const Validator = {
710

811
/**
@@ -19,6 +22,20 @@ export const Validator = {
1922
})
2023
})
2124
},
25+
/**
26+
* Validate the structure of an individual route
27+
*/
28+
validatePolicy (policy) {
29+
return new Promise((resolve, reject) => {
30+
joi.validate(policy, policySchema, (err, value) => {
31+
if (err) {
32+
return reject(err)
33+
}
34+
35+
return resolve(value)
36+
})
37+
})
38+
},
2239

2340
/**
2441
* Validate the structure of router

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@fabrix/spool-router",
3-
"version": "1.1.2",
3+
"version": "1.1.3",
44
"description": "Spool - Router for Fabrix",
55
"scripts": {
66
"build": "tsc -p ./lib/tsconfig.release.json",
@@ -49,7 +49,7 @@
4949
"lodash": "^4.17.10"
5050
},
5151
"devDependencies": {
52-
"@fabrix/fabrix": "^1.1.1",
52+
"@fabrix/fabrix": "^1.1.2",
5353
"@fabrix/lint": "^1.0.0-alpha.3",
5454
"@types/lodash": "^4.14.109",
5555
"@types/node": "~10.3.4",
@@ -64,7 +64,7 @@
6464
"typescript": "~2.8.1"
6565
},
6666
"peerDependencies": {
67-
"@fabrix/fabrix": "^1.1.1"
67+
"@fabrix/fabrix": "^1.1.2"
6868
},
6969
"license": "MIT",
7070
"bugs": {

test/fixtures/app.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ module.exports = {
2020
policies: {
2121
FooPolicy: class FooPolicy extends Policy {
2222
bar () { }
23+
},
24+
GlobalPolicy: class GlobalPolicy extends Policy {
25+
foo () { }
26+
},
27+
GetPolicy: class GetPolicy extends Policy {
28+
foo () { }
29+
},
30+
FooGetPolicy: class FooGetPolicy extends Policy {
31+
foo () { }
2332
}
2433
}
2534
},
@@ -37,6 +46,17 @@ module.exports = {
3746
router: {
3847
debug: true
3948
},
49+
policies: {
50+
'*': {
51+
'*': ['GlobalPolicy.foo'],
52+
'GET': ['GetPolicy.foo']
53+
},
54+
'TestController': {
55+
'foo': {
56+
'GET': ['FooGetPolicy.foo']
57+
}
58+
}
59+
},
4060
routes: {
4161
'/test/foo': {
4262
'GET': 'TestController.foo'

test/integration/lib/util.test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,43 @@ describe('lib.Util', () => {
7474
assert.equal(route.GET.config.pre[0], global.app.policies.FooPolicy.bar)
7575
})
7676
})
77+
describe('#policies', () => {
78+
it('should inherit the Global Policy on Every Method', () => {
79+
const { path, route} = lib.Utils.buildRoute(global.app, '/foo/bar', {
80+
'*': 'FooController.bar',
81+
config: {
82+
pre: [
83+
{
84+
method: 'FooPolicy.bar'
85+
}
86+
]
87+
}
88+
})
89+
assert.equal(route.GET.config.pre[0], global.app.policies.FooPolicy.bar)
90+
assert.equal(route.GET.config.pre[1], global.app.policies.GlobalPolicy.foo)
91+
assert.equal(route.HEAD.config.pre[0], global.app.policies.FooPolicy.bar)
92+
assert.equal(route.HEAD.config.pre[1], global.app.policies.GlobalPolicy.foo)
93+
assert.equal(route.POST.config.pre[0], global.app.policies.FooPolicy.bar)
94+
assert.equal(route.POST.config.pre[1], global.app.policies.GlobalPolicy.foo)
95+
assert.equal(route.PUT.config.pre[0], global.app.policies.FooPolicy.bar)
96+
assert.equal(route.PUT.config.pre[1], global.app.policies.GlobalPolicy.foo)
97+
})
7798

99+
it('should inherit the global policy and the global GET policy', () => {
100+
const { path, route} = lib.Utils.buildRoute(global.app, '/foo/bar', {
101+
'GET': 'FooController.bar'
102+
})
103+
assert.equal(route.GET.config.pre[0], global.app.policies.GlobalPolicy.foo)
104+
assert.equal(route.GET.config.pre[1], global.app.policies.GetPolicy.foo)
105+
})
106+
107+
it('should inherit the global policy and the global GET policy and the Controller Specific Get Policy', () => {
108+
const { path, route} = lib.Utils.buildRoute(global.app, '/foo/bar', {
109+
'GET': 'TestController.foo'
110+
})
111+
assert.equal(route.GET.config.pre[0], global.app.policies.GlobalPolicy.foo)
112+
assert.equal(route.GET.config.pre[1], global.app.policies.GetPolicy.foo)
113+
assert.equal(route.GET.config.pre[2], global.app.policies.FooGetPolicy.foo)
114+
})
115+
})
78116
})

0 commit comments

Comments
 (0)