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.
Files changed (38) hide show
  1. package/extend/bajo/hook.js +48 -51
  2. package/extend/bajo/intl/en-US.json +5 -5
  3. package/extend/bajo/intl/id.json +5 -5
  4. package/extend/dobo/feature/lat-lng.js +6 -1
  5. package/extend/dobo/feature/lat.js +1 -1
  6. package/extend/dobo/feature/lng.js +1 -1
  7. package/extend/dobo/feature/status.js +3 -1
  8. package/extend/dobo/fixture/site.json +1 -1
  9. package/extend/dobo/fixture/team-user.json +1 -1
  10. package/extend/dobo/fixture/team.json +2 -2
  11. package/extend/dobo/fixture/user.json +1 -1
  12. package/extend/dobo/model.js +82 -122
  13. package/extend/dobo/model.json +3 -9
  14. package/extend/sumba/route/anonymous.json +5 -0
  15. package/extend/sumba/route/secure.json +8 -0
  16. package/extend/waibuDb/schema/anonymous-guard.js +15 -0
  17. package/extend/waibuDb/schema/{route-guard.js → secure-guard.js} +2 -2
  18. package/extend/waibuDb/schema/team-user.js +2 -2
  19. package/extend/waibuDb/schema/team.json +1 -1
  20. package/extend/waibuMpa/extend/waibuAdmin/route/{x/model-guard → anonymous-guard}/@action.js +2 -3
  21. package/extend/waibuMpa/extend/waibuAdmin/route/{route-guard → secure-guard}/@action.js +2 -2
  22. package/extend/waibuMpa/route/access-token.js +3 -1
  23. package/extend/waibuRestApi/route/manage/anonymous-guard/model-builder.json +4 -0
  24. package/extend/waibuRestApi/route/manage/{route-guard → secure-guard}/model-builder.json +1 -1
  25. package/index.js +302 -98
  26. package/lib/get-site.js +1 -3
  27. package/lib/get-user.js +2 -6
  28. package/package.json +1 -1
  29. package/wiki/CHANGES.md +13 -0
  30. package/extend/dobo/fixture/x-route-guard.js +0 -24
  31. package/extend/waibuDb/schema/x-attrib-guard.js +0 -15
  32. package/extend/waibuDb/schema/x-model-guard.js +0 -15
  33. package/extend/waibuDb/schema/x-route-guard.js +0 -15
  34. package/extend/waibuMpa/extend/waibuAdmin/route/index.json +0 -3
  35. package/extend/waibuMpa/extend/waibuAdmin/route/x/attrib-guard/@action.js +0 -12
  36. package/extend/waibuMpa/extend/waibuAdmin/route/x/index.json +0 -3
  37. package/extend/waibuMpa/extend/waibuAdmin/route/x/route-guard/@action.js +0 -12
  38. package/lib/util.js +0 -201
@@ -1,11 +1,10 @@
1
1
  const action = {
2
2
  method: ['GET', 'POST'],
3
- title: 'xModelGuard',
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, 'SumbaXModelGuard', req, reply)
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: 'routeGuard',
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, 'SumbaRouteGuard', req, reply)
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
- if (!req.user) return ''
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
  }
@@ -0,0 +1,4 @@
1
+ {
2
+ "model": "SumbaAnonymousGuard",
3
+ "disabled": ["create", "update", "remove"]
4
+ }
@@ -1,4 +1,4 @@
1
1
  {
2
- "model": "SumbaRouteGuard",
2
+ "model": "SumbaSecureGuard",
3
3
  "disabled": ["create", "update", "remove"]
4
4
  }
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, uniq, isEmpty } = this.app.lib._
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.routeGuards = { local: [], global: [] }
159
- this.modelGuards = { local: [], global: [] }
160
- this.attribGuards = { local: [], global: [] }
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
- const { getModel } = this.app.dobo
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: 'routeGuard', href: `waibuAdmin:/${prefix}/route-guard/:action`, params },
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 item = items.find(i => i.title === 'xSite')
265
- item.children.push({ title: 'cacheStorage', href: `waibuAdmin:/${prefix}/x/cache/:action`, params })
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
- checkPathsByRoute = ({ req, paths = [], teamIds = [], guards = [] }) => {
381
- const { includes } = this.app.lib.aneka
479
+ checkRouteGuard = (guards, paths) => {
382
480
  const { outmatch } = this.app.lib
383
-
384
- for (const item of guards) {
385
- const matchPath = outmatch(item.path)
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 = 'Route') => {
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
- return {
553
- global: await getModel(`SumbaX${type}Guard`).findAllRecord(filter, options),
554
- local: await getModel(`Sumba${type}Guard`).findAllRecord(filter, options),
555
- siteIds: (await getModel('SumbaSite').findAllRecord(filter.options)).map(item => item.id + '')
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
- getRouteGuards = async (reread) => {
638
+ _getGuards = (inputs = []) => {
560
639
  const { routePath } = this.app.waibu
561
640
  const { orderBy } = this.app.lib._
562
- const { isSet } = this.app.lib.aneka
563
- if (!reread) return this.routeGuards
564
-
565
- const result = await this._fetchGuards('Route')
566
- for (const type of ['global', 'local']) {
567
- result[type] = result[type].map(item => {
568
- item.siteIds = item.siteIds ?? []
569
- if (item.siteIds.length === 0) item.siteIds = [...result.siteIds]
570
- if (item.siteId) item.siteIds = uniq([...item.siteIds, item.siteId].filter(i => isSet(i)).map(i => i + ''))
571
- item.teamIds = (item.teamIds ?? []).filter(i => isSet(i)).map(i => i + '')
572
- item.methods = item.methods ?? []
573
- delete item.siteId
574
- return item
575
- })
576
- this.routeGuards[type] = orderBy(result[type].filter(item => {
577
- try {
578
- item.path = routePath(item.path)
579
- return true
580
- } catch (err) {
581
- return false
582
- }
583
- }), ['weight', 'path'], ['desc', 'asc'])
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
- const result = await this._fetchGuards('Model')
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
- const result = await this._fetchGuards('Attrib')
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.call(this, ns, nsSetting, items)
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.call(this, ns, item.setting, items.filter(s => s.teamId === (team.id + '')))
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sumba",
3
- "version": "2.29.0",
3
+ "version": "2.31.0",
4
4
  "description": "Biz Suite for Bajo Framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
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
@@ -1,15 +0,0 @@
1
- async function xAttribGuard () {
2
- return {
3
- common: {
4
- widget: {
5
- siteIds: {
6
- attr: {
7
- refUrl: 'waibuAdmin:/{prefix}/site/details?id={id}'
8
- }
9
- }
10
- }
11
- }
12
- }
13
- }
14
-
15
- export default xAttribGuard