sumba 2.24.0 → 2.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/extend/bajo/hook/dobo.sumba-attrib-guard$dobo.sumba-x-attrib-guard@after-transaction.js +6 -0
  2. package/extend/bajo/hook/dobo.sumba-model-guard$dobo.sumba-x-model-guard@after-transaction.js +6 -0
  3. package/extend/bajo/hook/dobo.sumba-team$dobo.sumba-site@after-transaction.js +8 -0
  4. package/extend/bajo/hook/dobo@before-driver-create-record.js +1 -1
  5. package/extend/bajo/hook/dobo@before-driver-find-all-record.js +2 -7
  6. package/extend/bajo/hook/dobo@before-driver-find-record.js +74 -37
  7. package/extend/bajo/hook/dobo@before-driver-get-record.js +3 -2
  8. package/extend/bajo/hook/waibu-mpa@pre-parsing.js +2 -2
  9. package/extend/bajo/hook/waibu-rest-api@pre-parsing.js +2 -2
  10. package/extend/bajo/hook/waibu-static@pre-parsing.js +2 -2
  11. package/extend/bajo/hook/waibu@after-app-boot.js +2 -0
  12. package/extend/bajo/intl/en-US.json +11 -5
  13. package/extend/bajo/intl/id.json +12 -6
  14. package/extend/dobo/feature/site-ids.js +19 -0
  15. package/extend/dobo/feature/team-ids.js +1 -2
  16. package/extend/dobo/fixture/team-user.json +2 -2
  17. package/extend/dobo/fixture/{route-guard.js → x-route-guard.js} +0 -2
  18. package/extend/dobo/model/attrib-guard.js +26 -30
  19. package/extend/dobo/model/model-guard.js +42 -31
  20. package/extend/dobo/model/route-guard.js +42 -39
  21. package/extend/dobo/model/team-user.json +1 -1
  22. package/extend/dobo/model/x-attrib-guard.js +14 -0
  23. package/extend/dobo/model/x-model-guard.js +13 -0
  24. package/extend/dobo/model/x-route-guard.js +13 -0
  25. package/extend/waibuDb/schema/x-attrib-guard.js +15 -0
  26. package/extend/waibuDb/schema/x-model-guard.js +15 -0
  27. package/extend/waibuDb/schema/x-route-guard.js +15 -0
  28. package/extend/waibuMpa/extend/waibuAdmin/route/x/attrib-guard/@action.js +12 -0
  29. package/extend/waibuMpa/extend/waibuAdmin/route/{cache → x/cache}/@action.js +1 -1
  30. package/extend/waibuMpa/extend/waibuAdmin/route/x/model-guard/@action.js +12 -0
  31. package/extend/waibuMpa/extend/waibuAdmin/route/x/route-guard/@action.js +12 -0
  32. package/extend/waibuMpa/extend/waibuAdmin/route/{session → x/session}/@action.js +1 -1
  33. package/extend/waibuMpa/extend/waibuAdmin/route/{all-sites → x/site}/@action.js +1 -1
  34. package/index.js +111 -35
  35. package/lib/get-user.js +6 -5
  36. package/lib/util.js +52 -20
  37. package/package.json +1 -1
  38. package/wiki/CHANGES.md +11 -0
  39. /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,6 @@
1
+ async function afterTransaction (action, ...args) {
2
+ if (!['createRecord', 'updateRecord', 'removeRecord'].includes(action)) return
3
+ await this.getAttribGuards(true)
4
+ }
5
+
6
+ export default afterTransaction
@@ -0,0 +1,6 @@
1
+ async function afterTransaction (action, ...args) {
2
+ if (!['createRecord', 'updateRecord', 'removeRecord'].includes(action)) return
3
+ await this.getModelGuards(true)
4
+ }
5
+
6
+ export default afterTransaction
@@ -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.crossSite')) return
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,13 +1,8 @@
1
- import { rebuildFilter } from './dobo@before-driver-find-record.js'
1
+ import { handler } from './dobo@before-driver-find-record.js'
2
2
 
3
3
  const doboBeforeDriverFindAllRecord = {
4
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
- }
5
+ handler
11
6
  }
12
7
 
13
8
  export default doboBeforeDriverFindAllRecord
@@ -1,51 +1,69 @@
1
1
  async function applyModelGuard ({ model, q, teamIds, options }) {
2
- const { isEmpty, get, set, uniq } = this.app.lib._
2
+ const { get, set, orderBy, intersection, without } = this.app.lib._
3
+ const { include } = this.app.lib.aneka
3
4
  const { req } = options
4
- const { getModel } = this.app.dobo
5
+ const results = []
5
6
 
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)
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
- 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
- }
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
- 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] }))
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 (guards.length === 0 && !allowEmpty) throw this.error('_emptyColumnQuery') // signal driver to about with no result immediately
33
- q.$and.push(...guards)
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 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 ?? []))
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,17 +83,35 @@ 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.crossSite) return
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
72
90
  return
73
91
  }
92
+ if (!req.user) {
93
+ filter.query = q
94
+ return
95
+ }
96
+
97
+ const condUserId = {
98
+ $or: [
99
+ { userId: req.user.id + '' },
100
+ { userId: { $eq: null } }
101
+ ]
102
+ }
103
+
104
+ const condTeamId = {
105
+ $or: [
106
+ { teamId: { $in: teamIds } },
107
+ { teamId: { $eq: null } }
108
+ ]
109
+ }
74
110
 
75
111
  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 + '' })
112
+ if (hasUserId) q.$and.push({ $or: [condTeamId, condUserId] })
113
+ else q.$and.push(condTeamId)
114
+ } else if (hasUserId) q.$and.push(condUserId)
79
115
 
80
116
  await applyModelGuard.call(this, { model, q, teamIds, options })
81
117
  await applyAttribGuard.call(this, { model, teamIds, options })
@@ -84,7 +120,8 @@ export async function rebuildFilter (model, filter = {}, options = {}) {
84
120
 
85
121
  export async function handler (model, filter, options = {}) {
86
122
  const { isEmpty } = this.app.lib._
87
- if (options.noAutoFilter || !options.req || isEmpty((options.req ?? {}).site)) return
123
+ const { req = {} } = options
124
+ if (options.noAutoFilter || isEmpty(req) || isEmpty(req.site)) return
88
125
  await rebuildFilter.call(this, model, filter, options)
89
126
  }
90
127
 
@@ -1,9 +1,10 @@
1
1
  import { rebuildFilter } from './dobo@before-driver-find-record.js'
2
2
 
3
3
  export async function checker (model, id, options = {}) {
4
- const { req } = options
4
+ const { isEmpty } = this.app.lib._
5
+ const { req = {} } = options
6
+ if (options.noAutoFilter || isEmpty(req) || isEmpty(req.site)) return
5
7
 
6
- if (options.noAutoFilter || !req) return
7
8
  const filter = {}
8
9
  await rebuildFilter.call(this, model, filter, options)
9
10
  if (filter.query.$and) filter.query.$and.push({ id })
@@ -1,4 +1,4 @@
1
- import { checkUserId, checkTeam, checkTheme, checkIconset, checkCrossSite } from '../../../lib/util.js'
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 checkCrossSite.call(this, req, reply)
11
+ await checkXSite.call(this, req, reply)
12
12
  }
13
13
  }
14
14
 
@@ -1,4 +1,4 @@
1
- import { checkUserId, checkTeam, checkCrossSite } from '../../../lib/util.js'
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 checkCrossSite.call(this, req, reply)
9
+ await checkXSite.call(this, req, reply)
10
10
  }
11
11
  }
12
12
 
@@ -1,4 +1,4 @@
1
- import { checkUserId, checkTeam, checkCrossSite } from '../../../lib/util.js'
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 checkCrossSite.call(this, req, reply)
9
+ await checkXSite.call(this, req, reply)
10
10
  }
11
11
  }
12
12
 
@@ -1,5 +1,7 @@
1
1
  async function afterAppBoot () {
2
2
  await this.getRouteGuards(true)
3
+ await this.getModelGuards(true)
4
+ await this.getAttribGuards(true)
3
5
  }
4
6
 
5
7
  export default afterAppBoot
@@ -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
- "inverse": "Inverse?",
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": {
@@ -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
- "inverse": "Inverse?",
174
+ "negation": "Negation?",
170
175
  "anonymous": "Anonim?",
171
176
  "methods": "Metode",
172
177
  "teamIds": "Tim",
173
178
  "column": "Kolom",
174
- "hideCols": "Sembunyikan Kolom",
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
@@ -11,8 +11,7 @@ async function teamIds (opts = {}) {
11
11
  searchField: 'name',
12
12
  fields: ['id', 'alias', 'name']
13
13
  }
14
- },
15
- index: true
14
+ }
16
15
  }]
17
16
  }
18
17
  }
@@ -1,6 +1,6 @@
1
1
  [{
2
- "userId": "?:SumbaUser::username:admin",
3
- "teamId": "?:SumbaTeam::alias:administrator",
4
2
  "siteId": "?:SumbaSite::alias:default",
3
+ "userId": "?:SumbaUser::username:admin+siteId={siteId}",
4
+ "teamId": "?:SumbaTeam::alias:administrator+siteId={siteId}",
5
5
  "_immutable": true
6
6
  }]
@@ -16,8 +16,6 @@ async function routeGuard () {
16
16
  return {
17
17
  path: anonymous ? r.slice(1) : r,
18
18
  anonymous,
19
- _immutable: true,
20
- siteId: '?:SumbaSite::alias:default',
21
19
  status: 'ACTIVE'
22
20
  }
23
21
  })
@@ -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
- 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
- }
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
- 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
- }
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
- '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
- '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
 
@@ -8,9 +8,9 @@
8
8
  "features": [
9
9
  "dobo:createdAt",
10
10
  "dobo:updatedAt",
11
- "sumba:teamId",
12
11
  "sumba:siteId",
13
12
  "sumba:userId",
13
+ "sumba:teamId",
14
14
  "dobo:immutable"
15
15
  ]
16
16
  }
@@ -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,15 @@
1
+ async function xAttribGuard () {
2
+ return {
3
+ common: {
4
+ widget: {
5
+ siteIds: {
6
+ attr: {
7
+ refUrl: 'waibuAdmin:/{prefix}/site/details?id={id}'
8
+ }
9
+ }
10
+ }
11
+ }
12
+ }
13
+ }
14
+
15
+ export default xAttribGuard
@@ -0,0 +1,15 @@
1
+ async function xModelGuard () {
2
+ return {
3
+ common: {
4
+ widget: {
5
+ siteIds: {
6
+ attr: {
7
+ refUrl: 'waibuAdmin:/{prefix}/site/details?id={id}'
8
+ }
9
+ }
10
+ }
11
+ }
12
+ }
13
+ }
14
+
15
+ export default xModelGuard
@@ -0,0 +1,15 @@
1
+ async function xRouteGuard () {
2
+ return {
3
+ common: {
4
+ widget: {
5
+ siteIds: {
6
+ attr: {
7
+ refUrl: 'waibuAdmin:/{prefix}/site/details?id={id}'
8
+ }
9
+ }
10
+ }
11
+ }
12
+ }
13
+ }
14
+
15
+ 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
@@ -1,7 +1,7 @@
1
1
  const action = {
2
2
  method: ['GET', 'POST'],
3
3
  title: 'cacheStorage',
4
- crossSite: true,
4
+ xSite: true,
5
5
  handler: async function (req, reply) {
6
6
  if (!this.app.bajoCache) throw this.error('_notFound')
7
7
  const { importModule } = this.app.bajo
@@ -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
@@ -1,7 +1,7 @@
1
1
  const action = {
2
2
  method: ['GET', 'POST'],
3
3
  title: 'userSession',
4
- crossSite: true,
4
+ xSite: true,
5
5
  handler: async function (req, reply) {
6
6
  const { importModule } = this.app.bajo
7
7
  const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
@@ -1,7 +1,7 @@
1
1
  const action = {
2
2
  method: ['GET', 'POST'],
3
3
  title: 'manageAllSite',
4
- crossSite: true,
4
+ xSite: true,
5
5
  handler: async function (req, reply) {
6
6
  const { importModule } = this.app.bajo
7
7
  const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
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
+ xSiteAdmins: [], // 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
  }
@@ -171,10 +175,10 @@ async function factory (pkgName) {
171
175
 
172
176
  start = async () => {
173
177
  const { getModel } = this.app.dobo
174
- if (this.config.crossSiteAdmins.length === 0) {
178
+ if (this.config.xSiteAdmins.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.id)
181
+ this.config.xSiteAdmins.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/list` },
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/list` },
219
- { title: 'userSetting', href: `waibuAdmin:/${prefix}/user-setting/list` },
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/list` },
226
- { title: 'teamUser', href: `waibuAdmin:/${prefix}/team-user/list` },
227
- { title: 'teamSetting', href: `waibuAdmin:/${prefix}/team-setting/list` }
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/list` },
233
- { title: 'modelGuard', href: `waibuAdmin:/${prefix}/model-guard/list` },
234
- { title: 'attribGuard', href: `waibuAdmin:/${prefix}/attrib-guard/list` }
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/list` },
240
- { title: 'contactFormCat', href: `waibuAdmin:/${prefix}/contact-form-cat/list` },
241
- { title: 'ticket', href: `waibuAdmin:/${prefix}/ticket/list` },
242
- { title: 'ticketCat', href: `waibuAdmin:/${prefix}/ticket-cat/list` }
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/list` },
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 === 'misc')
253
- if (item) item.children.push({ title: 'cacheStorage', href: `waibuAdmin:/${prefix}/cache/list` })
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,89 @@ async function factory (pkgName) {
531
543
  return await hash(item, this.config.auth.common.apiKey.algo)
532
544
  }
533
545
 
534
- getRouteGuards = async (reread) => {
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
- const { map, orderBy } = this.app.lib._
559
+ const { orderBy } = this.app.lib._
560
+ const { isSet } = this.app.lib.aneka
538
561
  if (!reread) return this.routeGuards
539
- const model = getModel('SumbaRouteGuard')
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
- })
546
- this.routeGuards = orderBy(map(results, item => {
547
- item.path = routePath(item.path)
548
- return item
549
- }), ['weight', 'path'])
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(result[type].filter(item => {
575
+ try {
576
+ item.path = routePath(item.path)
577
+ return true
578
+ } catch (err) {
579
+ return false
580
+ }
581
+ }), ['weight', 'path'], ['desc', 'asc'])
582
+ }
550
583
  return this.routeGuards
551
584
  }
552
585
 
586
+ getModelGuards = async (reread) => {
587
+ const { isSet } = this.app.lib.aneka
588
+ if (!reread) return this.modelGuards
589
+
590
+ const result = await this._fetchGuards('Model')
591
+ for (const type of ['global', 'local']) {
592
+ this.modelGuards[type] = result[type].filter(item => (!isEmpty(item.value)) && (!isEmpty(item.models))).map(item => {
593
+ item.siteIds = item.siteIds ?? []
594
+ if (item.siteIds.length === 0) item.siteIds = [...result.siteIds]
595
+ if (item.siteId) item.siteIds = uniq([...item.siteIds, item.siteId].filter(i => isSet(i)).map(i => i + ''))
596
+ item.teamIds = (item.teamIds ?? []).filter(i => isSet(i)).map(i => i + '')
597
+ delete item.siteId
598
+ return item
599
+ })
600
+ }
601
+ return this.modelGuards
602
+ }
603
+
604
+ getAttribGuards = async (reread) => {
605
+ const { isSet } = this.app.lib.aneka
606
+ if (!reread) return this.attribGuards
607
+
608
+ const result = await this._fetchGuards('Attrib')
609
+ for (const type of ['global', 'local']) {
610
+ this.attribGuards[type] = result[type].map(item => {
611
+ item.siteIds = item.siteIds ?? []
612
+ if (item.siteIds.length === 0) item.siteIds = [...result.siteIds]
613
+ if (item.siteId) item.siteIds = uniq([...item.siteIds, item.siteId].filter(i => isSet(i)).map(i => i + ''))
614
+ item.teamIds = (item.teamIds ?? []).filter(i => isSet(i)).map(i => i + '')
615
+ item.models = item.models ?? []
616
+ item.hiddenCols = item.hiddenCols ?? []
617
+ delete item.siteId
618
+ return item
619
+ })
620
+ }
621
+ return this.attribGuards
622
+ }
623
+
624
+ getModelNames = (asValue) => {
625
+ const values = this.app.dobo.models.map(item => item.name).sort()
626
+ return asValue ? values.map(item => ({ value: item, text: item })) : values
627
+ }
628
+
553
629
  createNewSite = createNewSite
554
630
  removeSite = removeSite
555
631
  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.xSiteAdmins.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
- route.teams = route.teams ?? []
62
- if (route.teams.length === 0) return
61
+ const { outmatch } = this.app.lib
62
+ route.teamIds = route.teamIds ?? []
63
+ if (route.teamIds.length === 0) return
63
64
 
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 })
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 allGuards = (await this.getRouteGuards()).filter(item => item.siteId === req.site.id + '' && item.teams.length > 0)
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) })
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, teams, guards: allGuards.filter(item => item.inverse) })
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 allGuards = (await this.getRouteGuards()).filter(item => item.siteId === req.site.id + '')
108
- if (allGuards.length === 0) return false // no routes protected
109
- let securePath = await this.checkPathsByRoute({ paths, guards: allGuards.filter(item => !item.anonymous && !item.inverse) })
119
+ const guards = await this.getRouteGuards()
120
+ let securePath
121
+ let anonymousPath
122
+ const globalGuards = guards.global.filter(item => item.siteIds.includes(req.site.id + ''))
123
+ const localGuards = guards.local.filter(item => item.siteIds.includes(req.site.id + ''))
124
+ // find anonymousPath
125
+ anonymousPath = await this.checkPathsByRoute({ paths, guards: localGuards.filter(item => item.anonymous && !item.negation) })
126
+ if (anonymousPath) {
127
+ const neg = await this.checkPathsByRoute({ paths, guards: localGuards.filter(item => item.anonymous && item.negation) })
128
+ if (neg) anonymousPath = undefined
129
+ }
130
+ if (!anonymousPath) {
131
+ anonymousPath = await this.checkPathsByRoute({ paths, guards: globalGuards.filter(item => item.anonymous && !item.negation) })
132
+ if (anonymousPath) {
133
+ const neg = await this.checkPathsByRoute({ paths, guards: globalGuards.filter(item => item.anonymous && item.negation) })
134
+ if (neg) anonymousPath = undefined
135
+ }
136
+ }
137
+ // find securePath
138
+ securePath = await this.checkPathsByRoute({ paths, guards: localGuards.filter(item => !item.anonymous && !item.negation) })
110
139
  if (securePath) {
111
- const neg = await this.checkPathsByRoute({ paths, guards: allGuards.filter(item => !item.anonymous && item.inverse) })
140
+ const neg = await this.checkPathsByRoute({ paths, guards: localGuards.filter(item => !item.anonymous && item.negation) })
112
141
  if (neg) securePath = undefined
113
142
  }
114
- let anonymousPath = await this.checkPathsByRoute({ paths, guards: allGuards.filter(item => item.anonymous && !item.inverse) })
115
- if (anonymousPath) {
116
- const neg = await this.checkPathsByRoute({ paths, guards: allGuards.filter(item => item.anonymous && item.inverse) })
117
- if (neg) anonymousPath = undefined
143
+ if (!securePath) {
144
+ securePath = await this.checkPathsByRoute({ paths, guards: globalGuards.filter(item => !item.anonymous && !item.negation) })
145
+ if (securePath) {
146
+ const neg = await this.checkPathsByRoute({ paths, guards: globalGuards.filter(item => !item.anonymous && item.negation) })
147
+ if (neg) securePath = undefined
148
+ }
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 checkCrossSite (req, reply) {
197
+ export async function checkXSite (req, reply) {
166
198
  const { get } = this.app.lib._
167
- if (!get(req, 'routeOptions.config.crossSite')) return
168
- if (!get(req, 'user.crossSiteAdmin')) 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 })
169
201
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sumba",
3
- "version": "2.24.0",
3
+ "version": "2.25.1",
4
4
  "description": "Biz Suite for Bajo Framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/wiki/CHANGES.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-05-25
4
+
5
+ - [2.25.1] Bug fix in several places because name changing ```crossSiteAdmins``` to ```xSiteAdmins```
6
+
7
+ ## 2026-05-22
8
+
9
+ - [2.25.0] Reorganize admin menu layout & order
10
+ - [2.25.0] Add ```getModelGuards()```
11
+ - [2.25.0] Add ```getAttribGuards()```
12
+ - [2.25.0] Bug fix in ```getRouteGuards()```
13
+
3
14
  ## 2026-05-11
4
15
 
5
16
  - [2.24.0] Complete rewrite of route guards