sumba 2.30.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.
@@ -1,4 +1,3 @@
1
- import { checkUserId, checkTeam, checkXSite, checkTheme, checkIconset } from '../../lib/util.js'
2
1
  import { removeRefs } from '../../lib/remove-site.js'
3
2
  import { getAllFixtures, createRefs } from '../../lib/create-new-site.js'
4
3
  import path from 'path'
@@ -338,30 +337,30 @@ async function hook () {
338
337
  level: 10,
339
338
  name: 'waibuMpa:preParsing',
340
339
  handler: async function (req, reply) {
341
- await checkTheme.call(this, req, reply)
342
- await checkIconset.call(this, req, reply)
343
- const secure = await checkUserId.call(this, req, reply, 'waibuMpa')
340
+ await this.checkTheme(req, reply)
341
+ await this.checkIconset(req, reply)
342
+ const secure = await this.checkUser(req, reply, 'waibuMpa')
344
343
  if (!secure) return
345
- await checkTeam.call(this, req, reply)
346
- await checkXSite.call(this, req, reply)
344
+ await this.checkTeam(req, reply)
345
+ await this.checkXSite(req, reply)
347
346
  }
348
347
  }, {
349
348
  level: 10,
350
349
  name: 'waibuRestApi:preParsing',
351
350
  handler: async function (req, reply) {
352
- const secure = await checkUserId.call(this, req, reply, 'waibuRestApi')
351
+ const secure = await this.checkUser(req, reply, 'waibuRestApi')
353
352
  if (!secure) return
354
- await checkTeam.call(this, req, reply)
355
- await checkXSite.call(this, req, reply)
353
+ await this.checkTeam(req, reply)
354
+ await this.checkXSite(req, reply)
356
355
  }
357
356
  }, {
358
357
  level: 10,
359
358
  name: 'waibuStatic:preParsing',
360
359
  handler: async function (req, reply) {
361
- const secure = await checkUserId.call(this, req, reply, 'waibuStatic')
360
+ const secure = await this.checkUser(req, reply, 'waibuStatic')
362
361
  if (!secure) return
363
- await checkTeam.call(this, req, reply)
364
- await checkXSite.call(this, req, reply)
362
+ await this.checkTeam(req, reply)
363
+ await this.checkXSite(req, reply)
365
364
  }
366
365
  }, {
367
366
  name: 'waibu:afterAppBoot',
@@ -1,4 +1,9 @@
1
- import { latLngHook } from '../../../lib/util.js'
1
+ export async function latLngHook (body, options) {
2
+ const { isSet } = this.app.lib.aneka
3
+ const { round } = this.app.lib.aneka
4
+ if (!isSet(body[options.field])) return
5
+ body[options.field] = round(body[options.field], options.scale)
6
+ }
2
7
 
3
8
  async function latLng (opts = {}) {
4
9
  const { merge } = this.app.lib._
@@ -1,4 +1,4 @@
1
- import { latLngHook } from '../../../lib/util.js'
1
+ import { latLngHook } from './lat-lng.js'
2
2
 
3
3
  async function lat (opts = {}) {
4
4
  opts.field = opts.field ?? 'lat'
@@ -1,4 +1,4 @@
1
- import { latLngHook } from '../../../lib/util.js'
1
+ import { latLngHook } from './lat-lng.js'
2
2
 
3
3
  async function lng (opts = {}) {
4
4
  opts.field = opts.field ?? 'lng'
@@ -125,6 +125,7 @@
125
125
  "sumba:siteId",
126
126
  "sumba:userId",
127
127
  "sumba:teamId",
128
+ "sumba:status",
128
129
  "dobo:immutable"
129
130
  ]
130
131
  }, {
@@ -2,7 +2,7 @@ async function teamUser () {
2
2
  return {
3
3
  common: {
4
4
  layout: [
5
- { name: 'meta', fields: ['id', 'createdAt', 'updatedAt'] },
5
+ { name: 'meta', fields: ['id:3-md', 'createdAt:3-md', 'updatedAt:3-md', 'status:3-md'] },
6
6
  { name: 'general', fields: ['userId:6-md', 'teamId:6-md'] }
7
7
  ],
8
8
  widget: {
@@ -20,7 +20,7 @@ async function teamUser () {
20
20
  },
21
21
  view: {
22
22
  list: {
23
- fields: ['userId', 'teamId', 'createdAt', 'updatedAt'],
23
+ fields: ['userId', 'teamId', 'status', 'createdAt', 'updatedAt'],
24
24
  stat: {
25
25
  aggregate: [
26
26
  { fields: ['userId'], group: 'userId', aggregate: ['count'] },
package/index.js CHANGED
@@ -682,6 +682,155 @@ async function factory (pkgName) {
682
682
  return asValue ? values.map(item => ({ value: item, text: item })) : values
683
683
  }
684
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
+
685
834
  createNewSite = createNewSite
686
835
  removeSite = removeSite
687
836
  getSite = getSite
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 = 'ACTIVE'
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.30.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,10 @@
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
+
3
8
  ## 2026-06-10
4
9
 
5
10
  - [2.30.0] Refactoring all guards
package/lib/util.js DELETED
@@ -1,162 +0,0 @@
1
- export function parseNsSettings (ns, setting, items) {
2
- const { trim, set, isPlainObject, isArray, isEmpty, find } = this.app.lib._
3
- const { parseObject, dayjs } = this.app.lib
4
-
5
- for (const item of items) {
6
- if (item.ns === '_var' || ns === '_var') continue
7
- let value = trim([item.value] ?? '')
8
- if (value[0] === '#' && value[value.length - 1] === '#') {
9
- const val = value.slice(1, -1)
10
- const newValue = find(items, { ns: '_var', key: val })
11
- if (newValue) value = newValue.value
12
- }
13
- if (['[', '{'].includes(value[0]) && [']', '}'].includes(value[value.length - 1])) {
14
- try {
15
- value = parseObject(JSON.parse(value))
16
- } catch (err) {}
17
- } else if (Number(value)) value = Number(value)
18
- else if (['true', 'false'].includes(value)) value = value === 'true'
19
- else if (item.key.endsWith('$in')) value = value.split('\n').map(v => v.trim())
20
- else {
21
- const dt = dayjs(value)
22
- if (dt.isValid()) value = dt.toDate()
23
- }
24
- if ((isPlainObject(value) || isArray(value)) && isEmpty(value)) continue
25
- set(setting, `${ns}.${item.key}`, value)
26
- }
27
- }
28
-
29
- export function pathsToCheck (req) {
30
- const { uniq, without } = this.app.lib._
31
- const items = [req.routeOptions.url, req.url].map(url => url.split('?')[0].split('#')[0])
32
- return uniq(without(items, undefined, null))
33
- }
34
-
35
- export async function checkIconset (req, reply) {
36
- const { get, isString } = this.app.lib._
37
- const mpa = this.app.waibuMpa
38
-
39
- if (!req.site) return
40
- const siteIconset = get(req, 'site.setting.waibuMpa.iconset')
41
- req.iconset = siteIconset ?? get(mpa, 'config.iconset.set', 'default')
42
- const hiconset = req.headers['x-iconset']
43
- if (isString(hiconset) && mpa.getIconset(hiconset)) req.iconset = hiconset
44
- req.iconset = req.iconset ?? 'default'
45
- }
46
-
47
- export async function checkTheme (req, reply) {
48
- const { get, isString } = this.app.lib._
49
- const mpa = this.app.waibuMpa
50
-
51
- if (!req.site) return
52
- const siteTheme = get(req, 'site.setting.waibuMpa.theme')
53
- req.theme = siteTheme ?? get(mpa, 'config.theme.set', 'default')
54
- const htheme = req.headers['x-theme']
55
- if (isString(htheme) && mpa.getTheme(htheme)) req.theme = htheme
56
- req.theme = req.theme ?? 'default'
57
- }
58
-
59
- export async function checkTeam (req, reply) {
60
- const { includes } = this.app.lib.aneka
61
- const { outmatch } = this.app.lib
62
-
63
- if (req.user.isAdmin) return
64
- if (req.routeOptions.config.xSite && req.user.isXSiteAdmin) return
65
-
66
- const teamIds = req.user.teams.map(item => item.id + '')
67
- if (req.user.teams.map(item => item.alias).length === 0) throw this.error('accessDenied', { statusCode: 403 })
68
-
69
- const paths = pathsToCheck.call(this, req)
70
- const results = (await this.getSecureGuards()).filter(item => {
71
- if (item.siteId !== req.site.id + '' || item.path[0] === '!') return false
72
- return paths.some(outmatch([item.path]))
73
- })
74
- for (const result of results) {
75
- if (result.allTeams) continue
76
- if (!includes(teamIds, result.teamIds)) throw this.error('accessDenied', { statusCode: 403 })
77
- }
78
- // passed
79
- }
80
-
81
- export async function checkUserId (req, reply, source) {
82
- const { merge, isEmpty, camelCase, get } = this.app.lib._
83
- const { routePath } = this.app.waibu
84
- const userId = get(req, 'session.userId')
85
-
86
- const setUser = async () => {
87
- if (!userId) return
88
- try {
89
- const user = await this.getUserById(userId, req)
90
- if (user) req.user = user
91
- else req.session.userId = null
92
- } catch (err) {
93
- console.log(err)
94
- req.session.userId = null
95
- }
96
- }
97
-
98
- if (req.session) req.session.siteId = req.site.id
99
-
100
- const webApp = get(req, 'routeOptions.config.webApp', 'waibu')
101
- if (!req.routeOptions.url) {
102
- if (!req.session) return
103
- await setUser()
104
- return
105
- }
106
-
107
- const paths = pathsToCheck.call(this, req)
108
- let guards = (await this.getAnonymousGuards()).filter(item => item.siteId === req.site.id + '')
109
- const anonymous = this.checkRouteGuard(guards, paths)
110
- if (anonymous) {
111
- if (!userId) return false
112
- req.session.ref = req.url
113
- return reply.redirectTo(routePath(this.config.redirect.signout))
114
- }
115
- guards = (await this.getSecureGuards()).filter(item => item.siteId === req.site.id + '')
116
- const secure = this.checkRouteGuard(guards, paths)
117
- if (!secure) {
118
- if (userId) await setUser()
119
- return false // regular, unguarded path. Not secure & not anonymous path
120
- }
121
- if (userId) {
122
- await setUser()
123
- return secure
124
- }
125
- const silentOnError = this.config.auth[webApp].silentOnError ?? this.config.auth.common.silentOnError
126
- const payload = silentOnError ? { noContent: true } : undefined
127
- const authMethods = this.config.auth[webApp].methods ?? []
128
- if (isEmpty(authMethods)) throw this.error('noAuthMethod', merge({ statusCode: 500 }, payload))
129
- let success
130
- for (const m of authMethods) {
131
- const handler = this[camelCase(`verify ${m}`)]
132
- if (!handler) throw this.error('invalidAuthMethod%s', m, merge({ statusCode: 500 }, payload))
133
- const check = await handler(req, reply, source, payload)
134
- if (check) {
135
- success = check
136
- break
137
- }
138
- }
139
- if (!success) throw this.error('accessDeniedNoAuth', merge({ statusCode: 403 }, payload))
140
- return secure
141
- }
142
-
143
- export async function latLngHook (body, options) {
144
- const { isSet } = this.app.lib.aneka
145
- const { round } = this.app.lib.aneka
146
- if (!isSet(body[options.field])) return
147
- body[options.field] = round(body[options.field], options.scale)
148
- }
149
-
150
- /**
151
- * If current route is an inter site route user is an inter site admin, then let it passed
152
- *
153
- * @param {Object} req - Request object
154
- * @param {Object} reply - Reply object
155
- * @returns
156
- */
157
- export async function checkXSite (req, reply) {
158
- const { get } = this.app.lib._
159
- if (!this.config.multiSite.enabled) return
160
- if (!get(req, 'routeOptions.config.xSite')) return
161
- if (!get(req, 'user.isXSiteAdmin')) throw this.error('accessDenied', { statusCode: 403 })
162
- }