sumba 2.8.1 → 2.10.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@before-find-record.js +14 -3
- package/extend/dobo/model/site-setting.json +6 -2
- package/extend/masohiSocketIo/middleware/server/auth.js +1 -1
- package/extend/waibuMpa/route/user/forgot-password.js +2 -1
- package/index.js +10 -71
- package/lib/get-site.js +70 -0
- package/lib/password-rule.js +3 -2
- package/package.json +1 -1
- package/wiki/CHANGES.md +9 -0
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
const useAdmin = ['waibuAdmin']
|
|
2
2
|
|
|
3
3
|
export async function rebuildFilter (modelName, filter, req) {
|
|
4
|
-
const { isEmpty, map, find, get } = this.app.lib._
|
|
4
|
+
const { isEmpty, isPlainObject, map, find, get } = this.app.lib._
|
|
5
5
|
filter.query = filter.query ?? {}
|
|
6
|
+
|
|
7
|
+
const queryByModel = (query) => {
|
|
8
|
+
// by model
|
|
9
|
+
const setting = get(req, `site.setting.dobo.query.${modelName}`)
|
|
10
|
+
if (isPlainObject(setting) && !isEmpty(setting)) query.$and.push(setting)
|
|
11
|
+
return query
|
|
12
|
+
}
|
|
13
|
+
|
|
6
14
|
const model = this.app.dobo.getModel(modelName)
|
|
7
15
|
const hasSiteId = model.hasProperty('siteId')
|
|
8
16
|
const hasUserId = model.hasProperty('userId')
|
|
9
17
|
const hasTeamId = model.hasProperty('teamId')
|
|
10
18
|
const isAdmin = find(get(req, 'user.teams', []), { alias: 'administrator' }) && useAdmin.includes(get(req, 'routeOptions.config.ns'))
|
|
11
|
-
if (!(hasSiteId || hasUserId || hasTeamId)) return filter
|
|
12
19
|
const q = { $and: [] }
|
|
13
20
|
if (!isEmpty(filter.query)) {
|
|
14
21
|
if (filter.query.$and) q.$and.push(...filter.query.$and)
|
|
15
22
|
else q.$and.push(filter.query)
|
|
16
23
|
}
|
|
24
|
+
if (!(hasSiteId || hasUserId || hasTeamId)) {
|
|
25
|
+
filter.query = queryByModel(q)
|
|
26
|
+
return filter
|
|
27
|
+
}
|
|
17
28
|
if (hasSiteId) q.$and.push({ siteId: req.site.id })
|
|
18
29
|
if (hasTeamId && !isAdmin) {
|
|
19
30
|
const teamIds = map(req.user.teams, 'id')
|
|
@@ -22,7 +33,7 @@ export async function rebuildFilter (modelName, filter, req) {
|
|
|
22
33
|
} else if (!isAdmin) {
|
|
23
34
|
if (hasUserId) q.$and.push({ userId: req.user.id })
|
|
24
35
|
}
|
|
25
|
-
filter.query = q
|
|
36
|
+
filter.query = queryByModel(q)
|
|
26
37
|
return filter
|
|
27
38
|
}
|
|
28
39
|
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"properties": [
|
|
3
3
|
"ns,,50,true,true",
|
|
4
|
-
"key,,
|
|
4
|
+
"key,,255,true,true",
|
|
5
5
|
"value,text"
|
|
6
6
|
],
|
|
7
7
|
"indexes": [{
|
|
8
8
|
"fields": ["ns", "key", "siteId"],
|
|
9
9
|
"type": "unique"
|
|
10
10
|
}],
|
|
11
|
-
"features": [
|
|
11
|
+
"features": [
|
|
12
|
+
"sumba:siteId",
|
|
13
|
+
"dobo:immutable",
|
|
14
|
+
"dobo:updatedAt"
|
|
15
|
+
]
|
|
12
16
|
}
|
|
@@ -4,7 +4,7 @@ const auth = {
|
|
|
4
4
|
const { camelCase } = this.app.lib._
|
|
5
5
|
const { req } = socket
|
|
6
6
|
const { session } = req
|
|
7
|
-
const site = await this.getSite(session.siteId)
|
|
7
|
+
const site = await this.getSite(session.siteId, true)
|
|
8
8
|
socket.join(camelCase(`site ${site.alias}`))
|
|
9
9
|
let user
|
|
10
10
|
if (session.userId) {
|
|
@@ -4,6 +4,7 @@ const profile = {
|
|
|
4
4
|
if (!this.app.masohiMail) return await reply.view('sumba.template:/user/forgot-password.html')
|
|
5
5
|
const { defaultsDeep } = this.app.lib.aneka
|
|
6
6
|
const { dayjs } = this.app.lib
|
|
7
|
+
const { get } = this.app.lib._
|
|
7
8
|
const model = this.app.dobo.getModel('SumbaUser')
|
|
8
9
|
const form = defaultsDeep(req.body, {})
|
|
9
10
|
let error
|
|
@@ -16,7 +17,7 @@ const profile = {
|
|
|
16
17
|
const to = `${data.firstName} ${data.lastName} <${data.email}>`
|
|
17
18
|
const subject = req.t('forgotPasswordLink')
|
|
18
19
|
const options = { req, reply }
|
|
19
|
-
const exp = req
|
|
20
|
+
const exp = get(req, 'site.setting.sumba.forgotPasswordExpDur')
|
|
20
21
|
data.fpToken = Buffer.from(`${data.token}:${dayjs().add(exp, 'ms').unix()}`).toString('base64')
|
|
21
22
|
data._meta = { hostHeader: req.headers.host }
|
|
22
23
|
const payload = { to, subject, data }
|
package/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import createNewSite from './lib/create-new-site.js'
|
|
3
3
|
import removeSite from './lib/remove-site.js'
|
|
4
|
+
import getSite from './lib/get-site.js'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Plugin factory
|
|
@@ -20,7 +21,10 @@ async function factory (pkgName) {
|
|
|
20
21
|
constructor () {
|
|
21
22
|
super(pkgName, me.app)
|
|
22
23
|
this.config = {
|
|
23
|
-
multiSite:
|
|
24
|
+
multiSite: {
|
|
25
|
+
enabled: false,
|
|
26
|
+
catchAll: 'default'
|
|
27
|
+
},
|
|
24
28
|
waibu: {
|
|
25
29
|
title: 'site',
|
|
26
30
|
prefix: 'site'
|
|
@@ -129,7 +133,7 @@ async function factory (pkgName) {
|
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
135
|
this.unsafeUserFields = ['password']
|
|
132
|
-
this.selfBind(['createNewSite', 'removeSite'])
|
|
136
|
+
this.selfBind(['createNewSite', 'removeSite', 'getSite'])
|
|
133
137
|
}
|
|
134
138
|
|
|
135
139
|
init = async () => {
|
|
@@ -402,73 +406,6 @@ async function factory (pkgName) {
|
|
|
402
406
|
return guarded
|
|
403
407
|
}
|
|
404
408
|
|
|
405
|
-
getSite = async (hostname, useId) => {
|
|
406
|
-
const { omit } = this.app.lib._
|
|
407
|
-
const omitted = ['status']
|
|
408
|
-
|
|
409
|
-
const mergeSetting = async (site) => {
|
|
410
|
-
const { defaultsDeep } = this.app.lib.aneka
|
|
411
|
-
const { parseObject, dayjs } = this.app.lib
|
|
412
|
-
const { trim, get, filter } = this.app.lib._
|
|
413
|
-
const defSetting = {}
|
|
414
|
-
const nsSetting = {}
|
|
415
|
-
const names = this.app.getAllNs()
|
|
416
|
-
const query = {
|
|
417
|
-
ns: { $in: names },
|
|
418
|
-
siteId: site.id
|
|
419
|
-
}
|
|
420
|
-
const all = await this.app.dobo.getModel('SumbaSiteSetting').findAllRecord({ query })
|
|
421
|
-
for (const ns of names) {
|
|
422
|
-
nsSetting[ns] = {}
|
|
423
|
-
defSetting[ns] = get(this, `app.${ns}.config.siteSetting`, {})
|
|
424
|
-
const items = filter(all, { ns })
|
|
425
|
-
for (const item of items) {
|
|
426
|
-
let value = trim([item.value] ?? '')
|
|
427
|
-
if (['[', '{'].includes(value[0])) {
|
|
428
|
-
try {
|
|
429
|
-
value = parseObject(JSON.parse(value))
|
|
430
|
-
} catch (err) {}
|
|
431
|
-
} else if (Number(value)) value = Number(value)
|
|
432
|
-
else if (['true', 'false'].includes(value)) value = value === 'true'
|
|
433
|
-
else {
|
|
434
|
-
const dt = dayjs(value)
|
|
435
|
-
if (dt.isValid()) value = dt.toDate()
|
|
436
|
-
}
|
|
437
|
-
nsSetting[ns][item.key] = value
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
site.setting = defaultsDeep({}, nsSetting, defSetting)
|
|
441
|
-
// additional fields
|
|
442
|
-
const country = await this.app.dobo.getModel('CdbCountry').getRecord(site.country, { noHook: true })
|
|
443
|
-
site.countryName = (country ?? {}).name ?? site.country
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
let site = {}
|
|
447
|
-
|
|
448
|
-
if (!this.config.multiSite) {
|
|
449
|
-
const resp = await this.app.dobo.getModel('SumbaSite').findOneRecord({ query: { alias: 'default' } }, { noHook: true })
|
|
450
|
-
site = omit(resp, omitted)
|
|
451
|
-
await mergeSetting(site)
|
|
452
|
-
return site
|
|
453
|
-
}
|
|
454
|
-
let query
|
|
455
|
-
if (useId) query = { id: hostname }
|
|
456
|
-
else {
|
|
457
|
-
query = {
|
|
458
|
-
$or: [
|
|
459
|
-
{ hostname },
|
|
460
|
-
{ alias: hostname }
|
|
461
|
-
]
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
const row = await this.app.dobo.getModel('SumbaSite').findOneRecord({ query }, { noHook: true })
|
|
465
|
-
if (!row) throw this.error('unknownSite')
|
|
466
|
-
if (row.status !== 'ACTIVE') throw this.error('siteInactiveInfo')
|
|
467
|
-
site = omit(row, omitted)
|
|
468
|
-
await mergeSetting(site)
|
|
469
|
-
return site
|
|
470
|
-
}
|
|
471
|
-
|
|
472
409
|
signout = async ({ req, reply, reason }) => {
|
|
473
410
|
const { runHook } = this.app.bajo
|
|
474
411
|
const { getSessionId } = this.app.waibuMpa
|
|
@@ -499,9 +436,10 @@ async function factory (pkgName) {
|
|
|
499
436
|
return reply.redirectTo(url, { query, params })
|
|
500
437
|
}
|
|
501
438
|
|
|
502
|
-
generatePassword = (req) => {
|
|
439
|
+
generatePassword = (req = {}) => {
|
|
440
|
+
const { get } = this.app.lib._
|
|
503
441
|
const { generateId } = this.app.lib.aneka
|
|
504
|
-
const cfg = req
|
|
442
|
+
const cfg = get(req, 'site.setting.sumba.userPassword', this.config.siteSetting.userPassword)
|
|
505
443
|
let passwd = generateId()
|
|
506
444
|
if (cfg.minLowercase) passwd += generateId({ pattern: 'abcdefghijklmnopqrstuvwxyz', length: cfg.minLowercase })
|
|
507
445
|
if (cfg.minUppercase) passwd += generateId({ pattern: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', length: cfg.minUppercase })
|
|
@@ -580,6 +518,7 @@ async function factory (pkgName) {
|
|
|
580
518
|
|
|
581
519
|
createNewSite = createNewSite
|
|
582
520
|
removeSite = removeSite
|
|
521
|
+
getSite = getSite
|
|
583
522
|
}
|
|
584
523
|
|
|
585
524
|
return Sumba
|
package/lib/get-site.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
async function getSite (hostname, useId) {
|
|
2
|
+
const { omit } = this.app.lib._
|
|
3
|
+
const omitted = ['status']
|
|
4
|
+
|
|
5
|
+
const mergeSetting = async (site) => {
|
|
6
|
+
const { defaultsDeep, isSet } = this.app.lib.aneka
|
|
7
|
+
const { parseObject, dayjs } = this.app.lib
|
|
8
|
+
const { isEmpty, trim, get, filter, set, isPlainObject, isArray } = this.app.lib._
|
|
9
|
+
const defSetting = {}
|
|
10
|
+
const nsSetting = {}
|
|
11
|
+
const names = this.app.getAllNs()
|
|
12
|
+
const query = {
|
|
13
|
+
ns: { $in: names },
|
|
14
|
+
siteId: site.id
|
|
15
|
+
}
|
|
16
|
+
const all = await this.app.dobo.getModel('SumbaSiteSetting').findAllRecord({ query })
|
|
17
|
+
for (const ns of names) {
|
|
18
|
+
const item = get(this, `app.${ns}.config.siteSetting`)
|
|
19
|
+
if (isSet(item)) defSetting[ns] = item
|
|
20
|
+
const items = filter(all, { ns })
|
|
21
|
+
for (const item of items) {
|
|
22
|
+
let value = trim([item.value] ?? '')
|
|
23
|
+
if (['[', '{'].includes(value[0])) {
|
|
24
|
+
try {
|
|
25
|
+
value = parseObject(JSON.parse(value))
|
|
26
|
+
} catch (err) {}
|
|
27
|
+
} else if (Number(value)) value = Number(value)
|
|
28
|
+
else if (['true', 'false'].includes(value)) value = value === 'true'
|
|
29
|
+
else if (item.key.endsWith('$in')) value = value.split(',').map(v => v.trim())
|
|
30
|
+
else {
|
|
31
|
+
const dt = dayjs(value)
|
|
32
|
+
if (dt.isValid()) value = dt.toDate()
|
|
33
|
+
}
|
|
34
|
+
if ((isPlainObject(value) || isArray(value)) && isEmpty(value)) continue
|
|
35
|
+
set(nsSetting, `${ns}.${item.key}`, value)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
site.setting = defaultsDeep({}, nsSetting, defSetting)
|
|
39
|
+
// additional fields
|
|
40
|
+
const country = await this.app.dobo.getModel('CdbCountry').getRecord(site.country, { noHook: true })
|
|
41
|
+
site.countryName = (country ?? {}).name ?? site.country
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let site = {}
|
|
45
|
+
|
|
46
|
+
const multiSite = this.config.multiSite === true ? { enabled: true, catchAll: 'default' } : this.config.multiSite
|
|
47
|
+
if (!multiSite.enabled) {
|
|
48
|
+
const resp = await this.app.dobo.getModel('SumbaSite').findOneRecord({ query: { alias: 'default' } }, { noHook: true })
|
|
49
|
+
site = omit(resp, omitted)
|
|
50
|
+
await mergeSetting(site)
|
|
51
|
+
return site
|
|
52
|
+
}
|
|
53
|
+
let query
|
|
54
|
+
if (useId) query = { id: hostname }
|
|
55
|
+
else query = { hostname }
|
|
56
|
+
let row = await this.app.dobo.getModel('SumbaSite').findOneRecord({ query }, { noHook: true })
|
|
57
|
+
if (!row) {
|
|
58
|
+
if (multiSite.catchAll) {
|
|
59
|
+
query = { alias: multiSite.catchAll === true ? 'default' : multiSite.catchAll }
|
|
60
|
+
row = await this.app.dobo.getModel('SumbaSite').findOneRecord({ query }, { noHook: true })
|
|
61
|
+
}
|
|
62
|
+
if (!row) throw this.error('unknownSite')
|
|
63
|
+
}
|
|
64
|
+
if (row.status !== 'ACTIVE') throw this.error('siteInactiveInfo')
|
|
65
|
+
site = omit(row, omitted)
|
|
66
|
+
await mergeSetting(site)
|
|
67
|
+
return site
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default getSite
|
package/lib/password-rule.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { joiPasswordExtendCore } from 'joi-password'
|
|
2
2
|
|
|
3
|
-
async function passwordRule (req) {
|
|
3
|
+
async function passwordRule (req = {}) {
|
|
4
|
+
const { get } = this.app.lib._
|
|
4
5
|
const { importPkg } = this.app.bajo
|
|
5
6
|
const joi = await importPkg('dobo:joi')
|
|
6
7
|
const joiPassword = joi.extend(joiPasswordExtendCore)
|
|
@@ -9,7 +10,7 @@ async function passwordRule (req) {
|
|
|
9
10
|
.min(8)
|
|
10
11
|
.max(100)
|
|
11
12
|
.required()
|
|
12
|
-
const cfg = req
|
|
13
|
+
const cfg = get(req, 'site.setting.sumba.userPassword', this.config.siteSetting.userPassword)
|
|
13
14
|
if (cfg.minUppercase) password = password.minOfUppercase(cfg.minUppercase)
|
|
14
15
|
if (cfg.minLowercase) password = password.minOfLowercase(cfg.minLowercase)
|
|
15
16
|
if (cfg.minSpecialChar) password = password.minOfSpecialCharacters(cfg.minSpecialChar)
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-03-13
|
|
4
|
+
|
|
5
|
+
- [2.10.0] ```getSite()``` now accept object & array based on their keys to
|
|
6
|
+
|
|
7
|
+
## 2026-03-12
|
|
8
|
+
|
|
9
|
+
- [2.9.0] Add ability to restrict/filter dobo's records through site setting
|
|
10
|
+
- [2.9.0] Multisite config now accept object. If set to ```true``` it defaults to ```catchAll: 'default'```
|
|
11
|
+
|
|
3
12
|
## 2026-03-11
|
|
4
13
|
|
|
5
14
|
- [2.8.0] Add ```createNewSite()``` and ```applet.crateNewSite```
|