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.
- package/extend/bajo/hook/waibu@pre-parsing.js +2 -1
- package/extend/masohiSocketIo/middleware/server/auth.js +1 -1
- package/extend/waibuMpa/route/signin.js +1 -1
- package/extend/waibuRestApi/route/user/access-token/@type/create.js +2 -3
- package/index.js +20 -36
- package/lib/get-site.js +24 -11
- package/lib/get-user.js +56 -17
- package/lib/util.js +1 -1
- package/package.json +1 -1
- package/wiki/CHANGES.md +4 -0
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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', '
|
|
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
|
|
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
|
|
265
|
-
|
|
266
|
-
if (
|
|
267
|
-
|
|
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
|
-
|
|
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
|
|
332
|
-
if (!
|
|
333
|
-
if (
|
|
334
|
-
req.user =
|
|
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
|
-
|
|
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 (
|
|
4
|
-
const {
|
|
5
|
-
const {
|
|
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
|
-
|
|
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
|
|
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 (
|
|
45
|
-
else query = { hostname:
|
|
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
|
|
4
|
-
const {
|
|
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
|
-
|
|
46
|
-
|
|
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
|
|
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.
|
|
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