sumba 2.11.1 → 2.13.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.
@@ -1,7 +1,4 @@
1
- import checkUserId from '../../../lib/check-user-id.js'
2
- import checkTheme from '../../../lib/check-theme.js'
3
- import checkIconset from '../../../lib/check-iconset.js'
4
- import checkTeam from '../../../lib/check-team.js'
1
+ import { checkUserId, checkTeam, checkTheme, checkIconset } from '../../../lib/util.js'
5
2
 
6
3
  const preParsing = {
7
4
  level: 10,
@@ -1,5 +1,4 @@
1
- import checkUserId from '../../../lib/check-user-id.js'
2
- import checkTeam from '../../../lib/check-team.js'
1
+ import { checkUserId, checkTeam } from '../../../lib/util.js'
3
2
 
4
3
  const preParsing = {
5
4
  level: 10,
@@ -1,5 +1,4 @@
1
- import checkUserId from '../../../lib/check-user-id.js'
2
- import checkTeam from '../../../lib/check-team.js'
1
+ import { checkUserId, checkTeam } from '../../../lib/util.js'
3
2
 
4
3
  const preParsing = {
5
4
  level: 10,
@@ -1,5 +1,4 @@
1
- import collectRoutes from '../../../lib/collect-routes.js'
2
- import collectTeam from '../../../lib/collect-team.js'
1
+ import { collectRoutes, collectTeam } from '../../../lib/collect.js'
3
2
 
4
3
  async function afterAppBoot () {
5
4
  const { runHook } = this.app.bajo
@@ -1,7 +1,13 @@
1
+ import { checkNoRouteSetting, checkNoRouteSettingKey } from '../../../lib/util.js'
2
+
1
3
  const preParsing = {
2
4
  level: 10,
3
5
  handler: async function (req, reply) {
4
- req.site = await this.getSite(req)
6
+ const { get } = this.app.lib._
7
+ const { getHostname } = this.app.waibu
8
+ req.site = await this.getSite(getHostname(req))
9
+ const routes = get(req.site, checkNoRouteSettingKey)
10
+ checkNoRouteSetting.call(this, req, routes)
5
11
  }
6
12
  }
7
13
 
@@ -8,7 +8,7 @@ const auth = {
8
8
  socket.join(camelCase(`site ${site.alias}`))
9
9
  let user
10
10
  if (session.userId) {
11
- user = await this.getUser(session.userId)
11
+ user = await this.getUserById(session.userId)
12
12
  if (user) {
13
13
  socket.join(camelCase(`user ${user.username}`))
14
14
  for (const team of user.teams) {
@@ -7,7 +7,7 @@ const signin = {
7
7
  let error
8
8
  if (req.method === 'POST') {
9
9
  try {
10
- const user = await this.getUserFromUsernamePassword(username, password, req)
10
+ const user = await this.getUserByUsernamePassword(username, password, req)
11
11
  return await this.signin({ user, req, reply })
12
12
  } catch (err) {
13
13
  error = err
@@ -14,12 +14,11 @@ async function create () {
14
14
 
15
15
  const handler = async function (req, reply) {
16
16
  const { hash } = this.app.bajoExtra
17
- const { getUserFromUsernamePassword, createJwtFromUserRecord } = this
18
17
 
19
18
  if (!['api-key', 'jwt', 'apiKey'].includes(req.params.type)) throw this.error('invalidTokenType')
20
- const rec = await getUserFromUsernamePassword(req.body.username, req.body.password, req)
19
+ const rec = await this.getUserByUsernamePassword(req.body.username, req.body.password, req)
21
20
  if (req.params.type === 'jwt') {
22
- const jwt = await createJwtFromUserRecord(rec)
21
+ const jwt = await this.createJwtFromUserRecord(rec)
23
22
  return { data: jwt }
24
23
  }
25
24
  return { data: { token: await hash(rec.salt) } }
package/index.js CHANGED
@@ -2,7 +2,7 @@ import path from 'path'
2
2
  import createNewSite from './lib/create-new-site.js'
3
3
  import removeSite from './lib/remove-site.js'
4
4
  import getSite from './lib/get-site.js'
5
- import getUser from './lib/get-user.js'
5
+ import { getUserById, getUserByToken, getUserByUsernamePassword } from './lib/get-user.js'
6
6
 
7
7
  /**
8
8
  * Plugin factory
@@ -132,10 +132,15 @@ async function factory (pkgName) {
132
132
  noWhitespace: false,
133
133
  latinOnlyChars: false
134
134
  }
135
+ },
136
+ cacheTtl: {
137
+ getSiteDur: '1m',
138
+ getUserByIdDur: '1m',
139
+ getUserByTokenDur: '1m'
135
140
  }
136
141
  }
137
142
  this.unsafeUserFields = ['password']
138
- this.selfBind(['createNewSite', 'removeSite', 'getSite', 'getUser'])
143
+ this.selfBind(['createNewSite', 'removeSite', 'getSite', 'getUserById', 'getUserByToken', 'getUserByUsernamePassword'])
139
144
  }
140
145
 
141
146
  init = async () => {
@@ -204,22 +209,6 @@ async function factory (pkgName) {
204
209
  }]
205
210
  }
206
211
 
207
- getUserFromUsernamePassword = async (username = '', password = '', req) => {
208
- const { importPkg } = this.app.bajo
209
- const model = getModel('SumbaUser')
210
- await model.validate({ username, password }, null, { partial: true, ns: ['sumba', 'dobo'], fields: ['username', 'password'] })
211
- const bcrypt = await importPkg('bajoExtra:bcrypt')
212
-
213
- const query = { username, provider: 'local', siteId: req.site.id }
214
- const rows = await model.findRecord({ query }, { req, forceNoHidden: true, noHook: true })
215
- if (rows.length === 0) throw this.error('validationError', { details: [{ field: 'username', error: 'Unknown username' }], statusCode: 401 })
216
- const rec = rows[0]
217
- if (rec.status !== 'ACTIVE') throw this.error('validationError', { details: ['User is inactive or temporarily disabled'], statusCode: 401 })
218
- const verified = await bcrypt.compare(password, rec.password)
219
- if (!verified) throw this.error('validationError', { details: [{ field: 'password', error: 'invalidPassword' }], statusCode: 401 })
220
- return rec
221
- }
222
-
223
212
  createJwtFromUserRecord = async (rec) => {
224
213
  const { importPkg } = this.app.bajo
225
214
  const { dayjs } = this.app.lib
@@ -240,12 +229,11 @@ async function factory (pkgName) {
240
229
  }
241
230
 
242
231
  verifySession = async (req, reply, source, payload) => {
243
- const { getUser } = this
244
232
  const { routePath } = this.app.waibu
245
233
 
246
234
  if (!req.session) return false
247
235
  if (req.session.userId) {
248
- req.user = await getUser(req.session.userId)
236
+ req.user = await this.getUserById(req.session.userId, req)
249
237
  return true
250
238
  }
251
239
  const redir = routePath(this.config.redirect.signin, req)
@@ -256,22 +244,18 @@ async function factory (pkgName) {
256
244
  verifyApiKey = async (req, reply, source, payload) => {
257
245
  const { merge } = this.app.lib._
258
246
  const { isMd5, hash } = this.app.bajoExtra
259
- const { getUser } = this
260
247
 
261
248
  let token = await this._getToken('apiKey', req, source)
262
249
  if (!isMd5(token)) return false
263
250
  token = await hash(token)
264
- const query = { token }
265
- const rows = await getModel('SumbaUser').findRecord({ query }, { req, noHook: true })
266
- if (rows.length === 0) throw this.error('invalidKey', merge({ statusCode: 401 }, payload))
267
- if (rows[0].status !== 'ACTIVE') throw this.error('userInactive', merge({ details: [{ field: 'status', error: 'inactive' }], statusCode: 401 }, payload))
268
- req.user = await getUser(rows[0])
251
+ const user = await this.getUserByToken(token, req)
252
+ if (!user) throw this.error('invalidKey', merge({ statusCode: 401 }, payload))
253
+ if (user.status !== 'ACTIVE') throw this.error('userInactive', merge({ details: [{ field: 'status', error: 'inactive' }], statusCode: 401 }, payload))
254
+ req.user = user
269
255
  return true
270
256
  }
271
257
 
272
258
  verifyBasic = async (req, reply, source, payload) => {
273
- const { getUserFromUsernamePassword } = this
274
- const { getUser } = this
275
259
  const { isEmpty, merge } = this.app.lib._
276
260
 
277
261
  const setHeader = async (setting, reply) => {
@@ -299,8 +283,7 @@ async function factory (pkgName) {
299
283
  const decoded = Buffer.from(authInfo, 'base64').toString()
300
284
  const [username, password] = decoded.split(':')
301
285
  try {
302
- const user = await getUserFromUsernamePassword(username, password, req)
303
- req.user = await getUser(user)
286
+ req.user = await this.getUserByUsernamePassword(username, password, req)
304
287
  } catch (err) {
305
288
  if (err.statusCode === 401 && setting.realm) {
306
289
  await setHeader(setting, reply)
@@ -313,7 +296,6 @@ async function factory (pkgName) {
313
296
 
314
297
  verifyJwt = async (req, reply, source, payload) => {
315
298
  const { importPkg } = this.app.bajo
316
- const { getUser } = this
317
299
  const { isEmpty, merge } = this.app.lib._
318
300
 
319
301
  const fastJwt = await importPkg('bajoExtra:fast-jwt')
@@ -328,10 +310,10 @@ async function factory (pkgName) {
328
310
  const decoded = await verifier(token)
329
311
  const id = decoded.payload.uid
330
312
  try {
331
- const rec = await getModel('SumbaUser').getRecord(id, { req, noHook: true })
332
- if (!rec) throw this.error('invalidToken', { statusCode: 401 })
333
- if (rec.status !== 'ACTIVE') throw this.error('userInactive', { details: [{ field: 'status', error: 'inactive' }], statusCode: 401 })
334
- req.user = await getUser(rec)
313
+ const user = await this.getUserById(id, req)
314
+ if (!user) throw this.error('invalidToken', { statusCode: 401 })
315
+ if (user.status !== 'ACTIVE') throw this.error('userInactive', { details: [{ field: 'status', error: 'inactive' }], statusCode: 401 })
316
+ req.user = user
335
317
  } catch (err) {
336
318
  merge(err, payload)
337
319
  throw err
@@ -494,7 +476,9 @@ async function factory (pkgName) {
494
476
  createNewSite = createNewSite
495
477
  removeSite = removeSite
496
478
  getSite = getSite
497
- getUser = getUser
479
+ getUserById = getUserById
480
+ getUserByToken = getUserByToken
481
+ getUserByUsernamePassword = getUserByUsernamePassword
498
482
  }
499
483
 
500
484
  return Sumba
@@ -23,21 +23,38 @@ export async function collect ({ type = '', handler, container, file, ns, dir })
23
23
  }
24
24
  }
25
25
 
26
- function handler (item) {
26
+ function collectHandler (item, keys = []) {
27
27
  const { isString } = this.app.lib._
28
- for (const k of ['methods']) {
28
+ for (const k of keys) {
29
29
  item[k] = item[k] ?? []
30
30
  if (isString(item[k])) item[k] = item[k].split(',')
31
31
  }
32
32
  }
33
33
 
34
- async function collectRoutes (type) {
34
+ export async function collectRoutes (type) {
35
35
  const { eachPlugins } = this.app.bajo
36
36
  const me = this
37
+
38
+ function handler (item) {
39
+ collectHandler.call(this, item, ['methods'])
40
+ }
41
+
37
42
  await eachPlugins(async function ({ file, dir }) {
38
43
  const { ns } = this
39
44
  await collect.call(me, { type, container: 'Routes', handler, file, ns, dir })
40
45
  }, { glob: `route/${type}.*`, prefix: this.ns })
41
46
  }
42
47
 
43
- export default collectRoutes
48
+ export async function collectTeam () {
49
+ const { eachPlugins } = this.app.bajo
50
+ const me = this
51
+
52
+ function handler (item) {
53
+ collectHandler.call(this, item, ['methods', 'teams', 'features'])
54
+ }
55
+
56
+ await eachPlugins(async function ({ file, dir }) {
57
+ const { ns } = this
58
+ await collect.call(me, { type: 'team', container: 'Routes', handler, file, ns, dir })
59
+ }, { glob: 'route/team.*', prefix: this.ns })
60
+ }
package/lib/get-site.js CHANGED
@@ -1,12 +1,11 @@
1
1
  import { parseNsSettings } from './util.js'
2
2
 
3
- async function getSite (req) {
4
- const { runHook } = this.app.bajo
5
- const { omit, isPlainObject } = this.app.lib._
6
- const { getHostname } = this.app.waibu
3
+ async function getSite (input, byId = false) {
4
+ const { omit } = this.app.lib._
5
+ const { set: setCache, get: getCache } = this.app.bajoCache ?? {}
7
6
  const omitted = ['status']
8
7
 
9
- await runHook(`${this.ns}:beforeGetSite`, req)
8
+ let site = {}
10
9
  const mergeSetting = async (site) => {
11
10
  const { defaultsDeep, isSet } = this.app.lib.aneka
12
11
  const { get, filter } = this.app.lib._
@@ -28,21 +27,32 @@ async function getSite (req) {
28
27
  // additional fields
29
28
  const country = await this.app.dobo.getModel('CdbCountry').getRecord(site.country, { noHook: true })
30
29
  site.countryName = (country ?? {}).name ?? site.country
31
- await runHook(`${this.ns}:afterGetSite`, req, site)
32
30
  }
33
31
 
34
- let site = {}
35
-
36
32
  const multiSite = this.config.multiSite === true ? { enabled: true, catchAll: 'default' } : this.config.multiSite
37
33
  if (!multiSite.enabled) {
38
- const resp = await this.app.dobo.getModel('SumbaSite').findOneRecord({ query: { alias: 'default' } }, { noHook: true })
34
+ const filter = { query: { alias: 'default' } }
35
+ const key = 'getSite:default'
36
+ if (getCache) {
37
+ site = await getCache({ key })
38
+ if (site) return site
39
+ }
40
+ const resp = await this.app.dobo.getModel('SumbaSite').findOneRecord(filter, { noHook: true })
39
41
  site = omit(resp, omitted)
40
42
  await mergeSetting(site)
43
+ if (setCache) {
44
+ await setCache({ key, value: site, ttl: this.config.cacheTtl.getSiteDur })
45
+ }
41
46
  return site
42
47
  }
43
48
  let query
44
- if (!isPlainObject(req)) query = { id: req }
45
- else query = { hostname: getHostname(req) }
49
+ if (byId) query = { id: input }
50
+ else query = { hostname: input }
51
+ const key = `getSite:multiSite:${input}`
52
+ if (getCache) {
53
+ site = await getCache({ key })
54
+ if (site) return JSON.parse(site)
55
+ }
46
56
  let row = await this.app.dobo.getModel('SumbaSite').findOneRecord({ query }, { noHook: true })
47
57
  if (!row) {
48
58
  if (multiSite.catchAll) {
@@ -54,6 +64,9 @@ async function getSite (req) {
54
64
  if (row.status !== 'ACTIVE') throw this.error('siteInactiveInfo')
55
65
  site = omit(row, omitted)
56
66
  await mergeSetting(site)
67
+ if (setCache) {
68
+ await setCache({ key, value: JSON.stringify(site), ttl: this.config.cacheTtl.getSiteDur })
69
+ }
57
70
  return site
58
71
  }
59
72
 
package/lib/get-user.js CHANGED
@@ -1,19 +1,8 @@
1
1
  import { parseNsSettings } from './util.js'
2
2
 
3
- async function getUser (rec, safe = true) {
4
- const { runHook } = this.app.bajo
5
- const { map, pick, omit, isPlainObject } = this.app.lib._
3
+ export async function mergeTeam (user) {
4
+ const { map, pick } = this.app.lib._
6
5
  const { getModel } = this.app.dobo
7
- await runHook(`${this.ns}:beforeGetUser`, rec, safe)
8
- let user
9
- if (!isPlainObject(rec)) {
10
- const mdl = getModel('SumbaUser')
11
- user = await mdl.getRecord(rec, { noHook: true, throwNotFound: false })
12
- } else {
13
- user = rec
14
- }
15
- if (!user) return null
16
- // merge teams
17
6
  user.teams = []
18
7
  const query = { userId: user.id, siteId: user.siteId }
19
8
  let mdl = getModel('SumbaTeamUser')
@@ -25,7 +14,6 @@ async function getUser (rec, safe = true) {
25
14
  mdl = getModel('SumbaTeam')
26
15
  const teams = await mdl.findAllRecord({ query })
27
16
  if (teams.length > 0) {
28
- // setting
29
17
  delete query.id
30
18
  delete query.status
31
19
  query.siteId = user.siteId
@@ -42,9 +30,60 @@ async function getUser (rec, safe = true) {
42
30
  user.teams.push(item)
43
31
  }
44
32
  }
45
- user = safe ? omit(user, this.unsafeUserFields) : user
46
- await runHook(`${this.ns}:afterGetUser`, rec, safe, user)
33
+ for (const field of this.unsafeUserFields) {
34
+ delete user[field]
35
+ }
36
+ }
37
+
38
+ export async function getUserById (id, req) {
39
+ const { getModel } = this.app.dobo
40
+ const { set: setCache, get: getCache } = this.app.bajoCache ?? {}
41
+ const key = `getUserById:${id}`
42
+ let user
43
+ if (getCache) {
44
+ user = await getCache({ key })
45
+ if (user) return JSON.parse(user)
46
+ }
47
+ user = await getModel('SumbaUser').getRecord(id, { noHook: true, throwNotFound: false, req })
48
+ if (!user) return
49
+ await mergeTeam.call(this, user)
50
+ if (setCache) {
51
+ await setCache({ key, value: JSON.stringify(user), ttl: this.config.cacheTtl.getUserByIdDur })
52
+ }
53
+ return user
54
+ }
55
+
56
+ export async function getUserByToken (token, req) {
57
+ const { getModel } = this.app.dobo
58
+ const { set: setCache, get: getCache } = this.app.bajoCache ?? {}
59
+ const key = `getUserByToken:${token}`
60
+ let user
61
+ if (getCache) {
62
+ user = await getCache({ key })
63
+ if (user) return JSON.parse(user)
64
+ }
65
+ user = await getModel('SumbaUser').findOneRecord({ query: { token } }, { noHook: true, throwNotFound: false, req })
66
+ if (!user) return
67
+ await mergeTeam.call(this, user)
68
+ if (setCache) {
69
+ await setCache({ key, value: JSON.stringify(user), ttl: this.config.cacheTtl.getUserByTokenDur })
70
+ }
47
71
  return user
48
72
  }
49
73
 
50
- export default getUser
74
+ export async function getUserByUsernamePassword (username = '', password = '', req) {
75
+ const { getModel } = this.app.dobo
76
+ const { importPkg } = this.app.bajo
77
+ const model = getModel('SumbaUser')
78
+ await model.validate({ username, password }, null, { partial: true, ns: ['sumba', 'dobo'], fields: ['username', 'password'] })
79
+ const bcrypt = await importPkg('bajoExtra:bcrypt')
80
+
81
+ const query = { username, provider: 'local', siteId: req.site.id }
82
+ const user = await model.findOneRecord({ query }, { req, forceNoHidden: true, noHook: true })
83
+ if (!user) throw this.error('validationError', { details: [{ field: 'username', error: 'Unknown username' }], statusCode: 401 })
84
+ if (user.status !== 'ACTIVE') throw this.error('validationError', { details: ['User is inactive or temporarily disabled'], statusCode: 401 })
85
+ const verified = await bcrypt.compare(password, user.password)
86
+ if (!verified) throw this.error('validationError', { details: [{ field: 'password', error: 'invalidPassword' }], statusCode: 401 })
87
+ await mergeTeam.call(this, user)
88
+ return user
89
+ }
package/lib/util.js CHANGED
@@ -1,6 +1,9 @@
1
+ export const checkNoRouteSettingKey = 'setting.waibu.noRoutes.$in'
2
+
1
3
  export function parseNsSettings (ns, setting, items) {
2
- const { trim, set, isPlainObject, isArray, isEmpty, find } = this.app.lib._
4
+ const { trim, set, isPlainObject, isArray, isEmpty, find, get, isString } = this.app.lib._
3
5
  const { parseObject, dayjs } = this.app.lib
6
+ const { routePath } = this.app.waibu
4
7
 
5
8
  for (const item of items) {
6
9
  if (item.ns === '_var' || ns === '_var') continue
@@ -16,7 +19,7 @@ export function parseNsSettings (ns, setting, items) {
16
19
  } catch (err) {}
17
20
  } else if (Number(value)) value = Number(value)
18
21
  else if (['true', 'false'].includes(value)) value = value === 'true'
19
- else if (item.key.endsWith('$in')) value = value.split(',').map(v => v.trim())
22
+ else if (item.key.endsWith('$in')) value = value.split('\n').map(v => v.trim())
20
23
  else {
21
24
  const dt = dayjs(value)
22
25
  if (dt.isValid()) value = dt.toDate()
@@ -24,4 +27,149 @@ export function parseNsSettings (ns, setting, items) {
24
27
  if ((isPlainObject(value) || isArray(value)) && isEmpty(value)) continue
25
28
  set(setting, `${ns}.${item.key}`, value)
26
29
  }
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
+ }
55
+
56
+ export function pathsToCheck (req, withHome) {
57
+ const { uniq, without } = this.app.lib._
58
+ return uniq(without([req.routeOptions.url, req.url], undefined, null))
59
+ }
60
+
61
+ export async function checkIconset (req, reply) {
62
+ const { get, isString } = this.app.lib._
63
+ const mpa = this.app.waibuMpa
64
+
65
+ if (!req.site) return
66
+ const siteIconset = get(req, 'site.setting.waibuMpa.iconset')
67
+ req.iconset = siteIconset ?? get(mpa, 'config.iconset.set', 'default')
68
+ const hiconset = req.headers['x-iconset']
69
+ if (isString(hiconset) && mpa.getIconset(hiconset)) req.iconset = hiconset
70
+ req.iconset = req.iconset ?? 'default'
71
+ }
72
+
73
+ export async function checkTheme (req, reply) {
74
+ const { get, isString } = this.app.lib._
75
+ const mpa = this.app.waibuMpa
76
+
77
+ if (!req.site) return
78
+ const siteTheme = get(req, 'site.setting.waibuMpa.theme')
79
+ req.theme = siteTheme ?? get(mpa, 'config.theme.set', 'default')
80
+ const htheme = req.headers['x-theme']
81
+ if (isString(htheme) && mpa.getTheme(htheme)) req.theme = htheme
82
+ req.theme = req.theme ?? 'default'
83
+ }
84
+
85
+ export async function checkTeam (req, reply, source) {
86
+ const { get, isEmpty } = this.app.lib._
87
+ if (!req.user) return
88
+ const { map } = this.app.lib._
89
+ const paths = pathsToCheck.call(this, req, true)
90
+ const teams = map(req.user.teams, 'alias')
91
+ let match = this.checkPathsByTeam({ paths, method: req.method, teams, guards: this.teamRoutes })
92
+ if (!match) match = this.checkPathsByTeam({ paths, method: req.method, teams, guards: this.teamNegRoutes })
93
+ if (!match) {
94
+ const guards = map(this.teamRoutes, 'path')
95
+ match = !this.checkPathsByGuard({ paths, guards })
96
+ }
97
+ if (!match) throw this.error('accessDenied', { statusCode: 403 })
98
+ const routes = []
99
+ for (const team of (req.user.teams ?? [])) {
100
+ const items = get(team, checkNoRouteSettingKey, [])
101
+ if (isEmpty(items)) continue
102
+ routes.push(...items)
103
+ }
104
+ checkNoRouteSetting.call(this, req, routes)
105
+ }
106
+
107
+ export async function checkUserId (req, reply, source) {
108
+ const { merge, isEmpty, camelCase, get } = this.app.lib._
109
+ const { routePath } = this.app.waibu
110
+ const userId = get(req, 'session.userId')
111
+
112
+ const setUser = async () => {
113
+ const id = get(req, 'session.userId')
114
+ if (!id) return
115
+ try {
116
+ const user = await this.getUserById(id, req)
117
+ if (user) req.user = user
118
+ else req.session.userId = null
119
+ } catch (err) {
120
+ console.log(err)
121
+ req.session.userId = null
122
+ }
123
+ }
124
+
125
+ if (req.session) req.session.siteId = req.site.id
126
+
127
+ const webApp = get(req, 'routeOptions.config.webApp', 'waibu')
128
+ if (!req.routeOptions.url) {
129
+ if (!req.session) return
130
+ await setUser()
131
+ return
132
+ }
133
+
134
+ const paths = pathsToCheck.call(this, req)
135
+ let securePath = await this.checkPathsByRoute({ paths, method: req.method, guards: this.secureRoutes })
136
+ if (securePath) {
137
+ const neg = await this.checkPathsByRoute({ paths, method: req.method, guards: this.secureNegRoutes })
138
+ if (neg) securePath = undefined
139
+ }
140
+ let anonymousPath = await this.checkPathsByRoute({ paths, method: req.method, guards: this.anonymousRoutes })
141
+ if (anonymousPath) {
142
+ const neg = await this.checkPathsByRoute({ paths, method: req.method, guards: this.anonymousNegRoutes })
143
+ if (neg) anonymousPath = undefined
144
+ }
145
+ if (!securePath && !anonymousPath) {
146
+ if (userId) await setUser()
147
+ return
148
+ }
149
+ if (anonymousPath) {
150
+ if (!userId) return
151
+ req.session.ref = req.url
152
+ return reply.redirectTo(routePath(this.config.redirect.signout))
153
+ }
154
+ if (securePath) {
155
+ if (userId) {
156
+ await setUser()
157
+ return
158
+ }
159
+ const silentOnError = this.config.auth[webApp].silentOnError ?? this.config.auth.common.silentOnError
160
+ const payload = silentOnError ? { noContent: true } : undefined
161
+ const authMethods = this.config.auth[webApp].methods ?? []
162
+ if (isEmpty(authMethods)) throw this.error('noAuthMethod', merge({ statusCode: 500 }, payload))
163
+ let success
164
+ for (const m of authMethods) {
165
+ const handler = this[camelCase(`verify ${m}`)]
166
+ if (!handler) throw this.error('invalidAuthMethod%s', m, merge({ statusCode: 500 }, payload))
167
+ const check = await handler(req, reply, source, payload)
168
+ if (check) {
169
+ success = check
170
+ break
171
+ }
172
+ }
173
+ if (!success) throw this.error('accessDeniedNoAuth', merge({ statusCode: 403 }, payload))
174
+ }
27
175
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sumba",
3
- "version": "2.11.1",
3
+ "version": "2.13.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,15 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-03-25
4
+
5
+ - [2.13.0] Add bajo cache handling for ```getUser*()``` and ```getSite()```
6
+
7
+ ## 2026-03-22
8
+
9
+ - [2.12.0] Add no route settings sitewise
10
+ - [2.12.0] Add no route settings for teams
11
+ - [2.12.0] Some organizatorial changes in module files
12
+
3
13
  ## 2026-03-22
4
14
 
5
15
  - [2.11.0] Add ```Team Setting``` feature
@@ -1,13 +0,0 @@
1
- async function checkIconset (req, reply) {
2
- const { get, isString } = this.app.lib._
3
- const mpa = this.app.waibuMpa
4
-
5
- if (!req.site) return
6
- const siteIconset = get(req, 'site.setting.waibuMpa.iconset')
7
- req.iconset = siteIconset ?? get(mpa, 'config.iconset.set', 'default')
8
- const hiconset = req.headers['x-iconset']
9
- if (isString(hiconset) && mpa.getIconset(hiconset)) req.iconset = hiconset
10
- req.iconset = req.iconset ?? 'default'
11
- }
12
-
13
- export default checkIconset
package/lib/check-team.js DELETED
@@ -1,19 +0,0 @@
1
- import { pathsToCheck } from './check-user-id.js'
2
-
3
- async function checkTeam (req, reply, source) {
4
- if (!req.user) return
5
- const { map } = this.app.lib._
6
- const paths = pathsToCheck.call(this, req, true)
7
- const teams = map(req.user.teams, 'alias')
8
- const match = this.checkPathsByTeam({ paths, method: req.method, teams, guards: this.teamRoutes })
9
- if (match) return
10
- const negMatch = this.checkPathsByTeam({ paths, method: req.method, teams, guards: this.teamNegRoutes })
11
- if (negMatch) return
12
- const guards = map(this.teamRoutes, 'path')
13
- // fallback
14
- const guarded = this.checkPathsByGuard({ paths, guards })
15
- if (!guarded) return
16
- throw this.error('accessDenied', { statusCode: 403 })
17
- }
18
-
19
- export default checkTeam
@@ -1,13 +0,0 @@
1
- async function checkTheme (req, reply) {
2
- const { get, isString } = this.app.lib._
3
- const mpa = this.app.waibuMpa
4
-
5
- if (!req.site) return
6
- const siteTheme = get(req, 'site.setting.waibuMpa.theme')
7
- req.theme = siteTheme ?? get(mpa, 'config.theme.set', 'default')
8
- const htheme = req.headers['x-theme']
9
- if (isString(htheme) && mpa.getTheme(htheme)) req.theme = htheme
10
- req.theme = req.theme ?? 'default'
11
- }
12
-
13
- export default checkTheme
@@ -1,76 +0,0 @@
1
- export function pathsToCheck (req, withHome) {
2
- const { uniq, without } = this.app.lib._
3
- return uniq(without([req.routeOptions.url, req.url], undefined, null))
4
- }
5
-
6
- async function setUser (req) {
7
- const { get } = this.app.lib._
8
- const id = get(req, 'session.userId')
9
- if (!id) return
10
- try {
11
- const user = await this.getUser(id)
12
- if (user) req.user = user
13
- else req.session.userId = null
14
- } catch (err) {
15
- console.log(err)
16
- req.session.userId = null
17
- }
18
- }
19
-
20
- async function checkUserId (req, reply, source) {
21
- const { merge, isEmpty, camelCase, get } = this.app.lib._
22
- const { routePath } = this.app.waibu
23
- const userId = get(req, 'session.userId')
24
- if (req.session) req.session.siteId = req.site.id
25
-
26
- const webApp = get(req, 'routeOptions.config.webApp', 'waibu')
27
- if (!req.routeOptions.url) {
28
- if (!req.session) return
29
- await setUser.call(this, req)
30
- return
31
- }
32
-
33
- const paths = pathsToCheck.call(this, req)
34
- let securePath = await this.checkPathsByRoute({ paths, method: req.method, guards: this.secureRoutes })
35
- if (securePath) {
36
- const neg = await this.checkPathsByRoute({ paths, method: req.method, guards: this.secureNegRoutes })
37
- if (neg) securePath = undefined
38
- }
39
- let anonymousPath = await this.checkPathsByRoute({ paths, method: req.method, guards: this.anonymousRoutes })
40
- if (anonymousPath) {
41
- const neg = await this.checkPathsByRoute({ paths, method: req.method, guards: this.anonymousNegRoutes })
42
- if (neg) anonymousPath = undefined
43
- }
44
- if (!securePath && !anonymousPath) {
45
- if (userId) await setUser.call(this, req)
46
- return
47
- }
48
- if (anonymousPath) {
49
- if (!userId) return
50
- req.session.ref = req.url
51
- return reply.redirectTo(routePath(this.config.redirect.signout))
52
- }
53
- if (securePath) {
54
- if (userId) {
55
- await setUser.call(this, req)
56
- return
57
- }
58
- const silentOnError = this.config.auth[webApp].silentOnError ?? this.config.auth.common.silentOnError
59
- const payload = silentOnError ? { noContent: true } : undefined
60
- const authMethods = this.config.auth[webApp].methods ?? []
61
- if (isEmpty(authMethods)) throw this.error('noAuthMethod', merge({ statusCode: 500 }, payload))
62
- let success
63
- for (const m of authMethods) {
64
- const handler = this[camelCase(`verify ${m}`)]
65
- if (!handler) throw this.error('invalidAuthMethod%s', m, merge({ statusCode: 500 }, payload))
66
- const check = await handler(req, reply, source, payload)
67
- if (check) {
68
- success = check
69
- break
70
- }
71
- }
72
- if (!success) throw this.error('accessDeniedNoAuth', merge({ statusCode: 403 }, payload))
73
- }
74
- }
75
-
76
- export default checkUserId
@@ -1,5 +0,0 @@
1
- async function collectRedirects () {
2
- this.redirects = []
3
- }
4
-
5
- export default collectRedirects
@@ -1,20 +0,0 @@
1
- import { collect } from './collect-routes.js'
2
-
3
- function handler (item) {
4
- const { isString } = this.app.lib._
5
- for (const k of ['methods', 'teams', 'features']) {
6
- item[k] = item[k] ?? []
7
- if (isString(item[k])) item[k] = item[k].split(',')
8
- }
9
- }
10
-
11
- async function collectTeam () {
12
- const { eachPlugins } = this.app.bajo
13
- const me = this
14
- await eachPlugins(async function ({ file, dir }) {
15
- const { ns } = this
16
- await collect.call(me, { type: 'team', container: 'Routes', handler, file, ns, dir })
17
- }, { glob: 'route/team.*', prefix: this.ns })
18
- }
19
-
20
- export default collectTeam