sumba 2.13.1 → 2.15.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-site@after-create-record.js +8 -0
- package/extend/bajo/hook/dobo.sumba-site@after-remove-record.js +7 -0
- package/extend/bajo/hook/dobo.sumba-user@before-create-record.js +1 -3
- package/extend/bajo/hook/dobo.sumba-user@before-record-validation.js +1 -3
- package/extend/bajo/hook/dobo.sumba-user@before-update-record.js +1 -3
- package/extend/bajo/hook/dobo@before-create-record.js +1 -2
- package/extend/bajo/hook/dobo@before-find-record.js +4 -0
- package/extend/bajo/hook/waibu-mpa@pre-parsing.js +2 -1
- package/extend/bajo/hook/waibu-rest-api@pre-parsing.js +2 -1
- package/extend/bajo/hook/waibu-static@pre-parsing.js +2 -1
- package/extend/bajo/intl/en-US.json +4 -0
- package/extend/bajo/intl/id.json +4 -0
- package/extend/bajoCli/applet/create-new-site.js +1 -1
- package/extend/dobo/feature/lat-lng.js +1 -1
- package/extend/dobo/feature/lat.js +1 -1
- package/extend/dobo/feature/lng.js +1 -1
- package/extend/dobo/feature/user-id.js +1 -2
- package/extend/waibuDb/schema/site.js +46 -0
- package/extend/waibuMpa/extend/waibuAdmin/route/_is_/cache/@action.js +13 -0
- package/extend/waibuMpa/extend/waibuAdmin/route/{session → _is_/session}/@action.js +1 -0
- package/extend/waibuMpa/extend/waibuAdmin/route/_is_/site/@action.js +12 -0
- package/extend/waibuMpa/extend/waibuAdmin/route/reset-user-password.js +1 -3
- package/extend/waibuMpa/route/user/forgot-password/@fpl.js +1 -2
- package/extend/waibuMpa/route/your-stuff/change-password.js +1 -3
- package/index.js +83 -23
- package/lib/create-new-site.js +42 -26
- package/lib/get-site.js +5 -6
- package/lib/get-user.js +3 -2
- package/lib/remove-site.js +24 -20
- package/lib/util.js +15 -0
- package/package.json +1 -1
- package/wiki/CHANGES.md +11 -0
- package/extend/waibuDb/schema/site.json +0 -35
- package/lib/lat-lng-hook.js +0 -8
- package/lib/password-rule.js +0 -23
- package/lib/reset-token.js +0 -9
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createRefs, getAllFixtures } from '../../../lib/create-new-site.js'
|
|
2
|
+
|
|
3
|
+
async function afterCreateRecord (body, result, options) {
|
|
4
|
+
const fixtures = await getAllFixtures.call(this, result.data.alias)
|
|
5
|
+
await createRefs.call(this, result.data, fixtures, options)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default afterCreateRecord
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import passwordRule from '../../../lib/password-rule.js'
|
|
2
|
-
|
|
3
1
|
async function beforeRecordValidation (body, options = {}) {
|
|
4
2
|
const { set } = this.app.lib._
|
|
5
|
-
const password = await passwordRule
|
|
3
|
+
const password = await this.passwordRule(options.req)
|
|
6
4
|
const rule = { password }
|
|
7
5
|
set(options, 'validation.params.rule', rule)
|
|
8
6
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import resetToken from '../../../lib/reset-token.js'
|
|
2
|
-
|
|
3
1
|
async function beforeUpdateRecord (id, body, options = {}) {
|
|
4
2
|
if (body.salt) {
|
|
5
|
-
const { token, salt } = await resetToken
|
|
3
|
+
const { token, salt } = await this.resetToken(body.salt)
|
|
6
4
|
body.token = token
|
|
7
5
|
body.salt = salt
|
|
8
6
|
}
|
|
@@ -3,8 +3,7 @@ const doboBeforeCreateRecord = {
|
|
|
3
3
|
handler: async function (modelName, body, options = {}) {
|
|
4
4
|
const { get } = this.app.lib._
|
|
5
5
|
const { req } = options
|
|
6
|
-
|
|
7
|
-
if (options.noAutoFilter || !req) return
|
|
6
|
+
if (options.noAutoFilter || !req || get(req, 'routeOptions.config.interSite')) return
|
|
8
7
|
const item = { siteId: 'site.id', userId: 'user.id' }
|
|
9
8
|
const model = this.app.dobo.getModel(modelName)
|
|
10
9
|
for (const i in item) {
|
|
@@ -3,6 +3,10 @@ const useAdmin = ['waibuAdmin']
|
|
|
3
3
|
export async function rebuildFilter (modelName, filter, req) {
|
|
4
4
|
const { isEmpty, isPlainObject, map, find, get } = this.app.lib._
|
|
5
5
|
filter.query = filter.query ?? {}
|
|
6
|
+
if (req.routeOptions.config.interSite) {
|
|
7
|
+
filter.query = { $and: [filter.query] }
|
|
8
|
+
return
|
|
9
|
+
}
|
|
6
10
|
|
|
7
11
|
const queryBySiteSetting = (query) => {
|
|
8
12
|
if (!req.site) return
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { checkUserId, checkTeam, checkTheme, checkIconset } from '../../../lib/util.js'
|
|
1
|
+
import { checkUserId, checkTeam, checkTheme, checkIconset, checkinterSite } from '../../../lib/util.js'
|
|
2
2
|
|
|
3
3
|
const preParsing = {
|
|
4
4
|
level: 10,
|
|
@@ -7,6 +7,7 @@ const preParsing = {
|
|
|
7
7
|
await checkIconset.call(this, req, reply)
|
|
8
8
|
await checkUserId.call(this, req, reply, 'waibuMpa')
|
|
9
9
|
await checkTeam.call(this, req, reply, 'waibuMpa')
|
|
10
|
+
await checkinterSite.call(this, req, reply)
|
|
10
11
|
}
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { checkUserId, checkTeam } from '../../../lib/util.js'
|
|
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
6
|
await checkUserId.call(this, req, reply, 'waibuRestApi')
|
|
7
7
|
await checkTeam.call(this, req, reply, 'waibuRestApi')
|
|
8
|
+
await checkinterSite.call(this, req, reply)
|
|
8
9
|
}
|
|
9
10
|
}
|
|
10
11
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { checkUserId, checkTeam } from '../../../lib/util.js'
|
|
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
6
|
await checkUserId.call(this, req, reply, 'waibuStatic')
|
|
7
7
|
await checkTeam.call(this, req, reply, 'waibuStatic')
|
|
8
|
+
await checkinterSite.call(this, req, reply)
|
|
8
9
|
}
|
|
9
10
|
}
|
|
10
11
|
|
|
@@ -123,6 +123,10 @@
|
|
|
123
123
|
"aboutToDeleteSite%s": "You're about to delete a site with alias '%s'. Are you sure?",
|
|
124
124
|
"aboutToCreateSite%s": "You're about to create a site with alias '%s'. Continue?",
|
|
125
125
|
"removedFrom%s%s": "Removed from '%s' (%s record(s))",
|
|
126
|
+
"cacheStorage": "Cache Storage",
|
|
127
|
+
"protectedArea": "Protected Area",
|
|
128
|
+
"pleaseAuthenticate": "Please authenticate yourself, thank you!",
|
|
129
|
+
"manageAllSite": "Manage All Sites",
|
|
126
130
|
"field": {
|
|
127
131
|
"currentPassword": "Current Password",
|
|
128
132
|
"newPassword": "New Password",
|
package/extend/bajo/intl/id.json
CHANGED
|
@@ -124,6 +124,10 @@
|
|
|
124
124
|
"aboutToDeleteSite%s": "Anda akan menghapus sebuah situs dengan alias '%s'. Anda yakin?",
|
|
125
125
|
"aboutToCreateSite%s": "Anda akan membuat sebuah situs dengan alias '%s'. Lanjutkan?",
|
|
126
126
|
"removedFrom%s%s": "Dihapus dari '%s' (%s data)",
|
|
127
|
+
"cacheStorage": "Penyimpanan Cache",
|
|
128
|
+
"protectedArea": "Wilayah Dilindungi",
|
|
129
|
+
"pleaseAuthenticate": "Silahkan melakukan otentikasi terlebih dahulu, terima kasih!",
|
|
130
|
+
"manageAllSite": "Kelola Semua Situs",
|
|
127
131
|
"field": {
|
|
128
132
|
"currentPassword": "Kata Sandi Saat Ini",
|
|
129
133
|
"newPassword": "Kata Sandi Baru",
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
async function site (req = {}) {
|
|
2
|
+
const isInterSite = (req.url ?? '').includes('/_is_/')
|
|
3
|
+
let details
|
|
4
|
+
let edit
|
|
5
|
+
if (!isInterSite) {
|
|
6
|
+
details = {
|
|
7
|
+
control: {
|
|
8
|
+
noBackBtn: true,
|
|
9
|
+
noCloneBtn: true,
|
|
10
|
+
editHref: 'waibuAdmin:/site/site?edit=true'
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
edit = {
|
|
14
|
+
control: {
|
|
15
|
+
noBackBtn: true,
|
|
16
|
+
noCloneBtn: true,
|
|
17
|
+
detailsHref: 'waibuAdmin:/site/site'
|
|
18
|
+
},
|
|
19
|
+
readonly: ['id', 'createdAt', 'updatedAt']
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const result = {
|
|
23
|
+
common: {
|
|
24
|
+
attachment: true,
|
|
25
|
+
layout: [
|
|
26
|
+
{ name: 'meta', fields: ['id', 'createdAt', 'updatedAt'] },
|
|
27
|
+
{ name: 'general', fields: ['hostname', 'alias', 'title', 'orgName', 'email', 'status:4-md 6-sm'] },
|
|
28
|
+
{ name: 'personInCharge', fields: ['picName:3-md 6-sm:Name', 'picRole:3-md 6-sm:Role', 'picPhone:3-md 6-sm:Phone', 'picEmail:3-md 6-sm:Email'] },
|
|
29
|
+
{ name: 'address', fields: ['address1:12', 'address2:12', 'city:6-md 8-sm', 'zipCode:2-md 4-sm', 'provinceState:4-md', 'country:6-md', 'phone:6-md', 'website:12'] },
|
|
30
|
+
{ name: 'socialMedia', fields: ['socX:3-md 6-sm', 'socInstagram:3-md 6-sm', 'socFacebook:3-md 6-sm', 'socLinkedIn:3-md 6-sm'] }
|
|
31
|
+
],
|
|
32
|
+
widget: {
|
|
33
|
+
country: {
|
|
34
|
+
component: 'form-select-ext'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
view: {
|
|
39
|
+
details,
|
|
40
|
+
edit
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return result
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default site
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const action = {
|
|
2
|
+
method: ['GET', 'POST'],
|
|
3
|
+
title: 'cacheStorage',
|
|
4
|
+
interSite: true,
|
|
5
|
+
handler: async function (req, reply) {
|
|
6
|
+
if (!this.app.bajoCache) throw this.error('_notFound')
|
|
7
|
+
const { importModule } = this.app.bajo
|
|
8
|
+
const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
|
|
9
|
+
return await crudSkel.call(this, 'CacheStorage', req, reply)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default action
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const action = {
|
|
2
|
+
method: ['GET', 'POST'],
|
|
3
|
+
title: 'manageAllSite',
|
|
4
|
+
interSite: true,
|
|
5
|
+
handler: async function (req, reply) {
|
|
6
|
+
const { importModule } = this.app.bajo
|
|
7
|
+
const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
|
|
8
|
+
return await crudSkel.call(this, 'SumbaSite', req, reply)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default action
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import passwordRule from '../../../../../lib/password-rule.js'
|
|
2
|
-
|
|
3
1
|
const resetUserPassword = {
|
|
4
2
|
method: ['GET', 'POST'],
|
|
5
3
|
title: 'resetUserPassword',
|
|
@@ -12,7 +10,7 @@ const resetUserPassword = {
|
|
|
12
10
|
let error
|
|
13
11
|
if (req.method === 'POST') {
|
|
14
12
|
try {
|
|
15
|
-
const password = await passwordRule
|
|
13
|
+
const password = await this.passwordRule(req)
|
|
16
14
|
const schema = Joi.object({
|
|
17
15
|
username: Joi.string().max(50).required(),
|
|
18
16
|
password,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import passwordRule from '../../../../../lib/password-rule.js'
|
|
2
1
|
|
|
3
2
|
async function getUser (req, reply) {
|
|
4
3
|
const { dayjs } = this.app.lib
|
|
@@ -30,7 +29,7 @@ const forgotPasswordLink = {
|
|
|
30
29
|
let error
|
|
31
30
|
if (req.method === 'POST') {
|
|
32
31
|
try {
|
|
33
|
-
const newPassword = await passwordRule
|
|
32
|
+
const newPassword = await this.passwordRule(req)
|
|
34
33
|
const schema = Joi.object({
|
|
35
34
|
newPassword,
|
|
36
35
|
verifyNewPassword: Joi.ref('newPassword')
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import passwordRule from '../../../../lib/password-rule.js'
|
|
2
|
-
|
|
3
1
|
const profile = {
|
|
4
2
|
method: ['GET', 'POST'],
|
|
5
3
|
handler: async function (req, reply) {
|
|
@@ -12,7 +10,7 @@ const profile = {
|
|
|
12
10
|
let error
|
|
13
11
|
if (req.method === 'POST') {
|
|
14
12
|
try {
|
|
15
|
-
const newPassword = await passwordRule
|
|
13
|
+
const newPassword = await this.passwordRule(req)
|
|
16
14
|
const schema = Joi.object({
|
|
17
15
|
currentPassword: Joi.string().min(8).max(50).required(),
|
|
18
16
|
newPassword,
|
package/index.js
CHANGED
|
@@ -3,6 +3,13 @@ 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
5
|
import { getUserById, getUserByToken, getUserByUsernamePassword } from './lib/get-user.js'
|
|
6
|
+
import { joiPasswordExtendCore } from 'joi-password'
|
|
7
|
+
|
|
8
|
+
const defMultiSite = {
|
|
9
|
+
enabled: false,
|
|
10
|
+
catchAll: 'default',
|
|
11
|
+
admins: []
|
|
12
|
+
}
|
|
6
13
|
|
|
7
14
|
/**
|
|
8
15
|
* Plugin factory
|
|
@@ -13,6 +20,7 @@ import { getUserById, getUserByToken, getUserByUsernamePassword } from './lib/ge
|
|
|
13
20
|
async function factory (pkgName) {
|
|
14
21
|
const me = this
|
|
15
22
|
const { getModel } = this.app.dobo
|
|
23
|
+
const { cloneDeep } = this.app.lib._
|
|
16
24
|
|
|
17
25
|
/**
|
|
18
26
|
* Sumba class
|
|
@@ -23,10 +31,8 @@ async function factory (pkgName) {
|
|
|
23
31
|
constructor () {
|
|
24
32
|
super(pkgName, me.app)
|
|
25
33
|
this.config = {
|
|
26
|
-
multiSite:
|
|
27
|
-
|
|
28
|
-
catchAll: 'default'
|
|
29
|
-
},
|
|
34
|
+
multiSite: cloneDeep(defMultiSite),
|
|
35
|
+
interSiteAdmins: [],
|
|
30
36
|
waibu: {
|
|
31
37
|
title: 'site',
|
|
32
38
|
prefix: 'site'
|
|
@@ -85,16 +91,16 @@ async function factory (pkgName) {
|
|
|
85
91
|
apiKey: {
|
|
86
92
|
type: 'Bearer',
|
|
87
93
|
qsKey: 'apiKey',
|
|
88
|
-
headerKey: 'X-
|
|
94
|
+
headerKey: 'X-Auth-ApiKey'
|
|
89
95
|
},
|
|
90
96
|
basic: {
|
|
91
97
|
},
|
|
92
98
|
jwt: {
|
|
93
99
|
type: 'Bearer',
|
|
94
100
|
qsKey: 'token',
|
|
95
|
-
headerKey: 'X-
|
|
101
|
+
headerKey: 'X-Auth-Jwt',
|
|
96
102
|
secret: '668de9cf57316c7dbf52f7ff7611c299',
|
|
97
|
-
|
|
103
|
+
expiresInDur: '7d'
|
|
98
104
|
}
|
|
99
105
|
},
|
|
100
106
|
waibuRestApi: {
|
|
@@ -109,8 +115,8 @@ async function factory (pkgName) {
|
|
|
109
115
|
methods: ['basic', 'apiKey', 'jwt'],
|
|
110
116
|
basic: {
|
|
111
117
|
useUtf8: true,
|
|
112
|
-
realm:
|
|
113
|
-
warningMessage: '
|
|
118
|
+
realm: true,
|
|
119
|
+
warningMessage: 'pleaseAuthenticate'
|
|
114
120
|
},
|
|
115
121
|
silentOnError: false
|
|
116
122
|
}
|
|
@@ -125,12 +131,18 @@ async function factory (pkgName) {
|
|
|
125
131
|
forgotPasswordExpDur: '5m',
|
|
126
132
|
timeZone: 'UTC',
|
|
127
133
|
userPassword: {
|
|
128
|
-
|
|
129
|
-
|
|
134
|
+
minUpperCase: 1,
|
|
135
|
+
minLowerCase: 1,
|
|
130
136
|
minSpecialChar: 1,
|
|
131
137
|
minNumeric: 1,
|
|
132
138
|
noWhitespace: false,
|
|
133
|
-
latinOnlyChars: false
|
|
139
|
+
latinOnlyChars: false,
|
|
140
|
+
pattern: {
|
|
141
|
+
lowerCase: 'abcdefghijklmnopqrstuvwxyz',
|
|
142
|
+
upperCase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
143
|
+
specialChar: '!@#$%*',
|
|
144
|
+
numeric: '0123456789'
|
|
145
|
+
}
|
|
134
146
|
}
|
|
135
147
|
},
|
|
136
148
|
cacheTtl: {
|
|
@@ -151,6 +163,19 @@ async function factory (pkgName) {
|
|
|
151
163
|
this[`${type}Routes`] = this[`${type}Routes`] ?? []
|
|
152
164
|
this[`${type}NegRoutes`] = this[`${type}NegRoutes`] ?? []
|
|
153
165
|
}
|
|
166
|
+
if (this.config.multiSite === true) {
|
|
167
|
+
this.config.multiSite = cloneDeep(defMultiSite)
|
|
168
|
+
this.config.multiSite.enabled = true
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
start = async () => {
|
|
173
|
+
const { getModel } = this.app.dobo
|
|
174
|
+
if (this.config.interSiteAdmins.length === 0) {
|
|
175
|
+
const site = await getModel('SumbaSite').findOneRecord({ query: { alias: 'default' } }, { noHook: true })
|
|
176
|
+
const user = await getModel('SumbaUser').findOneRecord({ query: { username: 'admin', siteId: site.id } }, { noHook: true })
|
|
177
|
+
this.config.interSiteAdmins.push(user.id)
|
|
178
|
+
}
|
|
154
179
|
}
|
|
155
180
|
|
|
156
181
|
_getSetting = async (type, source) => {
|
|
@@ -201,10 +226,19 @@ async function factory (pkgName) {
|
|
|
201
226
|
{ title: 'siteSetting', href: `waibuAdmin:/${prefix}/site-setting/list` },
|
|
202
227
|
{ title: 'resetUserPassword', href: `waibuAdmin:/${prefix}/reset-user-password` }
|
|
203
228
|
]
|
|
229
|
+
}, {
|
|
230
|
+
title: 'manageAllSite',
|
|
231
|
+
interSite: true,
|
|
232
|
+
href: `waibuAdmin:/${prefix}/_is_/site/list`
|
|
233
|
+
}, {
|
|
234
|
+
title: '-',
|
|
235
|
+
interSite: true
|
|
204
236
|
}, {
|
|
205
237
|
title: 'misc',
|
|
238
|
+
interSite: true,
|
|
206
239
|
children: [
|
|
207
|
-
{ title: 'userSession', href: `waibuAdmin:/${prefix}/session/list` }
|
|
240
|
+
{ title: 'userSession', href: `waibuAdmin:/${prefix}/_is_/session/list` },
|
|
241
|
+
{ title: 'cacheStorage', href: `waibuAdmin:/${prefix}/_is_/cache/list` }
|
|
208
242
|
]
|
|
209
243
|
}]
|
|
210
244
|
}
|
|
@@ -218,13 +252,13 @@ async function factory (pkgName) {
|
|
|
218
252
|
const fastJwt = await importPkg('bajoExtra:fast-jwt')
|
|
219
253
|
const { createSigner } = fastJwt
|
|
220
254
|
|
|
221
|
-
const opts = pick(this.config.auth.common.jwt, ['
|
|
255
|
+
const opts = pick(this.config.auth.common.jwt, ['expiresInDur'])
|
|
222
256
|
opts.key = get(this.config, 'auth.common.jwt.secret')
|
|
223
257
|
const sign = createSigner(opts)
|
|
224
258
|
const apiKey = await hash(rec.password)
|
|
225
259
|
const payload = { uid: rec.id, apiKey }
|
|
226
260
|
const token = await sign(payload)
|
|
227
|
-
const expiresAt = dayjs().add(opts.
|
|
261
|
+
const expiresAt = dayjs().add(opts.expiresInDur).toDate()
|
|
228
262
|
return { token, expiresAt }
|
|
229
263
|
}
|
|
230
264
|
|
|
@@ -259,11 +293,9 @@ async function factory (pkgName) {
|
|
|
259
293
|
const { isEmpty, merge } = this.app.lib._
|
|
260
294
|
|
|
261
295
|
const setHeader = async (setting, reply) => {
|
|
262
|
-
const { isString } = this.app.lib._
|
|
263
|
-
|
|
264
296
|
let header = setting.type
|
|
265
297
|
const exts = []
|
|
266
|
-
if (
|
|
298
|
+
if (setting.realm) exts.push(`realm="${req.t('protectedArea')}"`)
|
|
267
299
|
if (setting.useUtf8) exts.push('charset="UTF-8"')
|
|
268
300
|
if (exts.length > 0) header += ` ${exts.join(', ')}`
|
|
269
301
|
reply.header('WWW-Authenticate', header)
|
|
@@ -277,7 +309,7 @@ async function factory (pkgName) {
|
|
|
277
309
|
if (isEmpty(authInfo)) {
|
|
278
310
|
if (setting.realm) {
|
|
279
311
|
await setHeader(setting, reply)
|
|
280
|
-
throw this.error(
|
|
312
|
+
throw this.error(req.t('pleaseAuthenticate'), { statusCode: 403 })
|
|
281
313
|
} else return false
|
|
282
314
|
}
|
|
283
315
|
const decoded = Buffer.from(authInfo, 'base64').toString()
|
|
@@ -397,10 +429,10 @@ async function factory (pkgName) {
|
|
|
397
429
|
const { generateId } = this.app.lib.aneka
|
|
398
430
|
const cfg = get(req, 'site.setting.sumba.userPassword', this.config.siteSetting.userPassword)
|
|
399
431
|
let passwd = generateId()
|
|
400
|
-
if (cfg.
|
|
401
|
-
if (cfg.
|
|
402
|
-
if (cfg.minSpecialChar) passwd += generateId({ pattern:
|
|
403
|
-
if (cfg.minNumeric) passwd += generateId({ pattern:
|
|
432
|
+
if (cfg.minLowerCase > 0) passwd += generateId({ pattern: cfg.pattern.lowerCase, length: cfg.minLowercase })
|
|
433
|
+
if (cfg.minUpperCase > 0) passwd += generateId({ pattern: cfg.pattern.upperCase, length: cfg.minUppercase })
|
|
434
|
+
if (cfg.minSpecialChar > 0) passwd += generateId({ pattern: cfg.pattern.specialChar, length: cfg.minSpecialChar })
|
|
435
|
+
if (cfg.minNumeric > 0) passwd += generateId({ pattern: cfg.pattern.numeric, length: cfg.minNumeric })
|
|
404
436
|
return passwd
|
|
405
437
|
}
|
|
406
438
|
|
|
@@ -472,6 +504,34 @@ async function factory (pkgName) {
|
|
|
472
504
|
await this.app.masohiMail.send({ payload, source: source ?? this.ns, conn })
|
|
473
505
|
}
|
|
474
506
|
|
|
507
|
+
resetToken = async (salt) => {
|
|
508
|
+
const { generateId } = this.app.lib.aneka
|
|
509
|
+
const { hash } = this.app.bajoExtra
|
|
510
|
+
salt = salt ?? generateId()
|
|
511
|
+
const token = await hash(await hash(salt))
|
|
512
|
+
return { salt, token }
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
passwordRule = async (req = {}) => {
|
|
516
|
+
const { get } = this.app.lib._
|
|
517
|
+
const { importPkg } = this.app.bajo
|
|
518
|
+
const joi = await importPkg('dobo:joi')
|
|
519
|
+
const joiPassword = joi.extend(joiPasswordExtendCore)
|
|
520
|
+
let password = joiPassword
|
|
521
|
+
.string()
|
|
522
|
+
.min(8)
|
|
523
|
+
.max(100)
|
|
524
|
+
.required()
|
|
525
|
+
const cfg = get(req, 'site.setting.sumba.userPassword', this.config.siteSetting.userPassword)
|
|
526
|
+
if (cfg.minUppeCase) password = password.minOfUppercase(cfg.minUpperCase)
|
|
527
|
+
if (cfg.minLowercase) password = password.minOfLowercase(cfg.minLowerCase)
|
|
528
|
+
if (cfg.minSpecialChar) password = password.minOfSpecialCharacters(cfg.minSpecialChar)
|
|
529
|
+
if (cfg.minNumeric) password = password.minOfNumeric(cfg.minNumeric)
|
|
530
|
+
if (cfg.noWhitespace) password = password.noWhiteSpaces()
|
|
531
|
+
if (cfg.latinOnlyChars) password = password.onlyLatinCharacters()
|
|
532
|
+
return password
|
|
533
|
+
}
|
|
534
|
+
|
|
475
535
|
createNewSite = createNewSite
|
|
476
536
|
removeSite = removeSite
|
|
477
537
|
getSite = getSite
|
package/lib/create-new-site.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
|
|
3
|
-
async function
|
|
3
|
+
export async function getAllFixtures (alias) {
|
|
4
4
|
const { getPluginDataDir, readConfig } = this.app.bajo
|
|
5
5
|
const { isEmpty, omit, kebabCase, isString } = this.app.lib._
|
|
6
6
|
const { getModel } = this.app.dobo
|
|
7
7
|
const { fastGlob } = this.app.lib
|
|
8
|
+
const data = {}
|
|
8
9
|
|
|
9
10
|
function replaceAlias (alias, items) {
|
|
10
11
|
for (const item of items) {
|
|
@@ -15,9 +16,6 @@ async function createNewSite (alias, hostname, verbose) {
|
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
if (isEmpty(alias)) throw this.error('aliasRequired%s', alias)
|
|
19
|
-
let spin
|
|
20
|
-
if (verbose) spin = this.print.spinner().start('processing...')
|
|
21
19
|
const formats = this.app.configHandlers.map(item => item.ext.slice(1))
|
|
22
20
|
const overrideBase = `${getPluginDataDir('sumba')}/create-new-site-fixtures/${alias}`
|
|
23
21
|
const models = this.app.dobo.models.filter(m => {
|
|
@@ -29,7 +27,6 @@ async function createNewSite (alias, hostname, verbose) {
|
|
|
29
27
|
const ext = path.extname(file)
|
|
30
28
|
return file.slice(0, file.length - ext.length)
|
|
31
29
|
})
|
|
32
|
-
const data = {}
|
|
33
30
|
for (const m of models) {
|
|
34
31
|
const model = getModel(m)
|
|
35
32
|
const file = `${overrideBase}/${kebabCase(m)}`
|
|
@@ -52,34 +49,53 @@ async function createNewSite (alias, hostname, verbose) {
|
|
|
52
49
|
}
|
|
53
50
|
data[m] = m === 'SumbaSite' ? fixtures[0] : fixtures
|
|
54
51
|
}
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
return data
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function createRefs (site, data, options, verbose) {
|
|
56
|
+
const { isString } = this.app.lib._
|
|
57
|
+
const { getModel } = this.app.dobo
|
|
58
|
+
for (const m in data) {
|
|
59
|
+
if (m === 'SumbaSite') continue
|
|
60
|
+
const mdl = getModel(m)
|
|
61
|
+
const fixtures = data[m]
|
|
62
|
+
for (const f of fixtures) {
|
|
63
|
+
f.siteId = site.id
|
|
64
|
+
for (const key in f) {
|
|
65
|
+
const val = f[key]
|
|
66
|
+
if (isString(val) && val.slice(0, 2) === '?:') f[key] = await mdl._simpleLookup(val.slice(2), options)
|
|
67
|
+
}
|
|
68
|
+
await mdl.createRecord(f, options)
|
|
69
|
+
}
|
|
70
|
+
if (verbose) this.print.succeed('writingModel%s%s', mdl.name, fixtures.length)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function createNewSite (alias, hostname, verbose) {
|
|
75
|
+
const { isEmpty } = this.app.lib._
|
|
76
|
+
const { getModel } = this.app.dobo
|
|
77
|
+
|
|
78
|
+
if (isEmpty(alias)) throw this.error('aliasRequired%s', alias)
|
|
79
|
+
let spin
|
|
80
|
+
if (verbose) spin = this.print.spinner().start('processing...')
|
|
57
81
|
const query = { $or: [{ alias }] }
|
|
58
82
|
if (!isEmpty(hostname)) query.$or.push({ hostname })
|
|
59
83
|
const model = getModel('SumbaSite')
|
|
60
84
|
const site = await model.findOneRecord({ query })
|
|
61
|
-
if (!isEmpty(site))
|
|
85
|
+
if (!isEmpty(site)) {
|
|
86
|
+
if (verbose) spin.stop()
|
|
87
|
+
throw this.error('aliasOrHOstnameExists%s%s', alias, hostname ?? '')
|
|
88
|
+
}
|
|
62
89
|
// lets go
|
|
63
|
-
|
|
64
|
-
if (
|
|
90
|
+
const fixtures = await getAllFixtures.call(this, alias)
|
|
91
|
+
if (verbose) spin.stop()
|
|
92
|
+
fixtures.SumbaSite.alias = alias
|
|
93
|
+
if (!isEmpty(hostname)) fixtures.SumbaSite.hostname = hostname
|
|
65
94
|
await model.transaction(async (trx) => {
|
|
66
|
-
const
|
|
67
|
-
const newSite = await model.createRecord(data.SumbaSite, options)
|
|
95
|
+
const newSite = await model.createRecord(fixtures.SumbaSite, { trx, noHook: true })
|
|
68
96
|
if (verbose) this.print.succeed('writingModel%s%s', model.name, 1)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const mdl = getModel(m)
|
|
72
|
-
const fixtures = data[m]
|
|
73
|
-
for (const f of fixtures) {
|
|
74
|
-
f.siteId = newSite.id + ''
|
|
75
|
-
for (const key in f) {
|
|
76
|
-
const val = f[key]
|
|
77
|
-
if (isString(val) && val.slice(0, 2) === '?:') f[key] = await mdl._simpleLookup(val.slice(2), options)
|
|
78
|
-
}
|
|
79
|
-
await mdl.createRecord(f, options)
|
|
80
|
-
}
|
|
81
|
-
if (verbose) this.print.succeed('writingModel%s%s', mdl.name, fixtures.length)
|
|
82
|
-
}
|
|
97
|
+
const options = { trx }
|
|
98
|
+
await createRefs.call(this, newSite, fixtures, options, verbose)
|
|
83
99
|
})
|
|
84
100
|
if (verbose) this.print.info('done')
|
|
85
101
|
}
|
package/lib/get-site.js
CHANGED
|
@@ -29,10 +29,9 @@ async function getSite (input, byId = false) {
|
|
|
29
29
|
site.countryName = (country ?? {}).name ?? site.country
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
if (!multiSite.enabled) {
|
|
32
|
+
if (!this.config.multiSite.enabled) {
|
|
34
33
|
const filter = { query: { alias: 'default' } }
|
|
35
|
-
const key = 'getSite
|
|
34
|
+
const key = 'getSite|default'
|
|
36
35
|
if (getCache) {
|
|
37
36
|
site = await getCache({ key })
|
|
38
37
|
if (site) return site
|
|
@@ -48,15 +47,15 @@ async function getSite (input, byId = false) {
|
|
|
48
47
|
let query
|
|
49
48
|
if (byId) query = { id: input }
|
|
50
49
|
else query = { hostname: input }
|
|
51
|
-
const key = `getSite
|
|
50
|
+
const key = `getSite|multiSite|${input}`
|
|
52
51
|
if (getCache) {
|
|
53
52
|
site = await getCache({ key })
|
|
54
53
|
if (site) return JSON.parse(site)
|
|
55
54
|
}
|
|
56
55
|
let row = await this.app.dobo.getModel('SumbaSite').findOneRecord({ query }, { noHook: true })
|
|
57
56
|
if (!row) {
|
|
58
|
-
if (multiSite.catchAll) {
|
|
59
|
-
query = { alias: multiSite.catchAll === true ? 'default' : multiSite.catchAll }
|
|
57
|
+
if (this.config.multiSite.catchAll) {
|
|
58
|
+
query = { alias: this.config.multiSite.catchAll === true ? 'default' : this.config.multiSite.catchAll }
|
|
60
59
|
row = await this.app.dobo.getModel('SumbaSite').findOneRecord({ query }, { noHook: true })
|
|
61
60
|
}
|
|
62
61
|
if (!row) throw this.error('unknownSite')
|
package/lib/get-user.js
CHANGED
|
@@ -3,6 +3,7 @@ import { parseNsSettings } from './util.js'
|
|
|
3
3
|
export async function mergeTeam (user) {
|
|
4
4
|
const { map, pick } = this.app.lib._
|
|
5
5
|
const { getModel } = this.app.dobo
|
|
6
|
+
user.interSiteAdmin = this.config.interSiteAdmins.includes(user.id)
|
|
6
7
|
user.teams = []
|
|
7
8
|
const query = { userId: user.id, siteId: user.siteId }
|
|
8
9
|
let mdl = getModel('SumbaTeamUser')
|
|
@@ -38,7 +39,7 @@ export async function mergeTeam (user) {
|
|
|
38
39
|
export async function getUserById (id, req) {
|
|
39
40
|
const { getModel } = this.app.dobo
|
|
40
41
|
const { set: setCache, get: getCache } = this.app.bajoCache ?? {}
|
|
41
|
-
const key = `getUserById
|
|
42
|
+
const key = `getUserById|${id}`
|
|
42
43
|
let user
|
|
43
44
|
if (getCache) {
|
|
44
45
|
user = await getCache({ key })
|
|
@@ -56,7 +57,7 @@ export async function getUserById (id, req) {
|
|
|
56
57
|
export async function getUserByToken (token, req) {
|
|
57
58
|
const { getModel } = this.app.dobo
|
|
58
59
|
const { set: setCache, get: getCache } = this.app.bajoCache ?? {}
|
|
59
|
-
const key = `getUserByToken
|
|
60
|
+
const key = `getUserByToken|${token}`
|
|
60
61
|
let user
|
|
61
62
|
if (getCache) {
|
|
62
63
|
user = await getCache({ key })
|
package/lib/remove-site.js
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
|
+
export async function removeRefs (site, options, verbose) {
|
|
2
|
+
const { orderBy } = this.app.lib._
|
|
3
|
+
const { getModel } = this.app.dobo
|
|
4
|
+
const models = orderBy(this.app.dobo.models, ['buildLevel'], ['desc']).filter(m => {
|
|
5
|
+
const prop = m.properties.find(p => p.name === 'siteId')
|
|
6
|
+
return !!prop
|
|
7
|
+
}).map(m => m.name)
|
|
8
|
+
for (const m of models) {
|
|
9
|
+
const mdl = getModel(m)
|
|
10
|
+
const rows = await mdl.findAllRecord({ query: { siteId: site.id } }, { ...options, dataOnly: true })
|
|
11
|
+
const ids = rows.map(item => item.id)
|
|
12
|
+
// TODO: backup
|
|
13
|
+
if (ids.length === 0) continue
|
|
14
|
+
for (const id of ids) {
|
|
15
|
+
await mdl.removeRecord(id, { noReturn: true, ...options })
|
|
16
|
+
}
|
|
17
|
+
if (verbose) this.print.succeed('removedFrom%s%s', mdl.name, ids.length)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
1
21
|
async function removeSite (alias, verbose) {
|
|
2
|
-
const { isEmpty
|
|
22
|
+
const { isEmpty } = this.app.lib._
|
|
3
23
|
const { getModel } = this.app.dobo
|
|
4
24
|
|
|
5
25
|
if (isEmpty(alias)) throw this.error('aliasRequired%s', alias)
|
|
@@ -7,26 +27,10 @@ async function removeSite (alias, verbose) {
|
|
|
7
27
|
const query = { alias }
|
|
8
28
|
const site = await model.findOneRecord({ query })
|
|
9
29
|
if (isEmpty(site)) throw this.error('aliasNotFound%s', alias)
|
|
10
|
-
let spin
|
|
11
|
-
if (verbose) spin = this.print.spinner().start('processing...')
|
|
12
|
-
const models = orderBy(this.app.dobo.models, ['buildLevel'], ['desc']).filter(m => {
|
|
13
|
-
const prop = m.properties.find(p => p.name === 'siteId')
|
|
14
|
-
return !!prop
|
|
15
|
-
}).map(m => m.name)
|
|
16
|
-
if (verbose) spin.stop()
|
|
17
30
|
await model.transaction(async (trx) => {
|
|
18
|
-
const options = { trx }
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const ids = (await mdl.findAllRecord({ query: { siteId: site.id } }, options)).map(item => item.id)
|
|
22
|
-
// TODO: backup
|
|
23
|
-
if (ids.length === 0) continue
|
|
24
|
-
for (const id of ids) {
|
|
25
|
-
await mdl.removeRecord(id, { noReturn: true, ...options })
|
|
26
|
-
}
|
|
27
|
-
if (verbose) this.print.succeed('removedFrom%s%s', mdl.name, ids.length)
|
|
28
|
-
}
|
|
29
|
-
await model.removeRecord(site.id, { noReturn: true, ...options })
|
|
31
|
+
const options = { trx, noHook: true }
|
|
32
|
+
await removeRefs.call(this, site, options, verbose)
|
|
33
|
+
await model.removeRecord(site.id, { noReturn: true, trx, noHook: true })
|
|
30
34
|
if (verbose) this.print.succeed('removedFrom%s%s', model.name, 1)
|
|
31
35
|
})
|
|
32
36
|
if (verbose) this.print.info('done')
|
package/lib/util.js
CHANGED
|
@@ -174,3 +174,18 @@ export async function checkUserId (req, reply, source) {
|
|
|
174
174
|
if (!success) throw this.error('accessDeniedNoAuth', merge({ statusCode: 403 }, payload))
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
|
+
|
|
178
|
+
export async function latLngHook (body, options) {
|
|
179
|
+
const { isSet } = this.app.lib.aneka
|
|
180
|
+
const { round } = this.app.lib.aneka
|
|
181
|
+
if (!isSet(body[options.fieldName])) return
|
|
182
|
+
body[options.fieldName] = round(body[options.fieldName], options.scale)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function checkinterSite (req, reply) {
|
|
186
|
+
const { get } = this.app.lib._
|
|
187
|
+
const isinterSite = get(req, 'routeOptions.config.interSite')
|
|
188
|
+
const isInterSiteAdmin = get(req, 'user.interSiteAdmin')
|
|
189
|
+
if (!isinterSite) return
|
|
190
|
+
if (!isInterSiteAdmin) throw this.error('accessDenied', { statusCode: 403 })
|
|
191
|
+
}
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-03-30
|
|
4
|
+
|
|
5
|
+
- [2.15.0] Add inter site module mechanism
|
|
6
|
+
- [2.15.0] Add admin sub route for inter site modules
|
|
7
|
+
- [2.15.0] Bug fix in ```createNewSite()```
|
|
8
|
+
- [2.15.0] Bug fix in ```removeSite()```
|
|
9
|
+
|
|
10
|
+
## 2026-03-27
|
|
11
|
+
|
|
12
|
+
- [2.14.0] Add support for cache storage management
|
|
13
|
+
|
|
3
14
|
## 2026-03-25
|
|
4
15
|
|
|
5
16
|
- [2.13.0] Add bajo cache handling for ```getUser*()``` and ```getSite()```
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"common": {
|
|
3
|
-
"attachment": true,
|
|
4
|
-
"disabled": ["remove"],
|
|
5
|
-
"layout": [
|
|
6
|
-
{ "name": "Meta", "fields": ["id", "createdAt", "updatedAt"] },
|
|
7
|
-
{ "name": "General", "fields": ["hostname", "alias", "title", "orgName", "email", "status:4-md 6-sm"] },
|
|
8
|
-
{ "name": "Person In Charge", "fields": ["picName:3-md 6-sm:Name", "picRole:3-md 6-sm:Role", "picPhone:3-md 6-sm:Phone", "picEmail:3-md 6-sm:Email"] },
|
|
9
|
-
{ "name": "Address", "fields": ["address1:12", "address2:12", "city:6-md 8-sm", "zipCode:2-md 4-sm", "provinceState:4-md", "country:6-md", "phone:6-md", "website:12"] },
|
|
10
|
-
{ "name": "Social Media", "fields": ["socX:3-md 6-sm", "socInstagram:3-md 6-sm", "socFacebook:3-md 6-sm", "socLinkedIn:3-md 6-sm"] }
|
|
11
|
-
],
|
|
12
|
-
"widget": {
|
|
13
|
-
"country": {
|
|
14
|
-
"component": "form-select-ext"
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
"view": {
|
|
19
|
-
"details": {
|
|
20
|
-
"control": {
|
|
21
|
-
"noBackBtn": true,
|
|
22
|
-
"noCloneBtn": true,
|
|
23
|
-
"editHref": "waibuAdmin:/site/site?edit=true"
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
"edit": {
|
|
27
|
-
"control": {
|
|
28
|
-
"noBackBtn": true,
|
|
29
|
-
"noCloneBtn": true,
|
|
30
|
-
"detailsHref": "waibuAdmin:/site/site"
|
|
31
|
-
},
|
|
32
|
-
"readonly": ["id", "createdAt", "updatedAt"]
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
package/lib/lat-lng-hook.js
DELETED
package/lib/password-rule.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { joiPasswordExtendCore } from 'joi-password'
|
|
2
|
-
|
|
3
|
-
async function passwordRule (req = {}) {
|
|
4
|
-
const { get } = this.app.lib._
|
|
5
|
-
const { importPkg } = this.app.bajo
|
|
6
|
-
const joi = await importPkg('dobo:joi')
|
|
7
|
-
const joiPassword = joi.extend(joiPasswordExtendCore)
|
|
8
|
-
let password = joiPassword
|
|
9
|
-
.string()
|
|
10
|
-
.min(8)
|
|
11
|
-
.max(100)
|
|
12
|
-
.required()
|
|
13
|
-
const cfg = get(req, 'site.setting.sumba.userPassword', this.config.siteSetting.userPassword)
|
|
14
|
-
if (cfg.minUppercase) password = password.minOfUppercase(cfg.minUppercase)
|
|
15
|
-
if (cfg.minLowercase) password = password.minOfLowercase(cfg.minLowercase)
|
|
16
|
-
if (cfg.minSpecialChar) password = password.minOfSpecialCharacters(cfg.minSpecialChar)
|
|
17
|
-
if (cfg.minNumeric) password = password.minOfNumeric(cfg.minNumeric)
|
|
18
|
-
if (cfg.noWhitespace) password = password.noWhiteSpaces()
|
|
19
|
-
if (cfg.latinOnlyChars) password = password.onlyLatinCharacters()
|
|
20
|
-
return password
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export default passwordRule
|
package/lib/reset-token.js
DELETED