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.
- package/extend/bajo/hook/bajo.extend@after-read-config.js +13 -0
- package/extend/bajo/hook/dobo.sumba-attrib-guard$dobo.sumba-x-attrib-guard@after-transaction.js +6 -0
- package/extend/bajo/hook/dobo.sumba-model-guard$dobo.sumba-x-model-guard@after-transaction.js +6 -0
- package/extend/bajo/hook/dobo.sumba-route-guard$dobo.sumba-x-route-guard@after-transaction.js +6 -0
- package/extend/bajo/hook/dobo.sumba-team$dobo.sumba-site@after-transaction.js +8 -0
- package/extend/bajo/hook/dobo@before-count-record.js +3 -3
- package/extend/bajo/hook/{dobo@before-create-record.js → dobo@before-driver-create-record.js} +6 -6
- package/extend/bajo/hook/dobo@before-driver-find-all-record.js +13 -0
- package/extend/bajo/hook/dobo@before-driver-find-record.js +114 -0
- package/extend/bajo/hook/dobo@before-driver-get-record.js +22 -0
- package/extend/bajo/hook/dobo@before-driver-remove-record.js +10 -0
- package/extend/bajo/hook/dobo@before-driver-update-record.js +10 -0
- package/extend/bajo/hook/waibu-mpa@pre-parsing.js +5 -4
- package/extend/bajo/hook/waibu-rest-api@pre-parsing.js +5 -4
- package/extend/bajo/hook/waibu-static@pre-parsing.js +5 -4
- package/extend/bajo/hook/waibu@after-app-boot.js +2 -0
- package/extend/bajo/intl/en-US.json +13 -2
- package/extend/bajo/intl/id.json +20 -2
- package/extend/dobo/feature/site-ids.js +19 -0
- package/extend/dobo/feature/team-ids.js +19 -0
- package/extend/dobo/fixture/{route-guard.js → x-route-guard.js} +2 -4
- package/extend/dobo/model/attrib-guard.js +32 -0
- package/extend/dobo/model/model-guard.js +48 -0
- package/extend/dobo/model/route-guard.js +42 -54
- package/extend/dobo/model/x-attrib-guard.js +14 -0
- package/extend/dobo/model/x-model-guard.js +13 -0
- package/extend/dobo/model/x-route-guard.js +13 -0
- package/extend/waibuDb/schema/attrib-guard.js +15 -0
- package/extend/waibuDb/schema/model-guard.js +15 -0
- package/extend/waibuDb/schema/route-guard.js +1 -1
- package/extend/waibuDb/schema/user.js +19 -26
- package/extend/waibuDb/schema/x-attrib-guard.js +15 -0
- package/extend/waibuDb/schema/x-model-guard.js +15 -0
- package/extend/waibuDb/schema/x-route-guard.js +15 -0
- package/extend/waibuMpa/extend/waibuAdmin/route/attrib-guard/@action.js +11 -0
- package/extend/waibuMpa/extend/waibuAdmin/route/model-guard/@action.js +11 -0
- package/extend/waibuMpa/extend/waibuAdmin/route/reset-user-password.js +2 -1
- package/extend/waibuMpa/extend/waibuAdmin/route/x/attrib-guard/@action.js +12 -0
- package/extend/waibuMpa/extend/waibuAdmin/route/{cache → x/cache}/@action.js +1 -1
- package/extend/waibuMpa/extend/waibuAdmin/route/x/model-guard/@action.js +12 -0
- package/extend/waibuMpa/extend/waibuAdmin/route/x/route-guard/@action.js +12 -0
- package/extend/waibuMpa/extend/waibuAdmin/route/{session → x/session}/@action.js +1 -1
- package/extend/waibuMpa/extend/waibuAdmin/route/{all-sites → x/site}/@action.js +1 -1
- package/index.js +114 -35
- package/lib/get-user.js +9 -8
- package/lib/util.js +58 -23
- package/package.json +1 -1
- package/wiki/CHANGES.md +13 -0
- package/extend/bajo/hook/dobo.sumba-team-guard@after-action.js +0 -6
- package/extend/bajo/hook/dobo.sumba-user-guard@after-action.js +0 -6
- package/extend/bajo/hook/dobo@before-find-record.js +0 -63
- package/extend/bajo/hook/dobo@before-get-record.js +0 -23
- package/extend/bajo/hook/dobo@before-remove-record.js +0 -10
- 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,
|
|
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
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
let match = this.checkPathsByRoute({ paths,
|
|
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,
|
|
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
|
|
104
|
-
|
|
105
|
-
let
|
|
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,
|
|
140
|
+
const neg = await this.checkPathsByRoute({ paths, guards: localGuards.filter(item => !item.anonymous && item.negation) })
|
|
108
141
|
if (neg) securePath = undefined
|
|
109
142
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
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
|
|
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
|
|
197
|
+
export async function checkXSite (req, reply) {
|
|
161
198
|
const { get } = this.app.lib._
|
|
162
|
-
|
|
163
|
-
|
|
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
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,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
|