sumba 2.22.0 → 2.23.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-team-guard@after-action.js +6 -0
- package/extend/bajo/hook/dobo.sumba-user-guard@after-action.js +6 -0
- package/extend/bajo/hook/dobo.sumba-user@after-update-record.js +2 -4
- package/extend/bajo/hook/waibu-mpa@pre-parsing.js +4 -4
- package/extend/bajo/hook/waibu-rest-api@pre-parsing.js +4 -4
- package/extend/bajo/hook/waibu-static@pre-parsing.js +4 -4
- package/extend/bajo/hook/waibu@after-app-boot.js +5 -23
- package/extend/bajo/hook/waibu@pre-parsing.js +0 -5
- package/extend/bajo/intl/en-US.json +8 -6
- package/extend/dobo/fixture/route-guard.js +26 -0
- package/extend/dobo/model/route-guard.js +60 -0
- package/extend/dobo/model/user.js +2 -1
- package/extend/waibuDb/schema/route-guard.js +15 -0
- package/extend/waibuMpa/extend/waibuAdmin/route/route-guard/@action.js +11 -0
- package/extend/waibuRestApi/route/manage/route-guard/model-builder.json +4 -0
- package/extend/waibuRestApi/route/your-stuff/profile/get.js +1 -1
- package/index.js +26 -43
- package/lib/util.js +45 -70
- package/package.json +1 -1
- package/wiki/CHANGES.md +9 -0
- package/extend/sumba/route-guard/anonymous.json +0 -10
- package/extend/sumba/route-guard/secure.json +0 -8
- package/lib/collect.js +0 -52
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
export async function clearCache (id, result) {
|
|
2
2
|
if (!this.app.bajoCache) return
|
|
3
|
-
const { hash } = this.app.bajoExtra
|
|
4
3
|
const { clear } = this.app.bajoCache ?? {}
|
|
5
|
-
const { get
|
|
6
|
-
|
|
7
|
-
if (!isEmpty(token)) token = await hash(token)
|
|
4
|
+
const { get } = this.app.lib._
|
|
5
|
+
const token = get(result, 'data.token', get(result, 'oldData.token', ''))
|
|
8
6
|
await clear({ key: `dobo|SumbaUser|getUserById|${id}` })
|
|
9
7
|
await clear({ key: `dobo|SumbaUser|getUserByToken|${token}` })
|
|
10
8
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { checkUserId, checkTeam, checkTheme, checkIconset,
|
|
1
|
+
import { checkUserId, checkTeam, checkTheme, checkIconset, checkInterSite } from '../../../lib/util.js'
|
|
2
2
|
|
|
3
3
|
const preParsing = {
|
|
4
4
|
level: 10,
|
|
5
5
|
handler: async function (req, reply) {
|
|
6
6
|
await checkTheme.call(this, req, reply)
|
|
7
7
|
await checkIconset.call(this, req, reply)
|
|
8
|
-
await checkUserId.call(this, req, reply, 'waibuMpa')
|
|
9
|
-
await checkTeam.call(this, req, reply, 'waibuMpa')
|
|
10
|
-
await
|
|
8
|
+
if (!await checkUserId.call(this, req, reply, 'waibuMpa')) return
|
|
9
|
+
if (!await checkTeam.call(this, req, reply, 'waibuMpa')) return
|
|
10
|
+
await checkInterSite.call(this, req, reply)
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { checkUserId, checkTeam,
|
|
1
|
+
import { checkUserId, checkTeam, checkInterSite } from '../../../lib/util.js'
|
|
2
2
|
|
|
3
3
|
const preParsing = {
|
|
4
4
|
level: 10,
|
|
5
5
|
handler: async function (req, reply) {
|
|
6
|
-
await checkUserId.call(this, req, reply, 'waibuRestApi')
|
|
7
|
-
await checkTeam.call(this, req, reply, 'waibuRestApi')
|
|
8
|
-
await
|
|
6
|
+
if (!await checkUserId.call(this, req, reply, 'waibuRestApi')) return
|
|
7
|
+
if (!await checkTeam.call(this, req, reply, 'waibuRestApi')) return
|
|
8
|
+
await checkInterSite.call(this, req, reply)
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { checkUserId, checkTeam,
|
|
1
|
+
import { checkUserId, checkTeam, checkInterSite } from '../../../lib/util.js'
|
|
2
2
|
|
|
3
3
|
const preParsing = {
|
|
4
4
|
level: 10,
|
|
5
5
|
handler: async function (req, reply) {
|
|
6
|
-
await checkUserId.call(this, req, reply, 'waibuStatic')
|
|
7
|
-
await checkTeam.call(this, req, reply, 'waibuStatic')
|
|
8
|
-
await
|
|
6
|
+
if (!await checkUserId.call(this, req, reply, 'waibuStatic')) return
|
|
7
|
+
if (!await checkTeam.call(this, req, reply, 'waibuStatic')) return
|
|
8
|
+
await checkInterSite.call(this, req, reply)
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -1,23 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
this.log.trace('collectingRouteGuards')
|
|
7
|
-
await collectRoutes.call(this, 'secure')
|
|
8
|
-
await runHook(`${this.ns}:afterCollectSecureRoutes`, this.secureRoutes, this.secureNegRoutes)
|
|
9
|
-
this.log.trace('secureRoutes%d', this.secureRoutes.length)
|
|
10
|
-
this.log.trace('secureNegRoutes%d', this.secureNegRoutes.length)
|
|
11
|
-
await collectRoutes.call(this, 'anonymous')
|
|
12
|
-
await runHook(`${this.ns}:afterCollectAnonymousRoutes`, this.anonymousRoutes, this.anonymousNegRoutes)
|
|
13
|
-
this.log.trace('anonRoutes%d', this.anonymousRoutes.length)
|
|
14
|
-
this.log.trace('anonNegRoutes%d', this.anonymousNegRoutes.length)
|
|
15
|
-
this.log.trace('collectingTeamGuards')
|
|
16
|
-
await collectTeam.call(this)
|
|
17
|
-
await runHook(`${this.ns}:afterCollectTeamRoutes`, this.teamRoutes, this.teamNegRoutes)
|
|
18
|
-
this.log.trace('teamRoutes%d', this.teamRoutes.length)
|
|
19
|
-
this.log.trace('teamNegRoutes%d', this.teamNegRoutes.length)
|
|
20
|
-
await runHook(`${this.ns}:afterBoot`)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export default afterAppBoot
|
|
1
|
+
async function afterAppBoot () {
|
|
2
|
+
await this.getRouteGuards(true)
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export default afterAppBoot
|
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import { checkNoRouteSetting, checkNoRouteSettingKey } from '../../../lib/util.js'
|
|
2
|
-
|
|
3
1
|
const preParsing = {
|
|
4
2
|
level: 10,
|
|
5
3
|
handler: async function (req, reply) {
|
|
6
|
-
const { get } = this.app.lib._
|
|
7
4
|
const { getHostname } = this.app.waibu
|
|
8
5
|
req.site = await this.getSite(getHostname(req))
|
|
9
|
-
const routes = get(req.site, checkNoRouteSettingKey)
|
|
10
|
-
checkNoRouteSetting.call(this, req, routes)
|
|
11
6
|
}
|
|
12
7
|
}
|
|
13
8
|
|
|
@@ -61,11 +61,6 @@
|
|
|
61
61
|
"troubleTickets": "Trouble Tickets",
|
|
62
62
|
"guest": "Guest",
|
|
63
63
|
"warningMemberOnly%s": "Please authenticate yourself first, because the <a href=\"%s\">page<a> your're trying to access is a member only page",
|
|
64
|
-
"collectingRouteGuards": "Collecting route guards:",
|
|
65
|
-
"secureRoutes%d": "- Secure routes: %d",
|
|
66
|
-
"secureNegRoutes%d": "- Secure, negated routes: %d",
|
|
67
|
-
"anonRoutes%d": "- Anonymous routes: %d",
|
|
68
|
-
"anonNegRoutes%d": "- Anonymous, negated routes: %d",
|
|
69
64
|
"replies": "Replies",
|
|
70
65
|
"compose": "Compose",
|
|
71
66
|
"yourReply": "Your Reply",
|
|
@@ -136,6 +131,8 @@
|
|
|
136
131
|
"statusOpen": "Open",
|
|
137
132
|
"statusClosed": "Closed",
|
|
138
133
|
"allSites": "All Sites",
|
|
134
|
+
"permission": "Permission",
|
|
135
|
+
"routeGuard": "Route Guard",
|
|
139
136
|
"field": {
|
|
140
137
|
"currentPassword": "Current Password",
|
|
141
138
|
"newPassword": "New Password",
|
|
@@ -159,7 +156,12 @@
|
|
|
159
156
|
"team": "Team",
|
|
160
157
|
"ns": "Module's Namespace",
|
|
161
158
|
"value": "Value",
|
|
162
|
-
"notes": "Notes"
|
|
159
|
+
"notes": "Notes",
|
|
160
|
+
"path": "Path",
|
|
161
|
+
"inverse": "Inverse?",
|
|
162
|
+
"anonymous": "Anonymous?",
|
|
163
|
+
"methods": "Methods",
|
|
164
|
+
"teams": "Teams"
|
|
163
165
|
},
|
|
164
166
|
"validation": {
|
|
165
167
|
"password": {
|
|
@@ -0,0 +1,26 @@
|
|
|
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 userGuard () {
|
|
14
|
+
return routes.map(r => {
|
|
15
|
+
const anonymous = r[0] === '~'
|
|
16
|
+
return {
|
|
17
|
+
path: anonymous ? r.slice(1) : r,
|
|
18
|
+
anonymous,
|
|
19
|
+
_immutable: true,
|
|
20
|
+
siteId: '?:SumbaSite::alias:default',
|
|
21
|
+
status: 'ACTIVE'
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default userGuard
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
async function routeGuard () {
|
|
2
|
+
return {
|
|
3
|
+
connection: 'memory',
|
|
4
|
+
properties: [
|
|
5
|
+
'path,,255,true,true',
|
|
6
|
+
{
|
|
7
|
+
name: 'inverse',
|
|
8
|
+
type: 'boolean',
|
|
9
|
+
required: true,
|
|
10
|
+
default: false
|
|
11
|
+
}, {
|
|
12
|
+
name: 'anonymous',
|
|
13
|
+
type: 'boolean',
|
|
14
|
+
required: true,
|
|
15
|
+
default: false
|
|
16
|
+
}, {
|
|
17
|
+
name: 'methods',
|
|
18
|
+
type: 'array',
|
|
19
|
+
required: true,
|
|
20
|
+
default: ['GET', 'POST', 'UPDATE', 'DELETE'],
|
|
21
|
+
values: ['GET', 'POST', 'UPDATE', 'DELETE']
|
|
22
|
+
}, {
|
|
23
|
+
name: 'teams',
|
|
24
|
+
type: 'array',
|
|
25
|
+
default: [],
|
|
26
|
+
ref: {
|
|
27
|
+
teams: {
|
|
28
|
+
model: 'SumbaTeam',
|
|
29
|
+
field: 'alias',
|
|
30
|
+
searchField: 'name',
|
|
31
|
+
fields: ['alias', 'name']
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}, {
|
|
35
|
+
name: 'weight',
|
|
36
|
+
type: 'smallint',
|
|
37
|
+
default: 0
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
features: [
|
|
41
|
+
{
|
|
42
|
+
name: 'sumba:status',
|
|
43
|
+
values: ['ACTIVE', 'INACTIVE'],
|
|
44
|
+
default: 'ACTIVE'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'dobo:unique',
|
|
48
|
+
fields: ['path', 'anonymous', 'methods', 'teams', 'inverse', 'weight', 'status', 'siteId']
|
|
49
|
+
},
|
|
50
|
+
'dobo:immutable',
|
|
51
|
+
'dobo:updatedAt',
|
|
52
|
+
'sumba:siteId'
|
|
53
|
+
],
|
|
54
|
+
options: {
|
|
55
|
+
persistence: false
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default routeGuard
|
|
@@ -28,6 +28,7 @@ async function user () {
|
|
|
28
28
|
maxLength: 100,
|
|
29
29
|
virtual: true,
|
|
30
30
|
getValue: async function (val, rec) {
|
|
31
|
+
if (!rec.salt) return
|
|
31
32
|
return await this.plugin.hash(rec.salt)
|
|
32
33
|
}
|
|
33
34
|
}, {
|
|
@@ -63,7 +64,7 @@ async function user () {
|
|
|
63
64
|
fields: ['email', 'siteId'],
|
|
64
65
|
type: 'unique'
|
|
65
66
|
}],
|
|
66
|
-
hidden: ['password'
|
|
67
|
+
hidden: ['password'],
|
|
67
68
|
features: [
|
|
68
69
|
'sumba:address',
|
|
69
70
|
'sumba:social',
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const action = {
|
|
2
|
+
method: ['GET', 'POST'],
|
|
3
|
+
title: 'routeGuard',
|
|
4
|
+
handler: async function (req, reply) {
|
|
5
|
+
const { importModule } = this.app.bajo
|
|
6
|
+
const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
|
|
7
|
+
return await crudSkel.call(this, 'SumbaRouteGuard', req, reply)
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default action
|
package/index.js
CHANGED
|
@@ -152,6 +152,8 @@ async function factory (pkgName) {
|
|
|
152
152
|
getUserByTokenDur: '1m'
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
+
this.userGuards = []
|
|
156
|
+
this.teamGuards = []
|
|
155
157
|
this.unsafeUserFields = ['password']
|
|
156
158
|
this.selfBind(['createNewSite', 'removeSite', 'getSite', 'getUserById', 'getUserByToken', 'getUserByUsernamePassword'])
|
|
157
159
|
}
|
|
@@ -160,10 +162,6 @@ async function factory (pkgName) {
|
|
|
160
162
|
const { getPluginDataDir } = this.app.bajo
|
|
161
163
|
this.downloadDir = `${getPluginDataDir(this.ns)}/download`
|
|
162
164
|
this.app.lib.fs.ensureDirSync(this.downloadDir)
|
|
163
|
-
for (const type of ['secure', 'anonymous', 'team']) {
|
|
164
|
-
this[`${type}Routes`] = this[`${type}Routes`] ?? []
|
|
165
|
-
this[`${type}NegRoutes`] = this[`${type}NegRoutes`] ?? []
|
|
166
|
-
}
|
|
167
165
|
if (this.config.multiSite === true) {
|
|
168
166
|
this.config.multiSite = cloneDeep(defMultiSite)
|
|
169
167
|
this.config.multiSite.enabled = true
|
|
@@ -173,8 +171,8 @@ async function factory (pkgName) {
|
|
|
173
171
|
start = async () => {
|
|
174
172
|
const { getModel } = this.app.dobo
|
|
175
173
|
if (this.config.interSiteAdmins.length === 0) {
|
|
176
|
-
const site = await getModel('SumbaSite').findOneRecord({ query: { alias: 'default' } }, {
|
|
177
|
-
const user = await getModel('SumbaUser').findOneRecord({ query: { username: 'admin', siteId: site.id } }, {
|
|
174
|
+
const site = await getModel('SumbaSite').findOneRecord({ query: { alias: 'default' } }, { noMagic: true })
|
|
175
|
+
const user = await getModel('SumbaUser').findOneRecord({ query: { username: 'admin', siteId: site.id } }, { noMagic: true })
|
|
178
176
|
this.config.interSiteAdmins.push(user.id)
|
|
179
177
|
}
|
|
180
178
|
}
|
|
@@ -227,6 +225,11 @@ async function factory (pkgName) {
|
|
|
227
225
|
{ title: 'teamUser', href: `waibuAdmin:/${prefix}/team-user/list` },
|
|
228
226
|
{ title: 'teamSetting', href: `waibuAdmin:/${prefix}/team-setting/list` }
|
|
229
227
|
]
|
|
228
|
+
}, {
|
|
229
|
+
title: 'permission',
|
|
230
|
+
children: [
|
|
231
|
+
{ title: 'routeGuard', href: `waibuAdmin:/${prefix}/route-guard/list` }
|
|
232
|
+
]
|
|
230
233
|
}, {
|
|
231
234
|
title: 'supportSystem',
|
|
232
235
|
children: [
|
|
@@ -358,7 +361,7 @@ async function factory (pkgName) {
|
|
|
358
361
|
return true
|
|
359
362
|
}
|
|
360
363
|
|
|
361
|
-
|
|
364
|
+
checkPathsByRoute = ({ paths = [], method = 'GET', teams = [], guards = [] }) => {
|
|
362
365
|
const { includes } = this.app.lib.aneka
|
|
363
366
|
const { outmatch } = this.app.lib
|
|
364
367
|
|
|
@@ -366,9 +369,8 @@ async function factory (pkgName) {
|
|
|
366
369
|
const matchPath = outmatch(item.path)
|
|
367
370
|
for (const path of paths) {
|
|
368
371
|
if (matchPath(path)) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
if (item.teams.length === 0) return item
|
|
372
|
+
if (item.methods.includes(method)) {
|
|
373
|
+
if (teams.length === 0) return item
|
|
372
374
|
if (includes(teams, item.teams)) return item
|
|
373
375
|
}
|
|
374
376
|
}
|
|
@@ -376,19 +378,6 @@ async function factory (pkgName) {
|
|
|
376
378
|
}
|
|
377
379
|
}
|
|
378
380
|
|
|
379
|
-
checkPathsByRoute = ({ paths = [], method = 'GET', guards = [] }) => {
|
|
380
|
-
const { outmatch } = this.app.lib
|
|
381
|
-
for (const item of guards) {
|
|
382
|
-
const matchPath = outmatch(item.path)
|
|
383
|
-
for (const path of paths) {
|
|
384
|
-
if (matchPath(path)) {
|
|
385
|
-
const matchMethods = outmatch(item.methods, { separator: false })
|
|
386
|
-
if (matchMethods(method)) return item
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
381
|
checkPathsByGuard = ({ guards, paths }) => {
|
|
393
382
|
const { outmatch } = this.app.lib
|
|
394
383
|
const matcher = outmatch(guards)
|
|
@@ -535,31 +524,25 @@ async function factory (pkgName) {
|
|
|
535
524
|
return password
|
|
536
525
|
}
|
|
537
526
|
|
|
538
|
-
parseRouteGuard = item => {
|
|
539
|
-
const { routePath, routePathHandlers } = this.app.waibu
|
|
540
|
-
const { isString, isEmpty } = this.app.lib._
|
|
541
|
-
if (isString(item)) {
|
|
542
|
-
let [path, methods] = item.split('|').map(i => i.trim())
|
|
543
|
-
methods = isEmpty(methods) ? ['*'] : methods.split(',').map(i => i.trim())
|
|
544
|
-
item = { path, methods }
|
|
545
|
-
}
|
|
546
|
-
item.methods = item.methods ?? ['*']
|
|
547
|
-
if (item.methods.includes('*')) item.methods = ['*']
|
|
548
|
-
const [, routeHandler] = item.path.split(':')[0].split('.')
|
|
549
|
-
if (!isEmpty(routeHandler) && !routePathHandlers[routeHandler]) return
|
|
550
|
-
const rns = isEmpty(routeHandler) ? 'waibuMpa' : routePathHandlers[routeHandler].ns
|
|
551
|
-
if (!this.app[rns]) return
|
|
552
|
-
item.inverse = item.path[0] === '!'
|
|
553
|
-
if (item.inverse) item.path = item.path.slice(1)
|
|
554
|
-
item.path = routePath(item.path, { defFormat: false })
|
|
555
|
-
return item
|
|
556
|
-
}
|
|
557
|
-
|
|
558
527
|
hash = async (item) => {
|
|
559
528
|
const { hash } = this.app.bajoExtra
|
|
560
529
|
return await hash(item, this.config.auth.common.apiKey.algo)
|
|
561
530
|
}
|
|
562
531
|
|
|
532
|
+
getRouteGuards = async (reread) => {
|
|
533
|
+
const { getModel } = this.app.dobo
|
|
534
|
+
const { routePath } = this.app.waibu
|
|
535
|
+
const { map, orderBy } = this.app.lib._
|
|
536
|
+
if (!reread) return this.routeGuards
|
|
537
|
+
const model = getModel('SumbaRouteGuard')
|
|
538
|
+
const results = await model.findAllRecord({ query: { status: 'ACTIVE' } }, { noMagic: true, dataOnly: true })
|
|
539
|
+
this.routeGuards = orderBy(map(results, item => {
|
|
540
|
+
item.path = routePath(item.path)
|
|
541
|
+
return item
|
|
542
|
+
}), ['weight', 'path'])
|
|
543
|
+
return this.routeGuards
|
|
544
|
+
}
|
|
545
|
+
|
|
563
546
|
createNewSite = createNewSite
|
|
564
547
|
removeSite = removeSite
|
|
565
548
|
getSite = getSite
|
package/lib/util.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
export const checkNoRouteSettingKey = 'setting.waibu.noRoutes.$in'
|
|
2
|
-
|
|
3
1
|
export function parseNsSettings (ns, setting, items) {
|
|
4
|
-
const { trim, set, isPlainObject, isArray, isEmpty, find
|
|
2
|
+
const { trim, set, isPlainObject, isArray, isEmpty, find } = this.app.lib._
|
|
5
3
|
const { parseObject, dayjs } = this.app.lib
|
|
6
|
-
const { routePath } = this.app.waibu
|
|
7
4
|
|
|
8
5
|
for (const item of items) {
|
|
9
6
|
if (item.ns === '_var' || ns === '_var') continue
|
|
@@ -27,30 +24,6 @@ export function parseNsSettings (ns, setting, items) {
|
|
|
27
24
|
if ((isPlainObject(value) || isArray(value)) && isEmpty(value)) continue
|
|
28
25
|
set(setting, `${ns}.${item.key}`, value)
|
|
29
26
|
}
|
|
30
|
-
const key = checkNoRouteSettingKey.slice(checkNoRouteSettingKey.indexOf('.') + 1)
|
|
31
|
-
let noRoutes = get(setting, key, [])
|
|
32
|
-
if (noRoutes.length > 0) {
|
|
33
|
-
noRoutes = noRoutes.map(item => {
|
|
34
|
-
if (!isString(item)) return item
|
|
35
|
-
let [url, methods] = item.split('|')
|
|
36
|
-
if (methods === '*' || !methods) methods = 'GET,POST,PUT,DELETE'
|
|
37
|
-
methods = methods.split(',').map(m => m.trim().toUpperCase())
|
|
38
|
-
return { url: routePath(url), methods }
|
|
39
|
-
})
|
|
40
|
-
set(setting, key, noRoutes)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function checkNoRouteSetting (req, routes) {
|
|
45
|
-
const { isEmpty } = this.app.lib._
|
|
46
|
-
const { outmatch } = this.app.lib
|
|
47
|
-
if (isEmpty(routes)) return
|
|
48
|
-
for (const route of routes) {
|
|
49
|
-
const isMatchUrl = outmatch(route.url)
|
|
50
|
-
if (isMatchUrl(req.url) || isMatchUrl(req.routeOptions.url)) {
|
|
51
|
-
if (route.methods.includes(req.method)) throw this.error('accessDenied', { statusCode: 403 })
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
27
|
}
|
|
55
28
|
|
|
56
29
|
export function pathsToCheck (req, withHome) {
|
|
@@ -84,25 +57,20 @@ export async function checkTheme (req, reply) {
|
|
|
84
57
|
}
|
|
85
58
|
|
|
86
59
|
export async function checkTeam (req, reply, source) {
|
|
87
|
-
const { get, isEmpty } = this.app.lib._
|
|
88
60
|
if (!req.user) return
|
|
89
61
|
const { map } = this.app.lib._
|
|
90
62
|
const paths = pathsToCheck.call(this, req, true)
|
|
91
63
|
const teams = map(req.user.teams, 'alias')
|
|
92
|
-
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
64
|
+
const allGuards = (await this.getRouteGuards()).filter(item => item.siteId === req.site.id + '' && item.teams.length > 0)
|
|
65
|
+
if (allGuards.length === 0) return // no route guarded
|
|
66
|
+
let match = this.checkPathsByRoute({ paths, method: req.method, teams, guards: allGuards.filter(item => !item.inverse) })
|
|
67
|
+
if (!match) return // route is NOT protected by team guard
|
|
68
|
+
if (match) {
|
|
69
|
+
const neg = this.checkPathsByRoute({ paths, method: req.method, teams, guards: allGuards.filter(item => item.inverse) })
|
|
70
|
+
if (neg) match = undefined
|
|
97
71
|
}
|
|
98
72
|
if (!match) throw this.error('accessDenied', { statusCode: 403 })
|
|
99
|
-
|
|
100
|
-
for (const team of (req.user.teams ?? [])) {
|
|
101
|
-
const items = get(team, checkNoRouteSettingKey, [])
|
|
102
|
-
if (isEmpty(items)) continue
|
|
103
|
-
routes.push(...items)
|
|
104
|
-
}
|
|
105
|
-
checkNoRouteSetting.call(this, req, routes)
|
|
73
|
+
// passed
|
|
106
74
|
}
|
|
107
75
|
|
|
108
76
|
export async function checkUserId (req, reply, source) {
|
|
@@ -111,10 +79,9 @@ export async function checkUserId (req, reply, source) {
|
|
|
111
79
|
const userId = get(req, 'session.userId')
|
|
112
80
|
|
|
113
81
|
const setUser = async () => {
|
|
114
|
-
|
|
115
|
-
if (!id) return
|
|
82
|
+
if (!userId) return
|
|
116
83
|
try {
|
|
117
|
-
const user = await this.getUserById(
|
|
84
|
+
const user = await this.getUserById(userId, req)
|
|
118
85
|
if (user) req.user = user
|
|
119
86
|
else req.session.userId = null
|
|
120
87
|
} catch (err) {
|
|
@@ -133,46 +100,47 @@ export async function checkUserId (req, reply, source) {
|
|
|
133
100
|
}
|
|
134
101
|
|
|
135
102
|
const paths = pathsToCheck.call(this, req)
|
|
136
|
-
|
|
103
|
+
const allGuards = (await this.getRouteGuards()).filter(item => item.siteId === req.site.id + '')
|
|
104
|
+
if (allGuards.length === 0) return false // no routes protected
|
|
105
|
+
let securePath = await this.checkPathsByRoute({ paths, method: req.method, guards: allGuards.filter(item => !item.anonymous && !item.inverse) })
|
|
137
106
|
if (securePath) {
|
|
138
|
-
const neg = await this.checkPathsByRoute({ paths, method: req.method, guards:
|
|
107
|
+
const neg = await this.checkPathsByRoute({ paths, method: req.method, guards: allGuards.filter(item => !item.anonymous && item.inverse) })
|
|
139
108
|
if (neg) securePath = undefined
|
|
140
109
|
}
|
|
141
|
-
let anonymousPath = await this.checkPathsByRoute({ paths, method: req.method, guards:
|
|
110
|
+
let anonymousPath = await this.checkPathsByRoute({ paths, method: req.method, guards: allGuards.filter(item => item.anonymous && !item.inverse) })
|
|
142
111
|
if (anonymousPath) {
|
|
143
|
-
const neg = await this.checkPathsByRoute({ paths, method: req.method, guards:
|
|
112
|
+
const neg = await this.checkPathsByRoute({ paths, method: req.method, guards: allGuards.filter(item => item.anonymous && item.inverse) })
|
|
144
113
|
if (neg) anonymousPath = undefined
|
|
145
114
|
}
|
|
146
115
|
if (!securePath && !anonymousPath) {
|
|
147
116
|
if (userId) await setUser()
|
|
148
|
-
return
|
|
117
|
+
return false // regular, unguarded path. Not secure & not anonymous path
|
|
149
118
|
}
|
|
150
119
|
if (anonymousPath) {
|
|
151
|
-
if (!userId) return
|
|
120
|
+
if (!userId) return false
|
|
152
121
|
req.session.ref = req.url
|
|
153
122
|
return reply.redirectTo(routePath(this.config.redirect.signout))
|
|
154
123
|
}
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
break
|
|
172
|
-
}
|
|
124
|
+
if (userId) {
|
|
125
|
+
await setUser()
|
|
126
|
+
return true
|
|
127
|
+
}
|
|
128
|
+
const silentOnError = this.config.auth[webApp].silentOnError ?? this.config.auth.common.silentOnError
|
|
129
|
+
const payload = silentOnError ? { noContent: true } : undefined
|
|
130
|
+
const authMethods = this.config.auth[webApp].methods ?? []
|
|
131
|
+
if (isEmpty(authMethods)) throw this.error('noAuthMethod', merge({ statusCode: 500 }, payload))
|
|
132
|
+
let success
|
|
133
|
+
for (const m of authMethods) {
|
|
134
|
+
const handler = this[camelCase(`verify ${m}`)]
|
|
135
|
+
if (!handler) throw this.error('invalidAuthMethod%s', m, merge({ statusCode: 500 }, payload))
|
|
136
|
+
const check = await handler(req, reply, source, payload)
|
|
137
|
+
if (check) {
|
|
138
|
+
success = check
|
|
139
|
+
break
|
|
173
140
|
}
|
|
174
|
-
if (!success) throw this.error('accessDeniedNoAuth', merge({ statusCode: 403 }, payload))
|
|
175
141
|
}
|
|
142
|
+
if (!success) throw this.error('accessDeniedNoAuth', merge({ statusCode: 403 }, payload))
|
|
143
|
+
return true
|
|
176
144
|
}
|
|
177
145
|
|
|
178
146
|
export async function latLngHook (body, options) {
|
|
@@ -182,7 +150,14 @@ export async function latLngHook (body, options) {
|
|
|
182
150
|
body[options.field] = round(body[options.field], options.scale)
|
|
183
151
|
}
|
|
184
152
|
|
|
185
|
-
|
|
153
|
+
/**
|
|
154
|
+
* If current route is an inter site route user is an inter site admin, then let it passed
|
|
155
|
+
*
|
|
156
|
+
* @param {Object} req - Request object
|
|
157
|
+
* @param {Object} reply - Reply object
|
|
158
|
+
* @returns
|
|
159
|
+
*/
|
|
160
|
+
export async function checkInterSite (req, reply) {
|
|
186
161
|
const { get } = this.app.lib._
|
|
187
162
|
const isinterSite = get(req, 'routeOptions.config.interSite')
|
|
188
163
|
const isInterSiteAdmin = get(req, 'user.interSiteAdmin')
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-05-11
|
|
4
|
+
|
|
5
|
+
- [2.23.0] Unify route guards and put it in ```SumbaRouteGuard``` database
|
|
6
|
+
- [2.23.0] Phased out ```setting.noRoutes```
|
|
7
|
+
|
|
8
|
+
## 2026-04-25
|
|
9
|
+
|
|
10
|
+
- [2.21.1] Bug fix on team routes collections
|
|
11
|
+
|
|
3
12
|
## 2026-04-25
|
|
4
13
|
|
|
5
14
|
- [2.21.0] Change options to format value using the new key set by dobo
|
package/lib/collect.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
export async function collect ({ type = '', handler, container, file, ns, dir }) {
|
|
2
|
-
const { readConfig } = this.app.bajo
|
|
3
|
-
const { parseRouteGuard } = this
|
|
4
|
-
const { camelCase, find, isEmpty } = this.app.lib._
|
|
5
|
-
let items = await readConfig(file, { ns, baseNs: this.ns, defValue: [] })
|
|
6
|
-
if (isEmpty(items)) items = []
|
|
7
|
-
for (let item of items) {
|
|
8
|
-
item = parseRouteGuard(item)
|
|
9
|
-
if (!item) continue
|
|
10
|
-
if (handler) await handler.call(this, item)
|
|
11
|
-
const guards = this[camelCase(`${type} ${item.reverse ? 'Neg' : ''} ${container}`)]
|
|
12
|
-
delete item.reverse
|
|
13
|
-
if (find(guards, { path: item.path })) continue
|
|
14
|
-
guards.push(item)
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function collectHandler (item, keys = []) {
|
|
19
|
-
const { isString } = this.app.lib._
|
|
20
|
-
for (const k of keys) {
|
|
21
|
-
item[k] = item[k] ?? []
|
|
22
|
-
if (isString(item[k])) item[k] = item[k].split(',')
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function collectRoutes (type) {
|
|
27
|
-
const { eachPlugins } = this.app.bajo
|
|
28
|
-
const me = this
|
|
29
|
-
|
|
30
|
-
function handler (item) {
|
|
31
|
-
collectHandler.call(this, item, ['methods'])
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
await eachPlugins(async function ({ file, dir }) {
|
|
35
|
-
const { ns } = this
|
|
36
|
-
await collect.call(me, { type, container: 'Routes', handler, file, ns, dir })
|
|
37
|
-
}, { glob: `route-guard/${type}.*`, prefix: this.ns })
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export async function collectTeam () {
|
|
41
|
-
const { eachPlugins } = this.app.bajo
|
|
42
|
-
const me = this
|
|
43
|
-
|
|
44
|
-
function handler (item) {
|
|
45
|
-
collectHandler.call(this, item, ['methods', 'teams', 'features'])
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
await eachPlugins(async function ({ file, dir }) {
|
|
49
|
-
const { ns } = this
|
|
50
|
-
await collect.call(me, { type: 'team', container: 'Routes', handler, file, ns, dir })
|
|
51
|
-
}, { glob: 'route/team.*', prefix: this.ns })
|
|
52
|
-
}
|