sumba 1.1.3 → 1.1.5
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/bajo/hook/dobo.sumba-user@after-record-validation.js +2 -2
- package/bajo/hook/dobo.sumba-user@before-record-create.js +9 -0
- package/bajo/hook/dobo.sumba-user@before-record-update.js +11 -0
- package/bajo/hook/dobo@before-record-find.js +6 -3
- package/bajo/hook/waibu@after-app-boot.js +3 -0
- package/bajo/intl/id.json +2 -2
- package/dobo/schema/user.json +6 -1
- package/lib/check-user-id.js +4 -4
- package/lib/collect-routes.js +9 -16
- package/lib/collect-team.js +1 -9
- package/lib/reset-token.js +9 -0
- package/package.json +1 -1
- package/plugin/factory.js +7 -0
- package/sumba/route/anonymous.json +11 -0
- package/sumba/route/secure.json +11 -0
- package/waibuMpa/route/my-stuff/profile.js +1 -1
- package/waibuMpa/route/my-stuff/reset-api-key.js +2 -2
- package/waibuMpa/route/user/activation.js +3 -3
- package/waibuRestApi/route/my-stuff/api-key/get.js +14 -0
- package/waibuRestApi/route/my-stuff/api-key/update.js +44 -0
- package/waibuRestApi/route/my-stuff/change-password/update.js +1 -1
- package/waibuRestApi/route/my-stuff/profile/get.js +1 -1
- package/waibuRestApi/route/user/access-token/@type/create.js +1 -1
- package/sumba/waibu-mpa@anonymous-routes.json +0 -7
- package/sumba/waibu-mpa@secure-routes.json +0 -10
- package/sumba/waibu-rest-api.restapi@anonymous-routes.json +0 -4
- package/sumba/waibu-rest-api.restapi@secure-routes.json +0 -7
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
async function doboSumbaUserAfterRecordValidation (body, options) {
|
|
2
|
-
const { isBcrypt,
|
|
2
|
+
const { isBcrypt, hash } = this.app.bajoExtra
|
|
3
3
|
const { has } = this.app.bajo.lib._
|
|
4
4
|
|
|
5
5
|
if (has(body, 'password') && !isBcrypt(body.password)) body.password = await hash(body.password, 'bcrypt')
|
|
6
|
-
if (has(body, 'token') && !isMd5(body.token)) body.token = await hash(body.
|
|
6
|
+
// if (has(body, 'token') && !isMd5(body.token)) body.token = await hash(body.token)
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export default doboSumbaUserAfterRecordValidation
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import resetToken from '../../lib/reset-token.js'
|
|
2
|
+
|
|
3
|
+
async function beforeRecordUpdate (id, body, options = {}) {
|
|
4
|
+
if (body.salt) {
|
|
5
|
+
const { token, salt } = await resetToken.call(this, body.salt)
|
|
6
|
+
body.token = token
|
|
7
|
+
body.salt = salt
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default beforeRecordUpdate
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
const useAdmin = ['waibuAdmin']
|
|
2
|
+
|
|
1
3
|
export async function rebuildFilter (model, filter, req) {
|
|
2
|
-
const { isEmpty, map } = this.app.bajo.lib._
|
|
4
|
+
const { isEmpty, map, find, get } = this.app.bajo.lib._
|
|
3
5
|
const { hasColumn } = this
|
|
4
6
|
filter.query = filter.query ?? {}
|
|
5
7
|
const hasSiteId = await hasColumn('siteId', model)
|
|
6
8
|
const hasUserId = await hasColumn('userId', model)
|
|
7
9
|
const hasTeamId = await hasColumn('teamId', model)
|
|
10
|
+
const isAdmin = find(req.user.teams, { alias: 'administrator' }) && useAdmin.includes(get(req, 'routeOptions.config.ns'))
|
|
8
11
|
if (!(hasSiteId || hasUserId || hasTeamId)) return filter
|
|
9
12
|
const q = { $and: [] }
|
|
10
13
|
if (!isEmpty(filter.query)) {
|
|
@@ -12,11 +15,11 @@ export async function rebuildFilter (model, filter, req) {
|
|
|
12
15
|
else q.$and.push(filter.query)
|
|
13
16
|
}
|
|
14
17
|
if (hasSiteId) q.$and.push({ siteId: req.site.id })
|
|
15
|
-
if (hasTeamId) {
|
|
18
|
+
if (hasTeamId && !isAdmin) {
|
|
16
19
|
const teamIds = map(req.user.teams, 'id')
|
|
17
20
|
if (hasUserId) q.$and.push({ $or: [{ teamId: { $in: teamIds } }, { userId: req.user.id }] })
|
|
18
21
|
else q.$and.push({ teamId: { $in: teamIds } })
|
|
19
|
-
} else {
|
|
22
|
+
} else if (!isAdmin) {
|
|
20
23
|
if (hasUserId) q.$and.push({ userId: req.user.id })
|
|
21
24
|
}
|
|
22
25
|
filter.query = q
|
|
@@ -2,6 +2,8 @@ import collectRoutes from '../../lib/collect-routes.js'
|
|
|
2
2
|
import collectTeam from '../../lib/collect-team.js'
|
|
3
3
|
|
|
4
4
|
async function afterAppBoot () {
|
|
5
|
+
const { runHook } = this.app.bajo
|
|
6
|
+
await runHook(`${this.name}:beforeBoot`)
|
|
5
7
|
this.log.trace('collectingRouteGuards')
|
|
6
8
|
await collectRoutes.call(this, 'secure')
|
|
7
9
|
this.log.trace('secureRoutes%d', this.secureRoutes.length)
|
|
@@ -13,6 +15,7 @@ async function afterAppBoot () {
|
|
|
13
15
|
await collectTeam.call(this)
|
|
14
16
|
this.log.trace('teamRoutes%d', this.teamRoutes.length)
|
|
15
17
|
this.log.trace('teamNegRoutes%d', this.teamNegRoutes.length)
|
|
18
|
+
await runHook(`${this.name}:afterBoot`)
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
export default afterAppBoot
|
package/bajo/intl/id.json
CHANGED
|
@@ -77,8 +77,8 @@
|
|
|
77
77
|
"forgotPasswordLink": "Tautan Lupa Kata Sandi",
|
|
78
78
|
"forgotPasswordChanged": "Kata Sandi Sukses Diubah",
|
|
79
79
|
"collectingTeamGuards": "Mengoleksi pertahanan tim:",
|
|
80
|
-
"teamRoutes%d": "-
|
|
81
|
-
"teamNegRoutes%d": "-
|
|
80
|
+
"teamRoutes%d": "- Jalur: %d",
|
|
81
|
+
"teamNegRoutes%d": "- Jalur, dinegasikan: %d",
|
|
82
82
|
"moreInfoContactAdmin": "Mohon <c:a href='sumba:/help/contact-form'>hubungi</c:a> Admin Anda untuk keterangan lebih lanjut, terima kasih!",
|
|
83
83
|
"restricted": "Pembatasan",
|
|
84
84
|
"fullscreen": "Layar Penuh",
|
package/dobo/schema/user.json
CHANGED
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
"type": "string",
|
|
16
16
|
"maxLength": 50,
|
|
17
17
|
"index": true
|
|
18
|
+
}, {
|
|
19
|
+
"name": "salt",
|
|
20
|
+
"type": "string",
|
|
21
|
+
"maxLength": 50,
|
|
22
|
+
"required": true
|
|
18
23
|
}, {
|
|
19
24
|
"name": "email",
|
|
20
25
|
"type": "string",
|
|
@@ -43,7 +48,7 @@
|
|
|
43
48
|
"fields": ["email", "siteId"],
|
|
44
49
|
"unique": true
|
|
45
50
|
}],
|
|
46
|
-
"hidden": ["password"],
|
|
51
|
+
"hidden": ["password", "token"],
|
|
47
52
|
"feature": {
|
|
48
53
|
"sumba.address": true,
|
|
49
54
|
"sumba.social": true,
|
package/lib/check-user-id.js
CHANGED
|
@@ -29,10 +29,10 @@ async function mergeSetting (req) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
async function checkUserId (req, reply, source) {
|
|
32
|
-
const { isEmpty, camelCase } = this.app.bajo.lib._
|
|
32
|
+
const { isEmpty, camelCase, get } = this.app.bajo.lib._
|
|
33
33
|
const { routePath } = this.app.waibu
|
|
34
34
|
|
|
35
|
-
const
|
|
35
|
+
const webApp = get(req, 'routeOptions.config.webApp', 'waibu')
|
|
36
36
|
if (!req.routeOptions.url) {
|
|
37
37
|
if (!req.session) return
|
|
38
38
|
await setUser.call(this, req)
|
|
@@ -65,13 +65,13 @@ async function checkUserId (req, reply, source) {
|
|
|
65
65
|
await setUser.call(this, req)
|
|
66
66
|
return
|
|
67
67
|
}
|
|
68
|
-
const authMethods = this.config.auth[
|
|
68
|
+
const authMethods = this.config.auth[webApp].methods ?? []
|
|
69
69
|
if (isEmpty(authMethods)) throw this.error('noAuthMethod', { statusCode: 500 })
|
|
70
70
|
let success
|
|
71
71
|
for (const m of authMethods) {
|
|
72
72
|
const handler = this[camelCase(`verify ${m}`)]
|
|
73
73
|
if (!handler) throw this.error('invalidAuthMethod%s', m, { statusCode: 500 })
|
|
74
|
-
const check = await handler(req, reply, source
|
|
74
|
+
const check = await handler(req, reply, source)
|
|
75
75
|
if (check) {
|
|
76
76
|
success = check
|
|
77
77
|
break
|
package/lib/collect-routes.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
export async function collect ({ type = '', handler, container, file, ns, dir }) {
|
|
2
2
|
const { readConfig } = this.app.bajo
|
|
3
|
-
const { routePath } = this.app.waibu
|
|
4
|
-
const { camelCase, find, isString } = this.app.bajo.lib._
|
|
3
|
+
const { routePath, routePathHandlers } = this.app.waibu
|
|
4
|
+
const { camelCase, find, isString, isEmpty } = this.app.bajo.lib._
|
|
5
5
|
const items = await readConfig(file, { ignoreError: true })
|
|
6
|
-
const [item] = file.replace(dir, '').split('@')
|
|
7
|
-
let [source, subNs] = item.split('.').map(s => camelCase(s))
|
|
8
|
-
subNs = subNs ? `.${subNs}` : ''
|
|
9
6
|
|
|
10
7
|
for (let item of items) {
|
|
11
8
|
if (isString(item)) item = { path: item }
|
|
12
|
-
|
|
9
|
+
const routeHandler = item.routeHandler
|
|
10
|
+
delete item.routeHandler
|
|
11
|
+
if (!isEmpty(routeHandler) && !routePathHandlers[routeHandler]) continue
|
|
12
|
+
const rns = routeHandler ? routePathHandlers[routeHandler].ns : 'waibuMpa'
|
|
13
|
+
if (!this.app[rns]) continue
|
|
13
14
|
const isNeg = item.path[0] === '!'
|
|
14
15
|
if (isNeg) item.path = item.path.slice(1)
|
|
15
|
-
item.path = routePath(`${ns}${
|
|
16
|
+
item.path = routePath(`${ns}${routeHandler ? ('.' + routeHandler) : ''}:${item.path}`)
|
|
16
17
|
item.methods = item.methods ?? ['*']
|
|
17
18
|
if (handler) await handler.call(this, item)
|
|
18
19
|
const guards = this[camelCase(`${type} ${isNeg ? 'Neg' : ''} ${container}`)]
|
|
@@ -31,18 +32,10 @@ function handler (item) {
|
|
|
31
32
|
|
|
32
33
|
async function collectRoutes (type) {
|
|
33
34
|
const { eachPlugins } = this.app.bajo
|
|
34
|
-
|
|
35
|
-
this[`${type}Routes`] = this[`${type}Routes`] ?? []
|
|
36
|
-
this[`${type}NegRoutes`] = this[`${type}NegRoutes`] ?? []
|
|
37
|
-
const items = []
|
|
38
|
-
if (this.app.waibuStatic) items.push('waibuStatic.asset', 'waibuStatic.virtual', 'waibu-static.asset', 'waibu-static.virtual')
|
|
39
|
-
if (this.app.waibuRestApi) items.push('waibuRestApi.restapi', 'waibu-rest-api.restapi')
|
|
40
|
-
if (this.app.waibuMpa) items.push('waibuMpa', 'waibu-mpa')
|
|
41
|
-
const pattern = `{${items.join(',')}}@${type}-routes.*`
|
|
42
35
|
const me = this
|
|
43
36
|
await eachPlugins(async function ({ file, ns, dir }) {
|
|
44
37
|
await collect.call(me, { type, container: 'Routes', handler, file, ns, dir })
|
|
45
|
-
}, { glob:
|
|
38
|
+
}, { glob: `route/${type}.*`, prefix: this.name })
|
|
46
39
|
}
|
|
47
40
|
|
|
48
41
|
export default collectRoutes
|
package/lib/collect-team.js
CHANGED
|
@@ -10,18 +10,10 @@ function handler (item) {
|
|
|
10
10
|
|
|
11
11
|
async function collectTeam () {
|
|
12
12
|
const { eachPlugins } = this.app.bajo
|
|
13
|
-
|
|
14
|
-
this.teamRoutes = this.teamRoutes ?? []
|
|
15
|
-
this.teamNegRoutes = this.teamNegRoutes ?? []
|
|
16
|
-
const items = []
|
|
17
|
-
if (this.app.waibuStatic) items.push('waibuStatic.asset', 'waibuStatic.virtual', 'waibu-static.asset', 'waibu-static.virtual')
|
|
18
|
-
if (this.app.waibuRestApi) items.push('waibuRestApi', 'waibu-rest-api')
|
|
19
|
-
if (this.app.waibuMpa) items.push('waibuMpa', 'waibu-mpa')
|
|
20
|
-
const pattern = `{${items.join(',')}}@team-routes.*`
|
|
21
13
|
const me = this
|
|
22
14
|
await eachPlugins(async function ({ file, ns, dir }) {
|
|
23
15
|
await collect.call(me, { type: 'team', container: 'Routes', handler, file, ns, dir })
|
|
24
|
-
}, { glob:
|
|
16
|
+
}, { glob: 'route/team.*', prefix: this.name })
|
|
25
17
|
}
|
|
26
18
|
|
|
27
19
|
export default collectTeam
|
package/package.json
CHANGED
package/plugin/factory.js
CHANGED
|
@@ -104,6 +104,13 @@ async function factory (pkgName) {
|
|
|
104
104
|
this.unsafeUserFields = ['password']
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
init = async () => {
|
|
108
|
+
for (const type of ['secure', 'anonymous', 'team']) {
|
|
109
|
+
this[`${type}Routes`] = this[`${type}Routes`] ?? []
|
|
110
|
+
this[`${type}NegRoutes`] = this[`${type}NegRoutes`] ?? []
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
107
114
|
hasColumn = async (name, model) => {
|
|
108
115
|
const { getInfo } = this.app.dobo
|
|
109
116
|
const { find } = this.app.bajo.lib._
|
|
@@ -6,7 +6,7 @@ const profile = {
|
|
|
6
6
|
const options = { forceNoHidden: true, noHook: true, noCache: true, attachment: true, mimeType: true }
|
|
7
7
|
const resp = await recordGet({ model: 'SumbaUser', req, id: req.user.id, options })
|
|
8
8
|
const form = resp.data
|
|
9
|
-
form.token = await hash(form.
|
|
9
|
+
form.token = await hash(form.salt)
|
|
10
10
|
return reply.view('sumba.template:/my-stuff/profile/view.html', { form })
|
|
11
11
|
}
|
|
12
12
|
}
|
|
@@ -9,7 +9,7 @@ const resetApiKey = {
|
|
|
9
9
|
const delay = await importPkg('delay')
|
|
10
10
|
const bcrypt = await importPkg('bajoExtra:bcrypt')
|
|
11
11
|
const Joi = await importPkg('dobo:joi')
|
|
12
|
-
const form = defaultsDeep(req.body, { apiKey: await hash(req.user.
|
|
12
|
+
const form = defaultsDeep(req.body, { apiKey: await hash(req.user.salt) })
|
|
13
13
|
let error
|
|
14
14
|
if (req.method === 'POST') {
|
|
15
15
|
try {
|
|
@@ -24,7 +24,7 @@ const resetApiKey = {
|
|
|
24
24
|
const rec = await recordGet(model, req.user.id, { forceNoHidden: true })
|
|
25
25
|
const verified = await bcrypt.compare(req.body.password, rec.password)
|
|
26
26
|
if (!verified) throw this.error('validationError', { details: [{ field: 'password', error: 'invalidPassword' }], statusCode: 400 })
|
|
27
|
-
await recordUpdate(model, req.user.id, {
|
|
27
|
+
await recordUpdate(model, req.user.id, { salt: generateId() }, { req, reply, noFlash: true })
|
|
28
28
|
await delay(2000) // ensure req.user cache is expired
|
|
29
29
|
req.flash('notify', req.t('resetApiKeySuccessfull'))
|
|
30
30
|
return reply.redirectTo('sumba:/my-stuff/profile')
|
|
@@ -4,15 +4,15 @@ const userActivation = {
|
|
|
4
4
|
method: ['GET', 'POST'],
|
|
5
5
|
handler: async function (req, reply) {
|
|
6
6
|
const { defaultsDeep } = this.app.bajo
|
|
7
|
-
const { recordFind, recordUpdate } = this.app.
|
|
7
|
+
const { recordFind, recordUpdate } = this.app.dobo
|
|
8
8
|
const form = defaultsDeep(req.body, { key: req.query.key })
|
|
9
9
|
let error
|
|
10
10
|
if (req.method === 'POST') {
|
|
11
11
|
try {
|
|
12
12
|
const query = { status: 'UNVERIFIED', token: req.body.key }
|
|
13
|
-
const result = await recordFind(
|
|
13
|
+
const result = await recordFind(model, { query, limit: 1 })
|
|
14
14
|
if (result.length === 0) throw this.error('validationError', { details: [{ field: 'key', error: 'invalidActivationKey' }] })
|
|
15
|
-
await recordUpdate(
|
|
15
|
+
await recordUpdate(model, result[0].id, { status: 'ACTIVE' }, { noValidation: true, noFlash: true })
|
|
16
16
|
req.flash('notify', req.t('userActivated'))
|
|
17
17
|
return reply.redirectTo(this.config.redirect.signin, req)
|
|
18
18
|
} catch (err) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { response } from './update.js'
|
|
2
|
+
|
|
3
|
+
async function get ({ ctx }) {
|
|
4
|
+
const { hash } = this.app.bajoExtra
|
|
5
|
+
const { recordGet } = this.app.dobo
|
|
6
|
+
const schema = { response: await response.call(this) }
|
|
7
|
+
const handler = async function get (req, reply) {
|
|
8
|
+
const rec = await recordGet('SumbaUser', req.user.id, { forceNoHidden: true })
|
|
9
|
+
return { data: { token: await hash(rec.salt) } }
|
|
10
|
+
}
|
|
11
|
+
return { schema, handler }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default get
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { data } from '../../../../lib/token-schema.js'
|
|
2
|
+
|
|
3
|
+
export function response () {
|
|
4
|
+
return {
|
|
5
|
+
'2xx': {
|
|
6
|
+
description: 'Successfull response',
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: this.app.waibuRestApi.transformResult({ data })
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const body = {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
password: {
|
|
17
|
+
type: 'string'
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const model = 'SumbaUser'
|
|
23
|
+
|
|
24
|
+
async function update ({ ctx }) {
|
|
25
|
+
const { importPkg, generateId } = this.app.bajo
|
|
26
|
+
const { recordGet, recordUpdate } = this.app.dobo
|
|
27
|
+
const { hash } = this.app.bajoExtra
|
|
28
|
+
const bcrypt = await importPkg('bajoExtra:bcrypt')
|
|
29
|
+
|
|
30
|
+
const schema = { body, response: await response.call(this) }
|
|
31
|
+
|
|
32
|
+
const handler = async function get (req, reply, options) {
|
|
33
|
+
const rec = await recordGet(model, req.user.id, { forceNoHidden: true })
|
|
34
|
+
const verified = await bcrypt.compare(req.body.password, rec.password)
|
|
35
|
+
if (!verified) throw this.error('invalidPassword', { details: [{ field: 'password', error: 'invalidPassword' }], statusCode: 400 })
|
|
36
|
+
const input = { salt: generateId() }
|
|
37
|
+
const resp = await recordUpdate(model, req.user.id, input, { forceNoHidden: true })
|
|
38
|
+
return { data: { token: await hash(resp.salt) } }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { schema, handler }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default update
|
|
@@ -40,7 +40,7 @@ async function update ({ ctx }) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
const handler = async function get (req, reply, options) {
|
|
43
|
-
const rec = await recordGet(model, req.user.id)
|
|
43
|
+
const rec = await recordGet(model, req.user.id, { forceNoHidden: true })
|
|
44
44
|
const verified = await bcrypt.compare(req.body.currentPassword, rec.password)
|
|
45
45
|
if (!verified) throw this.error('invalidCurrentPassword', { details: [{ field: 'current', error: 'invalidPassword' }], statusCode: 400 })
|
|
46
46
|
const input = { password: req.body.password }
|