sumba 2.23.0 → 2.24.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-route-guard@after-transaction.js +6 -0
- package/extend/bajo/hook/dobo@before-count-record.js +3 -3
- package/extend/bajo/hook/dobo@before-driver-create-record.js +17 -0
- package/extend/bajo/hook/dobo@before-driver-find-all-record.js +13 -0
- package/extend/bajo/hook/dobo@before-driver-find-record.js +96 -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/intl/en-US.json +7 -2
- package/extend/bajo/intl/id.json +14 -2
- package/extend/dobo/feature/team-ids.js +20 -0
- package/extend/dobo/fixture/route-guard.js +2 -2
- package/extend/dobo/model/attrib-guard.js +36 -0
- package/extend/dobo/model/model-guard.js +37 -0
- package/extend/dobo/model/route-guard.js +13 -28
- 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/waibuMpa/extend/waibuAdmin/route/all-sites/@action.js +1 -1
- package/extend/waibuMpa/extend/waibuAdmin/route/attrib-guard/@action.js +11 -0
- package/extend/waibuMpa/extend/waibuAdmin/route/cache/@action.js +1 -1
- 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/session/@action.js +1 -1
- package/index.js +20 -13
- package/lib/get-user.js +4 -4
- package/lib/util.js +21 -18
- package/package.json +1 -1
- package/wiki/CHANGES.md +6 -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-create-record.js +0 -17
- 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
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
async function afterReadConfig (file, result, options) {
|
|
4
|
+
const base = path.basename(file, path.extname(file))
|
|
5
|
+
// rewrite fixtures
|
|
6
|
+
if (!(base === 'route-guard' && Array.isArray(result) && file.includes('/fixture/'))) return
|
|
7
|
+
for (const res of result) {
|
|
8
|
+
if (res.path.slice(0, 2) === ':/') res.path = options.sourceNs + res.path
|
|
9
|
+
res.path = res.path.replaceAll('{ns}', options.sourceNs)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default afterReadConfig
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { handler } from './dobo@before-find-record.js'
|
|
1
|
+
import { handler } from './dobo@before-driver-find-record.js'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const doboBeforeDriverCountRecord = {
|
|
4
4
|
level: 1000,
|
|
5
5
|
handler
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export default
|
|
8
|
+
export default doboBeforeDriverCountRecord
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const doboBeforeDriverCreateRecord = {
|
|
2
|
+
level: 1000,
|
|
3
|
+
handler: async function (model, body, options = {}) {
|
|
4
|
+
const { get } = this.app.lib._
|
|
5
|
+
const { isSet } = this.app.lib.aneka
|
|
6
|
+
const { req } = options
|
|
7
|
+
if (options.noAutoFilter || !req || get(req, 'routeOptions.config.crossSite')) return
|
|
8
|
+
const item = { siteId: 'site.id', userId: 'user.id' }
|
|
9
|
+
for (const i in item) {
|
|
10
|
+
const rec = get(req, item[i])
|
|
11
|
+
const field = model.getProperty(i)
|
|
12
|
+
if (rec && field && !isSet(body[i])) body[i] = field.type === 'string' ? (rec + '') : rec
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default doboBeforeDriverCreateRecord
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { rebuildFilter } from './dobo@before-driver-find-record.js'
|
|
2
|
+
|
|
3
|
+
const doboBeforeDriverFindAllRecord = {
|
|
4
|
+
level: 1000,
|
|
5
|
+
handler: async function (model, filter, options) {
|
|
6
|
+
const { req } = options
|
|
7
|
+
const { isEmpty } = this.app.lib._
|
|
8
|
+
if (options.noAutoFilter || !req || isEmpty(req.site)) return
|
|
9
|
+
await rebuildFilter.call(this, model, filter, options)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default doboBeforeDriverFindAllRecord
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
async function applyModelGuard ({ model, q, teamIds, options }) {
|
|
2
|
+
const { isEmpty, get, set, uniq } = this.app.lib._
|
|
3
|
+
const { req } = options
|
|
4
|
+
const { getModel } = this.app.dobo
|
|
5
|
+
|
|
6
|
+
const guards = []
|
|
7
|
+
const query = { status: 'ACTIVE', siteId: req.site.id + '' }
|
|
8
|
+
const results = await getModel('SumbaModelGuard').findAllRecord({ query }, { noMagic: true, dataOnly: true, noDriverHook: true })
|
|
9
|
+
const item = {}
|
|
10
|
+
|
|
11
|
+
function add (res) {
|
|
12
|
+
item[res.column].push(...res.value)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
for (const res of results) {
|
|
16
|
+
res.teamIds = res.teamIds ?? []
|
|
17
|
+
if (!(res.models ?? []).includes(model.name) || isEmpty(res.value)) continue
|
|
18
|
+
item[res.column] = item[res.column] ?? []
|
|
19
|
+
if (teamIds.length > 0) {
|
|
20
|
+
for (const id of res.teamIds) {
|
|
21
|
+
if (teamIds.includes(id)) add(res)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const key in item) {
|
|
26
|
+
item[key] = uniq(item[key])
|
|
27
|
+
if (item[key].length === 0) continue
|
|
28
|
+
guards.push(set({}, key, { $in: item[key] }))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const allowEmpty = get(this, `config.dobo.model.${model.name}.allowEmptyQuery`, true)
|
|
32
|
+
if (guards.length === 0 && !allowEmpty) throw this.error('_emptyColumnQuery') // signal driver to about with no result immediately
|
|
33
|
+
q.$and.push(...guards)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function applyAttribGuard ({ model, teamIds, options }) {
|
|
37
|
+
const { getModel } = this.app.dobo
|
|
38
|
+
const { uniq } = this.app.lib._
|
|
39
|
+
const { req } = options
|
|
40
|
+
|
|
41
|
+
const query = { status: 'ACTIVE', siteId: req.site.id + '' }
|
|
42
|
+
const results = await getModel('SumbaAttribGuard').findAllRecord({ query }, { noMagic: true, dataOnly: true, noDriverHook: true })
|
|
43
|
+
const result = results.find(item => (item.models ?? []).includes(model.name))
|
|
44
|
+
if (!result) return
|
|
45
|
+
options.hidden = options.hidden ?? []
|
|
46
|
+
for (const id of result.teamIds ?? []) {
|
|
47
|
+
if (teamIds.includes(id)) options.hidden.push(...(result.hiddenCols ?? []))
|
|
48
|
+
}
|
|
49
|
+
options.hidden = uniq(options.hidden)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function rebuildFilter (model, filter = {}, options = {}) {
|
|
53
|
+
const { isEmpty, get } = this.app.lib._
|
|
54
|
+
const { req } = options
|
|
55
|
+
const hasSiteId = model.hasProperty('siteId')
|
|
56
|
+
const hasUserId = model.hasProperty('userId')
|
|
57
|
+
const hasTeamId = model.hasProperty('teamId')
|
|
58
|
+
const teams = get(req, 'user.teams', [])
|
|
59
|
+
const teamIds = teams.map(team => team.id + '')
|
|
60
|
+
const aliases = teams.map(team => team.alias)
|
|
61
|
+
const q = { $and: [] }
|
|
62
|
+
|
|
63
|
+
filter.query = filter.query ?? {}
|
|
64
|
+
if (!isEmpty(filter.query)) {
|
|
65
|
+
if (filter.query.$and) q.$and.push(...filter.query.$and)
|
|
66
|
+
else q.$and.push(filter.query)
|
|
67
|
+
}
|
|
68
|
+
if (req.routeOptions.config.crossSite) return
|
|
69
|
+
if (hasSiteId) q.$and.push({ siteId: req.site.id + '' })
|
|
70
|
+
if (aliases.includes('administrator')) {
|
|
71
|
+
filter.query = q
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (hasTeamId) {
|
|
76
|
+
if (hasUserId) q.$and.push({ $or: [{ teamId: { $in: teamIds } }, { userId: req.user.id + '' }] })
|
|
77
|
+
else q.$and.push({ teamId: { $in: teamIds } })
|
|
78
|
+
} else if (hasUserId) q.$and.push({ userId: req.user.id + '' })
|
|
79
|
+
|
|
80
|
+
await applyModelGuard.call(this, { model, q, teamIds, options })
|
|
81
|
+
await applyAttribGuard.call(this, { model, teamIds, options })
|
|
82
|
+
filter.query = q
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function handler (model, filter, options = {}) {
|
|
86
|
+
const { isEmpty } = this.app.lib._
|
|
87
|
+
if (options.noAutoFilter || !options.req || isEmpty((options.req ?? {}).site)) return
|
|
88
|
+
await rebuildFilter.call(this, model, filter, options)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const doboBeforeDriverFindRecord = {
|
|
92
|
+
level: 1000,
|
|
93
|
+
handler
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export default doboBeforeDriverFindRecord
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { rebuildFilter } from './dobo@before-driver-find-record.js'
|
|
2
|
+
|
|
3
|
+
export async function checker (model, id, options = {}) {
|
|
4
|
+
const { req } = options
|
|
5
|
+
|
|
6
|
+
if (options.noAutoFilter || !req) return
|
|
7
|
+
const filter = {}
|
|
8
|
+
await rebuildFilter.call(this, model, filter, options)
|
|
9
|
+
if (filter.query.$and) filter.query.$and.push({ id })
|
|
10
|
+
else filter.query.id = id
|
|
11
|
+
const row = await model.findOneRecord(filter, { count: false })
|
|
12
|
+
if (!row) throw this.app.dobo.error('recordNotFound%s%s', id, this.name, { statusCode: 404 })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const doboBeforeDriverGetRecord = {
|
|
16
|
+
level: 1000,
|
|
17
|
+
handler: async function (model, id, options) {
|
|
18
|
+
await checker.call(this, model, id, options)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default doboBeforeDriverGetRecord
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { checker } from './dobo@before-driver-get-record.js'
|
|
2
|
+
|
|
3
|
+
const doboBeforeDriverRemoveRecord = {
|
|
4
|
+
level: 1000,
|
|
5
|
+
handler: async function (model, id, options = {}) {
|
|
6
|
+
await checker.call(this, model, id, options.req)
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default doboBeforeDriverRemoveRecord
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { checker } from './dobo@before-driver-get-record.js'
|
|
2
|
+
|
|
3
|
+
const doboBeforeDriverUpdateRecord = {
|
|
4
|
+
level: 1000,
|
|
5
|
+
handler: async function (model, id, body, options = {}) {
|
|
6
|
+
await checker.call(this, model, id, options)
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default doboBeforeDriverUpdateRecord
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { checkUserId, checkTeam, checkTheme, checkIconset,
|
|
1
|
+
import { checkUserId, checkTeam, checkTheme, checkIconset, checkCrossSite } from '../../../lib/util.js'
|
|
2
2
|
|
|
3
3
|
const preParsing = {
|
|
4
4
|
level: 10,
|
|
5
5
|
handler: async function (req, reply) {
|
|
6
6
|
await checkTheme.call(this, req, reply)
|
|
7
7
|
await checkIconset.call(this, req, reply)
|
|
8
|
-
|
|
9
|
-
if (!
|
|
10
|
-
await
|
|
8
|
+
const secure = await checkUserId.call(this, req, reply, 'waibuMpa')
|
|
9
|
+
if (!secure) return
|
|
10
|
+
await checkTeam.call(this, req, reply, secure)
|
|
11
|
+
await checkCrossSite.call(this, req, reply)
|
|
11
12
|
}
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { checkUserId, checkTeam,
|
|
1
|
+
import { checkUserId, checkTeam, checkCrossSite } from '../../../lib/util.js'
|
|
2
2
|
|
|
3
3
|
const preParsing = {
|
|
4
4
|
level: 10,
|
|
5
5
|
handler: async function (req, reply) {
|
|
6
|
-
|
|
7
|
-
if (!
|
|
8
|
-
await
|
|
6
|
+
const secure = await checkUserId.call(this, req, reply, 'waibuRestApi')
|
|
7
|
+
if (!secure) return
|
|
8
|
+
await checkTeam.call(this, req, reply, secure)
|
|
9
|
+
await checkCrossSite.call(this, req, reply)
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { checkUserId, checkTeam,
|
|
1
|
+
import { checkUserId, checkTeam, checkCrossSite } from '../../../lib/util.js'
|
|
2
2
|
|
|
3
3
|
const preParsing = {
|
|
4
4
|
level: 10,
|
|
5
5
|
handler: async function (req, reply) {
|
|
6
|
-
|
|
7
|
-
if (!
|
|
8
|
-
await
|
|
6
|
+
const secure = await checkUserId.call(this, req, reply, 'waibuStatic')
|
|
7
|
+
if (!secure) return
|
|
8
|
+
await checkTeam.call(this, req, reply, secure)
|
|
9
|
+
await checkCrossSite.call(this, req, reply)
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -132,7 +132,9 @@
|
|
|
132
132
|
"statusClosed": "Closed",
|
|
133
133
|
"allSites": "All Sites",
|
|
134
134
|
"permission": "Permission",
|
|
135
|
-
"routeGuard": "Route
|
|
135
|
+
"routeGuard": "Route",
|
|
136
|
+
"modelGuard": "Model",
|
|
137
|
+
"attribGuard": "Attribute",
|
|
136
138
|
"field": {
|
|
137
139
|
"currentPassword": "Current Password",
|
|
138
140
|
"newPassword": "New Password",
|
|
@@ -161,7 +163,10 @@
|
|
|
161
163
|
"inverse": "Inverse?",
|
|
162
164
|
"anonymous": "Anonymous?",
|
|
163
165
|
"methods": "Methods",
|
|
164
|
-
"
|
|
166
|
+
"teamIds": "Teams",
|
|
167
|
+
"column": "Column",
|
|
168
|
+
"hiddenCols": "Hidden Columns",
|
|
169
|
+
"models": "Models"
|
|
165
170
|
},
|
|
166
171
|
"validation": {
|
|
167
172
|
"password": {
|
package/extend/bajo/intl/id.json
CHANGED
|
@@ -137,6 +137,10 @@
|
|
|
137
137
|
"statusOpen": "Terbuka",
|
|
138
138
|
"statusClosed": "Tertutup",
|
|
139
139
|
"allSites": "Semua Situs",
|
|
140
|
+
"permission": "Permisi",
|
|
141
|
+
"routeGuard": "Rute",
|
|
142
|
+
"modelGuard": "Model",
|
|
143
|
+
"attribGuard": "Atribut",
|
|
140
144
|
"field": {
|
|
141
145
|
"currentPassword": "Kata Sandi Saat Ini",
|
|
142
146
|
"newPassword": "Kata Sandi Baru",
|
|
@@ -159,8 +163,16 @@
|
|
|
159
163
|
"user": "Pengguna",
|
|
160
164
|
"team": "Tim",
|
|
161
165
|
"ns": "Ruang Nama Modul",
|
|
162
|
-
"value": "
|
|
163
|
-
"notes": "Catatan"
|
|
166
|
+
"value": "Nilai",
|
|
167
|
+
"notes": "Catatan",
|
|
168
|
+
"path": "Jejak",
|
|
169
|
+
"inverse": "Inverse?",
|
|
170
|
+
"anonymous": "Anonim?",
|
|
171
|
+
"methods": "Metode",
|
|
172
|
+
"teamIds": "Tim",
|
|
173
|
+
"column": "Kolom",
|
|
174
|
+
"hideCols": "Sembunyikan Kolom",
|
|
175
|
+
"models": "Model"
|
|
164
176
|
},
|
|
165
177
|
"validation": {
|
|
166
178
|
"password": {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
async function teamIds (opts = {}) {
|
|
2
|
+
return {
|
|
3
|
+
properties: [{
|
|
4
|
+
name: 'teamIds',
|
|
5
|
+
type: 'array',
|
|
6
|
+
default: [],
|
|
7
|
+
ref: {
|
|
8
|
+
teamIds: {
|
|
9
|
+
model: 'SumbaTeam',
|
|
10
|
+
field: 'id',
|
|
11
|
+
searchField: 'name',
|
|
12
|
+
fields: ['id', 'alias', 'name']
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
index: true
|
|
16
|
+
}]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default teamIds
|
|
@@ -10,7 +10,7 @@ const routes = [
|
|
|
10
10
|
'~sumba.restapi:/user/access-token/**/*'
|
|
11
11
|
]
|
|
12
12
|
|
|
13
|
-
async function
|
|
13
|
+
async function routeGuard () {
|
|
14
14
|
return routes.map(r => {
|
|
15
15
|
const anonymous = r[0] === '~'
|
|
16
16
|
return {
|
|
@@ -23,4 +23,4 @@ async function userGuard () {
|
|
|
23
23
|
})
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export default
|
|
26
|
+
export default routeGuard
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
async function routeGuard () {
|
|
2
|
+
return {
|
|
3
|
+
properties: [
|
|
4
|
+
{
|
|
5
|
+
name: 'models',
|
|
6
|
+
type: 'array',
|
|
7
|
+
required: true
|
|
8
|
+
},
|
|
9
|
+
'hiddenCols,array',
|
|
10
|
+
'teamIds,sumba:teamIds'
|
|
11
|
+
],
|
|
12
|
+
indexes: [{
|
|
13
|
+
type: 'unique',
|
|
14
|
+
fields: ['models', 'siteId']
|
|
15
|
+
}],
|
|
16
|
+
features: [
|
|
17
|
+
{
|
|
18
|
+
name: 'sumba:status',
|
|
19
|
+
values: ['ACTIVE', 'INACTIVE'],
|
|
20
|
+
default: 'ACTIVE'
|
|
21
|
+
},
|
|
22
|
+
'dobo:immutable',
|
|
23
|
+
'dobo:updatedAt',
|
|
24
|
+
'sumba:siteId'
|
|
25
|
+
],
|
|
26
|
+
options: {
|
|
27
|
+
attachment: false
|
|
28
|
+
},
|
|
29
|
+
buildEnd: async function (model) {
|
|
30
|
+
const prop = model.properties.find(prop => prop.name === 'models')
|
|
31
|
+
prop.values = this.app.dobo.models.map(model => model.name).sort().map(item => ({ value: item, text: item }))
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default routeGuard
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
async function routeGuard () {
|
|
2
|
+
return {
|
|
3
|
+
properties: [
|
|
4
|
+
{
|
|
5
|
+
name: 'models',
|
|
6
|
+
type: 'array',
|
|
7
|
+
required: true
|
|
8
|
+
},
|
|
9
|
+
'column,,50,true,true',
|
|
10
|
+
'value,array',
|
|
11
|
+
'teamIds,sumba:teamIds'
|
|
12
|
+
],
|
|
13
|
+
indexes: [{
|
|
14
|
+
type: 'unique',
|
|
15
|
+
fields: ['models', 'column', 'siteId']
|
|
16
|
+
}],
|
|
17
|
+
features: [
|
|
18
|
+
{
|
|
19
|
+
name: 'sumba:status',
|
|
20
|
+
values: ['ACTIVE', 'INACTIVE'],
|
|
21
|
+
default: 'ACTIVE'
|
|
22
|
+
},
|
|
23
|
+
'dobo:immutable',
|
|
24
|
+
'dobo:updatedAt',
|
|
25
|
+
'sumba:siteId'
|
|
26
|
+
],
|
|
27
|
+
options: {
|
|
28
|
+
attachment: false
|
|
29
|
+
},
|
|
30
|
+
buildEnd: async function (model) {
|
|
31
|
+
const prop = model.properties.find(prop => prop.name === 'models')
|
|
32
|
+
prop.values = this.app.dobo.models.map(model => model.name).sort().map(item => ({ value: item, text: item }))
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default routeGuard
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
async function routeGuard () {
|
|
2
2
|
return {
|
|
3
|
-
connection: 'memory',
|
|
4
3
|
properties: [
|
|
5
4
|
'path,,255,true,true',
|
|
6
5
|
{
|
|
6
|
+
name: 'methods',
|
|
7
|
+
type: 'array',
|
|
8
|
+
required: true,
|
|
9
|
+
default: ['GET', 'POST', 'UPDATE', 'DELETE'],
|
|
10
|
+
values: ['GET', 'POST', 'UPDATE', 'DELETE']
|
|
11
|
+
},
|
|
12
|
+
'teamIds,sumba:teamIds',
|
|
13
|
+
{
|
|
14
|
+
name: 'weight',
|
|
15
|
+
type: 'smallint',
|
|
16
|
+
default: 0
|
|
17
|
+
}, {
|
|
7
18
|
name: 'inverse',
|
|
8
19
|
type: 'boolean',
|
|
9
20
|
required: true,
|
|
@@ -13,28 +24,6 @@ async function routeGuard () {
|
|
|
13
24
|
type: 'boolean',
|
|
14
25
|
required: true,
|
|
15
26
|
default: false
|
|
16
|
-
}, {
|
|
17
|
-
name: 'methods',
|
|
18
|
-
type: 'array',
|
|
19
|
-
required: true,
|
|
20
|
-
default: ['GET', 'POST', 'UPDATE', 'DELETE'],
|
|
21
|
-
values: ['GET', 'POST', 'UPDATE', 'DELETE']
|
|
22
|
-
}, {
|
|
23
|
-
name: 'teams',
|
|
24
|
-
type: 'array',
|
|
25
|
-
default: [],
|
|
26
|
-
ref: {
|
|
27
|
-
teams: {
|
|
28
|
-
model: 'SumbaTeam',
|
|
29
|
-
field: 'alias',
|
|
30
|
-
searchField: 'name',
|
|
31
|
-
fields: ['alias', 'name']
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}, {
|
|
35
|
-
name: 'weight',
|
|
36
|
-
type: 'smallint',
|
|
37
|
-
default: 0
|
|
38
27
|
}
|
|
39
28
|
],
|
|
40
29
|
features: [
|
|
@@ -43,16 +32,12 @@ async function routeGuard () {
|
|
|
43
32
|
values: ['ACTIVE', 'INACTIVE'],
|
|
44
33
|
default: 'ACTIVE'
|
|
45
34
|
},
|
|
46
|
-
{
|
|
47
|
-
name: 'dobo:unique',
|
|
48
|
-
fields: ['path', 'anonymous', 'methods', 'teams', 'inverse', 'weight', 'status', 'siteId']
|
|
49
|
-
},
|
|
50
35
|
'dobo:immutable',
|
|
51
36
|
'dobo:updatedAt',
|
|
52
37
|
'sumba:siteId'
|
|
53
38
|
],
|
|
54
39
|
options: {
|
|
55
|
-
|
|
40
|
+
attachment: false
|
|
56
41
|
}
|
|
57
42
|
}
|
|
58
43
|
}
|
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
async function user ({ req } = {}) {
|
|
2
|
+
const { merge } = this.app.lib._
|
|
3
|
+
const details = {
|
|
4
|
+
forceVisible: ['password', 'token'],
|
|
5
|
+
format: {
|
|
6
|
+
password: function (val, rec) {
|
|
7
|
+
return `<a href="waibuAdmin:/site/reset-user-password?username=${rec.username}">${req.t('resetPassword')}</a>`
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
widget: {
|
|
11
|
+
password: {
|
|
12
|
+
component: 'form-plaintext'
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
2
16
|
return {
|
|
3
17
|
common: {
|
|
4
18
|
layout: [
|
|
5
19
|
{ name: 'meta', fields: ['id:3', 'createdAt:3', 'updatedAt:3', 'status:3'] },
|
|
6
|
-
{ name: 'account', fields: ['username:3', 'email:3', 'provider:3', 'password:3', 'firstName:3', 'lastName:3'
|
|
20
|
+
{ name: 'account', fields: ['username:3', 'email:3', 'provider:3', 'password:3', 'firstName:3', 'lastName:3'] },
|
|
7
21
|
{ name: 'address', fields: ['address1:12', 'address2:12', 'city:6-md 8-sm', 'zipCode:2-md 4-sm', 'provinceState:4-md', 'country:6-md', 'phone:6-md', 'website:12'] },
|
|
8
22
|
{ name: 'socialMedia', fields: ['socX:3-md 6-sm', 'socInstagram:3-md 6-sm', 'socFacebook:3-md 6-sm', 'socLinkedIn:3-md 6-sm'] }
|
|
9
23
|
],
|
|
@@ -19,7 +33,7 @@ async function user ({ req } = {}) {
|
|
|
19
33
|
sort: 'username:1',
|
|
20
34
|
limit: 10
|
|
21
35
|
},
|
|
22
|
-
fields: ['createdAt', 'status', 'username', 'provider', 'email', 'firstName', 'lastName', 'city', 'zipCode', 'provinceState', 'country', 'phone'
|
|
36
|
+
fields: ['createdAt', 'status', 'username', 'provider', 'email', 'firstName', 'lastName', 'city', 'zipCode', 'provinceState', 'country', 'phone'],
|
|
23
37
|
stat: {
|
|
24
38
|
aggregate: [
|
|
25
39
|
{ fields: ['status'], group: 'status', aggregate: ['count'] },
|
|
@@ -28,35 +42,14 @@ async function user ({ req } = {}) {
|
|
|
28
42
|
]
|
|
29
43
|
}
|
|
30
44
|
},
|
|
31
|
-
details
|
|
32
|
-
forceVisible: ['password', 'token'],
|
|
33
|
-
widget: {
|
|
34
|
-
password: {
|
|
35
|
-
component: 'form-plaintext',
|
|
36
|
-
attr: {
|
|
37
|
-
href: 'waibuAdmin:/site/reset-user-password?username={username}',
|
|
38
|
-
value: req.t('resetPassword')
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
},
|
|
45
|
+
details,
|
|
43
46
|
add: {
|
|
44
47
|
forceVisible: ['password'],
|
|
45
48
|
hidden: ['id', 'createdAt', 'updatedAt', 'provider']
|
|
46
49
|
},
|
|
47
|
-
edit: {
|
|
48
|
-
forceVisible: ['password', 'token'],
|
|
49
|
-
widget: {
|
|
50
|
-
password: {
|
|
51
|
-
component: 'form-plaintext',
|
|
52
|
-
attr: {
|
|
53
|
-
href: 'waibuAdmin:/site/reset-user-password?username={username}',
|
|
54
|
-
value: req.t('resetPassword')
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
},
|
|
50
|
+
edit: merge({}, details, {
|
|
58
51
|
readonly: ['id', 'createdAt', 'updatedAt', 'username', 'provider']
|
|
59
|
-
}
|
|
52
|
+
})
|
|
60
53
|
}
|
|
61
54
|
}
|
|
62
55
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const action = {
|
|
2
|
+
method: ['GET', 'POST'],
|
|
3
|
+
title: 'attribGuard',
|
|
4
|
+
handler: async function (req, reply) {
|
|
5
|
+
const { importModule } = this.app.bajo
|
|
6
|
+
const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
|
|
7
|
+
return await crudSkel.call(this, 'SumbaAttribGuard', req, reply)
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default action
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const action = {
|
|
2
|
+
method: ['GET', 'POST'],
|
|
3
|
+
title: 'modelGuard',
|
|
4
|
+
handler: async function (req, reply) {
|
|
5
|
+
const { importModule } = this.app.bajo
|
|
6
|
+
const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
|
|
7
|
+
return await crudSkel.call(this, 'SumbaModelGuard', req, reply)
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default action
|
|
@@ -4,13 +4,14 @@ const resetUserPassword = {
|
|
|
4
4
|
handler: async function (req, reply) {
|
|
5
5
|
const { importPkg } = this.app.bajo
|
|
6
6
|
const { defaultsDeep } = this.app.lib.aneka
|
|
7
|
+
const { passwordRule } = this.app.sumba
|
|
7
8
|
const Joi = await importPkg('dobo:joi')
|
|
8
9
|
const model = this.app.dobo.getModel('SumbaUser')
|
|
9
10
|
const form = defaultsDeep(req.body, { username: req.query.username })
|
|
10
11
|
let error
|
|
11
12
|
if (req.method === 'POST') {
|
|
12
13
|
try {
|
|
13
|
-
const password = await
|
|
14
|
+
const password = await passwordRule(req)
|
|
14
15
|
const schema = Joi.object({
|
|
15
16
|
username: Joi.string().max(50).required(),
|
|
16
17
|
password,
|
package/index.js
CHANGED
|
@@ -32,11 +32,14 @@ async function factory (pkgName) {
|
|
|
32
32
|
super(pkgName, me.app)
|
|
33
33
|
this.config = {
|
|
34
34
|
multiSite: cloneDeep(defMultiSite),
|
|
35
|
-
|
|
35
|
+
crossSiteAdmins: [],
|
|
36
36
|
waibu: {
|
|
37
37
|
title: 'site',
|
|
38
38
|
prefix: 'site'
|
|
39
39
|
},
|
|
40
|
+
dobo: {
|
|
41
|
+
model: {}
|
|
42
|
+
},
|
|
40
43
|
waibuMpa: {
|
|
41
44
|
home: 'sumba:/your-stuff/profile',
|
|
42
45
|
icon: 'globe',
|
|
@@ -152,8 +155,6 @@ async function factory (pkgName) {
|
|
|
152
155
|
getUserByTokenDur: '1m'
|
|
153
156
|
}
|
|
154
157
|
}
|
|
155
|
-
this.userGuards = []
|
|
156
|
-
this.teamGuards = []
|
|
157
158
|
this.unsafeUserFields = ['password']
|
|
158
159
|
this.selfBind(['createNewSite', 'removeSite', 'getSite', 'getUserById', 'getUserByToken', 'getUserByUsernamePassword'])
|
|
159
160
|
}
|
|
@@ -170,10 +171,10 @@ async function factory (pkgName) {
|
|
|
170
171
|
|
|
171
172
|
start = async () => {
|
|
172
173
|
const { getModel } = this.app.dobo
|
|
173
|
-
if (this.config.
|
|
174
|
+
if (this.config.crossSiteAdmins.length === 0) {
|
|
174
175
|
const site = await getModel('SumbaSite').findOneRecord({ query: { alias: 'default' } }, { noMagic: true })
|
|
175
176
|
const user = await getModel('SumbaUser').findOneRecord({ query: { username: 'admin', siteId: site.id } }, { noMagic: true })
|
|
176
|
-
this.config.
|
|
177
|
+
this.config.crossSiteAdmins.push(user.id)
|
|
177
178
|
}
|
|
178
179
|
}
|
|
179
180
|
|
|
@@ -228,7 +229,9 @@ async function factory (pkgName) {
|
|
|
228
229
|
}, {
|
|
229
230
|
title: 'permission',
|
|
230
231
|
children: [
|
|
231
|
-
{ title: 'routeGuard', href: `waibuAdmin:/${prefix}/route-guard/list` }
|
|
232
|
+
{ title: 'routeGuard', href: `waibuAdmin:/${prefix}/route-guard/list` },
|
|
233
|
+
{ title: 'modelGuard', href: `waibuAdmin:/${prefix}/model-guard/list` },
|
|
234
|
+
{ title: 'attribGuard', href: `waibuAdmin:/${prefix}/attrib-guard/list` }
|
|
232
235
|
]
|
|
233
236
|
}, {
|
|
234
237
|
title: 'supportSystem',
|
|
@@ -272,13 +275,14 @@ async function factory (pkgName) {
|
|
|
272
275
|
|
|
273
276
|
verifySession = async (req, reply, source, payload) => {
|
|
274
277
|
const { routePath } = this.app.waibu
|
|
278
|
+
const { query, params } = req
|
|
275
279
|
|
|
276
280
|
if (!req.session) return false
|
|
277
281
|
if (req.session.userId) {
|
|
278
282
|
req.user = await this.getUserById(req.session.userId, req)
|
|
279
283
|
return true
|
|
280
284
|
}
|
|
281
|
-
const redir = routePath(this.config.redirect.signin,
|
|
285
|
+
const redir = routePath(this.config.redirect.signin, { query, params })
|
|
282
286
|
req.session.ref = req.url
|
|
283
287
|
throw this.error('_redirect', { redirect: redir })
|
|
284
288
|
}
|
|
@@ -361,7 +365,7 @@ async function factory (pkgName) {
|
|
|
361
365
|
return true
|
|
362
366
|
}
|
|
363
367
|
|
|
364
|
-
checkPathsByRoute = ({ paths = [],
|
|
368
|
+
checkPathsByRoute = ({ paths = [], teamIds = [], guards = [] }) => {
|
|
365
369
|
const { includes } = this.app.lib.aneka
|
|
366
370
|
const { outmatch } = this.app.lib
|
|
367
371
|
|
|
@@ -369,10 +373,8 @@ async function factory (pkgName) {
|
|
|
369
373
|
const matchPath = outmatch(item.path)
|
|
370
374
|
for (const path of paths) {
|
|
371
375
|
if (matchPath(path)) {
|
|
372
|
-
if (item.
|
|
373
|
-
|
|
374
|
-
if (includes(teams, item.teams)) return item
|
|
375
|
-
}
|
|
376
|
+
if (includes(teamIds, item.teamIds)) return item
|
|
377
|
+
if (teamIds.length === 0) return item
|
|
376
378
|
}
|
|
377
379
|
}
|
|
378
380
|
}
|
|
@@ -535,7 +537,12 @@ async function factory (pkgName) {
|
|
|
535
537
|
const { map, orderBy } = this.app.lib._
|
|
536
538
|
if (!reread) return this.routeGuards
|
|
537
539
|
const model = getModel('SumbaRouteGuard')
|
|
538
|
-
|
|
540
|
+
let results = await model.findAllRecord({ query: { status: 'ACTIVE' } }, { noMagic: true, noCache: true, noDriverHook: true, dataOnly: true })
|
|
541
|
+
results = results.map(item => {
|
|
542
|
+
item.teamIds = item.teamIds ?? []
|
|
543
|
+
item.methods = item.methods ?? []
|
|
544
|
+
return item
|
|
545
|
+
})
|
|
539
546
|
this.routeGuards = orderBy(map(results, item => {
|
|
540
547
|
item.path = routePath(item.path)
|
|
541
548
|
return item
|
package/lib/get-user.js
CHANGED
|
@@ -3,7 +3,7 @@ import { parseNsSettings } from './util.js'
|
|
|
3
3
|
export async function mergeTeam (user) {
|
|
4
4
|
const { map, pick } = this.app.lib._
|
|
5
5
|
const { getModel } = this.app.dobo
|
|
6
|
-
user.
|
|
6
|
+
user.crossSiteAdmin = this.config.crossSiteAdmins.includes(user.id)
|
|
7
7
|
user.teams = []
|
|
8
8
|
const query = { userId: user.id, siteId: user.siteId }
|
|
9
9
|
let mdl = getModel('SumbaTeamUser')
|
|
@@ -45,7 +45,7 @@ export async function getUserById (id, req) {
|
|
|
45
45
|
user = await getCache({ key })
|
|
46
46
|
if (user) return JSON.parse(user)
|
|
47
47
|
}
|
|
48
|
-
user = await getModel('SumbaUser').getRecord(id, { noHook: true, throwNotFound: false, req })
|
|
48
|
+
user = await getModel('SumbaUser').getRecord(id, { noHook: true, noDriverHook: true, throwNotFound: false, req })
|
|
49
49
|
if (!user) return
|
|
50
50
|
await mergeTeam.call(this, user)
|
|
51
51
|
if (setCache) {
|
|
@@ -63,7 +63,7 @@ export async function getUserByToken (token, req) {
|
|
|
63
63
|
user = await getCache({ key })
|
|
64
64
|
if (user) return JSON.parse(user)
|
|
65
65
|
}
|
|
66
|
-
user = await getModel('SumbaUser').findOneRecord({ query: { token } }, { noHook: true, throwNotFound: false, req })
|
|
66
|
+
user = await getModel('SumbaUser').findOneRecord({ query: { token } }, { noHook: true, noDriverHook: true, throwNotFound: false, req })
|
|
67
67
|
if (!user) return
|
|
68
68
|
await mergeTeam.call(this, user)
|
|
69
69
|
if (setCache) {
|
|
@@ -80,7 +80,7 @@ export async function getUserByUsernamePassword (username = '', password = '', r
|
|
|
80
80
|
const bcrypt = await importPkg('bajoExtra:bcrypt')
|
|
81
81
|
|
|
82
82
|
const query = { username, provider: 'local', siteId: req.site.id }
|
|
83
|
-
const user = await model.findOneRecord({ query }, { req, forceNoHidden: true, noHook: true })
|
|
83
|
+
const user = await model.findOneRecord({ query }, { req, forceNoHidden: true, noHook: true, noDriverHook: true })
|
|
84
84
|
if (!user) throw this.error('validationError', { details: [{ field: 'username', error: 'Unknown username' }], statusCode: 401 })
|
|
85
85
|
if (user.status !== 'ACTIVE') throw this.error('validationError', { details: ['User is inactive or temporarily disabled'], statusCode: 401 })
|
|
86
86
|
const verified = await bcrypt.compare(password, user.password)
|
package/lib/util.js
CHANGED
|
@@ -56,17 +56,21 @@ 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._
|
|
62
|
-
|
|
61
|
+
route.teams = route.teams ?? []
|
|
62
|
+
if (route.teams.length === 0) return
|
|
63
|
+
|
|
63
64
|
const teams = map(req.user.teams, 'alias')
|
|
65
|
+
if (teams.includes('administrator')) return
|
|
66
|
+
if (teams.length === 0) throw this.error('accessDenied', { statusCode: 403 })
|
|
67
|
+
|
|
68
|
+
const paths = pathsToCheck.call(this, req, true)
|
|
64
69
|
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,
|
|
67
|
-
if (!match) return // route is NOT protected by team guard
|
|
70
|
+
if (allGuards.length === 0) return // no route to be team guarded
|
|
71
|
+
let match = this.checkPathsByRoute({ paths, teams, guards: allGuards.filter(item => !item.inverse) })
|
|
68
72
|
if (match) {
|
|
69
|
-
const neg = this.checkPathsByRoute({ paths,
|
|
73
|
+
const neg = this.checkPathsByRoute({ paths, teams, guards: allGuards.filter(item => item.inverse) })
|
|
70
74
|
if (neg) match = undefined
|
|
71
75
|
}
|
|
72
76
|
if (!match) throw this.error('accessDenied', { statusCode: 403 })
|
|
@@ -102,14 +106,14 @@ export async function checkUserId (req, reply, source) {
|
|
|
102
106
|
const paths = pathsToCheck.call(this, req)
|
|
103
107
|
const allGuards = (await this.getRouteGuards()).filter(item => item.siteId === req.site.id + '')
|
|
104
108
|
if (allGuards.length === 0) return false // no routes protected
|
|
105
|
-
let securePath = await this.checkPathsByRoute({ paths,
|
|
109
|
+
let securePath = await this.checkPathsByRoute({ paths, guards: allGuards.filter(item => !item.anonymous && !item.inverse) })
|
|
106
110
|
if (securePath) {
|
|
107
|
-
const neg = await this.checkPathsByRoute({ paths,
|
|
111
|
+
const neg = await this.checkPathsByRoute({ paths, guards: allGuards.filter(item => !item.anonymous && item.inverse) })
|
|
108
112
|
if (neg) securePath = undefined
|
|
109
113
|
}
|
|
110
|
-
let anonymousPath = await this.checkPathsByRoute({ paths,
|
|
114
|
+
let anonymousPath = await this.checkPathsByRoute({ paths, guards: allGuards.filter(item => item.anonymous && !item.inverse) })
|
|
111
115
|
if (anonymousPath) {
|
|
112
|
-
const neg = await this.checkPathsByRoute({ paths,
|
|
116
|
+
const neg = await this.checkPathsByRoute({ paths, guards: allGuards.filter(item => item.anonymous && item.inverse) })
|
|
113
117
|
if (neg) anonymousPath = undefined
|
|
114
118
|
}
|
|
115
119
|
if (!securePath && !anonymousPath) {
|
|
@@ -121,9 +125,10 @@ export async function checkUserId (req, reply, source) {
|
|
|
121
125
|
req.session.ref = req.url
|
|
122
126
|
return reply.redirectTo(routePath(this.config.redirect.signout))
|
|
123
127
|
}
|
|
128
|
+
if (!(securePath.methods ?? []).includes(req.method)) throw this.error('accessDenied', { statusCode: 403 })
|
|
124
129
|
if (userId) {
|
|
125
130
|
await setUser()
|
|
126
|
-
return
|
|
131
|
+
return securePath
|
|
127
132
|
}
|
|
128
133
|
const silentOnError = this.config.auth[webApp].silentOnError ?? this.config.auth.common.silentOnError
|
|
129
134
|
const payload = silentOnError ? { noContent: true } : undefined
|
|
@@ -140,7 +145,7 @@ export async function checkUserId (req, reply, source) {
|
|
|
140
145
|
}
|
|
141
146
|
}
|
|
142
147
|
if (!success) throw this.error('accessDeniedNoAuth', merge({ statusCode: 403 }, payload))
|
|
143
|
-
return
|
|
148
|
+
return securePath
|
|
144
149
|
}
|
|
145
150
|
|
|
146
151
|
export async function latLngHook (body, options) {
|
|
@@ -157,10 +162,8 @@ export async function latLngHook (body, options) {
|
|
|
157
162
|
* @param {Object} reply - Reply object
|
|
158
163
|
* @returns
|
|
159
164
|
*/
|
|
160
|
-
export async function
|
|
165
|
+
export async function checkCrossSite (req, reply) {
|
|
161
166
|
const { get } = this.app.lib._
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (!isinterSite) return
|
|
165
|
-
if (!isInterSiteAdmin) throw this.error('accessDenied', { statusCode: 403 })
|
|
167
|
+
if (!get(req, 'routeOptions.config.crossSite')) return
|
|
168
|
+
if (!get(req, 'user.crossSiteAdmin')) throw this.error('accessDenied', { statusCode: 403 })
|
|
166
169
|
}
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## 2026-05-11
|
|
4
4
|
|
|
5
|
+
- [2.24.0] Complete rewrite of route guards
|
|
6
|
+
- [2.24.0] Add model guards
|
|
7
|
+
- [2.24.0] Add attribute guards
|
|
8
|
+
|
|
9
|
+
## 2026-05-11
|
|
10
|
+
|
|
5
11
|
- [2.23.0] Unify route guards and put it in ```SumbaRouteGuard``` database
|
|
6
12
|
- [2.23.0] Phased out ```setting.noRoutes```
|
|
7
13
|
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
const doboBeforeCreateRecord = {
|
|
2
|
-
level: 1000,
|
|
3
|
-
handler: async function (modelName, body, options = {}) {
|
|
4
|
-
const { get } = this.app.lib._
|
|
5
|
-
const { req } = options
|
|
6
|
-
if (options.noAutoFilter || !req || get(req, 'routeOptions.config.interSite')) return
|
|
7
|
-
const item = { siteId: 'site.id', userId: 'user.id' }
|
|
8
|
-
const model = this.app.dobo.getModel(modelName)
|
|
9
|
-
for (const i in item) {
|
|
10
|
-
const rec = get(req, item[i])
|
|
11
|
-
const field = model.getProperty(i)
|
|
12
|
-
if (rec && field) body[i] = field.type === 'string' ? (rec + '') : rec
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export default doboBeforeCreateRecord
|
|
@@ -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
|