sumba 2.12.0 → 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.
@@ -4,7 +4,8 @@ const preParsing = {
4
4
  level: 10,
5
5
  handler: async function (req, reply) {
6
6
  const { get } = this.app.lib._
7
- req.site = await this.getSite(req)
7
+ const { getHostname } = this.app.waibu
8
+ req.site = await this.getSite(getHostname(req))
8
9
  const routes = get(req.site, checkNoRouteSettingKey)
9
10
  checkNoRouteSetting.call(this, req, routes)
10
11
  }
@@ -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
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
@@ -113,7 +113,7 @@ export async function checkUserId (req, reply, source) {
113
113
  const id = get(req, 'session.userId')
114
114
  if (!id) return
115
115
  try {
116
- const user = await this.getUser(id)
116
+ const user = await this.getUserById(id, req)
117
117
  if (user) req.user = user
118
118
  else req.session.userId = null
119
119
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sumba",
3
- "version": "2.12.0",
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,9 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-03-25
4
+
5
+ - [2.13.0] Add bajo cache handling for ```getUser*()``` and ```getSite()```
6
+
3
7
  ## 2026-03-22
4
8
 
5
9
  - [2.12.0] Add no route settings sitewise