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.
@@ -1,9 +1,9 @@
1
1
  async function doboSumbaUserAfterRecordValidation (body, options) {
2
- const { isBcrypt, isMd5, hash } = this.app.bajoExtra
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.password ?? body.token)
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,9 @@
1
+ import resetToken from '../../lib/reset-token.js'
2
+
3
+ async function afterRecordCreate (body, options = {}) {
4
+ const { token, salt } = await resetToken.call(this)
5
+ body.token = token
6
+ body.salt = salt
7
+ }
8
+
9
+ export default afterRecordCreate
@@ -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": "- Akses: %d",
81
- "teamNegRoutes%d": "- Akses, dinegasikan: %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",
@@ -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,
@@ -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 ctx = this.app[source].instance
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[securePath.source].methods ?? []
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, ctx)
74
+ const check = await handler(req, reply, source)
75
75
  if (check) {
76
76
  success = check
77
77
  break
@@ -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
- item.source = source
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}${subNs}:${item.path}`)
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: pattern, prefix: this.name })
38
+ }, { glob: `route/${type}.*`, prefix: this.name })
46
39
  }
47
40
 
48
41
  export default collectRoutes
@@ -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: pattern, prefix: this.name })
16
+ }, { glob: 'route/team.*', prefix: this.name })
25
17
  }
26
18
 
27
19
  export default collectTeam
@@ -0,0 +1,9 @@
1
+ async function resetToken (salt) {
2
+ const { generateId } = this.app.bajo
3
+ const { hash } = this.app.bajoExtra
4
+ salt = salt ?? generateId()
5
+ const token = await hash(await hash(salt))
6
+ return { salt, token }
7
+ }
8
+
9
+ export default resetToken
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sumba",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Bajo Framework's Biz Suite",
5
5
  "main": "index.js",
6
6
  "scripts": {
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._
@@ -0,0 +1,11 @@
1
+ [{
2
+ "path": "/user/**/*",
3
+ "methods": ["*"]
4
+ }, {
5
+ "path": "/signin",
6
+ "methods": ["*"]
7
+ }, {
8
+ "path": "/user/access-token/**/*",
9
+ "routeHandler": "restapi",
10
+ "methods": ["*"]
11
+ }]
@@ -0,0 +1,11 @@
1
+ [
2
+ "/my-stuff/**/*",
3
+ "/signout",
4
+ "/help/trouble-tickets/**/*",
5
+ {
6
+ "path": "/user/api-key",
7
+ "routeHandler": "restapi"
8
+ }, {
9
+ "path": "/my-stuff/**/*",
10
+ "routeHandler": "restapi"
11
+ }]
@@ -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.token)
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.token) })
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, { token: generateId() }, { req, reply, noFlash: true })
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.waibuDb
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({ model, req, reply, options: { dataOnly: true, query, limit: 1, noHook: true } })
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({ model, req, reply, id: result[0].id, body: { status: 'ACTIVE' }, options: { noValidation: true, noFlash: true } })
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 }
@@ -1,5 +1,5 @@
1
1
  const model = 'SumbaUser'
2
- const hidden = ['password', 'token', 'siteId']
2
+ const hidden = ['password', 'token', 'siteId', 'salt']
3
3
 
4
4
  async function get ({ ctx }) {
5
5
  const { recordGet } = this.app.waibuDb
@@ -22,7 +22,7 @@ async function create () {
22
22
  const jwt = await createJwtFromUserRecord(rec)
23
23
  return { data: jwt }
24
24
  }
25
- return { data: { token: await hash(rec.password) } }
25
+ return { data: { token: await hash(rec.salt) } }
26
26
  }
27
27
  return { schema, handler }
28
28
  }
@@ -1,7 +0,0 @@
1
- [{
2
- "path": "/user/**/*",
3
- "methods": ["*"]
4
- }, {
5
- "path": "/signin",
6
- "methods": ["*"]
7
- }]
@@ -1,10 +0,0 @@
1
- [{
2
- "path": "/my-stuff/**/*",
3
- "methods": ["*"]
4
- }, {
5
- "path": "/signout",
6
- "methods": ["*"]
7
- }, {
8
- "path": "/help/trouble-tickets/**/*",
9
- "methods": ["*"]
10
- }]
@@ -1,4 +0,0 @@
1
- [{
2
- "path": "/user/access-token/**/*",
3
- "methods": ["*"]
4
- }]
@@ -1,7 +0,0 @@
1
- [{
2
- "path": "/user/api-key",
3
- "methods": ["*"]
4
- }, {
5
- "path": "/my-stuff/**/*",
6
- "methods": ["*"]
7
- }]