sumba 2.23.0 → 2.25.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 (54) hide show
  1. package/extend/bajo/hook/bajo.extend@after-read-config.js +13 -0
  2. package/extend/bajo/hook/dobo.sumba-attrib-guard$dobo.sumba-x-attrib-guard@after-transaction.js +6 -0
  3. package/extend/bajo/hook/dobo.sumba-model-guard$dobo.sumba-x-model-guard@after-transaction.js +6 -0
  4. package/extend/bajo/hook/dobo.sumba-route-guard$dobo.sumba-x-route-guard@after-transaction.js +6 -0
  5. package/extend/bajo/hook/dobo.sumba-team$dobo.sumba-site@after-transaction.js +8 -0
  6. package/extend/bajo/hook/dobo@before-count-record.js +3 -3
  7. package/extend/bajo/hook/{dobo@before-create-record.js → dobo@before-driver-create-record.js} +6 -6
  8. package/extend/bajo/hook/dobo@before-driver-find-all-record.js +13 -0
  9. package/extend/bajo/hook/dobo@before-driver-find-record.js +114 -0
  10. package/extend/bajo/hook/dobo@before-driver-get-record.js +22 -0
  11. package/extend/bajo/hook/dobo@before-driver-remove-record.js +10 -0
  12. package/extend/bajo/hook/dobo@before-driver-update-record.js +10 -0
  13. package/extend/bajo/hook/waibu-mpa@pre-parsing.js +5 -4
  14. package/extend/bajo/hook/waibu-rest-api@pre-parsing.js +5 -4
  15. package/extend/bajo/hook/waibu-static@pre-parsing.js +5 -4
  16. package/extend/bajo/hook/waibu@after-app-boot.js +2 -0
  17. package/extend/bajo/intl/en-US.json +13 -2
  18. package/extend/bajo/intl/id.json +20 -2
  19. package/extend/dobo/feature/site-ids.js +19 -0
  20. package/extend/dobo/feature/team-ids.js +19 -0
  21. package/extend/dobo/fixture/{route-guard.js → x-route-guard.js} +2 -4
  22. package/extend/dobo/model/attrib-guard.js +32 -0
  23. package/extend/dobo/model/model-guard.js +48 -0
  24. package/extend/dobo/model/route-guard.js +42 -54
  25. package/extend/dobo/model/x-attrib-guard.js +14 -0
  26. package/extend/dobo/model/x-model-guard.js +13 -0
  27. package/extend/dobo/model/x-route-guard.js +13 -0
  28. package/extend/waibuDb/schema/attrib-guard.js +15 -0
  29. package/extend/waibuDb/schema/model-guard.js +15 -0
  30. package/extend/waibuDb/schema/route-guard.js +1 -1
  31. package/extend/waibuDb/schema/user.js +19 -26
  32. package/extend/waibuDb/schema/x-attrib-guard.js +15 -0
  33. package/extend/waibuDb/schema/x-model-guard.js +15 -0
  34. package/extend/waibuDb/schema/x-route-guard.js +15 -0
  35. package/extend/waibuMpa/extend/waibuAdmin/route/attrib-guard/@action.js +11 -0
  36. package/extend/waibuMpa/extend/waibuAdmin/route/model-guard/@action.js +11 -0
  37. package/extend/waibuMpa/extend/waibuAdmin/route/reset-user-password.js +2 -1
  38. package/extend/waibuMpa/extend/waibuAdmin/route/x/attrib-guard/@action.js +12 -0
  39. package/extend/waibuMpa/extend/waibuAdmin/route/{cache → x/cache}/@action.js +1 -1
  40. package/extend/waibuMpa/extend/waibuAdmin/route/x/model-guard/@action.js +12 -0
  41. package/extend/waibuMpa/extend/waibuAdmin/route/x/route-guard/@action.js +12 -0
  42. package/extend/waibuMpa/extend/waibuAdmin/route/{session → x/session}/@action.js +1 -1
  43. package/extend/waibuMpa/extend/waibuAdmin/route/{all-sites → x/site}/@action.js +1 -1
  44. package/index.js +114 -35
  45. package/lib/get-user.js +9 -8
  46. package/lib/util.js +58 -23
  47. package/package.json +1 -1
  48. package/wiki/CHANGES.md +13 -0
  49. package/extend/bajo/hook/dobo.sumba-team-guard@after-action.js +0 -6
  50. package/extend/bajo/hook/dobo.sumba-user-guard@after-action.js +0 -6
  51. package/extend/bajo/hook/dobo@before-find-record.js +0 -63
  52. package/extend/bajo/hook/dobo@before-get-record.js +0 -23
  53. package/extend/bajo/hook/dobo@before-remove-record.js +0 -10
  54. package/extend/bajo/hook/dobo@before-update-record.js +0 -10
package/lib/util.js CHANGED
@@ -56,19 +56,35 @@ export async function checkTheme (req, reply) {
56
56
  req.theme = req.theme ?? 'default'
57
57
  }
58
58
 
59
- export async function checkTeam (req, reply, source) {
60
- if (!req.user) return
59
+ export async function checkTeam (req, reply, route) {
61
60
  const { map } = this.app.lib._
61
+ const { outmatch } = this.app.lib
62
+ route.teamIds = route.teamIds ?? []
63
+ if (route.teamIds.length === 0) return
64
+
65
+ const teamIds = map(req.user.teams, 'id')
66
+ const teamAliases = map(req.user.teams, 'alias')
67
+ if (req.user.isAdmin) return
68
+ const matchXSite = outmatch(route.path)
69
+ if (req.routeOptions.config.xSite && matchXSite(req.url) && req.user.isXSiteAdmin) return
70
+ if (teamAliases.length === 0) throw this.error('accessDenied', { statusCode: 403 })
71
+
62
72
  const paths = pathsToCheck.call(this, req, true)
63
- const teams = map(req.user.teams, 'alias')
64
- const allGuards = (await this.getRouteGuards()).filter(item => item.siteId === req.site.id + '' && item.teams.length > 0)
65
- if (allGuards.length === 0) return // no route guarded
66
- let match = this.checkPathsByRoute({ paths, method: req.method, teams, guards: allGuards.filter(item => !item.inverse) })
67
- if (!match) return // route is NOT protected by team guard
73
+ const guards = await this.getRouteGuards()
74
+ const globalGuards = guards.global.filter(item => item.siteIds.includes(req.site.id + '') && item.teamIds.length > 0)
75
+ const localGuards = guards.local.filter(item => item.siteIds.includes(req.site.id + '') && item.teamIds.length > 0)
76
+ let match = this.checkPathsByRoute({ paths, teamIds, guards: localGuards.filter(item => !item.negation) })
68
77
  if (match) {
69
- const neg = this.checkPathsByRoute({ paths, method: req.method, teams, guards: allGuards.filter(item => item.inverse) })
78
+ const neg = this.checkPathsByRoute({ paths, teamIds, guards: localGuards.filter(item => item.negation) })
70
79
  if (neg) match = undefined
71
80
  }
81
+ if (!match) {
82
+ match = this.checkPathsByRoute({ paths, teamIds, guards: globalGuards.filter(item => !item.negation) })
83
+ if (match) {
84
+ const neg = this.checkPathsByRoute({ paths, teamIds, guards: globalGuards.filter(item => item.negation) })
85
+ if (neg) match = undefined
86
+ }
87
+ }
72
88
  if (!match) throw this.error('accessDenied', { statusCode: 403 })
73
89
  // passed
74
90
  }
@@ -100,18 +116,38 @@ export async function checkUserId (req, reply, source) {
100
116
  }
101
117
 
102
118
  const paths = pathsToCheck.call(this, req)
103
- const allGuards = (await this.getRouteGuards()).filter(item => item.siteId === req.site.id + '')
104
- if (allGuards.length === 0) return false // no routes protected
105
- let securePath = await this.checkPathsByRoute({ paths, method: req.method, guards: allGuards.filter(item => !item.anonymous && !item.inverse) })
119
+ const guards = await this.getRouteGuards()
120
+ let securePath
121
+ let anonymousPath
122
+ const globalGuards = guards.global.filter(item => item.siteIds.includes(req.site.id + ''))
123
+ const localGuards = guards.local.filter(item => item.siteIds.includes(req.site.id + ''))
124
+ // find anonymousPath
125
+ anonymousPath = await this.checkPathsByRoute({ paths, guards: localGuards.filter(item => item.anonymous && !item.negation) })
126
+ if (anonymousPath) {
127
+ const neg = await this.checkPathsByRoute({ paths, guards: localGuards.filter(item => item.anonymous && item.negation) })
128
+ if (neg) anonymousPath = undefined
129
+ }
130
+ if (!anonymousPath) {
131
+ anonymousPath = await this.checkPathsByRoute({ paths, guards: globalGuards.filter(item => item.anonymous && !item.negation) })
132
+ if (anonymousPath) {
133
+ const neg = await this.checkPathsByRoute({ paths, guards: globalGuards.filter(item => item.anonymous && item.negation) })
134
+ if (neg) anonymousPath = undefined
135
+ }
136
+ }
137
+ // find securePath
138
+ securePath = await this.checkPathsByRoute({ paths, guards: localGuards.filter(item => !item.anonymous && !item.negation) })
106
139
  if (securePath) {
107
- const neg = await this.checkPathsByRoute({ paths, method: req.method, guards: allGuards.filter(item => !item.anonymous && item.inverse) })
140
+ const neg = await this.checkPathsByRoute({ paths, guards: localGuards.filter(item => !item.anonymous && item.negation) })
108
141
  if (neg) securePath = undefined
109
142
  }
110
- let anonymousPath = await this.checkPathsByRoute({ paths, method: req.method, guards: allGuards.filter(item => item.anonymous && !item.inverse) })
111
- if (anonymousPath) {
112
- const neg = await this.checkPathsByRoute({ paths, method: req.method, guards: allGuards.filter(item => item.anonymous && item.inverse) })
113
- if (neg) anonymousPath = undefined
143
+ if (!securePath) {
144
+ securePath = await this.checkPathsByRoute({ paths, guards: globalGuards.filter(item => !item.anonymous && !item.negation) })
145
+ if (securePath) {
146
+ const neg = await this.checkPathsByRoute({ paths, guards: globalGuards.filter(item => !item.anonymous && item.negation) })
147
+ if (neg) securePath = undefined
148
+ }
114
149
  }
150
+ // checking...
115
151
  if (!securePath && !anonymousPath) {
116
152
  if (userId) await setUser()
117
153
  return false // regular, unguarded path. Not secure & not anonymous path
@@ -121,9 +157,10 @@ export async function checkUserId (req, reply, source) {
121
157
  req.session.ref = req.url
122
158
  return reply.redirectTo(routePath(this.config.redirect.signout))
123
159
  }
160
+ if (!(securePath.methods ?? []).includes(req.method)) throw this.error('accessDenied', { statusCode: 403 })
124
161
  if (userId) {
125
162
  await setUser()
126
- return true
163
+ return securePath
127
164
  }
128
165
  const silentOnError = this.config.auth[webApp].silentOnError ?? this.config.auth.common.silentOnError
129
166
  const payload = silentOnError ? { noContent: true } : undefined
@@ -140,7 +177,7 @@ export async function checkUserId (req, reply, source) {
140
177
  }
141
178
  }
142
179
  if (!success) throw this.error('accessDeniedNoAuth', merge({ statusCode: 403 }, payload))
143
- return true
180
+ return securePath
144
181
  }
145
182
 
146
183
  export async function latLngHook (body, options) {
@@ -157,10 +194,8 @@ export async function latLngHook (body, options) {
157
194
  * @param {Object} reply - Reply object
158
195
  * @returns
159
196
  */
160
- export async function checkInterSite (req, reply) {
197
+ export async function checkXSite (req, reply) {
161
198
  const { get } = this.app.lib._
162
- const isinterSite = get(req, 'routeOptions.config.interSite')
163
- const isInterSiteAdmin = get(req, 'user.interSiteAdmin')
164
- if (!isinterSite) return
165
- if (!isInterSiteAdmin) throw this.error('accessDenied', { statusCode: 403 })
199
+ if (!get(req, 'routeOptions.config.xSite')) return
200
+ if (!get(req, 'user.isXSiteAdmin')) throw this.error('accessDenied', { statusCode: 403 })
166
201
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sumba",
3
- "version": "2.23.0",
3
+ "version": "2.25.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-05-22
4
+
5
+ - [2.25.0] Reorganize admin menu layout & order
6
+ - [2.25.0] Add ```getModelGuards()```
7
+ - [2.25.0] Add ```getAttribGuards()```
8
+ - [2.25.0] Bug fix in ```getRouteGuards()```
9
+
10
+ ## 2026-05-11
11
+
12
+ - [2.24.0] Complete rewrite of route guards
13
+ - [2.24.0] Add model guards
14
+ - [2.24.0] Add attribute guards
15
+
3
16
  ## 2026-05-11
4
17
 
5
18
  - [2.23.0] Unify route guards and put it in ```SumbaRouteGuard``` database
@@ -1,6 +0,0 @@
1
- async function afterAction (action, ...args) {
2
- if (!['createRecord', 'updateRecord', 'removeRecord'].includes(action)) return
3
- await this.getTeamGuards(true)
4
- }
5
-
6
- export default afterAction
@@ -1,6 +0,0 @@
1
- async function afterAction (action, ...args) {
2
- if (!['createRecord', 'updateRecord', 'removeRecord'].includes(action)) return
3
- await this.getUserGuards(true)
4
- }
5
-
6
- export default afterAction
@@ -1,63 +0,0 @@
1
- const useAdmin = ['waibuAdmin']
2
-
3
- export async function rebuildFilter (modelName, filter, req) {
4
- const { isEmpty, isPlainObject, map, find, get } = this.app.lib._
5
- filter.query = filter.query ?? {}
6
- if (req.routeOptions.config.interSite) {
7
- filter.query = { $and: [filter.query] }
8
- return
9
- }
10
-
11
- const queryBySiteSetting = (query) => {
12
- if (!req.site) return
13
- const setting = get(req, `site.setting.dobo.query.${modelName}`)
14
- if (isPlainObject(setting) && !isEmpty(setting)) query.$and.push(setting)
15
- }
16
-
17
- const queryByTeamSetting = (query) => {
18
- if (!req.user) return
19
- const q = []
20
- for (const team of req.user.teams) {
21
- const item = get(team, `setting.dobo.query.${modelName}`)
22
- if (item) q.push(item)
23
- }
24
- if (isEmpty(q)) return
25
- if (q.length === 1) query.$and.push(q[0])
26
- else query.$and.push({ $or: q })
27
- }
28
-
29
- const model = this.app.dobo.getModel(modelName)
30
- const hasSiteId = model.hasProperty('siteId')
31
- const hasUserId = model.hasProperty('userId')
32
- const hasTeamId = model.hasProperty('teamId')
33
- const isAdmin = find(get(req, 'user.teams', []), { alias: 'administrator' }) && useAdmin.includes(get(req, 'routeOptions.config.ns'))
34
- const q = { $and: [] }
35
- queryBySiteSetting(q)
36
- queryByTeamSetting(q)
37
- if (!isEmpty(filter.query)) {
38
- if (filter.query.$and) q.$and.push(...filter.query.$and)
39
- else q.$and.push(filter.query)
40
- }
41
- if (hasSiteId) q.$and.push({ siteId: req.site.id })
42
- if (hasTeamId && !isAdmin) {
43
- const teamIds = map(req.user.teams, 'id')
44
- if (hasUserId) q.$and.push({ $or: [{ teamId: { $in: teamIds } }, { userId: req.user.id }] })
45
- else q.$and.push({ teamId: { $in: teamIds } })
46
- } else if (!isAdmin) {
47
- if (hasUserId) q.$and.push({ userId: req.user.id })
48
- }
49
- filter.query = q
50
- }
51
-
52
- export async function handler (modelName, filter, options = {}) {
53
- const { req } = options
54
- if (options.noAutoFilter || !req) return
55
- await rebuildFilter.call(this, modelName, filter, req)
56
- }
57
-
58
- const doboBeforeFindRecord = {
59
- level: 1000,
60
- handler
61
- }
62
-
63
- export default doboBeforeFindRecord
@@ -1,23 +0,0 @@
1
- import { rebuildFilter } from './dobo@before-find-record.js'
2
-
3
- export async function checker (modelName, id, options = {}) {
4
- const { req } = options
5
-
6
- const model = this.app.dobo.getModel(modelName)
7
- if (options.noAutoFilter || !req) return
8
- const filter = {}
9
- await rebuildFilter.call(this, modelName, filter, req)
10
- if (filter.query.$and) filter.query.$and.push({ id })
11
- else filter.query.id = id
12
- const row = await model.findOneRecord(filter, { count: false })
13
- if (!row) throw this.app.dobo.error('recordNotFound%s%s', id, this.name, { statusCode: 404 })
14
- }
15
-
16
- const doboBeforeGetRecord = {
17
- level: 1000,
18
- handler: async function (modelName, id, options) {
19
- await checker.call(this, modelName, id, options)
20
- }
21
- }
22
-
23
- export default doboBeforeGetRecord
@@ -1,10 +0,0 @@
1
- import { checker } from './dobo@before-get-record.js'
2
-
3
- const doboBeforeRemoveRecord = {
4
- level: 1000,
5
- handler: async function (modelName, id, options = {}) {
6
- await checker.call(this, modelName, id, options.req)
7
- }
8
- }
9
-
10
- export default doboBeforeRemoveRecord
@@ -1,10 +0,0 @@
1
- import { checker } from './dobo@before-get-record.js'
2
-
3
- const doboBeforeUpdateRecord = {
4
- level: 1000,
5
- handler: async function (modelName, id, body, options = {}) {
6
- await checker.call(this, modelName, id, options)
7
- }
8
- }
9
-
10
- export default doboBeforeUpdateRecord