sumba 2.24.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/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-team$dobo.sumba-site@after-transaction.js +8 -0
- package/extend/bajo/hook/dobo@before-driver-create-record.js +1 -1
- package/extend/bajo/hook/dobo@before-driver-find-record.js +51 -33
- package/extend/bajo/hook/waibu-mpa@pre-parsing.js +2 -2
- package/extend/bajo/hook/waibu-rest-api@pre-parsing.js +2 -2
- package/extend/bajo/hook/waibu-static@pre-parsing.js +2 -2
- package/extend/bajo/hook/waibu@after-app-boot.js +2 -0
- package/extend/bajo/intl/en-US.json +11 -5
- package/extend/bajo/intl/id.json +12 -6
- package/extend/dobo/feature/site-ids.js +19 -0
- package/extend/dobo/feature/team-ids.js +1 -2
- package/extend/dobo/fixture/{route-guard.js → x-route-guard.js} +0 -2
- package/extend/dobo/model/attrib-guard.js +26 -30
- package/extend/dobo/model/model-guard.js +42 -31
- package/extend/dobo/model/route-guard.js +42 -39
- 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/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/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 +105 -33
- package/lib/get-user.js +6 -5
- package/lib/util.js +52 -20
- package/package.json +1 -1
- package/wiki/CHANGES.md +7 -0
- /package/extend/bajo/hook/{dobo.sumba-route-guard@after-transaction.js → dobo.sumba-route-guard$dobo.sumba-x-route-guard@after-transaction.js} +0 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
async function afterTransaction (action, ...args) {
|
|
2
|
+
if (!['createRecord', 'updateRecord', 'removeRecord'].includes(action)) return
|
|
3
|
+
await this.getRouteGuards(true)
|
|
4
|
+
await this.getModelGuards(true)
|
|
5
|
+
await this.getAttribGuards(true)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default afterTransaction
|
|
@@ -4,7 +4,7 @@ const doboBeforeDriverCreateRecord = {
|
|
|
4
4
|
const { get } = this.app.lib._
|
|
5
5
|
const { isSet } = this.app.lib.aneka
|
|
6
6
|
const { req } = options
|
|
7
|
-
if (options.noAutoFilter || !req || get(req, 'routeOptions.config.
|
|
7
|
+
if (options.noAutoFilter || !req || get(req, 'routeOptions.config.xSite')) return
|
|
8
8
|
const item = { siteId: 'site.id', userId: 'user.id' }
|
|
9
9
|
for (const i in item) {
|
|
10
10
|
const rec = get(req, item[i])
|
|
@@ -1,51 +1,69 @@
|
|
|
1
1
|
async function applyModelGuard ({ model, q, teamIds, options }) {
|
|
2
|
-
const {
|
|
2
|
+
const { get, set, orderBy, intersection, without } = this.app.lib._
|
|
3
|
+
const { include } = this.app.lib.aneka
|
|
3
4
|
const { req } = options
|
|
4
|
-
const
|
|
5
|
+
const results = []
|
|
5
6
|
|
|
6
|
-
const guards =
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
item
|
|
7
|
+
const guards = await this.getModelGuards()
|
|
8
|
+
const columns = model.getNonVirtualProperties().map(prop => prop.name)
|
|
9
|
+
const filterFn = item => {
|
|
10
|
+
const bySiteId = item.siteIds.includes(req.site.id + '')
|
|
11
|
+
const byModel = item.models.includes(model.name)
|
|
12
|
+
const byTeamId = item.teamIds.length === 0 || include(item.teamIds, teamIds)
|
|
13
|
+
const byColumn = columns.includes(item.column)
|
|
14
|
+
return bySiteId && byModel && byTeamId && byColumn
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
17
|
+
guards.global = orderBy(guards.global.filter(filterFn), ['column'])
|
|
18
|
+
guards.local = orderBy(guards.local.filter(filterFn), ['column'])
|
|
19
|
+
for (const col of columns) {
|
|
20
|
+
let values = []
|
|
21
|
+
let items = guards.global.filter(item => item.column === col && !item.negation)
|
|
22
|
+
for (const item of items) {
|
|
23
|
+
values.push(...item.value)
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
guards.
|
|
25
|
+
items = guards.global.filter(item => item.column === col && item.negation)
|
|
26
|
+
for (const item of items) {
|
|
27
|
+
values = without(values, ...item.value)
|
|
28
|
+
}
|
|
29
|
+
items = guards.local.filter(item => item.column === col && !item.negation)
|
|
30
|
+
const newValues = []
|
|
31
|
+
for (const item of items) {
|
|
32
|
+
newValues.push(...item.value)
|
|
33
|
+
}
|
|
34
|
+
if (newValues.length > 0) values = intersection(values, newValues)
|
|
35
|
+
items = guards.local.filter(item => item.column === col && item.negation)
|
|
36
|
+
for (const item of items) {
|
|
37
|
+
values = without(values, ...item.value)
|
|
38
|
+
}
|
|
39
|
+
if (values.length) results.push(set({}, col, { $in: values }))
|
|
29
40
|
}
|
|
30
41
|
|
|
31
42
|
const allowEmpty = get(this, `config.dobo.model.${model.name}.allowEmptyQuery`, true)
|
|
32
|
-
if (
|
|
33
|
-
q.$and.push(...
|
|
43
|
+
if (results.length === 0 && !allowEmpty) throw this.error('_emptyColumnQuery') // signal driver to about with no result immediately
|
|
44
|
+
q.$and.push(...results)
|
|
34
45
|
}
|
|
35
46
|
|
|
36
47
|
export async function applyAttribGuard ({ model, teamIds, options }) {
|
|
37
|
-
const { getModel } = this.app.dobo
|
|
38
48
|
const { uniq } = this.app.lib._
|
|
49
|
+
const { include } = this.app.lib.aneka
|
|
39
50
|
const { req } = options
|
|
51
|
+
const results = []
|
|
40
52
|
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (teamIds.includes(id)) options.hidden.push(...(result.hiddenCols ?? []))
|
|
53
|
+
const guards = await this.getAttribGuards()
|
|
54
|
+
const filterFn = item => {
|
|
55
|
+
const bySiteId = item.siteIds.includes(req.site.id + '')
|
|
56
|
+
const byModel = item.models.includes(model.name)
|
|
57
|
+
const byTeamId = item.teamIds.length === 0 || include(item.teamIds, teamIds)
|
|
58
|
+
return bySiteId && byModel && byTeamId
|
|
48
59
|
}
|
|
60
|
+
|
|
61
|
+
let item = guards.global.filter(filterFn)[0]
|
|
62
|
+
if (item) results.push(...item.hiddenCols)
|
|
63
|
+
item = guards.local.filter(filterFn)[0]
|
|
64
|
+
if (item) results.push(...item.hiddenCols)
|
|
65
|
+
options.hidden = options.hidden ?? []
|
|
66
|
+
if (results.length > 0) options.hidden.push(...results)
|
|
49
67
|
options.hidden = uniq(options.hidden)
|
|
50
68
|
}
|
|
51
69
|
|
|
@@ -65,7 +83,7 @@ export async function rebuildFilter (model, filter = {}, options = {}) {
|
|
|
65
83
|
if (filter.query.$and) q.$and.push(...filter.query.$and)
|
|
66
84
|
else q.$and.push(filter.query)
|
|
67
85
|
}
|
|
68
|
-
if (req.routeOptions.config.
|
|
86
|
+
if (req.routeOptions.config.xSite) return
|
|
69
87
|
if (hasSiteId) q.$and.push({ siteId: req.site.id + '' })
|
|
70
88
|
if (aliases.includes('administrator')) {
|
|
71
89
|
filter.query = q
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { checkUserId, checkTeam, checkTheme, checkIconset,
|
|
1
|
+
import { checkUserId, checkTeam, checkTheme, checkIconset, checkXSite } from '../../../lib/util.js'
|
|
2
2
|
|
|
3
3
|
const preParsing = {
|
|
4
4
|
level: 10,
|
|
@@ -8,7 +8,7 @@ const preParsing = {
|
|
|
8
8
|
const secure = await checkUserId.call(this, req, reply, 'waibuMpa')
|
|
9
9
|
if (!secure) return
|
|
10
10
|
await checkTeam.call(this, req, reply, secure)
|
|
11
|
-
await
|
|
11
|
+
await checkXSite.call(this, req, reply)
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { checkUserId, checkTeam,
|
|
1
|
+
import { checkUserId, checkTeam, checkXSite } from '../../../lib/util.js'
|
|
2
2
|
|
|
3
3
|
const preParsing = {
|
|
4
4
|
level: 10,
|
|
@@ -6,7 +6,7 @@ const preParsing = {
|
|
|
6
6
|
const secure = await checkUserId.call(this, req, reply, 'waibuRestApi')
|
|
7
7
|
if (!secure) return
|
|
8
8
|
await checkTeam.call(this, req, reply, secure)
|
|
9
|
-
await
|
|
9
|
+
await checkXSite.call(this, req, reply)
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { checkUserId, checkTeam,
|
|
1
|
+
import { checkUserId, checkTeam, checkXSite } from '../../../lib/util.js'
|
|
2
2
|
|
|
3
3
|
const preParsing = {
|
|
4
4
|
level: 10,
|
|
@@ -6,7 +6,7 @@ const preParsing = {
|
|
|
6
6
|
const secure = await checkUserId.call(this, req, reply, 'waibuStatic')
|
|
7
7
|
if (!secure) return
|
|
8
8
|
await checkTeam.call(this, req, reply, secure)
|
|
9
|
-
await
|
|
9
|
+
await checkXSite.call(this, req, reply)
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -132,9 +132,14 @@
|
|
|
132
132
|
"statusClosed": "Closed",
|
|
133
133
|
"allSites": "All Sites",
|
|
134
134
|
"permission": "Permission",
|
|
135
|
-
"routeGuard": "Route",
|
|
136
|
-
"modelGuard": "Model",
|
|
137
|
-
"attribGuard": "Attribute",
|
|
135
|
+
"routeGuard": "Route Guard",
|
|
136
|
+
"modelGuard": "Model Guard",
|
|
137
|
+
"attribGuard": "Attribute Guard",
|
|
138
|
+
"xRouteGuard": "Route Guard",
|
|
139
|
+
"xModelGuard": "Model Guard",
|
|
140
|
+
"xAttribGuard": "Attribute Guard",
|
|
141
|
+
"xSite": "Cross-Site",
|
|
142
|
+
"xSiteAdminArea": "Cross-Site Admin Area",
|
|
138
143
|
"field": {
|
|
139
144
|
"currentPassword": "Current Password",
|
|
140
145
|
"newPassword": "New Password",
|
|
@@ -160,13 +165,14 @@
|
|
|
160
165
|
"value": "Value",
|
|
161
166
|
"notes": "Notes",
|
|
162
167
|
"path": "Path",
|
|
163
|
-
"
|
|
168
|
+
"negation": "Negation?",
|
|
164
169
|
"anonymous": "Anonymous?",
|
|
165
170
|
"methods": "Methods",
|
|
166
171
|
"teamIds": "Teams",
|
|
167
172
|
"column": "Column",
|
|
168
173
|
"hiddenCols": "Hidden Columns",
|
|
169
|
-
"models": "Models"
|
|
174
|
+
"models": "Models",
|
|
175
|
+
"siteIds": "Sites"
|
|
170
176
|
},
|
|
171
177
|
"validation": {
|
|
172
178
|
"password": {
|
package/extend/bajo/intl/id.json
CHANGED
|
@@ -138,9 +138,14 @@
|
|
|
138
138
|
"statusClosed": "Tertutup",
|
|
139
139
|
"allSites": "Semua Situs",
|
|
140
140
|
"permission": "Permisi",
|
|
141
|
-
"routeGuard": "Rute",
|
|
142
|
-
"modelGuard": "Model",
|
|
143
|
-
"attribGuard": "Atribut",
|
|
141
|
+
"routeGuard": "Pelindung Rute",
|
|
142
|
+
"modelGuard": "Pelindung Model",
|
|
143
|
+
"attribGuard": "Pelindung Atribut",
|
|
144
|
+
"xRouteGuard": "Pelindung Rute",
|
|
145
|
+
"xModelGuard": "Pelindung Model",
|
|
146
|
+
"xAttribGuard": "Pelindung Atribut",
|
|
147
|
+
"xSite": "Antar Situs",
|
|
148
|
+
"xSiteAdminArea": "Area Admin Antar Situs",
|
|
144
149
|
"field": {
|
|
145
150
|
"currentPassword": "Kata Sandi Saat Ini",
|
|
146
151
|
"newPassword": "Kata Sandi Baru",
|
|
@@ -166,13 +171,14 @@
|
|
|
166
171
|
"value": "Nilai",
|
|
167
172
|
"notes": "Catatan",
|
|
168
173
|
"path": "Jejak",
|
|
169
|
-
"
|
|
174
|
+
"negation": "Negation?",
|
|
170
175
|
"anonymous": "Anonim?",
|
|
171
176
|
"methods": "Metode",
|
|
172
177
|
"teamIds": "Tim",
|
|
173
178
|
"column": "Kolom",
|
|
174
|
-
"
|
|
175
|
-
"models": "Model"
|
|
179
|
+
"hiddenCols": "Kolom Tersembunyi",
|
|
180
|
+
"models": "Model",
|
|
181
|
+
"siteIds": "Situs"
|
|
176
182
|
},
|
|
177
183
|
"validation": {
|
|
178
184
|
"password": {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
async function siteIds (opts = {}) {
|
|
2
|
+
return {
|
|
3
|
+
properties: [{
|
|
4
|
+
name: 'siteIds',
|
|
5
|
+
type: 'array',
|
|
6
|
+
default: [],
|
|
7
|
+
ref: {
|
|
8
|
+
siteIds: {
|
|
9
|
+
model: 'SumbaSite',
|
|
10
|
+
field: 'id',
|
|
11
|
+
searchField: 'hostname',
|
|
12
|
+
fields: ['id', 'hostname']
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default siteIds
|
|
@@ -1,35 +1,31 @@
|
|
|
1
|
+
import { buildEnd, options } from './model-guard.js'
|
|
2
|
+
|
|
3
|
+
export const properties = [
|
|
4
|
+
{
|
|
5
|
+
name: 'models',
|
|
6
|
+
type: 'array',
|
|
7
|
+
required: true
|
|
8
|
+
},
|
|
9
|
+
'hiddenCols,array',
|
|
10
|
+
'siteId,sumba:siteId',
|
|
11
|
+
'teamIds,sumba:teamIds'
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
export const features = [
|
|
15
|
+
{
|
|
16
|
+
name: 'sumba:status',
|
|
17
|
+
values: ['ACTIVE', 'INACTIVE'],
|
|
18
|
+
default: 'ACTIVE'
|
|
19
|
+
},
|
|
20
|
+
'dobo:updatedAt'
|
|
21
|
+
]
|
|
22
|
+
|
|
1
23
|
async function routeGuard () {
|
|
2
24
|
return {
|
|
3
|
-
properties
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
}
|
|
25
|
+
properties,
|
|
26
|
+
features,
|
|
27
|
+
options,
|
|
28
|
+
buildEnd
|
|
33
29
|
}
|
|
34
30
|
}
|
|
35
31
|
|
|
@@ -1,36 +1,47 @@
|
|
|
1
|
+
export async function buildEnd (model) {
|
|
2
|
+
const prop = model.properties.find(prop => prop.name === 'models')
|
|
3
|
+
if (prop) prop.values = this.app.sumba.getModelNames(true)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const properties = [
|
|
7
|
+
{
|
|
8
|
+
name: 'models',
|
|
9
|
+
type: 'array',
|
|
10
|
+
required: true
|
|
11
|
+
},
|
|
12
|
+
'column,,50,true,true',
|
|
13
|
+
{
|
|
14
|
+
name: 'negation',
|
|
15
|
+
type: 'boolean',
|
|
16
|
+
required: true,
|
|
17
|
+
default: false
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'status',
|
|
21
|
+
type: 'sumba:status',
|
|
22
|
+
values: ['ACTIVE', 'INACTIVE'],
|
|
23
|
+
default: 'ACTIVE'
|
|
24
|
+
},
|
|
25
|
+
'value,array,,,true',
|
|
26
|
+
'siteId,sumba:siteId',
|
|
27
|
+
'teamIds,sumba:teamIds'
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
export const options = {
|
|
31
|
+
attachment: false,
|
|
32
|
+
cache: { ttlDur: 0 }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const features = [
|
|
36
|
+
'dobo:updatedAt'
|
|
37
|
+
]
|
|
38
|
+
|
|
1
39
|
async function routeGuard () {
|
|
2
40
|
return {
|
|
3
|
-
properties
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
}
|
|
41
|
+
properties,
|
|
42
|
+
features,
|
|
43
|
+
options,
|
|
44
|
+
buildEnd
|
|
34
45
|
}
|
|
35
46
|
}
|
|
36
47
|
|
|
@@ -1,44 +1,47 @@
|
|
|
1
|
+
import { options } from './model-guard.js'
|
|
2
|
+
|
|
3
|
+
export const properties = [
|
|
4
|
+
'path,,255,true,true',
|
|
5
|
+
{
|
|
6
|
+
name: 'methods',
|
|
7
|
+
type: 'array',
|
|
8
|
+
required: true,
|
|
9
|
+
default: ['GET', 'POST', 'UPDATE', 'DELETE'],
|
|
10
|
+
values: ['GET', 'POST', 'UPDATE', 'DELETE']
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'weight',
|
|
14
|
+
type: 'smallint',
|
|
15
|
+
default: 0
|
|
16
|
+
}, {
|
|
17
|
+
name: 'negation',
|
|
18
|
+
type: 'boolean',
|
|
19
|
+
required: true,
|
|
20
|
+
default: false
|
|
21
|
+
}, {
|
|
22
|
+
name: 'anonymous',
|
|
23
|
+
type: 'boolean',
|
|
24
|
+
required: true,
|
|
25
|
+
default: false
|
|
26
|
+
},
|
|
27
|
+
'siteId,sumba:siteId',
|
|
28
|
+
'teamIds,sumba:teamIds'
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
export const features = [
|
|
32
|
+
{
|
|
33
|
+
name: 'sumba:status',
|
|
34
|
+
values: ['ACTIVE', 'INACTIVE'],
|
|
35
|
+
default: 'ACTIVE'
|
|
36
|
+
},
|
|
37
|
+
'dobo:updatedAt'
|
|
38
|
+
]
|
|
39
|
+
|
|
1
40
|
async function routeGuard () {
|
|
2
41
|
return {
|
|
3
|
-
properties
|
|
4
|
-
|
|
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
|
-
}, {
|
|
18
|
-
name: 'inverse',
|
|
19
|
-
type: 'boolean',
|
|
20
|
-
required: true,
|
|
21
|
-
default: false
|
|
22
|
-
}, {
|
|
23
|
-
name: 'anonymous',
|
|
24
|
-
type: 'boolean',
|
|
25
|
-
required: true,
|
|
26
|
-
default: false
|
|
27
|
-
}
|
|
28
|
-
],
|
|
29
|
-
features: [
|
|
30
|
-
{
|
|
31
|
-
name: 'sumba:status',
|
|
32
|
-
values: ['ACTIVE', 'INACTIVE'],
|
|
33
|
-
default: 'ACTIVE'
|
|
34
|
-
},
|
|
35
|
-
'dobo:immutable',
|
|
36
|
-
'dobo:updatedAt',
|
|
37
|
-
'sumba:siteId'
|
|
38
|
-
],
|
|
39
|
-
options: {
|
|
40
|
-
attachment: false
|
|
41
|
-
}
|
|
42
|
+
properties,
|
|
43
|
+
features,
|
|
44
|
+
options
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { properties, features } from './attrib-guard.js'
|
|
2
|
+
import { options, buildEnd } from './model-guard.js'
|
|
3
|
+
|
|
4
|
+
async function routeGuard () {
|
|
5
|
+
const { isString } = this.app.lib._
|
|
6
|
+
return {
|
|
7
|
+
properties: properties.filter(prop => !isString(prop) || (isString(prop) && !prop.startsWith('teamIds'))),
|
|
8
|
+
features: features.filter(feat => !isString(feat) || (isString(feat) && !['sumba:siteId', 'dobo:updatedAt'].includes(feat))).concat('sumba:siteIds', 'dobo:updatedAt'),
|
|
9
|
+
options,
|
|
10
|
+
buildEnd
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default routeGuard
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { buildEnd, properties, features, options } from './model-guard.js'
|
|
2
|
+
|
|
3
|
+
async function xModelGuard () {
|
|
4
|
+
const { isString } = this.app.lib._
|
|
5
|
+
return {
|
|
6
|
+
properties: properties.filter(prop => !isString(prop) || (isString(prop) && !prop.startsWith('teamIds'))),
|
|
7
|
+
features: features.filter(feat => !isString(feat) || (isString(feat) && !['sumba:siteId', 'dobo:updatedAt'].includes(feat))).concat('sumba:siteIds', 'dobo:updatedAt'),
|
|
8
|
+
options,
|
|
9
|
+
buildEnd
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default xModelGuard
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { properties, features } from './route-guard.js'
|
|
2
|
+
import { options } from './model-guard.js'
|
|
3
|
+
|
|
4
|
+
async function xRouteGuard () {
|
|
5
|
+
const { isString } = this.app.lib._
|
|
6
|
+
return {
|
|
7
|
+
properties: properties.filter(prop => !isString(prop) || (isString(prop) && !prop.startsWith('teamIds'))),
|
|
8
|
+
features: features.filter(feat => !isString(feat) || (isString(feat) && !['sumba:siteId', 'dobo:updatedAt'].includes(feat))).concat('sumba:siteIds', 'dobo:updatedAt'),
|
|
9
|
+
options
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default xRouteGuard
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const action = {
|
|
2
|
+
method: ['GET', 'POST'],
|
|
3
|
+
title: 'xAttribGuard',
|
|
4
|
+
xSite: true,
|
|
5
|
+
handler: async function (req, reply) {
|
|
6
|
+
const { importModule } = this.app.bajo
|
|
7
|
+
const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
|
|
8
|
+
return await crudSkel.call(this, 'SumbaXAttribGuard', req, reply)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default action
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const action = {
|
|
2
|
+
method: ['GET', 'POST'],
|
|
3
|
+
title: 'xModelGuard',
|
|
4
|
+
xSite: true,
|
|
5
|
+
handler: async function (req, reply) {
|
|
6
|
+
const { importModule } = this.app.bajo
|
|
7
|
+
const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
|
|
8
|
+
return await crudSkel.call(this, 'SumbaXModelGuard', req, reply)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default action
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const action = {
|
|
2
|
+
method: ['GET', 'POST'],
|
|
3
|
+
title: 'xRouteGuard',
|
|
4
|
+
xSite: true,
|
|
5
|
+
handler: async function (req, reply) {
|
|
6
|
+
const { importModule } = this.app.bajo
|
|
7
|
+
const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
|
|
8
|
+
return await crudSkel.call(this, 'SumbaXRouteGuard', req, reply)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default action
|
package/index.js
CHANGED
|
@@ -20,7 +20,7 @@ const defMultiSite = {
|
|
|
20
20
|
async function factory (pkgName) {
|
|
21
21
|
const me = this
|
|
22
22
|
const { getModel } = this.app.dobo
|
|
23
|
-
const { cloneDeep } = this.app.lib._
|
|
23
|
+
const { cloneDeep, uniq, isEmpty } = this.app.lib._
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Sumba class
|
|
@@ -32,7 +32,7 @@ async function factory (pkgName) {
|
|
|
32
32
|
super(pkgName, me.app)
|
|
33
33
|
this.config = {
|
|
34
34
|
multiSite: cloneDeep(defMultiSite),
|
|
35
|
-
crossSiteAdmins: [],
|
|
35
|
+
crossSiteAdmins: [], // format: "<siteAlias>:<username>"
|
|
36
36
|
waibu: {
|
|
37
37
|
title: 'site',
|
|
38
38
|
prefix: 'site'
|
|
@@ -155,6 +155,10 @@ async function factory (pkgName) {
|
|
|
155
155
|
getUserByTokenDur: '1m'
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
|
+
this.routeGuards = { local: [], global: [] }
|
|
159
|
+
this.modelGuards = { local: [], global: [] }
|
|
160
|
+
this.attribGuards = { local: [], global: [] }
|
|
161
|
+
|
|
158
162
|
this.unsafeUserFields = ['password']
|
|
159
163
|
this.selfBind(['createNewSite', 'removeSite', 'getSite', 'getUserById', 'getUserByToken', 'getUserByUsernamePassword'])
|
|
160
164
|
}
|
|
@@ -174,7 +178,7 @@ async function factory (pkgName) {
|
|
|
174
178
|
if (this.config.crossSiteAdmins.length === 0) {
|
|
175
179
|
const site = await getModel('SumbaSite').findOneRecord({ query: { alias: 'default' } }, { noMagic: true })
|
|
176
180
|
const user = await getModel('SumbaUser').findOneRecord({ query: { username: 'admin', siteId: site.id } }, { noMagic: true })
|
|
177
|
-
this.config.crossSiteAdmins.push(user.
|
|
181
|
+
this.config.crossSiteAdmins.push(`${site.alias}:${user.username}`)
|
|
178
182
|
}
|
|
179
183
|
}
|
|
180
184
|
|
|
@@ -205,52 +209,60 @@ async function factory (pkgName) {
|
|
|
205
209
|
if (!this.app.waibuAdmin) return
|
|
206
210
|
const { getPluginPrefix } = this.app.waibu
|
|
207
211
|
const prefix = getPluginPrefix(this.ns)
|
|
212
|
+
const params = { action: 'list' }
|
|
208
213
|
const items = [{
|
|
214
|
+
title: 'xSite',
|
|
215
|
+
children: [
|
|
216
|
+
{ title: 'allSites', href: `waibuAdmin:/${prefix}/x/site/:action`, params },
|
|
217
|
+
{ title: 'xRouteGuard', href: `waibuAdmin:/${prefix}/x/route-guard/:action`, params },
|
|
218
|
+
{ title: 'xModelGuard', href: `waibuAdmin:/${prefix}/x/model-guard/:action`, params },
|
|
219
|
+
{ title: 'xAttribGuard', href: `waibuAdmin:/${prefix}/x/attrib-guard/:action`, params },
|
|
220
|
+
{ title: 'userSession', href: `waibuAdmin:/${prefix}/x/session/:action`, params }
|
|
221
|
+
]
|
|
222
|
+
}, {
|
|
209
223
|
title: 'manageSite',
|
|
210
224
|
children: [
|
|
211
225
|
{ title: 'siteProfile', href: `waibuAdmin:/${prefix}/site` },
|
|
212
|
-
{ title: 'siteSetting', href: `waibuAdmin:/${prefix}/site-setting
|
|
213
|
-
{ title: 'allSites', href: `waibuAdmin:/${prefix}/all-sites/list` }
|
|
226
|
+
{ title: 'siteSetting', href: `waibuAdmin:/${prefix}/site-setting/:action`, params }
|
|
214
227
|
]
|
|
215
228
|
}, {
|
|
216
229
|
title: 'manageUser',
|
|
217
230
|
children: [
|
|
218
|
-
{ title: 'userProfile', href: `waibuAdmin:/${prefix}/user
|
|
219
|
-
{ title: 'userSetting', href: `waibuAdmin:/${prefix}/user-setting
|
|
231
|
+
{ title: 'userProfile', href: `waibuAdmin:/${prefix}/user/:action`, params },
|
|
232
|
+
{ title: 'userSetting', href: `waibuAdmin:/${prefix}/user-setting/:action`, params },
|
|
220
233
|
{ title: 'resetUserPassword', href: `waibuAdmin:/${prefix}/reset-user-password` }
|
|
221
234
|
]
|
|
222
235
|
}, {
|
|
223
236
|
title: 'manageTeam',
|
|
224
237
|
children: [
|
|
225
|
-
{ title: 'teamProfile', href: `waibuAdmin:/${prefix}/team
|
|
226
|
-
{ title: 'teamUser', href: `waibuAdmin:/${prefix}/team-user
|
|
227
|
-
{ title: 'teamSetting', href: `waibuAdmin:/${prefix}/team-setting
|
|
238
|
+
{ title: 'teamProfile', href: `waibuAdmin:/${prefix}/team/:action`, params },
|
|
239
|
+
{ title: 'teamUser', href: `waibuAdmin:/${prefix}/team-user/:action`, params },
|
|
240
|
+
{ title: 'teamSetting', href: `waibuAdmin:/${prefix}/team-setting/:action`, params }
|
|
228
241
|
]
|
|
229
242
|
}, {
|
|
230
243
|
title: 'permission',
|
|
231
244
|
children: [
|
|
232
|
-
{ title: 'routeGuard', href: `waibuAdmin:/${prefix}/route-guard
|
|
233
|
-
{ title: 'modelGuard', href: `waibuAdmin:/${prefix}/model-guard
|
|
234
|
-
{ title: 'attribGuard', href: `waibuAdmin:/${prefix}/attrib-guard
|
|
245
|
+
{ title: 'routeGuard', href: `waibuAdmin:/${prefix}/route-guard/:action`, params },
|
|
246
|
+
{ title: 'modelGuard', href: `waibuAdmin:/${prefix}/model-guard/:action`, params },
|
|
247
|
+
{ title: 'attribGuard', href: `waibuAdmin:/${prefix}/attrib-guard/:action`, params }
|
|
235
248
|
]
|
|
236
249
|
}, {
|
|
237
250
|
title: 'supportSystem',
|
|
238
251
|
children: [
|
|
239
|
-
{ title: 'contactForm', href: `waibuAdmin:/${prefix}/contact-form
|
|
240
|
-
{ title: 'contactFormCat', href: `waibuAdmin:/${prefix}/contact-form-cat
|
|
241
|
-
{ title: 'ticket', href: `waibuAdmin:/${prefix}/ticket
|
|
242
|
-
{ title: 'ticketCat', href: `waibuAdmin:/${prefix}/ticket-cat
|
|
252
|
+
{ title: 'contactForm', href: `waibuAdmin:/${prefix}/contact-form/:action`, params },
|
|
253
|
+
{ title: 'contactFormCat', href: `waibuAdmin:/${prefix}/contact-form-cat/:action`, params },
|
|
254
|
+
{ title: 'ticket', href: `waibuAdmin:/${prefix}/ticket/:action`, params },
|
|
255
|
+
{ title: 'ticketCat', href: `waibuAdmin:/${prefix}/ticket-cat/:action`, params }
|
|
243
256
|
]
|
|
244
257
|
}, {
|
|
245
258
|
title: 'misc',
|
|
246
259
|
children: [
|
|
247
|
-
{ title: 'manageDownload', href: `waibuAdmin:/${prefix}/download
|
|
248
|
-
{ title: 'userSession', href: `waibuAdmin:/${prefix}/session/list` }
|
|
260
|
+
{ title: 'manageDownload', href: `waibuAdmin:/${prefix}/download/:action`, params }
|
|
249
261
|
]
|
|
250
262
|
}]
|
|
251
263
|
if (this.app.bajoCache) {
|
|
252
|
-
const item = items.find(i => i.title === '
|
|
253
|
-
|
|
264
|
+
const item = items.find(i => i.title === 'xSite')
|
|
265
|
+
item.children.push({ title: 'cacheStorage', href: `waibuAdmin:/${prefix}/x/cache/:action`, params })
|
|
254
266
|
}
|
|
255
267
|
return items
|
|
256
268
|
}
|
|
@@ -531,25 +543,85 @@ async function factory (pkgName) {
|
|
|
531
543
|
return await hash(item, this.config.auth.common.apiKey.algo)
|
|
532
544
|
}
|
|
533
545
|
|
|
534
|
-
|
|
546
|
+
_fetchGuards = async (type = 'Route') => {
|
|
535
547
|
const { getModel } = this.app.dobo
|
|
548
|
+
const options = { noMagic: true, noCache: true, noDriverHook: true, dataOnly: true }
|
|
549
|
+
const filter = { query: { status: 'ACTIVE' } }
|
|
550
|
+
return {
|
|
551
|
+
global: await getModel(`SumbaX${type}Guard`).findAllRecord(filter, options),
|
|
552
|
+
local: await getModel(`Sumba${type}Guard`).findAllRecord(filter, options),
|
|
553
|
+
siteIds: (await getModel('SumbaSite').findAllRecord(filter.options)).map(item => item.id + '')
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
getRouteGuards = async (reread) => {
|
|
536
558
|
const { routePath } = this.app.waibu
|
|
537
559
|
const { map, orderBy } = this.app.lib._
|
|
560
|
+
const { isSet } = this.app.lib.aneka
|
|
538
561
|
if (!reread) return this.routeGuards
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
562
|
+
|
|
563
|
+
const result = await this._fetchGuards('Route')
|
|
564
|
+
for (const type of ['global', 'local']) {
|
|
565
|
+
result[type] = result[type].map(item => {
|
|
566
|
+
item.siteIds = item.siteIds ?? []
|
|
567
|
+
if (item.siteIds.length === 0) item.siteIds = [...result.siteIds]
|
|
568
|
+
if (item.siteId) item.siteIds = uniq([...item.siteIds, item.siteId].filter(i => isSet(i)).map(i => i + ''))
|
|
569
|
+
item.teamIds = (item.teamIds ?? []).filter(i => isSet(i)).map(i => i + '')
|
|
570
|
+
item.methods = item.methods ?? []
|
|
571
|
+
delete item.siteId
|
|
572
|
+
return item
|
|
573
|
+
})
|
|
574
|
+
this.routeGuards[type] = orderBy(map(result[type], item => {
|
|
575
|
+
item.path = routePath(item.path)
|
|
576
|
+
return item
|
|
577
|
+
}), ['weight', 'path'], ['desc', 'asc'])
|
|
578
|
+
}
|
|
550
579
|
return this.routeGuards
|
|
551
580
|
}
|
|
552
581
|
|
|
582
|
+
getModelGuards = async (reread) => {
|
|
583
|
+
const { isSet } = this.app.lib.aneka
|
|
584
|
+
if (!reread) return this.modelGuards
|
|
585
|
+
|
|
586
|
+
const result = await this._fetchGuards('Model')
|
|
587
|
+
for (const type of ['global', 'local']) {
|
|
588
|
+
this.modelGuards[type] = result[type].filter(item => (!isEmpty(item.value)) && (!isEmpty(item.models))).map(item => {
|
|
589
|
+
item.siteIds = item.siteIds ?? []
|
|
590
|
+
if (item.siteIds.length === 0) item.siteIds = [...result.siteIds]
|
|
591
|
+
if (item.siteId) item.siteIds = uniq([...item.siteIds, item.siteId].filter(i => isSet(i)).map(i => i + ''))
|
|
592
|
+
item.teamIds = (item.teamIds ?? []).filter(i => isSet(i)).map(i => i + '')
|
|
593
|
+
delete item.siteId
|
|
594
|
+
return item
|
|
595
|
+
})
|
|
596
|
+
}
|
|
597
|
+
return this.modelGuards
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
getAttribGuards = async (reread) => {
|
|
601
|
+
const { isSet } = this.app.lib.aneka
|
|
602
|
+
if (!reread) return this.attribGuards
|
|
603
|
+
|
|
604
|
+
const result = await this._fetchGuards('Attrib')
|
|
605
|
+
for (const type of ['global', 'local']) {
|
|
606
|
+
this.attribGuards[type] = result[type].map(item => {
|
|
607
|
+
item.siteIds = item.siteIds ?? []
|
|
608
|
+
if (item.siteIds.length === 0) item.siteIds = [...result.siteIds]
|
|
609
|
+
if (item.siteId) item.siteIds = uniq([...item.siteIds, item.siteId].filter(i => isSet(i)).map(i => i + ''))
|
|
610
|
+
item.teamIds = (item.teamIds ?? []).filter(i => isSet(i)).map(i => i + '')
|
|
611
|
+
item.models = item.models ?? []
|
|
612
|
+
item.hiddenCols = item.hiddenCols ?? []
|
|
613
|
+
delete item.siteId
|
|
614
|
+
return item
|
|
615
|
+
})
|
|
616
|
+
}
|
|
617
|
+
return this.attribGuards
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
getModelNames = (asValue) => {
|
|
621
|
+
const values = this.app.dobo.models.map(item => item.name).sort()
|
|
622
|
+
return asValue ? values.map(item => ({ value: item, text: item })) : values
|
|
623
|
+
}
|
|
624
|
+
|
|
553
625
|
createNewSite = createNewSite
|
|
554
626
|
removeSite = removeSite
|
|
555
627
|
getSite = getSite
|
package/lib/get-user.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { parseNsSettings } from './util.js'
|
|
2
2
|
|
|
3
|
-
export async function mergeTeam (user) {
|
|
3
|
+
export async function mergeTeam (user, req) {
|
|
4
4
|
const { map, pick } = this.app.lib._
|
|
5
5
|
const { getModel } = this.app.dobo
|
|
6
|
-
user.crossSiteAdmin = this.config.crossSiteAdmins.includes(user.id)
|
|
7
6
|
user.teams = []
|
|
8
7
|
const query = { userId: user.id, siteId: user.siteId }
|
|
9
8
|
let mdl = getModel('SumbaTeamUser')
|
|
@@ -31,6 +30,8 @@ export async function mergeTeam (user) {
|
|
|
31
30
|
user.teams.push(item)
|
|
32
31
|
}
|
|
33
32
|
}
|
|
33
|
+
user.isXSiteAdmin = this.config.crossSiteAdmins.includes(`${req.site.alias}:${user.username}`)
|
|
34
|
+
user.isAdmin = user.teams.some(t => t.alias === 'administrator')
|
|
34
35
|
for (const field of this.unsafeUserFields) {
|
|
35
36
|
delete user[field]
|
|
36
37
|
}
|
|
@@ -47,7 +48,7 @@ export async function getUserById (id, req) {
|
|
|
47
48
|
}
|
|
48
49
|
user = await getModel('SumbaUser').getRecord(id, { noHook: true, noDriverHook: true, throwNotFound: false, req })
|
|
49
50
|
if (!user) return
|
|
50
|
-
await mergeTeam.call(this, user)
|
|
51
|
+
await mergeTeam.call(this, user, req)
|
|
51
52
|
if (setCache) {
|
|
52
53
|
await setCache({ key, value: JSON.stringify(user), ttl: this.config.cacheTtl.getUserByIdDur })
|
|
53
54
|
}
|
|
@@ -65,7 +66,7 @@ export async function getUserByToken (token, req) {
|
|
|
65
66
|
}
|
|
66
67
|
user = await getModel('SumbaUser').findOneRecord({ query: { token } }, { noHook: true, noDriverHook: true, throwNotFound: false, req })
|
|
67
68
|
if (!user) return
|
|
68
|
-
await mergeTeam.call(this, user)
|
|
69
|
+
await mergeTeam.call(this, user, req)
|
|
69
70
|
if (setCache) {
|
|
70
71
|
await setCache({ key, value: JSON.stringify(user), ttl: this.config.cacheTtl.getUserByTokenDur })
|
|
71
72
|
}
|
|
@@ -85,6 +86,6 @@ export async function getUserByUsernamePassword (username = '', password = '', r
|
|
|
85
86
|
if (user.status !== 'ACTIVE') throw this.error('validationError', { details: ['User is inactive or temporarily disabled'], statusCode: 401 })
|
|
86
87
|
const verified = await bcrypt.compare(password, user.password)
|
|
87
88
|
if (!verified) throw this.error('validationError', { details: [{ field: 'password', error: 'invalidPassword' }], statusCode: 401 })
|
|
88
|
-
await mergeTeam.call(this, user)
|
|
89
|
+
await mergeTeam.call(this, user, req)
|
|
89
90
|
return user
|
|
90
91
|
}
|
package/lib/util.js
CHANGED
|
@@ -58,21 +58,33 @@ export async function checkTheme (req, reply) {
|
|
|
58
58
|
|
|
59
59
|
export async function checkTeam (req, reply, route) {
|
|
60
60
|
const { map } = this.app.lib._
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
const { outmatch } = this.app.lib
|
|
62
|
+
route.teamIds = route.teamIds ?? []
|
|
63
|
+
if (route.teamIds.length === 0) return
|
|
63
64
|
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
if (
|
|
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 })
|
|
67
71
|
|
|
68
72
|
const paths = pathsToCheck.call(this, req, true)
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
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) })
|
|
72
77
|
if (match) {
|
|
73
|
-
const neg = this.checkPathsByRoute({ paths,
|
|
78
|
+
const neg = this.checkPathsByRoute({ paths, teamIds, guards: localGuards.filter(item => item.negation) })
|
|
74
79
|
if (neg) match = undefined
|
|
75
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
|
+
}
|
|
76
88
|
if (!match) throw this.error('accessDenied', { statusCode: 403 })
|
|
77
89
|
// passed
|
|
78
90
|
}
|
|
@@ -104,18 +116,38 @@ export async function checkUserId (req, reply, source) {
|
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
const paths = pathsToCheck.call(this, req)
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
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) })
|
|
110
139
|
if (securePath) {
|
|
111
|
-
const neg = await this.checkPathsByRoute({ paths, guards:
|
|
140
|
+
const neg = await this.checkPathsByRoute({ paths, guards: localGuards.filter(item => !item.anonymous && item.negation) })
|
|
112
141
|
if (neg) securePath = undefined
|
|
113
142
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
+
}
|
|
118
149
|
}
|
|
150
|
+
// checking...
|
|
119
151
|
if (!securePath && !anonymousPath) {
|
|
120
152
|
if (userId) await setUser()
|
|
121
153
|
return false // regular, unguarded path. Not secure & not anonymous path
|
|
@@ -162,8 +194,8 @@ export async function latLngHook (body, options) {
|
|
|
162
194
|
* @param {Object} reply - Reply object
|
|
163
195
|
* @returns
|
|
164
196
|
*/
|
|
165
|
-
export async function
|
|
197
|
+
export async function checkXSite (req, reply) {
|
|
166
198
|
const { get } = this.app.lib._
|
|
167
|
-
if (!get(req, 'routeOptions.config.
|
|
168
|
-
if (!get(req, 'user.
|
|
199
|
+
if (!get(req, 'routeOptions.config.xSite')) return
|
|
200
|
+
if (!get(req, 'user.isXSiteAdmin')) throw this.error('accessDenied', { statusCode: 403 })
|
|
169
201
|
}
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
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
|
+
|
|
3
10
|
## 2026-05-11
|
|
4
11
|
|
|
5
12
|
- [2.24.0] Complete rewrite of route guards
|