sumba 2.29.0 → 2.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/extend/bajo/hook.js +40 -42
  2. package/extend/bajo/intl/en-US.json +5 -5
  3. package/extend/bajo/intl/id.json +5 -5
  4. package/extend/dobo/feature/status.js +3 -1
  5. package/extend/dobo/fixture/site.json +1 -1
  6. package/extend/dobo/fixture/team-user.json +1 -1
  7. package/extend/dobo/fixture/team.json +2 -2
  8. package/extend/dobo/fixture/user.json +1 -1
  9. package/extend/dobo/model.js +82 -122
  10. package/extend/dobo/model.json +2 -9
  11. package/extend/sumba/route/anonymous.json +5 -0
  12. package/extend/sumba/route/secure.json +8 -0
  13. package/extend/waibuDb/schema/anonymous-guard.js +15 -0
  14. package/extend/waibuDb/schema/{route-guard.js → secure-guard.js} +2 -2
  15. package/extend/waibuDb/schema/team.json +1 -1
  16. package/extend/waibuMpa/extend/waibuAdmin/route/{x/model-guard → anonymous-guard}/@action.js +2 -3
  17. package/extend/waibuMpa/extend/waibuAdmin/route/{route-guard → secure-guard}/@action.js +2 -2
  18. package/extend/waibuMpa/route/access-token.js +3 -1
  19. package/extend/waibuRestApi/route/manage/anonymous-guard/model-builder.json +4 -0
  20. package/extend/waibuRestApi/route/manage/{route-guard → secure-guard}/model-builder.json +1 -1
  21. package/index.js +153 -98
  22. package/lib/get-user.js +1 -1
  23. package/lib/util.js +28 -67
  24. package/package.json +1 -1
  25. package/wiki/CHANGES.md +8 -0
  26. package/extend/dobo/fixture/x-route-guard.js +0 -24
  27. package/extend/waibuDb/schema/x-attrib-guard.js +0 -15
  28. package/extend/waibuDb/schema/x-model-guard.js +0 -15
  29. package/extend/waibuDb/schema/x-route-guard.js +0 -15
  30. package/extend/waibuMpa/extend/waibuAdmin/route/index.json +0 -3
  31. package/extend/waibuMpa/extend/waibuAdmin/route/x/attrib-guard/@action.js +0 -12
  32. package/extend/waibuMpa/extend/waibuAdmin/route/x/index.json +0 -3
  33. package/extend/waibuMpa/extend/waibuAdmin/route/x/route-guard/@action.js +0 -12
@@ -22,45 +22,30 @@ async function clearCacheUser (id, result) {
22
22
  }
23
23
 
24
24
  async function applyModelGuard ({ model, q, teamIds, options }) {
25
- const { get, set, orderBy, intersection, without } = this.app.lib._
25
+ const { get, set, orderBy, without } = this.app.lib._
26
26
  const { includes } = this.app.lib.aneka
27
27
  const { req } = options
28
28
  const results = []
29
29
 
30
30
  const guards = await this.getModelGuards()
31
- const columns = model.getNonVirtualProperties().map(prop => prop.name)
31
+ const fields = model.getNonVirtualProperties().map(prop => prop.name)
32
32
  const filterFn = item => {
33
- const bySiteId = item.siteIds ? item.siteIds.includes(req.site.id + '') : true
33
+ const bySiteId = item.siteId === req.site.id + ''
34
34
  const byModel = item.models.includes(model.name)
35
- const byTeamId = item.teamIds ? includes(item.teamIds, teamIds) : true
36
- const byColumn = columns.includes(item.column)
37
- return bySiteId && byModel && byTeamId && byColumn
35
+ const byTeamId = includes(item.teamIds, teamIds)
36
+ const byFields = fields.includes(item.field)
37
+ return bySiteId && byModel && byTeamId && byFields
38
38
  }
39
39
 
40
- guards.global = orderBy(guards.global.filter(filterFn), ['column'])
41
- guards.local = orderBy(guards.local.filter(filterFn), ['column'])
42
- for (const col of columns) {
40
+ const rules = orderBy(guards.filter(filterFn), ['field'])
41
+ for (const field of fields) {
43
42
  let values = []
44
- let items = guards.global.filter(item => item.column === col && !item.negation)
43
+ const items = rules.filter(item => item.field === field)
45
44
  for (const item of items) {
46
- values.push(...item.value)
45
+ if (item.behavior === 'NIN') values = without(values, ...item.value)
46
+ else if (item.behavior === 'IN') values.push(...item.value)
47
47
  }
48
- items = guards.global.filter(item => item.column === col && item.negation)
49
- for (const item of items) {
50
- values = without(values, ...item.value)
51
- }
52
- items = guards.local.filter(item => item.column === col && !item.negation)
53
- const newValues = []
54
- for (const item of items) {
55
- newValues.push(...item.value)
56
- }
57
- if (values.length === 0) values = newValues
58
- else if (newValues.length > 0) values = intersection(values, newValues)
59
- items = guards.local.filter(item => item.column === col && item.negation)
60
- for (const item of items) {
61
- values = without(values, ...item.value)
62
- }
63
- if (values.length) results.push(set({}, col, { $in: values }))
48
+ if (values.length) results.push(set({}, field, { $in: values }))
64
49
  }
65
50
 
66
51
  const allowEmpty = get(this, `config.dobo.model.${model.name}.allowEmptyQuery`, true)
@@ -76,16 +61,14 @@ export async function applyAttribGuard ({ model, teamIds, options }) {
76
61
 
77
62
  const guards = await this.getAttribGuards()
78
63
  const filterFn = item => {
79
- const bySiteId = item.siteIds ? item.siteIds.includes(req.site.id + '') : true
64
+ const bySiteId = item.siteId === req.site.id + ''
80
65
  const byModel = item.models.includes(model.name)
81
- const byTeamId = item.teamIds ? includes(item.teamIds, teamIds) : true
66
+ const byTeamId = includes(item.teamIds, teamIds)
82
67
  return bySiteId && byModel && byTeamId
83
68
  }
84
69
 
85
- let item = guards.global.filter(filterFn)[0]
86
- if (item) results.push(...item.hiddenCols)
87
- item = guards.local.filter(filterFn)[0]
88
- if (item) results.push(...item.hiddenCols)
70
+ const item = guards.filter(filterFn)[0]
71
+ if (item) results.push(...item.hiddenFields)
89
72
  options.hidden = options.hidden ?? []
90
73
  if (results.length > 0) options.hidden.push(...results)
91
74
  options.hidden = uniq(options.hidden)
@@ -168,7 +151,7 @@ async function hook () {
168
151
  }
169
152
  }
170
153
  }, {
171
- name: ['dobo.sumbaAttribGuard:afterTransaction', 'dobo.sumbaXAttribGuard:afterTransaction'],
154
+ name: ['dobo.sumbaAttribGuard:afterTransaction'],
172
155
  handler: async function (action, ...args) {
173
156
  if (!['createRecord', 'updateRecord', 'removeRecord'].includes(action)) return
174
157
  await this.getAttribGuards(true)
@@ -205,16 +188,22 @@ async function hook () {
205
188
  options.checksumId = true
206
189
  }
207
190
  }, {
208
- name: ['dobo.sumbaModelGuard:afterTransaction', 'dobo.sumbaXModelGuard:afterTransaction'],
191
+ name: ['dobo.sumbaModelGuard:afterTransaction'],
209
192
  handler: async function (action, ...args) {
210
193
  if (!['createRecord', 'updateRecord', 'removeRecord'].includes(action)) return
211
194
  await this.getModelGuards(true)
212
195
  }
213
196
  }, {
214
- name: ['dobo.sumbaRouteGuard:afterTransaction', 'dobo.sumbaXRouteGuard:afterTransaction'],
197
+ name: ['dobo.sumbaSecureGuard:afterTransaction'],
215
198
  handler: async function (action, ...args) {
216
199
  if (!['createRecord', 'updateRecord', 'removeRecord'].includes(action)) return
217
- await this.getRouteGuards(true)
200
+ await this.getSecureGuards(true)
201
+ }
202
+ }, {
203
+ name: ['dobo.sumbaAnonymousGuard:afterTransaction'],
204
+ handler: async function (action, ...args) {
205
+ if (!['createRecord', 'updateRecord', 'removeRecord'].includes(action)) return
206
+ await this.getAnonymousGuards(true)
218
207
  }
219
208
  }, {
220
209
  name: 'dobo.sumbaSite:afterCreateRecord',
@@ -237,7 +226,7 @@ async function hook () {
237
226
  name: ['dobo.sumbaTeam:afterTransaction', 'dobo.sumbaSite:afterTransaction'],
238
227
  handler: async function (action, ...args) {
239
228
  if (!['createRecord', 'updateRecord', 'removeRecord'].includes(action)) return
240
- await this.getRouteGuards(true)
229
+ await this.getSecureGuards(true)
241
230
  await this.getModelGuards(true)
242
231
  await this.getAttribGuards(true)
243
232
  }
@@ -353,7 +342,7 @@ async function hook () {
353
342
  await checkIconset.call(this, req, reply)
354
343
  const secure = await checkUserId.call(this, req, reply, 'waibuMpa')
355
344
  if (!secure) return
356
- await checkTeam.call(this, req, reply, secure)
345
+ await checkTeam.call(this, req, reply)
357
346
  await checkXSite.call(this, req, reply)
358
347
  }
359
348
  }, {
@@ -362,7 +351,7 @@ async function hook () {
362
351
  handler: async function (req, reply) {
363
352
  const secure = await checkUserId.call(this, req, reply, 'waibuRestApi')
364
353
  if (!secure) return
365
- await checkTeam.call(this, req, reply, secure)
354
+ await checkTeam.call(this, req, reply)
366
355
  await checkXSite.call(this, req, reply)
367
356
  }
368
357
  }, {
@@ -371,13 +360,14 @@ async function hook () {
371
360
  handler: async function (req, reply) {
372
361
  const secure = await checkUserId.call(this, req, reply, 'waibuStatic')
373
362
  if (!secure) return
374
- await checkTeam.call(this, req, reply, secure)
363
+ await checkTeam.call(this, req, reply)
375
364
  await checkXSite.call(this, req, reply)
376
365
  }
377
366
  }, {
378
367
  name: 'waibu:afterAppBoot',
379
368
  handler: async function () {
380
- await this.getRouteGuards(true)
369
+ await this.getAnonymousGuards(true)
370
+ await this.getSecureGuards(true)
381
371
  await this.getModelGuards(true)
382
372
  await this.getAttribGuards(true)
383
373
  }
@@ -392,6 +382,14 @@ async function hook () {
392
382
  handler: async function (req, reply) {
393
383
  const { getHostname } = this.app.waibu
394
384
  req.site = await this.getSite(getHostname(req))
385
+ req.user = {}
386
+ }
387
+ }, {
388
+ name: 'waibu:beforeStart',
389
+ handler: async function () {
390
+ if (this.app.sumba.config.multiSite.enabled) return
391
+ const routes = ['waibuAdmin:/site/x/site/*']
392
+ this.app.waibu.config.route.disabled.push(...routes)
395
393
  }
396
394
  }]
397
395
  }
@@ -132,15 +132,14 @@
132
132
  "statusClosed": "Closed",
133
133
  "allSites": "All Sites",
134
134
  "permission": "Permission",
135
- "routeGuard": "Route Guard",
135
+ "anonymousGuard": "Anonymous Guard",
136
+ "secureGuard": "Secure Guard",
136
137
  "modelGuard": "Model Guard",
137
138
  "attribGuard": "Attribute Guard",
138
- "xRouteGuard": "Route Guard",
139
- "xModelGuard": "Model Guard",
140
- "xAttribGuard": "Attribute Guard",
141
139
  "xSite": "Cross-Site",
142
140
  "xSiteAdminArea": "Cross-Site Admin Area",
143
141
  "x": "Cross-Site",
142
+ "misc": "Miscellaneous",
144
143
  "field": {
145
144
  "currentPassword": "Current Password",
146
145
  "newPassword": "New Password",
@@ -173,7 +172,8 @@
173
172
  "column": "Column",
174
173
  "hiddenCols": "Hidden Columns",
175
174
  "models": "Models",
176
- "siteIds": "Sites"
175
+ "siteIds": "Sites",
176
+ "allTeams": "All Teams?"
177
177
  },
178
178
  "validation": {
179
179
  "password": {
@@ -138,15 +138,14 @@
138
138
  "statusClosed": "Tertutup",
139
139
  "allSites": "Semua Situs",
140
140
  "permission": "Permisi",
141
- "routeGuard": "Pelindung Rute",
141
+ "anonymousGuard": "Pelindung Rute Anonim",
142
+ "secureGuard": "Pelindung Rute Aman",
142
143
  "modelGuard": "Pelindung Model",
143
144
  "attribGuard": "Pelindung Atribut",
144
- "xRouteGuard": "Pelindung Rute",
145
- "xModelGuard": "Pelindung Model",
146
- "xAttribGuard": "Pelindung Atribut",
147
145
  "xSite": "Antar Situs",
148
146
  "xSiteAdminArea": "Area Admin Antar Situs",
149
147
  "x": "Antar Situs",
148
+ "misc": "Lain-lain",
150
149
  "field": {
151
150
  "currentPassword": "Kata Sandi Saat Ini",
152
151
  "newPassword": "Kata Sandi Baru",
@@ -179,7 +178,8 @@
179
178
  "column": "Kolom",
180
179
  "hiddenCols": "Kolom Tersembunyi",
181
180
  "models": "Model",
182
- "siteIds": "Situs"
181
+ "siteIds": "Situs",
182
+ "allTeams": "Semua Tim?"
183
183
  },
184
184
  "validation": {
185
185
  "password": {
@@ -1,13 +1,15 @@
1
1
  async function status (opts = {}) {
2
2
  opts.field = opts.field ?? 'status'
3
3
  opts.required = opts.required ?? true
4
- opts.values = opts.values ?? ['UNVERIFIED', 'ACTIVE', 'INACTIVE']
4
+ opts.default = opts.default ?? 'ACTIVE'
5
+ opts.values = opts.values ?? ['ACTIVE', 'INACTIVE']
5
6
  return {
6
7
  properties: [{
7
8
  name: opts.field ?? 'status',
8
9
  type: 'string',
9
10
  maxLength: 50,
10
11
  index: true,
12
+ default: opts.default,
11
13
  required: opts.required,
12
14
  values: opts.values
13
15
  }],
@@ -14,5 +14,5 @@
14
14
  "phone": "+001-000000001",
15
15
  "waPhone": "+001-111111111",
16
16
  "status": "ACTIVE",
17
- "_immutable": true
17
+ "_immutable": ["*"]
18
18
  }]
@@ -2,5 +2,5 @@
2
2
  "siteId": "?:SumbaSite::alias:default",
3
3
  "userId": "?:SumbaUser::username:'admin'+siteId:'{siteId}'",
4
4
  "teamId": "?:SumbaTeam::alias:'administrator'+siteId:'{siteId}'",
5
- "_immutable": true
5
+ "_immutable": ["*"]
6
6
  }]
@@ -2,6 +2,6 @@
2
2
  "alias": "administrator",
3
3
  "name": "Administrator",
4
4
  "siteId": "?:SumbaSite::alias:default",
5
- "status": "ENABLED",
6
- "_immutable": true
5
+ "status": "ACTIVE",
6
+ "_immutable": ["*"]
7
7
  }]
@@ -13,5 +13,5 @@
13
13
  "provider": "local",
14
14
  "siteId": "?:SumbaSite::alias:default",
15
15
  "status": "ACTIVE",
16
- "_immutable": true
16
+ "_immutable": ["*"]
17
17
  }]
@@ -3,142 +3,102 @@ const buildEnd = async function (model) {
3
3
  if (prop) prop.values = this.getModelNames(true)
4
4
  }
5
5
 
6
- const mgProperties = [
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
6
  const options = {
31
7
  attachment: false,
32
8
  cache: { ttlDur: 0 }
33
9
  }
34
10
 
35
- const mgFeatures = [
36
- 'dobo:updatedAt'
37
- ]
38
-
39
- const agProperties = [
40
- {
41
- name: 'models',
42
- type: 'array',
43
- required: true
44
- },
45
- 'hiddenCols,array',
46
- 'siteId,sumba:siteId',
47
- 'teamIds,sumba:teamIds'
48
- ]
49
-
50
- const agFeatures = [
51
- {
52
- name: 'sumba:status',
53
- values: ['ACTIVE', 'INACTIVE'],
54
- default: 'ACTIVE'
55
- },
56
- 'dobo:updatedAt'
57
- ]
58
-
59
- export const rgProperties = [
60
- 'path,,255,true,true',
61
- {
62
- name: 'methods',
63
- type: 'array',
64
- required: true,
65
- default: ['GET', 'POST', 'UPDATE', 'DELETE'],
66
- values: ['GET', 'POST', 'UPDATE', 'DELETE']
67
- },
68
- {
69
- name: 'weight',
70
- type: 'smallint',
71
- default: 0
72
- }, {
73
- name: 'negation',
74
- type: 'boolean',
75
- required: true,
76
- default: false
77
- }, {
78
- name: 'anonymous',
79
- type: 'boolean',
80
- required: true,
81
- default: false
82
- },
83
- 'siteId,sumba:siteId',
84
- 'teamIds,sumba:teamIds'
85
- ]
11
+ const allTeams = {
12
+ name: 'allTeams',
13
+ type: 'boolean',
14
+ default: true,
15
+ required: true
16
+ }
86
17
 
87
- export const rgFeatures = [
88
- {
89
- name: 'sumba:status',
90
- values: ['ACTIVE', 'INACTIVE'],
91
- default: 'ACTIVE'
92
- },
93
- 'dobo:updatedAt'
94
- ]
18
+ const routeGuard = {
19
+ properties: [
20
+ {
21
+ name: 'path',
22
+ maxLength: 255,
23
+ required: true
24
+ },
25
+ allTeams,
26
+ 'teamIds,sumba:teamIds',
27
+ {
28
+ name: 'weight',
29
+ type: 'smallint',
30
+ index: true,
31
+ default: 0
32
+ },
33
+ 'siteId,sumba:siteId'
34
+ ],
35
+ features: [
36
+ 'sumba:status',
37
+ 'dobo:updatedAt',
38
+ 'dobo:immutable'
39
+ ],
40
+ indexes: [{
41
+ type: 'unique',
42
+ fields: ['siteId', 'path']
43
+ }],
44
+ options: { ...options }
45
+ }
95
46
 
96
47
  async function model () {
97
- const { isString } = this.app.lib._
48
+ const { merge, cloneDeep } = this.app.lib._
98
49
  return [{
99
- baseName: 'route-guard',
100
- properties: rgProperties,
101
- features: rgFeatures,
102
- options
103
- }, {
104
- baseName: 'x-route-guard',
105
- properties: rgProperties.filter(prop => {
106
- if (!isString(prop)) return true
107
- return !(prop.startsWith('teamIds') || prop.startsWith('siteId'))
108
- }).concat('siteIds,sumba:siteIds'),
109
- features: rgFeatures,
110
- options
111
- }, {
112
50
  baseName: 'attrib-guard',
113
- properties: agProperties,
114
- features: agFeatures,
115
- options,
116
- buildEnd
117
- }, {
118
- baseName: 'x-attrib-guard',
119
- properties: agProperties.filter(prop => {
120
- if (!isString(prop)) return true
121
- return !(prop.startsWith('teamIds') || prop.startsWith('siteId'))
122
- }).concat('siteIds,sumba:siteIds'),
123
- features: agFeatures,
124
- options,
51
+ properties: [
52
+ {
53
+ name: 'models',
54
+ type: 'array',
55
+ required: true
56
+ },
57
+ 'hiddenFields,array',
58
+ allTeams,
59
+ 'teamIds,sumba:teamIds',
60
+ 'siteId,sumba:siteId'
61
+ ],
62
+ features: [
63
+ 'sumba:status',
64
+ 'dobo:updatedAt',
65
+ 'dobo:immutable'
66
+ ],
67
+ options: { ...options },
125
68
  buildEnd
126
69
  }, {
127
70
  baseName: 'model-guard',
128
- properties: mgProperties,
129
- features: mgFeatures,
130
- options,
131
- buildEnd
132
- }, {
133
- baseName: 'x-model-guard',
134
- properties: mgProperties.filter(prop => {
135
- if (!isString(prop)) return true
136
- return !(prop.startsWith('teamIds') || prop.startsWith('siteId'))
137
- }).concat('siteIds,sumba:siteIds'),
138
- features: mgFeatures,
139
- options,
71
+ properties: [
72
+ {
73
+ name: 'models',
74
+ type: 'array',
75
+ required: true
76
+ },
77
+ 'field,,50,true,true',
78
+ {
79
+ name: 'behavior',
80
+ type: 'string',
81
+ maxLength: 20,
82
+ required: true,
83
+ values: ['IN', 'NIN'],
84
+ default: 'IN'
85
+ },
86
+ 'value,array,,,true',
87
+ allTeams,
88
+ 'teamIds,sumba:teamIds',
89
+ 'siteId,sumba:siteId'
90
+ ],
91
+ features: [
92
+ 'sumba:status',
93
+ 'dobo:updatedAt',
94
+ 'dobo:immutable'
95
+ ],
96
+ options: { ...options },
140
97
  buildEnd
141
- }, {
98
+ },
99
+ merge(cloneDeep(routeGuard), { baseName: 'anonymous-guard' }),
100
+ merge(cloneDeep(routeGuard), { baseName: 'secure-guard' }),
101
+ {
142
102
  buildLevel: 2,
143
103
  baseName: 'user',
144
104
  properties: [{
@@ -88,10 +88,7 @@
88
88
  "sumba:personInCharge",
89
89
  "sumba:address",
90
90
  "sumba:social",
91
- {
92
- "name": "sumba:status",
93
- "default": "ACTIVE"
94
- },
91
+ "sumba:status",
95
92
  "dobo:createdAt",
96
93
  "dobo:updatedAt",
97
94
  "dobo:immutable"
@@ -146,11 +143,7 @@
146
143
  "dobo:updatedAt",
147
144
  "dobo:immutable",
148
145
  "sumba:siteId",
149
- {
150
- "name": "sumba:status",
151
- "default": "ENABLED",
152
- "values": ["ENABLED", "DISABLED"]
153
- },
146
+ "sumba:status",
154
147
  "dobo:immutable"
155
148
  ]
156
149
  }, {
@@ -0,0 +1,5 @@
1
+ [
2
+ "/user/**/*",
3
+ "/signin",
4
+ "restapi:/user/access-token/**/*"
5
+ ]
@@ -0,0 +1,8 @@
1
+ [
2
+ "/your-stuff/**/*",
3
+ "/signout",
4
+ "/help/trouble-tickets/**/*",
5
+ "restapi:/user/api-key",
6
+ "restapi:/your-stuff/**/*",
7
+ "restapi:/manage/**/*"
8
+ ]
@@ -0,0 +1,15 @@
1
+ async function anonymousGuard () {
2
+ return {
3
+ common: {
4
+ widget: {
5
+ teamIds: {
6
+ attr: {
7
+ refUrl: 'waibuAdmin:/{prefix}/team/details?id={id}'
8
+ }
9
+ }
10
+ }
11
+ }
12
+ }
13
+ }
14
+
15
+ export default anonymousGuard
@@ -1,4 +1,4 @@
1
- async function routeGuard () {
1
+ async function secureGuard () {
2
2
  return {
3
3
  common: {
4
4
  widget: {
@@ -12,4 +12,4 @@ async function routeGuard () {
12
12
  }
13
13
  }
14
14
 
15
- export default routeGuard
15
+ export default secureGuard
@@ -21,7 +21,7 @@
21
21
  "details": {
22
22
  },
23
23
  "add": {
24
- "hidden": ["id", "createdAt", "updatedAt", "status"]
24
+ "hidden": ["id", "createdAt", "updatedAt"]
25
25
  },
26
26
  "edit": {
27
27
  "readonly": ["id", "createdAt", "updatedAt"]
@@ -1,11 +1,10 @@
1
1
  const action = {
2
2
  method: ['GET', 'POST'],
3
- title: 'xModelGuard',
4
- xSite: true,
3
+ title: 'anonymousGuard',
5
4
  handler: async function (req, reply) {
6
5
  const { importModule } = this.app.bajo
7
6
  const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
8
- return await crudSkel.call(this, 'SumbaXModelGuard', req, reply)
7
+ return await crudSkel.call(this, 'SumbaAnonymousGuard', req, reply)
9
8
  }
10
9
  }
11
10
 
@@ -1,10 +1,10 @@
1
1
  const action = {
2
2
  method: ['GET', 'POST'],
3
- title: 'routeGuard',
3
+ title: 'secureGuard',
4
4
  handler: async function (req, reply) {
5
5
  const { importModule } = this.app.bajo
6
6
  const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
7
- return await crudSkel.call(this, 'SumbaRouteGuard', req, reply)
7
+ return await crudSkel.call(this, 'SumbaSecureGuard', req, reply)
8
8
  }
9
9
  }
10
10
 
@@ -1,7 +1,9 @@
1
1
  const apiToken = {
2
2
  method: 'POST',
3
3
  handler: async function (req, reply) {
4
- if (!req.user) return ''
4
+ const { get } = this.app.lib._
5
+ const uid = get(req, 'user.id')
6
+ if (!uid) return ''
5
7
  const rec = await this.app.dobo.getModel('SumbaUser').getRecord(req.user.id, { forceNoHidden: true, noCache: true })
6
8
  return (await this.createJwtFromUserRecord(rec)).token
7
9
  }
@@ -0,0 +1,4 @@
1
+ {
2
+ "model": "SumbaAnonymousGuard",
3
+ "disabled": ["create", "update", "remove"]
4
+ }
@@ -1,4 +1,4 @@
1
1
  {
2
- "model": "SumbaRouteGuard",
2
+ "model": "SumbaSecureGuard",
3
3
  "disabled": ["create", "update", "remove"]
4
4
  }
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, uniq, isEmpty } = this.app.lib._
23
+ const { cloneDeep, isEmpty } = this.app.lib._
24
24
 
25
25
  /**
26
26
  * Sumba class
@@ -52,6 +52,14 @@ async function factory (pkgName) {
52
52
  '/help': 'sumba:/help/contact-form',
53
53
  '/help/trouble-tickets': 'sumba:/help/trouble-tickets/list'
54
54
  },
55
+ redirectSubRoute: {
56
+ waibuAdmin: {
57
+ '/': 'waibuAdmin:/site/site',
58
+ '/x': 'waibuAdmin:/site/x/site/list',
59
+ '/x/*': 'waibuAdmin:/site/x/{2}/list',
60
+ '/*': 'waibuAdmin:/site/{1}/list'
61
+ }
62
+ },
55
63
  menuHandler: [{
56
64
  title: 'account',
57
65
  icon: 'person',
@@ -155,9 +163,11 @@ async function factory (pkgName) {
155
163
  getUserByTokenDur: '1m'
156
164
  }
157
165
  }
158
- this.routeGuards = { local: [], global: [] }
159
- this.modelGuards = { local: [], global: [] }
160
- this.attribGuards = { local: [], global: [] }
166
+ this.secureGuards = []
167
+ this.modelGuards = []
168
+ this.attribGuards = []
169
+ this.secureGuards = []
170
+ this.anonymousGuards = []
161
171
 
162
172
  this.unsafeUserFields = ['password']
163
173
  this.selfBind(['createNewSite', 'removeSite', 'getSite', 'getUserById', 'getUserByToken', 'getUserByUsernamePassword'])
@@ -174,7 +184,11 @@ async function factory (pkgName) {
174
184
  }
175
185
 
176
186
  start = async () => {
177
- const { getModel } = this.app.dobo
187
+ await this.populateRouteGuards()
188
+ if (!this.config.multiSite.enabled) {
189
+ this.config.xSiteAdmins = []
190
+ return
191
+ }
178
192
  if (this.config.xSiteAdmins.length === 0) {
179
193
  const site = await getModel('SumbaSite').findOneRecord({ query: { alias: 'default' } }, { noMagic: true })
180
194
  const user = await getModel('SumbaUser').findOneRecord({ query: { username: 'admin', siteId: site.id } }, { noMagic: true })
@@ -182,6 +196,84 @@ async function factory (pkgName) {
182
196
  }
183
197
  }
184
198
 
199
+ populateRouteGuards = async () => {
200
+ const { isString, get, difference } = this.app.lib._
201
+ const { pascalCase } = this.app.lib.aneka
202
+ const { eachPlugins, readConfig, breakNsPath } = this.app.bajo
203
+ const { getModel } = this.app.dobo
204
+
205
+ const allNs = this.app.getAllNs()
206
+ const sites = await getModel('SumbaSite').findAllRecord({ query: { status: 'ACTIVE' } }, { noMagic: true, dataOnly: true, fields: ['id', 'hostname'] })
207
+
208
+ const sanitize = (item, ns) => {
209
+ if (isString(item)) item = { path: item }
210
+ let [prefix, ...args] = item.path.split(':')
211
+ const neg = prefix[0] === '!'
212
+ if (neg) prefix = prefix.slice(1)
213
+ if (isEmpty(args)) {
214
+ args = prefix
215
+ prefix = null
216
+ } else args = args.join(':')
217
+ if (isEmpty(prefix)) prefix = ns
218
+ else {
219
+ const [_ns, subNs] = prefix.split('.')
220
+ if (subNs) prefix = `${ns}.${subNs}`
221
+ else if (!allNs.includes(_ns)) prefix = `${ns}.${_ns}`
222
+ else prefix = _ns
223
+ }
224
+ item.path = `${neg ? '!' : ''}${prefix}:${args}`
225
+ item._immutable = item._immutable ?? ['path']
226
+ if (neg) {
227
+ item.allTeams = true
228
+ item.teamIds = []
229
+ item._immutable = ['path', 'teamIds', 'allTeams']
230
+ }
231
+ return item
232
+ }
233
+
234
+ const filterFn = item => {
235
+ let [ns] = (item.path.split(':')[0] ?? '').split('.')
236
+ if (ns[0] === '!') ns = ns.slice(1)
237
+ return allNs.includes(ns)
238
+ }
239
+
240
+ for (const type of ['secure', 'anonymous']) {
241
+ const routes = []
242
+ // get it from <pluginDir>/extend/sumba/route/*
243
+ await eachPlugins(async function ({ file }) {
244
+ const { ns } = this
245
+ const items = (await readConfig(file)).map(item => sanitize(item, ns)).filter(filterFn)
246
+ routes.push(...items)
247
+ }, { glob: `route/${type}.*`, prefix: this.ns })
248
+ // get it from config
249
+ const items = get(this, `config.route.${type}`, []).map(item => {
250
+ if (isString(item)) item = { path: item }
251
+ const neg = item.path[0] === '!'
252
+ if (neg) item.path.slice(1)
253
+ const { fullNs } = breakNsPath(item.path)
254
+ if (neg) item.path = '!' + item.path
255
+ return sanitize(item, fullNs)
256
+ }).filter(filterFn)
257
+ routes.push(...items)
258
+ const paths = routes.map(item => item.path)
259
+ const model = getModel(pascalCase(`Sumba ${type} Guard`))
260
+ for (const site of sites) {
261
+ const query = { path: { $in: paths }, siteId: site.id }
262
+ const recs = await model.findAllRecord({ query }, { noMagic: true, dataOnly: true, fields: ['path', 'status'] })
263
+ const spaths = difference(paths, recs.map(rec => rec.path))
264
+ for (const path of spaths) {
265
+ const body = cloneDeep(routes.find(r => r.path === path))
266
+ body.status = 'ACTIVE'
267
+ body.siteId = site.id
268
+ await model.sanitizeFixture({ body, lookupValue: body })
269
+ try {
270
+ await model.createRecord(body, { noMagic: true, noReturn: true })
271
+ } catch (err) {}
272
+ }
273
+ }
274
+ }
275
+ }
276
+
185
277
  _getSetting = async (type, source) => {
186
278
  const { defaultsDeep } = this.app.lib.aneka
187
279
  const { get } = this.app.lib._
@@ -208,18 +300,10 @@ async function factory (pkgName) {
208
300
  adminMenu = async (locals, req) => {
209
301
  if (!this.app.waibuAdmin) return
210
302
  const { getPluginPrefix } = this.app.waibu
303
+ const { findIndex } = this.app.lib._
211
304
  const prefix = getPluginPrefix(this.ns)
212
305
  const params = { action: 'list' }
213
306
  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
- }, {
223
307
  title: 'manageSite',
224
308
  children: [
225
309
  { title: 'siteProfile', href: `waibuAdmin:/${prefix}/site` },
@@ -242,7 +326,8 @@ async function factory (pkgName) {
242
326
  }, {
243
327
  title: 'permission',
244
328
  children: [
245
- { title: 'routeGuard', href: `waibuAdmin:/${prefix}/route-guard/:action`, params },
329
+ { title: 'secureGuard', href: `waibuAdmin:/${prefix}/secure-guard/:action`, params },
330
+ { title: 'anonymousGuard', href: `waibuAdmin:/${prefix}/anonymous-guard/:action`, params },
246
331
  { title: 'modelGuard', href: `waibuAdmin:/${prefix}/model-guard/:action`, params },
247
332
  { title: 'attribGuard', href: `waibuAdmin:/${prefix}/attrib-guard/:action`, params }
248
333
  ]
@@ -260,9 +345,23 @@ async function factory (pkgName) {
260
345
  { title: 'manageDownload', href: `waibuAdmin:/${prefix}/download/:action`, params }
261
346
  ]
262
347
  }]
348
+ const sessionMenu = { title: 'userSession', href: `waibuAdmin:/${prefix}/x/session/:action`, params }
349
+ const cacheMenu = { title: 'cacheStorage', href: `waibuAdmin:/${prefix}/x/cache/:action`, params }
350
+ if (this.config.multiSite.enabled) {
351
+ items.unshift({
352
+ title: 'xSite',
353
+ children: [
354
+ { title: 'allSites', href: `waibuAdmin:/${prefix}/x/site/:action`, params },
355
+ sessionMenu
356
+ ]
357
+ })
358
+ } else {
359
+ const idx = findIndex(items, i => i.title === 'misc')
360
+ if (idx > -1) items[idx].children.push(sessionMenu)
361
+ }
263
362
  if (this.app.bajoCache) {
264
- const item = items.find(i => i.title === 'xSite')
265
- item.children.push({ title: 'cacheStorage', href: `waibuAdmin:/${prefix}/x/cache/:action`, params })
363
+ const idx = findIndex(items, i => i.title === this.config.multiSite.enabled ? 'xSite' : 'misc')
364
+ if (idx > -1)items[idx].children.push(cacheMenu)
266
365
  }
267
366
  return items
268
367
  }
@@ -377,31 +476,11 @@ async function factory (pkgName) {
377
476
  return true
378
477
  }
379
478
 
380
- checkPathsByRoute = ({ req, paths = [], teamIds = [], guards = [] }) => {
381
- const { includes } = this.app.lib.aneka
479
+ checkRouteGuard = (guards, paths) => {
382
480
  const { outmatch } = this.app.lib
383
-
384
- for (const item of guards) {
385
- const matchPath = outmatch(item.path)
386
- for (const path of paths) {
387
- if (matchPath(path)) {
388
- if (item.methods.includes(req.method)) {
389
- if (includes(teamIds, item.teamIds)) return item
390
- if (teamIds.length === 0) return item
391
- }
392
- }
393
- }
394
- }
395
- }
396
-
397
- checkPathsByGuard = ({ guards, paths }) => {
398
- const { outmatch } = this.app.lib
399
- const matcher = outmatch(guards)
400
- let guarded
401
- for (const path of paths) {
402
- if (!guarded) guarded = matcher(path)
403
- }
404
- return guarded
481
+ const all = guards.map(item => item.path)
482
+ const isMatch = outmatch(all)
483
+ return paths.find(isMatch)
405
484
  }
406
485
 
407
486
  signout = async ({ req, reply, reason }) => {
@@ -545,81 +624,56 @@ async function factory (pkgName) {
545
624
  return await hash(item, this.config.auth.common.apiKey.algo)
546
625
  }
547
626
 
548
- _fetchGuards = async (type = 'Route') => {
627
+ _fetchGuards = async (type) => {
549
628
  const { getModel } = this.app.dobo
550
629
  const options = { noMagic: true, noCache: true, noDriverHook: true, dataOnly: true }
551
630
  const filter = { query: { status: 'ACTIVE' } }
552
- return {
553
- global: await getModel(`SumbaX${type}Guard`).findAllRecord(filter, options),
554
- local: await getModel(`Sumba${type}Guard`).findAllRecord(filter, options),
555
- siteIds: (await getModel('SumbaSite').findAllRecord(filter.options)).map(item => item.id + '')
556
- }
631
+ const results = await getModel(`Sumba${type}Guard`).findAllRecord(filter, options)
632
+ return results.map(result => {
633
+ result.teamIds = result.teamIds.map(item => item + '')
634
+ return result
635
+ })
557
636
  }
558
637
 
559
- getRouteGuards = async (reread) => {
638
+ _getGuards = (inputs = []) => {
560
639
  const { routePath } = this.app.waibu
561
640
  const { orderBy } = this.app.lib._
562
- const { isSet } = this.app.lib.aneka
563
- if (!reread) return this.routeGuards
564
-
565
- const result = await this._fetchGuards('Route')
566
- for (const type of ['global', 'local']) {
567
- result[type] = result[type].map(item => {
568
- item.siteIds = item.siteIds ?? []
569
- if (item.siteIds.length === 0) item.siteIds = [...result.siteIds]
570
- if (item.siteId) item.siteIds = uniq([...item.siteIds, item.siteId].filter(i => isSet(i)).map(i => i + ''))
571
- item.teamIds = (item.teamIds ?? []).filter(i => isSet(i)).map(i => i + '')
572
- item.methods = item.methods ?? []
573
- delete item.siteId
574
- return item
575
- })
576
- this.routeGuards[type] = orderBy(result[type].filter(item => {
577
- try {
578
- item.path = routePath(item.path)
579
- return true
580
- } catch (err) {
581
- return false
582
- }
583
- }), ['weight', 'path'], ['desc', 'asc'])
584
- }
585
- return this.routeGuards
641
+ const normal = orderBy(inputs.filter(input => input.path[0] !== '!').map(result => {
642
+ result.path = routePath(result.path)
643
+ return result
644
+ }), ['weight', 'path'], ['desc', 'asc'])
645
+ const inverse = orderBy(inputs.filter(input => input.path[0] === '!').map(result => {
646
+ result.path = routePath(result.path)
647
+ return result
648
+ }), ['weight', 'path'], ['desc', 'asc'])
649
+ return [...normal, ...inverse]
650
+ }
651
+
652
+ getAnonymousGuards = async (reread) => {
653
+ if (!reread) return this.anonymousGuards
654
+ const guards = await this._fetchGuards('Anonymous')
655
+ this.anonymousGuards = this._getGuards(guards)
656
+ return this.anonymousGuards
657
+ }
658
+
659
+ getSecureGuards = async (reread) => {
660
+ if (!reread) return this.secureGuards
661
+ const guards = await this._fetchGuards('Secure')
662
+ this.secureGuards = this._getGuards(guards)
663
+ return this.secureGuards
586
664
  }
587
665
 
588
666
  getModelGuards = async (reread) => {
589
- const { isSet } = this.app.lib.aneka
590
667
  if (!reread) return this.modelGuards
591
668
 
592
- const result = await this._fetchGuards('Model')
593
- for (const type of ['global', 'local']) {
594
- this.modelGuards[type] = result[type].filter(item => (!isEmpty(item.value)) && (!isEmpty(item.models))).map(item => {
595
- item.siteIds = item.siteIds ?? []
596
- if (item.siteIds.length === 0) item.siteIds = [...result.siteIds]
597
- if (item.siteId) item.siteIds = uniq([...item.siteIds, item.siteId].filter(i => isSet(i)).map(i => i + ''))
598
- item.teamIds = (item.teamIds ?? []).filter(i => isSet(i)).map(i => i + '')
599
- delete item.siteId
600
- return item
601
- })
602
- }
669
+ this.modelGuards = await this._fetchGuards('Model')
603
670
  return this.modelGuards
604
671
  }
605
672
 
606
673
  getAttribGuards = async (reread) => {
607
- const { isSet } = this.app.lib.aneka
608
674
  if (!reread) return this.attribGuards
609
675
 
610
- const result = await this._fetchGuards('Attrib')
611
- for (const type of ['global', 'local']) {
612
- this.attribGuards[type] = result[type].map(item => {
613
- item.siteIds = item.siteIds ?? []
614
- if (item.siteIds.length === 0) item.siteIds = [...result.siteIds]
615
- if (item.siteId) item.siteIds = uniq([...item.siteIds, item.siteId].filter(i => isSet(i)).map(i => i + ''))
616
- item.teamIds = (item.teamIds ?? []).filter(i => isSet(i)).map(i => i + '')
617
- item.models = item.models ?? []
618
- item.hiddenCols = item.hiddenCols ?? []
619
- delete item.siteId
620
- return item
621
- })
622
- }
676
+ this.attribGuards = await this._fetchGuards('Model')
623
677
  return this.attribGuards
624
678
  }
625
679
 
@@ -632,6 +686,7 @@ async function factory (pkgName) {
632
686
  removeSite = removeSite
633
687
  getSite = getSite
634
688
  getUserById = getUserById
689
+
635
690
  getUserByToken = getUserByToken
636
691
  getUserByUsernamePassword = getUserByUsernamePassword
637
692
  }
package/lib/get-user.js CHANGED
@@ -10,7 +10,7 @@ export async function mergeTeam (user, req) {
10
10
  if (userTeam.length === 0) return
11
11
  delete query.userId
12
12
  query.id = { $in: map(userTeam, 'teamId') }
13
- query.status = 'ENABLED'
13
+ query.status = 'ACTIVE'
14
14
  mdl = getModel('SumbaTeam')
15
15
  const teams = await mdl.findAllRecord({ query })
16
16
  if (teams.length > 0) {
package/lib/util.js CHANGED
@@ -26,7 +26,7 @@ export function parseNsSettings (ns, setting, items) {
26
26
  }
27
27
  }
28
28
 
29
- export function pathsToCheck (req, withHome) {
29
+ export function pathsToCheck (req) {
30
30
  const { uniq, without } = this.app.lib._
31
31
  const items = [req.routeOptions.url, req.url].map(url => url.split('?')[0].split('#')[0])
32
32
  return uniq(without(items, undefined, null))
@@ -56,36 +56,25 @@ export async function checkTheme (req, reply) {
56
56
  req.theme = req.theme ?? 'default'
57
57
  }
58
58
 
59
- export async function checkTeam (req, reply, route) {
60
- const { map } = this.app.lib._
59
+ export async function checkTeam (req, reply) {
60
+ const { includes } = this.app.lib.aneka
61
61
  const { outmatch } = this.app.lib
62
- route.teamIds = route.teamIds ?? []
63
- if (route.teamIds.length === 0) return
64
62
 
65
- const teamIds = map(req.user.teams, 'id')
66
- const teamAliases = map(req.user.teams, 'alias')
67
63
  if (req.user.isAdmin) return
68
- const matchXSite = outmatch(route.path)
69
- if (req.routeOptions.config.xSite && matchXSite(req.url) && req.user.isXSiteAdmin) return
70
- if (teamAliases.length === 0) throw this.error('accessDenied', { statusCode: 403 })
71
-
72
- const paths = pathsToCheck.call(this, req, true)
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) })
77
- if (match) {
78
- const neg = this.checkPathsByRoute({ paths, teamIds, guards: localGuards.filter(item => item.negation) })
79
- if (neg) match = undefined
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
- }
64
+ if (req.routeOptions.config.xSite && req.user.isXSiteAdmin) return
65
+
66
+ const teamIds = req.user.teams.map(item => item.id + '')
67
+ if (req.user.teams.map(item => item.alias).length === 0) throw this.error('accessDenied', { statusCode: 403 })
68
+
69
+ const paths = pathsToCheck.call(this, req)
70
+ const results = (await this.getSecureGuards()).filter(item => {
71
+ if (item.siteId !== req.site.id + '' || item.path[0] === '!') return false
72
+ return paths.some(outmatch([item.path]))
73
+ })
74
+ for (const result of results) {
75
+ if (result.allTeams) continue
76
+ if (!includes(teamIds, result.teamIds)) throw this.error('accessDenied', { statusCode: 403 })
87
77
  }
88
- if (!match) throw this.error('accessDenied', { statusCode: 403 })
89
78
  // passed
90
79
  }
91
80
 
@@ -116,51 +105,22 @@ export async function checkUserId (req, reply, source) {
116
105
  }
117
106
 
118
107
  const paths = pathsToCheck.call(this, req)
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({ req, paths, guards: localGuards.filter(item => item.anonymous && !item.negation) })
126
- if (anonymousPath) {
127
- const neg = await this.checkPathsByRoute({ req, paths, guards: localGuards.filter(item => item.anonymous && item.negation) })
128
- if (neg) anonymousPath = undefined
129
- }
130
- if (!anonymousPath) {
131
- anonymousPath = await this.checkPathsByRoute({ req, paths, guards: globalGuards.filter(item => item.anonymous && !item.negation) })
132
- if (anonymousPath) {
133
- const neg = await this.checkPathsByRoute({ req, paths, guards: globalGuards.filter(item => item.anonymous && item.negation) })
134
- if (neg) anonymousPath = undefined
135
- }
136
- }
137
- // find securePath
138
- securePath = await this.checkPathsByRoute({ req, paths, guards: localGuards.filter(item => !item.anonymous && !item.negation) })
139
- if (securePath) {
140
- const neg = await this.checkPathsByRoute({ req, paths, guards: localGuards.filter(item => !item.anonymous && item.negation) })
141
- if (neg) securePath = undefined
142
- }
143
- if (!securePath) {
144
- securePath = await this.checkPathsByRoute({ req, paths, guards: globalGuards.filter(item => !item.anonymous && !item.negation) })
145
- if (securePath) {
146
- const neg = await this.checkPathsByRoute({ req, paths, guards: globalGuards.filter(item => !item.anonymous && item.negation) })
147
- if (neg) securePath = undefined
148
- }
149
- }
150
- // checking...
151
- if (!securePath && !anonymousPath) {
152
- if (userId) await setUser()
153
- return false // regular, unguarded path. Not secure & not anonymous path
154
- }
155
- if (anonymousPath) {
108
+ let guards = (await this.getAnonymousGuards()).filter(item => item.siteId === req.site.id + '')
109
+ const anonymous = this.checkRouteGuard(guards, paths)
110
+ if (anonymous) {
156
111
  if (!userId) return false
157
112
  req.session.ref = req.url
158
113
  return reply.redirectTo(routePath(this.config.redirect.signout))
159
114
  }
160
- if (!(securePath.methods ?? []).includes(req.method)) throw this.error('accessDenied', { statusCode: 403 })
115
+ guards = (await this.getSecureGuards()).filter(item => item.siteId === req.site.id + '')
116
+ const secure = this.checkRouteGuard(guards, paths)
117
+ if (!secure) {
118
+ if (userId) await setUser()
119
+ return false // regular, unguarded path. Not secure & not anonymous path
120
+ }
161
121
  if (userId) {
162
122
  await setUser()
163
- return securePath
123
+ return secure
164
124
  }
165
125
  const silentOnError = this.config.auth[webApp].silentOnError ?? this.config.auth.common.silentOnError
166
126
  const payload = silentOnError ? { noContent: true } : undefined
@@ -177,7 +137,7 @@ export async function checkUserId (req, reply, source) {
177
137
  }
178
138
  }
179
139
  if (!success) throw this.error('accessDeniedNoAuth', merge({ statusCode: 403 }, payload))
180
- return securePath
140
+ return secure
181
141
  }
182
142
 
183
143
  export async function latLngHook (body, options) {
@@ -196,6 +156,7 @@ export async function latLngHook (body, options) {
196
156
  */
197
157
  export async function checkXSite (req, reply) {
198
158
  const { get } = this.app.lib._
159
+ if (!this.config.multiSite.enabled) return
199
160
  if (!get(req, 'routeOptions.config.xSite')) return
200
161
  if (!get(req, 'user.isXSiteAdmin')) throw this.error('accessDenied', { statusCode: 403 })
201
162
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sumba",
3
- "version": "2.29.0",
3
+ "version": "2.30.0",
4
4
  "description": "Biz Suite for Bajo Framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/wiki/CHANGES.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-06-10
4
+
5
+ - [2.30.0] Refactoring all guards
6
+ - [2.30.0] Feature ```sumba:status``` now by default using ```ACTIVE``` and ```INACTIVE``` states
7
+ - [2.30.0] Bug in ```multiSite``` handling
8
+ - [2.30.0] Add ```populateRouteGuards()```
9
+ - [2.30.0] Bug fix in ```hook.js```
10
+
3
11
  ## 2026-06-03
4
12
 
5
13
  - [2.29.0] Some models with property ```values``` now use the newly introduced function values
@@ -1,24 +0,0 @@
1
- const routes = [
2
- 'sumba:/your-stuff/**/*',
3
- 'sumba:/signout',
4
- 'sumba:/help/trouble-tickets/**/*',
5
- 'sumba.restapi:/user/api-key',
6
- 'sumba.restapi:/your-stuff/**/*',
7
- 'sumba.restapi:/manage/**/*',
8
- '~sumba:/user/**/*',
9
- '~sumba:/signin',
10
- '~sumba.restapi:/user/access-token/**/*'
11
- ]
12
-
13
- async function routeGuard () {
14
- return routes.map(r => {
15
- const anonymous = r[0] === '~'
16
- return {
17
- path: anonymous ? r.slice(1) : r,
18
- anonymous,
19
- status: 'ACTIVE'
20
- }
21
- })
22
- }
23
-
24
- export default routeGuard
@@ -1,15 +0,0 @@
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
@@ -1,15 +0,0 @@
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
@@ -1,15 +0,0 @@
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
@@ -1,3 +0,0 @@
1
- {
2
- "redirect": "waibuAdmin:/site/site"
3
- }
@@ -1,12 +0,0 @@
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,3 +0,0 @@
1
- {
2
- "redirect": "waibuAdmin:/site/x/site/list"
3
- }
@@ -1,12 +0,0 @@
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