sumba 2.30.0 → 2.32.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'
@@ -22,8 +21,9 @@ async function clearCacheUser (id, result) {
22
21
  }
23
22
 
24
23
  async function applyModelGuard ({ model, q, teamIds, options }) {
25
- const { get, set, orderBy, without } = this.app.lib._
24
+ const { set, orderBy } = this.app.lib._
26
25
  const { includes } = this.app.lib.aneka
26
+ const { sanitizeByType } = this.app.dobo
27
27
  const { req } = options
28
28
  const results = []
29
29
 
@@ -32,24 +32,29 @@ async function applyModelGuard ({ model, q, teamIds, options }) {
32
32
  const filterFn = item => {
33
33
  const bySiteId = item.siteId === req.site.id + ''
34
34
  const byModel = item.models.includes(model.name)
35
- const byTeamId = includes(item.teamIds, teamIds)
36
35
  const byFields = fields.includes(item.field)
37
- return bySiteId && byModel && byTeamId && byFields
36
+ return bySiteId && byModel && byFields
38
37
  }
39
38
 
40
39
  const rules = orderBy(guards.filter(filterFn), ['field'])
41
40
  for (const field of fields) {
42
- let values = []
41
+ if (!model.getNonVirtualProperties(true).includes(field)) continue // or, should it throws exception instead?
42
+ const prop = model.getProperty(field)
43
43
  const items = rules.filter(item => item.field === field)
44
44
  for (const item of items) {
45
- if (item.behavior === 'NIN') values = without(values, ...item.value)
46
- else if (item.behavior === 'IN') values.push(...item.value)
45
+ const values = item.value.map(val => {
46
+ return sanitizeByType(val, prop.type, { strict: true, inputFormat: 'string', model: model.name })
47
+ })
48
+ const op = item.condition.toLowerCase()
49
+ let value
50
+ if (['in', 'nin'].includes(op)) value = set({}, '$' + op, values)
51
+ else if (op === 'between') value = { $gte: values[0], $lte: values[1] }
52
+ else value = set({}, '$' + op, values[0])
53
+ const teamOk = item.allTeams ? true : (item.teamIds.length === 0 ? false : includes(item.teamIds, teamIds))
54
+ if (!teamOk) throw this.error('_abortAction')
55
+ results.push(set({}, field, value))
47
56
  }
48
- if (values.length) results.push(set({}, field, { $in: values }))
49
57
  }
50
-
51
- const allowEmpty = get(this, `config.dobo.model.${model.name}.allowEmptyQuery`, true)
52
- if (results.length === 0 && !allowEmpty) throw this.error('_emptyColumnQuery') // signal driver to about with no result immediately
53
58
  q.$and.push(...results)
54
59
  }
55
60
 
@@ -63,20 +68,19 @@ export async function applyAttribGuard ({ model, teamIds, options }) {
63
68
  const filterFn = item => {
64
69
  const bySiteId = item.siteId === req.site.id + ''
65
70
  const byModel = item.models.includes(model.name)
66
- const byTeamId = includes(item.teamIds, teamIds)
71
+ const byTeamId = item.allTeams ? true : includes(item.teamIds, teamIds)
67
72
  return bySiteId && byModel && byTeamId
68
73
  }
69
-
70
- const item = guards.filter(filterFn)[0]
71
- if (item) results.push(...item.hiddenFields)
74
+ guards.filter(filterFn).forEach(item => results.push(...item.hiddenFields))
72
75
  options.hidden = options.hidden ?? []
73
- if (results.length > 0) options.hidden.push(...results)
76
+ options.hidden.push(...results)
74
77
  options.hidden = uniq(options.hidden)
75
78
  }
76
79
 
77
80
  async function rebuildFilter (model, filter = {}, options = {}) {
78
81
  const { isEmpty, get } = this.app.lib._
79
82
  const { req } = options
83
+ const allowEmpty = !get(this, `app.${model.ns}.config.sumba.allowEmptyQuery`, []).includes(model.name)
80
84
  const hasSiteId = model.hasProperty('siteId')
81
85
  const hasUserId = model.hasProperty('userId')
82
86
  const hasTeamId = model.hasProperty('teamId')
@@ -90,13 +94,17 @@ async function rebuildFilter (model, filter = {}, options = {}) {
90
94
  if (filter.query.$and) q.$and.push(...filter.query.$and)
91
95
  else q.$and.push(filter.query)
92
96
  }
93
- if (req.routeOptions.config.xSite) return
97
+ if (req.routeOptions.config.xSite) {
98
+ filter.query = q
99
+ return
100
+ }
94
101
  if (hasSiteId) q.$and.push({ siteId: req.site.id + '' })
95
102
  if (aliases.includes('administrator')) {
96
103
  filter.query = q
97
104
  return
98
105
  }
99
- if (!req.user) {
106
+ if (isEmpty(req.user)) {
107
+ if (q.$and.length === 0 && !allowEmpty) throw this.error('_emptyColumnQuery')
100
108
  filter.query = q
101
109
  return
102
110
  }
@@ -120,8 +128,9 @@ async function rebuildFilter (model, filter = {}, options = {}) {
120
128
  else q.$and.push(condTeamId)
121
129
  } else if (hasUserId) q.$and.push(condUserId)
122
130
 
123
- await applyModelGuard.call(this, { model, q, teamIds, options })
131
+ await applyModelGuard.call(this, { model, q, teamIds, options, allowEmpty })
124
132
  await applyAttribGuard.call(this, { model, teamIds, options })
133
+ if (q.$and.length === 0 && !allowEmpty) throw this.error('_emptyColumnQuery')
125
134
  filter.query = q
126
135
  }
127
136
 
@@ -338,30 +347,30 @@ async function hook () {
338
347
  level: 10,
339
348
  name: 'waibuMpa:preParsing',
340
349
  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')
350
+ await this.checkTheme(req, reply)
351
+ await this.checkIconset(req, reply)
352
+ const secure = await this.checkUser(req, reply, 'waibuMpa')
344
353
  if (!secure) return
345
- await checkTeam.call(this, req, reply)
346
- await checkXSite.call(this, req, reply)
354
+ await this.checkTeam(req, reply)
355
+ await this.checkXSite(req, reply)
347
356
  }
348
357
  }, {
349
358
  level: 10,
350
359
  name: 'waibuRestApi:preParsing',
351
360
  handler: async function (req, reply) {
352
- const secure = await checkUserId.call(this, req, reply, 'waibuRestApi')
361
+ const secure = await this.checkUser(req, reply, 'waibuRestApi')
353
362
  if (!secure) return
354
- await checkTeam.call(this, req, reply)
355
- await checkXSite.call(this, req, reply)
363
+ await this.checkTeam(req, reply)
364
+ await this.checkXSite(req, reply)
356
365
  }
357
366
  }, {
358
367
  level: 10,
359
368
  name: 'waibuStatic:preParsing',
360
369
  handler: async function (req, reply) {
361
- const secure = await checkUserId.call(this, req, reply, 'waibuStatic')
370
+ const secure = await this.checkUser(req, reply, 'waibuStatic')
362
371
  if (!secure) return
363
- await checkTeam.call(this, req, reply)
364
- await checkXSite.call(this, req, reply)
372
+ await this.checkTeam(req, reply)
373
+ await this.checkXSite(req, reply)
365
374
  }
366
375
  }, {
367
376
  name: 'waibu:afterAppBoot',
@@ -173,7 +173,9 @@
173
173
  "hiddenCols": "Hidden Columns",
174
174
  "models": "Models",
175
175
  "siteIds": "Sites",
176
- "allTeams": "All Teams?"
176
+ "allTeams": "All Teams?",
177
+ "condition": "Condition",
178
+ "hiddenFields": "Hide Fields"
177
179
  },
178
180
  "validation": {
179
181
  "password": {
@@ -179,7 +179,9 @@
179
179
  "hiddenCols": "Kolom Tersembunyi",
180
180
  "models": "Model",
181
181
  "siteIds": "Situs",
182
- "allTeams": "Semua Tim?"
182
+ "allTeams": "Semua Tim?",
183
+ "condition": "Kondisi",
184
+ "hiddenFields": "Sembunyikan Kolom"
183
185
  },
184
186
  "validation": {
185
187
  "password": {
@@ -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'
@@ -76,11 +76,11 @@ async function model () {
76
76
  },
77
77
  'field,,50,true,true',
78
78
  {
79
- name: 'behavior',
79
+ name: 'condition',
80
80
  type: 'string',
81
81
  maxLength: 20,
82
82
  required: true,
83
- values: ['IN', 'NIN'],
83
+ values: ['IN', 'NIN', 'EQ', 'NE', 'GT', 'GTE', 'LT', 'LTE', 'BETWEEN'],
84
84
  default: 'IN'
85
85
  },
86
86
  'value,array,,,true',
@@ -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
@@ -174,8 +174,7 @@ async function factory (pkgName) {
174
174
  }
175
175
 
176
176
  init = async () => {
177
- const { getPluginDataDir } = this.app.bajo
178
- this.downloadDir = `${getPluginDataDir(this.ns)}/download`
177
+ this.downloadDir = `${this.app.getPluginDataDir(this.ns)}/download`
179
178
  this.app.lib.fs.ensureDirSync(this.downloadDir)
180
179
  if (this.config.multiSite === true) {
181
180
  this.config.multiSite = cloneDeep(defMultiSite)
@@ -526,9 +525,8 @@ async function factory (pkgName) {
526
525
  }
527
526
 
528
527
  pushDownload = async ({ description, worker, data, source, req, file, type }) => {
529
- const { getPlugin } = this.app.bajo
530
- const { createRecord } = getPlugin('waibuDb')
531
- const { push } = getPlugin('bajoQueue')
528
+ const { createRecord } = this.app.getPlugin('waibuDb')
529
+ const { push } = this.app.getPlugin('bajoQueue')
532
530
  description = description ?? file
533
531
  const jobQueue = {
534
532
  worker,
@@ -638,13 +636,21 @@ async function factory (pkgName) {
638
636
  _getGuards = (inputs = []) => {
639
637
  const { routePath } = this.app.waibu
640
638
  const { orderBy } = this.app.lib._
641
- const normal = orderBy(inputs.filter(input => input.path[0] !== '!').map(result => {
642
- result.path = routePath(result.path)
643
- return result
639
+ const normal = orderBy(inputs.filter(input => {
640
+ try {
641
+ input.path = routePath(input.path)
642
+ } catch (err) {
643
+ return false
644
+ }
645
+ return input.path[0] !== '!'
644
646
  }), ['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
647
+ const inverse = orderBy(inputs.filter(input => {
648
+ try {
649
+ input.path = routePath(input.path)
650
+ } catch (err) {
651
+ return false
652
+ }
653
+ return input.path[0] === '!'
648
654
  }), ['weight', 'path'], ['desc', 'asc'])
649
655
  return [...normal, ...inverse]
650
656
  }
@@ -673,7 +679,7 @@ async function factory (pkgName) {
673
679
  getAttribGuards = async (reread) => {
674
680
  if (!reread) return this.attribGuards
675
681
 
676
- this.attribGuards = await this._fetchGuards('Model')
682
+ this.attribGuards = await this._fetchGuards('Attrib')
677
683
  return this.attribGuards
678
684
  }
679
685
 
@@ -682,6 +688,155 @@ async function factory (pkgName) {
682
688
  return asValue ? values.map(item => ({ value: item, text: item })) : values
683
689
  }
684
690
 
691
+ pathsToCheck = (req) => {
692
+ const { uniq, without } = this.app.lib._
693
+ const items = [req.routeOptions.url, req.url].map(url => url.split('?')[0].split('#')[0])
694
+ return uniq(without(items, undefined, null))
695
+ }
696
+
697
+ checkIconset = async (req, reply) => {
698
+ const { get, isString } = this.app.lib._
699
+ const mpa = this.app.waibuMpa
700
+
701
+ if (!req.site) return
702
+ const siteIconset = get(req, 'site.setting.waibuMpa.iconset')
703
+ req.iconset = siteIconset ?? get(mpa, 'config.iconset.set', 'default')
704
+ const hiconset = req.headers['x-iconset']
705
+ if (isString(hiconset) && mpa.getIconset(hiconset)) req.iconset = hiconset
706
+ req.iconset = req.iconset ?? 'default'
707
+ }
708
+
709
+ checkTheme = async (req, reply) => {
710
+ const { get, isString } = this.app.lib._
711
+ const mpa = this.app.waibuMpa
712
+
713
+ if (!req.site) return
714
+ const siteTheme = get(req, 'site.setting.waibuMpa.theme')
715
+ req.theme = siteTheme ?? get(mpa, 'config.theme.set', 'default')
716
+ const htheme = req.headers['x-theme']
717
+ if (isString(htheme) && mpa.getTheme(htheme)) req.theme = htheme
718
+ req.theme = req.theme ?? 'default'
719
+ }
720
+
721
+ checkTeam = async (req, reply) => {
722
+ const { includes } = this.app.lib.aneka
723
+ const { outmatch } = this.app.lib
724
+
725
+ if (req.user.isAdmin) return
726
+ if (req.routeOptions.config.xSite && req.user.isXSiteAdmin) return
727
+
728
+ const teamIds = req.user.teams.map(item => item.id + '')
729
+ if (req.user.teams.map(item => item.alias).length === 0) throw this.error('accessDenied', { statusCode: 403 })
730
+
731
+ const paths = this.pathsToCheck(req)
732
+ const results = (await this.getSecureGuards()).filter(item => {
733
+ if (item.siteId !== req.site.id + '' || item.path[0] === '!') return false
734
+ return paths.some(outmatch([item.path]))
735
+ })
736
+ for (const result of results) {
737
+ if (result.allTeams) continue
738
+ if (!includes(teamIds, result.teamIds)) throw this.error('accessDenied', { statusCode: 403 })
739
+ }
740
+ // passed
741
+ }
742
+
743
+ checkUser = async (req, reply, source) => {
744
+ const { merge, isEmpty, camelCase, get } = this.app.lib._
745
+ const { routePath } = this.app.waibu
746
+ const userId = get(req, 'session.userId')
747
+
748
+ const setUser = async () => {
749
+ if (!userId) return
750
+ try {
751
+ const user = await this.getUserById(userId, req)
752
+ if (user) req.user = user
753
+ else req.session.userId = null
754
+ } catch (err) {
755
+ console.log(err)
756
+ req.session.userId = null
757
+ }
758
+ }
759
+
760
+ if (req.session) req.session.siteId = req.site.id
761
+
762
+ const webApp = get(req, 'routeOptions.config.webApp', 'waibu')
763
+ if (!req.routeOptions.url) {
764
+ if (!req.session) return
765
+ await setUser()
766
+ return
767
+ }
768
+
769
+ const paths = this.pathsToCheck(req)
770
+ let guards = (await this.getAnonymousGuards()).filter(item => item.siteId === req.site.id + '')
771
+ const anonymous = this.checkRouteGuard(guards, paths)
772
+ if (anonymous) {
773
+ if (!userId) return false
774
+ req.session.ref = req.url
775
+ return reply.redirectTo(routePath(this.config.redirect.signout))
776
+ }
777
+ guards = (await this.getSecureGuards()).filter(item => item.siteId === req.site.id + '')
778
+ const secure = this.checkRouteGuard(guards, paths)
779
+ if (!secure) {
780
+ if (userId) await setUser()
781
+ return false // regular, unguarded path. Not secure & not anonymous path
782
+ }
783
+ if (userId) {
784
+ await setUser()
785
+ return secure
786
+ }
787
+ const silentOnError = this.config.auth[webApp].silentOnError ?? this.config.auth.common.silentOnError
788
+ const payload = silentOnError ? { noContent: true } : undefined
789
+ const authMethods = this.config.auth[webApp].methods ?? []
790
+ if (isEmpty(authMethods)) throw this.error('noAuthMethod', merge({ statusCode: 500 }, payload))
791
+ let success
792
+ for (const m of authMethods) {
793
+ const handler = this[camelCase(`verify ${m}`)]
794
+ if (!handler) throw this.error('invalidAuthMethod%s', m, merge({ statusCode: 500 }, payload))
795
+ const check = await handler(req, reply, source, payload)
796
+ if (check) {
797
+ success = check
798
+ break
799
+ }
800
+ }
801
+ if (!success) throw this.error('accessDeniedNoAuth', merge({ statusCode: 403 }, payload))
802
+ return secure
803
+ }
804
+
805
+ checkXSite = async (req, reply) => {
806
+ const { get } = this.app.lib._
807
+ if (!this.config.multiSite.enabled) return
808
+ if (!get(req, 'routeOptions.config.xSite')) return
809
+ if (!get(req, 'user.isXSiteAdmin')) throw this.error('accessDenied', { statusCode: 403 })
810
+ }
811
+
812
+ parseNsSettings = (ns, setting, items) => {
813
+ const { trim, set, isPlainObject, isArray, isEmpty, find } = this.app.lib._
814
+ const { parseObject, dayjs } = this.app.lib
815
+
816
+ for (const item of items) {
817
+ if (item.ns === '_var' || ns === '_var') continue
818
+ let value = trim([item.value] ?? '')
819
+ if (value[0] === '#' && value[value.length - 1] === '#') {
820
+ const val = value.slice(1, -1)
821
+ const newValue = find(items, { ns: '_var', key: val })
822
+ if (newValue) value = newValue.value
823
+ }
824
+ if (['[', '{'].includes(value[0]) && [']', '}'].includes(value[value.length - 1])) {
825
+ try {
826
+ value = parseObject(JSON.parse(value))
827
+ } catch (err) {}
828
+ } else if (Number(value)) value = Number(value)
829
+ else if (['true', 'false'].includes(value)) value = value === 'true'
830
+ else if (item.key.endsWith('$in')) value = value.split('\n').map(v => v.trim())
831
+ else {
832
+ const dt = dayjs(value)
833
+ if (dt.isValid()) value = dt.toDate()
834
+ }
835
+ if ((isPlainObject(value) || isArray(value)) && isEmpty(value)) continue
836
+ set(setting, `${ns}.${item.key}`, value)
837
+ }
838
+ }
839
+
685
840
  createNewSite = createNewSite
686
841
  removeSite = removeSite
687
842
  getSite = getSite
@@ -1,7 +1,7 @@
1
1
  import path from 'path'
2
2
 
3
3
  export async function getAllFixtures (alias) {
4
- const { getPluginDataDir, readConfig } = this.app.bajo
4
+ const { readConfig } = this.app.bajo
5
5
  const { isEmpty, omit, kebabCase, isString } = this.app.lib._
6
6
  const { getModel } = this.app.dobo
7
7
  const { fastGlob } = this.app.lib
@@ -17,7 +17,7 @@ export async function getAllFixtures (alias) {
17
17
  }
18
18
 
19
19
  const formats = this.app.configHandlers.map(item => item.ext.slice(1))
20
- const overrideBase = `${getPluginDataDir('sumba')}/create-new-site-fixtures/${alias}`
20
+ const overrideBase = `${this.app.getPluginDataDir('sumba')}/create-new-site-fixtures/${alias}`
21
21
  const models = this.app.dobo.models.filter(m => {
22
22
  const prop = m.properties.find(p => p.name === 'siteId')
23
23
  return !!prop
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.32.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-12
4
+
5
+ - [2.32.0] Necessary updates to ```bajo@2.18.0``` specs
6
+ - [2.32.0] Bug fix in ```_getGuards()```
7
+ - [2.32.0] Bug fix in ```getAttribGuards()```
8
+ - [2.32.0] Bug fix in ```hooks.js```
9
+ - [2.32.0] Change ```behavior``` to ```condition``` in ```SumbaModelGuard``` model
10
+
11
+ ## 2026-06-11
12
+
13
+ - [2.31.0] Add ```status``` field on ```SumbaTeamUser```
14
+ - [2.31.0] Remove ```util.js``` as all functions now moved to base class
15
+
3
16
  ## 2026-06-10
4
17
 
5
18
  - [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
- }