sumba 2.29.0 → 2.31.0
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/extend/bajo/hook.js +48 -51
- package/extend/bajo/intl/en-US.json +5 -5
- package/extend/bajo/intl/id.json +5 -5
- package/extend/dobo/feature/lat-lng.js +6 -1
- package/extend/dobo/feature/lat.js +1 -1
- package/extend/dobo/feature/lng.js +1 -1
- package/extend/dobo/feature/status.js +3 -1
- package/extend/dobo/fixture/site.json +1 -1
- package/extend/dobo/fixture/team-user.json +1 -1
- package/extend/dobo/fixture/team.json +2 -2
- package/extend/dobo/fixture/user.json +1 -1
- package/extend/dobo/model.js +82 -122
- package/extend/dobo/model.json +3 -9
- package/extend/sumba/route/anonymous.json +5 -0
- package/extend/sumba/route/secure.json +8 -0
- package/extend/waibuDb/schema/anonymous-guard.js +15 -0
- package/extend/waibuDb/schema/{route-guard.js → secure-guard.js} +2 -2
- package/extend/waibuDb/schema/team-user.js +2 -2
- package/extend/waibuDb/schema/team.json +1 -1
- package/extend/waibuMpa/extend/waibuAdmin/route/{x/model-guard → anonymous-guard}/@action.js +2 -3
- package/extend/waibuMpa/extend/waibuAdmin/route/{route-guard → secure-guard}/@action.js +2 -2
- package/extend/waibuMpa/route/access-token.js +3 -1
- package/extend/waibuRestApi/route/manage/anonymous-guard/model-builder.json +4 -0
- package/extend/waibuRestApi/route/manage/{route-guard → secure-guard}/model-builder.json +1 -1
- package/index.js +302 -98
- package/lib/get-site.js +1 -3
- package/lib/get-user.js +2 -6
- package/package.json +1 -1
- package/wiki/CHANGES.md +13 -0
- package/extend/dobo/fixture/x-route-guard.js +0 -24
- package/extend/waibuDb/schema/x-attrib-guard.js +0 -15
- package/extend/waibuDb/schema/x-model-guard.js +0 -15
- package/extend/waibuDb/schema/x-route-guard.js +0 -15
- package/extend/waibuMpa/extend/waibuAdmin/route/index.json +0 -3
- package/extend/waibuMpa/extend/waibuAdmin/route/x/attrib-guard/@action.js +0 -12
- package/extend/waibuMpa/extend/waibuAdmin/route/x/index.json +0 -3
- package/extend/waibuMpa/extend/waibuAdmin/route/x/route-guard/@action.js +0 -12
- package/lib/util.js +0 -201
package/extend/waibuMpa/extend/waibuAdmin/route/{x/model-guard → anonymous-guard}/@action.js
RENAMED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
const action = {
|
|
2
2
|
method: ['GET', 'POST'],
|
|
3
|
-
title: '
|
|
4
|
-
xSite: true,
|
|
3
|
+
title: 'anonymousGuard',
|
|
5
4
|
handler: async function (req, reply) {
|
|
6
5
|
const { importModule } = this.app.bajo
|
|
7
6
|
const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
|
|
8
|
-
return await crudSkel.call(this, '
|
|
7
|
+
return await crudSkel.call(this, 'SumbaAnonymousGuard', req, reply)
|
|
9
8
|
}
|
|
10
9
|
}
|
|
11
10
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const action = {
|
|
2
2
|
method: ['GET', 'POST'],
|
|
3
|
-
title: '
|
|
3
|
+
title: 'secureGuard',
|
|
4
4
|
handler: async function (req, reply) {
|
|
5
5
|
const { importModule } = this.app.bajo
|
|
6
6
|
const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
|
|
7
|
-
return await crudSkel.call(this, '
|
|
7
|
+
return await crudSkel.call(this, 'SumbaSecureGuard', req, reply)
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
const apiToken = {
|
|
2
2
|
method: 'POST',
|
|
3
3
|
handler: async function (req, reply) {
|
|
4
|
-
|
|
4
|
+
const { get } = this.app.lib._
|
|
5
|
+
const uid = get(req, 'user.id')
|
|
6
|
+
if (!uid) return ''
|
|
5
7
|
const rec = await this.app.dobo.getModel('SumbaUser').getRecord(req.user.id, { forceNoHidden: true, noCache: true })
|
|
6
8
|
return (await this.createJwtFromUserRecord(rec)).token
|
|
7
9
|
}
|
package/index.js
CHANGED
|
@@ -20,7 +20,7 @@ const defMultiSite = {
|
|
|
20
20
|
async function factory (pkgName) {
|
|
21
21
|
const me = this
|
|
22
22
|
const { getModel } = this.app.dobo
|
|
23
|
-
const { cloneDeep,
|
|
23
|
+
const { cloneDeep, isEmpty } = this.app.lib._
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Sumba class
|
|
@@ -52,6 +52,14 @@ async function factory (pkgName) {
|
|
|
52
52
|
'/help': 'sumba:/help/contact-form',
|
|
53
53
|
'/help/trouble-tickets': 'sumba:/help/trouble-tickets/list'
|
|
54
54
|
},
|
|
55
|
+
redirectSubRoute: {
|
|
56
|
+
waibuAdmin: {
|
|
57
|
+
'/': 'waibuAdmin:/site/site',
|
|
58
|
+
'/x': 'waibuAdmin:/site/x/site/list',
|
|
59
|
+
'/x/*': 'waibuAdmin:/site/x/{2}/list',
|
|
60
|
+
'/*': 'waibuAdmin:/site/{1}/list'
|
|
61
|
+
}
|
|
62
|
+
},
|
|
55
63
|
menuHandler: [{
|
|
56
64
|
title: 'account',
|
|
57
65
|
icon: 'person',
|
|
@@ -155,9 +163,11 @@ async function factory (pkgName) {
|
|
|
155
163
|
getUserByTokenDur: '1m'
|
|
156
164
|
}
|
|
157
165
|
}
|
|
158
|
-
this.
|
|
159
|
-
this.modelGuards =
|
|
160
|
-
this.attribGuards =
|
|
166
|
+
this.secureGuards = []
|
|
167
|
+
this.modelGuards = []
|
|
168
|
+
this.attribGuards = []
|
|
169
|
+
this.secureGuards = []
|
|
170
|
+
this.anonymousGuards = []
|
|
161
171
|
|
|
162
172
|
this.unsafeUserFields = ['password']
|
|
163
173
|
this.selfBind(['createNewSite', 'removeSite', 'getSite', 'getUserById', 'getUserByToken', 'getUserByUsernamePassword'])
|
|
@@ -174,7 +184,11 @@ async function factory (pkgName) {
|
|
|
174
184
|
}
|
|
175
185
|
|
|
176
186
|
start = async () => {
|
|
177
|
-
|
|
187
|
+
await this.populateRouteGuards()
|
|
188
|
+
if (!this.config.multiSite.enabled) {
|
|
189
|
+
this.config.xSiteAdmins = []
|
|
190
|
+
return
|
|
191
|
+
}
|
|
178
192
|
if (this.config.xSiteAdmins.length === 0) {
|
|
179
193
|
const site = await getModel('SumbaSite').findOneRecord({ query: { alias: 'default' } }, { noMagic: true })
|
|
180
194
|
const user = await getModel('SumbaUser').findOneRecord({ query: { username: 'admin', siteId: site.id } }, { noMagic: true })
|
|
@@ -182,6 +196,84 @@ async function factory (pkgName) {
|
|
|
182
196
|
}
|
|
183
197
|
}
|
|
184
198
|
|
|
199
|
+
populateRouteGuards = async () => {
|
|
200
|
+
const { isString, get, difference } = this.app.lib._
|
|
201
|
+
const { pascalCase } = this.app.lib.aneka
|
|
202
|
+
const { eachPlugins, readConfig, breakNsPath } = this.app.bajo
|
|
203
|
+
const { getModel } = this.app.dobo
|
|
204
|
+
|
|
205
|
+
const allNs = this.app.getAllNs()
|
|
206
|
+
const sites = await getModel('SumbaSite').findAllRecord({ query: { status: 'ACTIVE' } }, { noMagic: true, dataOnly: true, fields: ['id', 'hostname'] })
|
|
207
|
+
|
|
208
|
+
const sanitize = (item, ns) => {
|
|
209
|
+
if (isString(item)) item = { path: item }
|
|
210
|
+
let [prefix, ...args] = item.path.split(':')
|
|
211
|
+
const neg = prefix[0] === '!'
|
|
212
|
+
if (neg) prefix = prefix.slice(1)
|
|
213
|
+
if (isEmpty(args)) {
|
|
214
|
+
args = prefix
|
|
215
|
+
prefix = null
|
|
216
|
+
} else args = args.join(':')
|
|
217
|
+
if (isEmpty(prefix)) prefix = ns
|
|
218
|
+
else {
|
|
219
|
+
const [_ns, subNs] = prefix.split('.')
|
|
220
|
+
if (subNs) prefix = `${ns}.${subNs}`
|
|
221
|
+
else if (!allNs.includes(_ns)) prefix = `${ns}.${_ns}`
|
|
222
|
+
else prefix = _ns
|
|
223
|
+
}
|
|
224
|
+
item.path = `${neg ? '!' : ''}${prefix}:${args}`
|
|
225
|
+
item._immutable = item._immutable ?? ['path']
|
|
226
|
+
if (neg) {
|
|
227
|
+
item.allTeams = true
|
|
228
|
+
item.teamIds = []
|
|
229
|
+
item._immutable = ['path', 'teamIds', 'allTeams']
|
|
230
|
+
}
|
|
231
|
+
return item
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const filterFn = item => {
|
|
235
|
+
let [ns] = (item.path.split(':')[0] ?? '').split('.')
|
|
236
|
+
if (ns[0] === '!') ns = ns.slice(1)
|
|
237
|
+
return allNs.includes(ns)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
for (const type of ['secure', 'anonymous']) {
|
|
241
|
+
const routes = []
|
|
242
|
+
// get it from <pluginDir>/extend/sumba/route/*
|
|
243
|
+
await eachPlugins(async function ({ file }) {
|
|
244
|
+
const { ns } = this
|
|
245
|
+
const items = (await readConfig(file)).map(item => sanitize(item, ns)).filter(filterFn)
|
|
246
|
+
routes.push(...items)
|
|
247
|
+
}, { glob: `route/${type}.*`, prefix: this.ns })
|
|
248
|
+
// get it from config
|
|
249
|
+
const items = get(this, `config.route.${type}`, []).map(item => {
|
|
250
|
+
if (isString(item)) item = { path: item }
|
|
251
|
+
const neg = item.path[0] === '!'
|
|
252
|
+
if (neg) item.path.slice(1)
|
|
253
|
+
const { fullNs } = breakNsPath(item.path)
|
|
254
|
+
if (neg) item.path = '!' + item.path
|
|
255
|
+
return sanitize(item, fullNs)
|
|
256
|
+
}).filter(filterFn)
|
|
257
|
+
routes.push(...items)
|
|
258
|
+
const paths = routes.map(item => item.path)
|
|
259
|
+
const model = getModel(pascalCase(`Sumba ${type} Guard`))
|
|
260
|
+
for (const site of sites) {
|
|
261
|
+
const query = { path: { $in: paths }, siteId: site.id }
|
|
262
|
+
const recs = await model.findAllRecord({ query }, { noMagic: true, dataOnly: true, fields: ['path', 'status'] })
|
|
263
|
+
const spaths = difference(paths, recs.map(rec => rec.path))
|
|
264
|
+
for (const path of spaths) {
|
|
265
|
+
const body = cloneDeep(routes.find(r => r.path === path))
|
|
266
|
+
body.status = 'ACTIVE'
|
|
267
|
+
body.siteId = site.id
|
|
268
|
+
await model.sanitizeFixture({ body, lookupValue: body })
|
|
269
|
+
try {
|
|
270
|
+
await model.createRecord(body, { noMagic: true, noReturn: true })
|
|
271
|
+
} catch (err) {}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
185
277
|
_getSetting = async (type, source) => {
|
|
186
278
|
const { defaultsDeep } = this.app.lib.aneka
|
|
187
279
|
const { get } = this.app.lib._
|
|
@@ -208,18 +300,10 @@ async function factory (pkgName) {
|
|
|
208
300
|
adminMenu = async (locals, req) => {
|
|
209
301
|
if (!this.app.waibuAdmin) return
|
|
210
302
|
const { getPluginPrefix } = this.app.waibu
|
|
303
|
+
const { findIndex } = this.app.lib._
|
|
211
304
|
const prefix = getPluginPrefix(this.ns)
|
|
212
305
|
const params = { action: 'list' }
|
|
213
306
|
const items = [{
|
|
214
|
-
title: 'xSite',
|
|
215
|
-
children: [
|
|
216
|
-
{ title: 'allSites', href: `waibuAdmin:/${prefix}/x/site/:action`, params },
|
|
217
|
-
{ title: 'xRouteGuard', href: `waibuAdmin:/${prefix}/x/route-guard/:action`, params },
|
|
218
|
-
{ title: 'xModelGuard', href: `waibuAdmin:/${prefix}/x/model-guard/:action`, params },
|
|
219
|
-
{ title: 'xAttribGuard', href: `waibuAdmin:/${prefix}/x/attrib-guard/:action`, params },
|
|
220
|
-
{ title: 'userSession', href: `waibuAdmin:/${prefix}/x/session/:action`, params }
|
|
221
|
-
]
|
|
222
|
-
}, {
|
|
223
307
|
title: 'manageSite',
|
|
224
308
|
children: [
|
|
225
309
|
{ title: 'siteProfile', href: `waibuAdmin:/${prefix}/site` },
|
|
@@ -242,7 +326,8 @@ async function factory (pkgName) {
|
|
|
242
326
|
}, {
|
|
243
327
|
title: 'permission',
|
|
244
328
|
children: [
|
|
245
|
-
{ title: '
|
|
329
|
+
{ title: 'secureGuard', href: `waibuAdmin:/${prefix}/secure-guard/:action`, params },
|
|
330
|
+
{ title: 'anonymousGuard', href: `waibuAdmin:/${prefix}/anonymous-guard/:action`, params },
|
|
246
331
|
{ title: 'modelGuard', href: `waibuAdmin:/${prefix}/model-guard/:action`, params },
|
|
247
332
|
{ title: 'attribGuard', href: `waibuAdmin:/${prefix}/attrib-guard/:action`, params }
|
|
248
333
|
]
|
|
@@ -260,9 +345,23 @@ async function factory (pkgName) {
|
|
|
260
345
|
{ title: 'manageDownload', href: `waibuAdmin:/${prefix}/download/:action`, params }
|
|
261
346
|
]
|
|
262
347
|
}]
|
|
348
|
+
const sessionMenu = { title: 'userSession', href: `waibuAdmin:/${prefix}/x/session/:action`, params }
|
|
349
|
+
const cacheMenu = { title: 'cacheStorage', href: `waibuAdmin:/${prefix}/x/cache/:action`, params }
|
|
350
|
+
if (this.config.multiSite.enabled) {
|
|
351
|
+
items.unshift({
|
|
352
|
+
title: 'xSite',
|
|
353
|
+
children: [
|
|
354
|
+
{ title: 'allSites', href: `waibuAdmin:/${prefix}/x/site/:action`, params },
|
|
355
|
+
sessionMenu
|
|
356
|
+
]
|
|
357
|
+
})
|
|
358
|
+
} else {
|
|
359
|
+
const idx = findIndex(items, i => i.title === 'misc')
|
|
360
|
+
if (idx > -1) items[idx].children.push(sessionMenu)
|
|
361
|
+
}
|
|
263
362
|
if (this.app.bajoCache) {
|
|
264
|
-
const
|
|
265
|
-
|
|
363
|
+
const idx = findIndex(items, i => i.title === this.config.multiSite.enabled ? 'xSite' : 'misc')
|
|
364
|
+
if (idx > -1)items[idx].children.push(cacheMenu)
|
|
266
365
|
}
|
|
267
366
|
return items
|
|
268
367
|
}
|
|
@@ -377,31 +476,11 @@ async function factory (pkgName) {
|
|
|
377
476
|
return true
|
|
378
477
|
}
|
|
379
478
|
|
|
380
|
-
|
|
381
|
-
const { includes } = this.app.lib.aneka
|
|
479
|
+
checkRouteGuard = (guards, paths) => {
|
|
382
480
|
const { outmatch } = this.app.lib
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
for (const path of paths) {
|
|
387
|
-
if (matchPath(path)) {
|
|
388
|
-
if (item.methods.includes(req.method)) {
|
|
389
|
-
if (includes(teamIds, item.teamIds)) return item
|
|
390
|
-
if (teamIds.length === 0) return item
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
checkPathsByGuard = ({ guards, paths }) => {
|
|
398
|
-
const { outmatch } = this.app.lib
|
|
399
|
-
const matcher = outmatch(guards)
|
|
400
|
-
let guarded
|
|
401
|
-
for (const path of paths) {
|
|
402
|
-
if (!guarded) guarded = matcher(path)
|
|
403
|
-
}
|
|
404
|
-
return guarded
|
|
481
|
+
const all = guards.map(item => item.path)
|
|
482
|
+
const isMatch = outmatch(all)
|
|
483
|
+
return paths.find(isMatch)
|
|
405
484
|
}
|
|
406
485
|
|
|
407
486
|
signout = async ({ req, reply, reason }) => {
|
|
@@ -545,81 +624,56 @@ async function factory (pkgName) {
|
|
|
545
624
|
return await hash(item, this.config.auth.common.apiKey.algo)
|
|
546
625
|
}
|
|
547
626
|
|
|
548
|
-
_fetchGuards = async (type
|
|
627
|
+
_fetchGuards = async (type) => {
|
|
549
628
|
const { getModel } = this.app.dobo
|
|
550
629
|
const options = { noMagic: true, noCache: true, noDriverHook: true, dataOnly: true }
|
|
551
630
|
const filter = { query: { status: 'ACTIVE' } }
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
}
|
|
631
|
+
const results = await getModel(`Sumba${type}Guard`).findAllRecord(filter, options)
|
|
632
|
+
return results.map(result => {
|
|
633
|
+
result.teamIds = result.teamIds.map(item => item + '')
|
|
634
|
+
return result
|
|
635
|
+
})
|
|
557
636
|
}
|
|
558
637
|
|
|
559
|
-
|
|
638
|
+
_getGuards = (inputs = []) => {
|
|
560
639
|
const { routePath } = this.app.waibu
|
|
561
640
|
const { orderBy } = this.app.lib._
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
result
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
return this.routeGuards
|
|
641
|
+
const normal = orderBy(inputs.filter(input => input.path[0] !== '!').map(result => {
|
|
642
|
+
result.path = routePath(result.path)
|
|
643
|
+
return result
|
|
644
|
+
}), ['weight', 'path'], ['desc', 'asc'])
|
|
645
|
+
const inverse = orderBy(inputs.filter(input => input.path[0] === '!').map(result => {
|
|
646
|
+
result.path = routePath(result.path)
|
|
647
|
+
return result
|
|
648
|
+
}), ['weight', 'path'], ['desc', 'asc'])
|
|
649
|
+
return [...normal, ...inverse]
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
getAnonymousGuards = async (reread) => {
|
|
653
|
+
if (!reread) return this.anonymousGuards
|
|
654
|
+
const guards = await this._fetchGuards('Anonymous')
|
|
655
|
+
this.anonymousGuards = this._getGuards(guards)
|
|
656
|
+
return this.anonymousGuards
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
getSecureGuards = async (reread) => {
|
|
660
|
+
if (!reread) return this.secureGuards
|
|
661
|
+
const guards = await this._fetchGuards('Secure')
|
|
662
|
+
this.secureGuards = this._getGuards(guards)
|
|
663
|
+
return this.secureGuards
|
|
586
664
|
}
|
|
587
665
|
|
|
588
666
|
getModelGuards = async (reread) => {
|
|
589
|
-
const { isSet } = this.app.lib.aneka
|
|
590
667
|
if (!reread) return this.modelGuards
|
|
591
668
|
|
|
592
|
-
|
|
593
|
-
for (const type of ['global', 'local']) {
|
|
594
|
-
this.modelGuards[type] = result[type].filter(item => (!isEmpty(item.value)) && (!isEmpty(item.models))).map(item => {
|
|
595
|
-
item.siteIds = item.siteIds ?? []
|
|
596
|
-
if (item.siteIds.length === 0) item.siteIds = [...result.siteIds]
|
|
597
|
-
if (item.siteId) item.siteIds = uniq([...item.siteIds, item.siteId].filter(i => isSet(i)).map(i => i + ''))
|
|
598
|
-
item.teamIds = (item.teamIds ?? []).filter(i => isSet(i)).map(i => i + '')
|
|
599
|
-
delete item.siteId
|
|
600
|
-
return item
|
|
601
|
-
})
|
|
602
|
-
}
|
|
669
|
+
this.modelGuards = await this._fetchGuards('Model')
|
|
603
670
|
return this.modelGuards
|
|
604
671
|
}
|
|
605
672
|
|
|
606
673
|
getAttribGuards = async (reread) => {
|
|
607
|
-
const { isSet } = this.app.lib.aneka
|
|
608
674
|
if (!reread) return this.attribGuards
|
|
609
675
|
|
|
610
|
-
|
|
611
|
-
for (const type of ['global', 'local']) {
|
|
612
|
-
this.attribGuards[type] = result[type].map(item => {
|
|
613
|
-
item.siteIds = item.siteIds ?? []
|
|
614
|
-
if (item.siteIds.length === 0) item.siteIds = [...result.siteIds]
|
|
615
|
-
if (item.siteId) item.siteIds = uniq([...item.siteIds, item.siteId].filter(i => isSet(i)).map(i => i + ''))
|
|
616
|
-
item.teamIds = (item.teamIds ?? []).filter(i => isSet(i)).map(i => i + '')
|
|
617
|
-
item.models = item.models ?? []
|
|
618
|
-
item.hiddenCols = item.hiddenCols ?? []
|
|
619
|
-
delete item.siteId
|
|
620
|
-
return item
|
|
621
|
-
})
|
|
622
|
-
}
|
|
676
|
+
this.attribGuards = await this._fetchGuards('Model')
|
|
623
677
|
return this.attribGuards
|
|
624
678
|
}
|
|
625
679
|
|
|
@@ -628,10 +682,160 @@ async function factory (pkgName) {
|
|
|
628
682
|
return asValue ? values.map(item => ({ value: item, text: item })) : values
|
|
629
683
|
}
|
|
630
684
|
|
|
685
|
+
pathsToCheck = (req) => {
|
|
686
|
+
const { uniq, without } = this.app.lib._
|
|
687
|
+
const items = [req.routeOptions.url, req.url].map(url => url.split('?')[0].split('#')[0])
|
|
688
|
+
return uniq(without(items, undefined, null))
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
checkIconset = async (req, reply) => {
|
|
692
|
+
const { get, isString } = this.app.lib._
|
|
693
|
+
const mpa = this.app.waibuMpa
|
|
694
|
+
|
|
695
|
+
if (!req.site) return
|
|
696
|
+
const siteIconset = get(req, 'site.setting.waibuMpa.iconset')
|
|
697
|
+
req.iconset = siteIconset ?? get(mpa, 'config.iconset.set', 'default')
|
|
698
|
+
const hiconset = req.headers['x-iconset']
|
|
699
|
+
if (isString(hiconset) && mpa.getIconset(hiconset)) req.iconset = hiconset
|
|
700
|
+
req.iconset = req.iconset ?? 'default'
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
checkTheme = async (req, reply) => {
|
|
704
|
+
const { get, isString } = this.app.lib._
|
|
705
|
+
const mpa = this.app.waibuMpa
|
|
706
|
+
|
|
707
|
+
if (!req.site) return
|
|
708
|
+
const siteTheme = get(req, 'site.setting.waibuMpa.theme')
|
|
709
|
+
req.theme = siteTheme ?? get(mpa, 'config.theme.set', 'default')
|
|
710
|
+
const htheme = req.headers['x-theme']
|
|
711
|
+
if (isString(htheme) && mpa.getTheme(htheme)) req.theme = htheme
|
|
712
|
+
req.theme = req.theme ?? 'default'
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
checkTeam = async (req, reply) => {
|
|
716
|
+
const { includes } = this.app.lib.aneka
|
|
717
|
+
const { outmatch } = this.app.lib
|
|
718
|
+
|
|
719
|
+
if (req.user.isAdmin) return
|
|
720
|
+
if (req.routeOptions.config.xSite && req.user.isXSiteAdmin) return
|
|
721
|
+
|
|
722
|
+
const teamIds = req.user.teams.map(item => item.id + '')
|
|
723
|
+
if (req.user.teams.map(item => item.alias).length === 0) throw this.error('accessDenied', { statusCode: 403 })
|
|
724
|
+
|
|
725
|
+
const paths = this.pathsToCheck(req)
|
|
726
|
+
const results = (await this.getSecureGuards()).filter(item => {
|
|
727
|
+
if (item.siteId !== req.site.id + '' || item.path[0] === '!') return false
|
|
728
|
+
return paths.some(outmatch([item.path]))
|
|
729
|
+
})
|
|
730
|
+
for (const result of results) {
|
|
731
|
+
if (result.allTeams) continue
|
|
732
|
+
if (!includes(teamIds, result.teamIds)) throw this.error('accessDenied', { statusCode: 403 })
|
|
733
|
+
}
|
|
734
|
+
// passed
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
checkUser = async (req, reply, source) => {
|
|
738
|
+
const { merge, isEmpty, camelCase, get } = this.app.lib._
|
|
739
|
+
const { routePath } = this.app.waibu
|
|
740
|
+
const userId = get(req, 'session.userId')
|
|
741
|
+
|
|
742
|
+
const setUser = async () => {
|
|
743
|
+
if (!userId) return
|
|
744
|
+
try {
|
|
745
|
+
const user = await this.getUserById(userId, req)
|
|
746
|
+
if (user) req.user = user
|
|
747
|
+
else req.session.userId = null
|
|
748
|
+
} catch (err) {
|
|
749
|
+
console.log(err)
|
|
750
|
+
req.session.userId = null
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (req.session) req.session.siteId = req.site.id
|
|
755
|
+
|
|
756
|
+
const webApp = get(req, 'routeOptions.config.webApp', 'waibu')
|
|
757
|
+
if (!req.routeOptions.url) {
|
|
758
|
+
if (!req.session) return
|
|
759
|
+
await setUser()
|
|
760
|
+
return
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const paths = this.pathsToCheck(req)
|
|
764
|
+
let guards = (await this.getAnonymousGuards()).filter(item => item.siteId === req.site.id + '')
|
|
765
|
+
const anonymous = this.checkRouteGuard(guards, paths)
|
|
766
|
+
if (anonymous) {
|
|
767
|
+
if (!userId) return false
|
|
768
|
+
req.session.ref = req.url
|
|
769
|
+
return reply.redirectTo(routePath(this.config.redirect.signout))
|
|
770
|
+
}
|
|
771
|
+
guards = (await this.getSecureGuards()).filter(item => item.siteId === req.site.id + '')
|
|
772
|
+
const secure = this.checkRouteGuard(guards, paths)
|
|
773
|
+
if (!secure) {
|
|
774
|
+
if (userId) await setUser()
|
|
775
|
+
return false // regular, unguarded path. Not secure & not anonymous path
|
|
776
|
+
}
|
|
777
|
+
if (userId) {
|
|
778
|
+
await setUser()
|
|
779
|
+
return secure
|
|
780
|
+
}
|
|
781
|
+
const silentOnError = this.config.auth[webApp].silentOnError ?? this.config.auth.common.silentOnError
|
|
782
|
+
const payload = silentOnError ? { noContent: true } : undefined
|
|
783
|
+
const authMethods = this.config.auth[webApp].methods ?? []
|
|
784
|
+
if (isEmpty(authMethods)) throw this.error('noAuthMethod', merge({ statusCode: 500 }, payload))
|
|
785
|
+
let success
|
|
786
|
+
for (const m of authMethods) {
|
|
787
|
+
const handler = this[camelCase(`verify ${m}`)]
|
|
788
|
+
if (!handler) throw this.error('invalidAuthMethod%s', m, merge({ statusCode: 500 }, payload))
|
|
789
|
+
const check = await handler(req, reply, source, payload)
|
|
790
|
+
if (check) {
|
|
791
|
+
success = check
|
|
792
|
+
break
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (!success) throw this.error('accessDeniedNoAuth', merge({ statusCode: 403 }, payload))
|
|
796
|
+
return secure
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
checkXSite = async (req, reply) => {
|
|
800
|
+
const { get } = this.app.lib._
|
|
801
|
+
if (!this.config.multiSite.enabled) return
|
|
802
|
+
if (!get(req, 'routeOptions.config.xSite')) return
|
|
803
|
+
if (!get(req, 'user.isXSiteAdmin')) throw this.error('accessDenied', { statusCode: 403 })
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
parseNsSettings = (ns, setting, items) => {
|
|
807
|
+
const { trim, set, isPlainObject, isArray, isEmpty, find } = this.app.lib._
|
|
808
|
+
const { parseObject, dayjs } = this.app.lib
|
|
809
|
+
|
|
810
|
+
for (const item of items) {
|
|
811
|
+
if (item.ns === '_var' || ns === '_var') continue
|
|
812
|
+
let value = trim([item.value] ?? '')
|
|
813
|
+
if (value[0] === '#' && value[value.length - 1] === '#') {
|
|
814
|
+
const val = value.slice(1, -1)
|
|
815
|
+
const newValue = find(items, { ns: '_var', key: val })
|
|
816
|
+
if (newValue) value = newValue.value
|
|
817
|
+
}
|
|
818
|
+
if (['[', '{'].includes(value[0]) && [']', '}'].includes(value[value.length - 1])) {
|
|
819
|
+
try {
|
|
820
|
+
value = parseObject(JSON.parse(value))
|
|
821
|
+
} catch (err) {}
|
|
822
|
+
} else if (Number(value)) value = Number(value)
|
|
823
|
+
else if (['true', 'false'].includes(value)) value = value === 'true'
|
|
824
|
+
else if (item.key.endsWith('$in')) value = value.split('\n').map(v => v.trim())
|
|
825
|
+
else {
|
|
826
|
+
const dt = dayjs(value)
|
|
827
|
+
if (dt.isValid()) value = dt.toDate()
|
|
828
|
+
}
|
|
829
|
+
if ((isPlainObject(value) || isArray(value)) && isEmpty(value)) continue
|
|
830
|
+
set(setting, `${ns}.${item.key}`, value)
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
631
834
|
createNewSite = createNewSite
|
|
632
835
|
removeSite = removeSite
|
|
633
836
|
getSite = getSite
|
|
634
837
|
getUserById = getUserById
|
|
838
|
+
|
|
635
839
|
getUserByToken = getUserByToken
|
|
636
840
|
getUserByUsernamePassword = getUserByUsernamePassword
|
|
637
841
|
}
|
package/lib/get-site.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { parseNsSettings } from './util.js'
|
|
2
|
-
|
|
3
1
|
async function getSite (input, byId = false) {
|
|
4
2
|
const { omit } = this.app.lib._
|
|
5
3
|
const { set: setCache, get: getCache } = this.app.bajoCache ?? {}
|
|
@@ -21,7 +19,7 @@ async function getSite (input, byId = false) {
|
|
|
21
19
|
const item = get(this, `app.${ns}.config.siteSetting`)
|
|
22
20
|
if (isSet(item)) defSetting[ns] = item
|
|
23
21
|
const items = filter(all, { ns })
|
|
24
|
-
parseNsSettings
|
|
22
|
+
this.parseNsSettings(ns, nsSetting, items)
|
|
25
23
|
}
|
|
26
24
|
site.setting = defaultsDeep({}, nsSetting, defSetting)
|
|
27
25
|
// additional fields
|
package/lib/get-user.js
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
import { parseNsSettings } from './util.js'
|
|
2
|
-
|
|
3
1
|
export async function mergeTeam (user, req) {
|
|
4
2
|
const { map, pick } = this.app.lib._
|
|
5
3
|
const { getModel } = this.app.dobo
|
|
6
4
|
user.teams = []
|
|
7
|
-
const query = { userId: user.id, siteId: user.siteId }
|
|
5
|
+
const query = { userId: user.id, siteId: user.siteId, status: 'ACTIVE' }
|
|
8
6
|
let mdl = getModel('SumbaTeamUser')
|
|
9
7
|
const userTeam = await mdl.findAllRecord({ query })
|
|
10
8
|
if (userTeam.length === 0) return
|
|
11
9
|
delete query.userId
|
|
12
10
|
query.id = { $in: map(userTeam, 'teamId') }
|
|
13
|
-
query.status = 'ENABLED'
|
|
14
11
|
mdl = getModel('SumbaTeam')
|
|
15
12
|
const teams = await mdl.findAllRecord({ query })
|
|
16
13
|
if (teams.length > 0) {
|
|
17
14
|
delete query.id
|
|
18
15
|
delete query.status
|
|
19
|
-
query.siteId = user.siteId
|
|
20
16
|
query.teamId = { $in: teams.map(t => t.id + '') }
|
|
21
17
|
mdl = getModel('SumbaTeamSetting')
|
|
22
18
|
const items = await mdl.findAllRecord({ query })
|
|
@@ -25,7 +21,7 @@ export async function mergeTeam (user, req) {
|
|
|
25
21
|
const item = pick(team, ['id', 'alias'])
|
|
26
22
|
item.setting = {}
|
|
27
23
|
for (const ns of names) {
|
|
28
|
-
parseNsSettings
|
|
24
|
+
this.parseNsSettings(ns, item.setting, items.filter(s => s.teamId === (team.id + '')))
|
|
29
25
|
}
|
|
30
26
|
user.teams.push(item)
|
|
31
27
|
}
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-06-11
|
|
4
|
+
|
|
5
|
+
- [2.31.0] Add ```status``` field on ```SumbaTeamUser```
|
|
6
|
+
- [2.31.0] Remove ```util.js``` as all functions now moved to base class
|
|
7
|
+
|
|
8
|
+
## 2026-06-10
|
|
9
|
+
|
|
10
|
+
- [2.30.0] Refactoring all guards
|
|
11
|
+
- [2.30.0] Feature ```sumba:status``` now by default using ```ACTIVE``` and ```INACTIVE``` states
|
|
12
|
+
- [2.30.0] Bug in ```multiSite``` handling
|
|
13
|
+
- [2.30.0] Add ```populateRouteGuards()```
|
|
14
|
+
- [2.30.0] Bug fix in ```hook.js```
|
|
15
|
+
|
|
3
16
|
## 2026-06-03
|
|
4
17
|
|
|
5
18
|
- [2.29.0] Some models with property ```values``` now use the newly introduced function values
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
const routes = [
|
|
2
|
-
'sumba:/your-stuff/**/*',
|
|
3
|
-
'sumba:/signout',
|
|
4
|
-
'sumba:/help/trouble-tickets/**/*',
|
|
5
|
-
'sumba.restapi:/user/api-key',
|
|
6
|
-
'sumba.restapi:/your-stuff/**/*',
|
|
7
|
-
'sumba.restapi:/manage/**/*',
|
|
8
|
-
'~sumba:/user/**/*',
|
|
9
|
-
'~sumba:/signin',
|
|
10
|
-
'~sumba.restapi:/user/access-token/**/*'
|
|
11
|
-
]
|
|
12
|
-
|
|
13
|
-
async function routeGuard () {
|
|
14
|
-
return routes.map(r => {
|
|
15
|
-
const anonymous = r[0] === '~'
|
|
16
|
-
return {
|
|
17
|
-
path: anonymous ? r.slice(1) : r,
|
|
18
|
-
anonymous,
|
|
19
|
-
status: 'ACTIVE'
|
|
20
|
-
}
|
|
21
|
-
})
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export default routeGuard
|