sumba 2.14.0 → 2.15.1

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.
Files changed (43) hide show
  1. package/extend/bajo/hook/dobo.sumba-site@after-create-record.js +8 -0
  2. package/extend/bajo/hook/dobo.sumba-site@after-remove-record.js +7 -0
  3. package/extend/bajo/hook/dobo.sumba-user@before-create-record.js +1 -3
  4. package/extend/bajo/hook/dobo.sumba-user@before-record-validation.js +1 -3
  5. package/extend/bajo/hook/dobo.sumba-user@before-update-record.js +1 -3
  6. package/extend/bajo/hook/dobo@before-create-record.js +1 -2
  7. package/extend/bajo/hook/dobo@before-find-record.js +4 -0
  8. package/extend/bajo/hook/dobo@before-get-record.js +2 -3
  9. package/extend/bajo/hook/waibu-mpa@pre-parsing.js +2 -1
  10. package/extend/bajo/hook/waibu-rest-api@pre-parsing.js +2 -1
  11. package/extend/bajo/hook/waibu-static@pre-parsing.js +2 -1
  12. package/extend/bajo/intl/en-US.json +3 -0
  13. package/extend/bajo/intl/id.json +3 -0
  14. package/extend/bajoCli/applet/create-new-site.js +1 -1
  15. package/extend/dobo/feature/lat-lng.js +1 -1
  16. package/extend/dobo/feature/lat.js +1 -1
  17. package/extend/dobo/feature/lng.js +1 -1
  18. package/extend/dobo/feature/slug.js +3 -3
  19. package/extend/dobo/feature/user-id.js +1 -2
  20. package/extend/waibuDb/schema/site.js +46 -0
  21. package/extend/waibuMpa/extend/waibuAdmin/route/{cache → _is_/cache}/@action.js +1 -0
  22. package/extend/waibuMpa/extend/waibuAdmin/route/{session → _is_/session}/@action.js +1 -0
  23. package/extend/waibuMpa/extend/waibuAdmin/route/_is_/site/@action.js +12 -0
  24. package/extend/waibuMpa/extend/waibuAdmin/route/reset-user-password.js +1 -3
  25. package/extend/waibuMpa/route/help/contact-form.js +2 -2
  26. package/extend/waibuMpa/route/help/trouble-tickets/add.js +2 -2
  27. package/extend/waibuMpa/route/help/trouble-tickets/details/@id.js +2 -2
  28. package/extend/waibuMpa/route/user/activation.js +3 -3
  29. package/extend/waibuMpa/route/user/forgot-password/@fpl.js +5 -7
  30. package/extend/waibuMpa/route/user/forgot-password.js +2 -3
  31. package/extend/waibuMpa/route/your-stuff/change-password.js +1 -3
  32. package/index.js +83 -24
  33. package/lib/create-new-site.js +42 -26
  34. package/lib/get-site.js +5 -6
  35. package/lib/get-user.js +3 -2
  36. package/lib/remove-site.js +24 -20
  37. package/lib/util.js +15 -0
  38. package/package.json +1 -1
  39. package/wiki/CHANGES.md +8 -0
  40. package/extend/waibuDb/schema/site.json +0 -35
  41. package/lib/lat-lng-hook.js +0 -8
  42. package/lib/password-rule.js +0 -23
  43. 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
@@ -0,0 +1,7 @@
1
+ import { removeRefs } from '../../../lib/remove-site.js'
2
+
3
+ async function afterRemoveRecord (id, result, options) {
4
+ await removeRefs.call(this, result.oldData, options)
5
+ }
6
+
7
+ export default afterRemoveRecord
@@ -1,7 +1,5 @@
1
- import resetToken from '../../../lib/reset-token.js'
2
-
3
1
  async function beforeCreateRecord (body, options = {}) {
4
- const { token, salt } = await resetToken.call(this)
2
+ const { token, salt } = await this.resetToken()
5
3
  body.token = token
6
4
  body.salt = salt
7
5
  }
@@ -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.call(this, options.req)
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.call(this, body.salt)
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
@@ -9,9 +9,8 @@ export async function checker (modelName, id, options = {}) {
9
9
  await rebuildFilter.call(this, modelName, filter, req)
10
10
  if (filter.query.$and) filter.query.$and.push({ id })
11
11
  else filter.query.id = id
12
- filter.limit = 1
13
- const rows = await model.findRecord(filter, { count: false })
14
- if (rows.length === 0) throw this.app.dobo.error('recordNotFound%s%s', id, this.name, { statusCode: 404 })
12
+ const row = await model.findOneRecord(filter, { count: false })
13
+ if (!row) throw this.app.dobo.error('recordNotFound%s%s', id, this.name, { statusCode: 404 })
15
14
  }
16
15
 
17
16
  const doboBeforeGetRecord = {
@@ -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
 
@@ -124,6 +124,9 @@
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
126
  "cacheStorage": "Cache Storage",
127
+ "protectedArea": "Protected Area",
128
+ "pleaseAuthenticate": "Please authenticate yourself, thank you!",
129
+ "manageAllSite": "Manage All Sites",
127
130
  "field": {
128
131
  "currentPassword": "Current Password",
129
132
  "newPassword": "New Password",
@@ -125,6 +125,9 @@
125
125
  "aboutToCreateSite%s": "Anda akan membuat sebuah situs dengan alias '%s'. Lanjutkan?",
126
126
  "removedFrom%s%s": "Dihapus dari '%s' (%s data)",
127
127
  "cacheStorage": "Penyimpanan Cache",
128
+ "protectedArea": "Wilayah Dilindungi",
129
+ "pleaseAuthenticate": "Silahkan melakukan otentikasi terlebih dahulu, terima kasih!",
130
+ "manageAllSite": "Kelola Semua Situs",
128
131
  "field": {
129
132
  "currentPassword": "Kata Sandi Saat Ini",
130
133
  "newPassword": "Kata Sandi Baru",
@@ -13,7 +13,7 @@ async function createSite (path, ...args) {
13
13
  try {
14
14
  await createNewSite(alias, hostname, true)
15
15
  } catch (err) {
16
- this.print.fatal(err.message)
16
+ this.print.fatal(err)
17
17
  }
18
18
  this.app.exit()
19
19
  }
@@ -1,4 +1,4 @@
1
- import latLngHook from '../../../lib/lat-lng-hook.js'
1
+ import { latLngHook } from '../../../lib/util.js'
2
2
 
3
3
  async function latLng (opts = {}) {
4
4
  const { merge } = this.app.lib._
@@ -1,4 +1,4 @@
1
- import latLngHook from '../../../lib/lat-lng-hook.js'
1
+ import { latLngHook } from '../../../lib/util.js'
2
2
 
3
3
  async function lat (opts = {}) {
4
4
  opts.fieldName = opts.fieldName ?? 'lat'
@@ -1,4 +1,4 @@
1
- import latLngHook from '../../../lib/lat-lng-hook.js'
1
+ import { latLngHook } from '../../../lib/util.js'
2
2
 
3
3
  async function lng (opts = {}) {
4
4
  opts.fieldName = opts.fieldName ?? 'lng'
@@ -5,9 +5,9 @@ async function autoInc (body, opts) {
5
5
  const query = set({}, opts.fieldName, { $regex: new RegExp('^' + body[opts.fieldName]) })
6
6
  const sort = set({}, opts.fieldName, -1)
7
7
  const options = { noHook: true, skipCache: true, thrownNotFound: false }
8
- const resp = await this.findRecord({ query, limit: 1, sort }, options)
9
- if (resp.length === 0) return body[opts.fieldName]
10
- const rslugs = resp[0][opts.fieldName].split('-')
8
+ const resp = await this.findOneRecord({ query, sort }, options)
9
+ if (resp) return body[opts.fieldName]
10
+ const rslugs = resp[opts.fieldName].split('-')
11
11
  const slugs = body[opts.fieldName].split('-')
12
12
  let num
13
13
  if (Number(last(rslugs)) && body[opts.fieldName] === rslugs.slice(0, rslugs.length - 1).join('-')) {
@@ -1,8 +1,7 @@
1
1
  async function userId (opts = {}) {
2
- opts.fieldName = opts.fieldName ?? 'userId'
3
2
  return {
4
3
  properties: [{
5
- name: opts.fieldName,
4
+ name: 'userId',
6
5
  type: 'string',
7
6
  maxLength: 50,
8
7
  ref: {
@@ -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
@@ -1,6 +1,7 @@
1
1
  const action = {
2
2
  method: ['GET', 'POST'],
3
3
  title: 'cacheStorage',
4
+ interSite: true,
4
5
  handler: async function (req, reply) {
5
6
  if (!this.app.bajoCache) throw this.error('_notFound')
6
7
  const { importModule } = this.app.bajo
@@ -1,6 +1,7 @@
1
1
  const action = {
2
2
  method: ['GET', 'POST'],
3
3
  title: 'userSession',
4
+ interSite: true,
4
5
  handler: async function (req, reply) {
5
6
  const { importModule } = this.app.bajo
6
7
  const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
@@ -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.call(this, req)
13
+ const password = await this.passwordRule(req)
16
14
  const schema = Joi.object({
17
15
  username: Joi.string().max(50).required(),
18
16
  password,
@@ -3,7 +3,7 @@ const contactForm = {
3
3
  handler: async function (req, reply) {
4
4
  const { defaultsDeep } = this.app.lib.aneka
5
5
  const { pick } = this.app.lib._
6
- const { createRecord, findRecord } = this.app.waibuDb
6
+ const { createRecord, findAllRecord } = this.app.waibuDb
7
7
 
8
8
  const def = req.user ? pick(req.user, ['firstName', 'lastName', 'email']) : {}
9
9
  const form = defaultsDeep(req.body, def)
@@ -17,7 +17,7 @@ const contactForm = {
17
17
  error = err
18
18
  }
19
19
  }
20
- const cats = await findRecord({ model: 'SumbaContactFormCat', req, options: { sort: 'level:1+name:1', limit: -1, dataOnly: true } })
20
+ const cats = await findAllRecord({ model: 'SumbaContactFormCat', req, options: { sort: 'level:1+name:1', dataOnly: true } })
21
21
  return await reply.view('sumba.template:/help/contact-form/form.html', { form, error, cats })
22
22
  }
23
23
  }
@@ -4,7 +4,7 @@ const add = {
4
4
  method: ['GET', 'POST'],
5
5
  handler: async function (req, reply) {
6
6
  const { defaultsDeep } = this.app.lib.aneka
7
- const { createRecord, findRecord } = this.app.waibuDb
7
+ const { createRecord, findAllRecord } = this.app.waibuDb
8
8
  const options = {}
9
9
  const form = defaultsDeep(req.body, {})
10
10
  let error
@@ -16,7 +16,7 @@ const add = {
16
16
  error = err
17
17
  }
18
18
  }
19
- const cats = await findRecord({ model: 'SumbaTicketCat', req, options: { sort: 'level:1+name:1', limit: -1, dataOnly: true } })
19
+ const cats = await findAllRecord({ model: 'SumbaTicketCat', req, options: { sort: 'level:1+name:1', dataOnly: true } })
20
20
  return await reply.view('sumba.template:/help/trouble-tickets/add.html', { form, error, cats })
21
21
  }
22
22
  }
@@ -4,10 +4,10 @@ const id = {
4
4
  method: ['GET', 'POST'],
5
5
  handler: async function (req, reply) {
6
6
  const { cloneDeep } = this.app.lib._
7
- const { createRecord, findRecord, getSchemaExt } = this.app.waibuDb
7
+ const { createRecord, findOneRecord, findRecord, getSchemaExt } = this.app.waibuDb
8
8
  const { schema } = await getSchemaExt(model, 'list')
9
9
 
10
- const master = (await findRecord({ model: 'SumbaTicket', req, options: { dataOnly: true, query: { id: req.params.id }, limit: 1 } }))[0]
10
+ const master = await findOneRecord({ model: 'SumbaTicket', req, options: { dataOnly: true, query: { id: req.params.id } } })
11
11
  if (!master) throw this.error('_notFound')
12
12
  const form = cloneDeep(req.body)
13
13
  let error
@@ -8,10 +8,10 @@ const userActivation = {
8
8
  if (req.method === 'POST') {
9
9
  try {
10
10
  const query = { status: 'UNVERIFIED', token: req.body.key }
11
- const result = await model.findRecord({ query, limit: 1 })
12
- if (result.length === 0) throw this.error('validationError', { details: [{ field: 'key', error: 'invalidActivationKey' }] })
11
+ const result = await model.findOneRecord({ query })
12
+ if (result) throw this.error('validationError', { details: [{ field: 'key', error: 'invalidActivationKey' }] })
13
13
  await model.transaction(async (trx) => {
14
- await model.updateRecord(result[0].id, { status: 'ACTIVE' }, { req, noValidation: true, noFlash: true, trx })
14
+ await model.updateRecord(result.id, { status: 'ACTIVE' }, { req, noValidation: true, noFlash: true, trx })
15
15
  })
16
16
  req.flash('notify', req.t('userActivated'))
17
17
  return reply.redirectTo(this.config.redirect.signin, req)
@@ -1,17 +1,15 @@
1
- import passwordRule from '../../../../../lib/password-rule.js'
2
-
3
1
  async function getUser (req, reply) {
4
2
  const { dayjs } = this.app.lib
5
- const { findRecord } = this.app.waibuDb
3
+ const { findOneRecord } = this.app.waibuDb
6
4
  const invalidFpl = 'sumba.template:/user/fpl-invalid.html'
7
5
  if (Buffer.from(req.params.fpl, 'base64').toString('base64') !== req.params.fpl) return invalidFpl
8
6
  const fpToken = Buffer.from(req.params.fpl, 'base64').toString()
9
7
  const [token, sec] = fpToken.split(':')
10
8
  if (dayjs().unix() > Number(sec)) return invalidFpl
11
9
  const query = { token, status: 'ACTIVE' }
12
- const users = await findRecord({ model: 'SumbaUser', req, options: { query, limit: 1, dataOnly: true, noHook: true } })
13
- if (users.length === 0) return invalidFpl
14
- return users[0]
10
+ const user = await findOneRecord({ model: 'SumbaUser', req, options: { query, dataOnly: true, noHook: true } })
11
+ if (user) return invalidFpl
12
+ return user
15
13
  }
16
14
 
17
15
  const forgotPasswordLink = {
@@ -30,7 +28,7 @@ const forgotPasswordLink = {
30
28
  let error
31
29
  if (req.method === 'POST') {
32
30
  try {
33
- const newPassword = await passwordRule.call(this, req)
31
+ const newPassword = await this.passwordRule(req)
34
32
  const schema = Joi.object({
35
33
  newPassword,
36
34
  verifyNewPassword: Joi.ref('newPassword')
@@ -11,9 +11,8 @@ const profile = {
11
11
  if (req.method === 'POST') {
12
12
  try {
13
13
  const query = { status: 'ACTIVE', $or: [{ username: req.body.usernameEmail }, { email: req.body.usernameEmail }] }
14
- const result = await model.findRecord({ query, limit: 1 }, { dataOnly: true, noHook: true, forceNoHidden: true })
15
- if (result.length === 0) throw this.error('validationError', { details: [{ field: 'usernameEmail', error: 'unknownUsernameEmailOrInactive' }] })
16
- const data = result[0]
14
+ const data = await model.findOneRecord({ query }, { dataOnly: true, noHook: true, forceNoHidden: true })
15
+ if (data) throw this.error('validationError', { details: [{ field: 'usernameEmail', error: 'unknownUsernameEmailOrInactive' }] })
17
16
  const to = `${data.firstName} ${data.lastName} <${data.email}>`
18
17
  const subject = req.t('forgotPasswordLink')
19
18
  const options = { req, reply }
@@ -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.call(this, req)
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
- enabled: false,
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-Sumba-ApiKey'
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-Sumba-Token',
101
+ headerKey: 'X-Auth-Jwt',
96
102
  secret: '668de9cf57316c7dbf52f7ff7611c299',
97
- expiresIn: 604800000
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: 'Protected Area',
113
- warningMessage: 'Please authenticate yourself, thank you!'
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
- minUppercase: 1,
129
- minLowercase: 1,
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,11 +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` },
208
- { title: 'cacheStorage', href: `waibuAdmin:/${prefix}/cache/list` }
240
+ { title: 'userSession', href: `waibuAdmin:/${prefix}/_is_/session/list` },
241
+ { title: 'cacheStorage', href: `waibuAdmin:/${prefix}/_is_/cache/list` }
209
242
  ]
210
243
  }]
211
244
  }
@@ -219,13 +252,13 @@ async function factory (pkgName) {
219
252
  const fastJwt = await importPkg('bajoExtra:fast-jwt')
220
253
  const { createSigner } = fastJwt
221
254
 
222
- const opts = pick(this.config.auth.common.jwt, ['expiresIn'])
255
+ const opts = pick(this.config.auth.common.jwt, ['expiresInDur'])
223
256
  opts.key = get(this.config, 'auth.common.jwt.secret')
224
257
  const sign = createSigner(opts)
225
258
  const apiKey = await hash(rec.password)
226
259
  const payload = { uid: rec.id, apiKey }
227
260
  const token = await sign(payload)
228
- const expiresAt = dayjs().add(opts.expiresIn).toDate()
261
+ const expiresAt = dayjs().add(opts.expiresInDur).toDate()
229
262
  return { token, expiresAt }
230
263
  }
231
264
 
@@ -260,11 +293,9 @@ async function factory (pkgName) {
260
293
  const { isEmpty, merge } = this.app.lib._
261
294
 
262
295
  const setHeader = async (setting, reply) => {
263
- const { isString } = this.app.lib._
264
-
265
296
  let header = setting.type
266
297
  const exts = []
267
- if (isString(setting.realm)) exts.push(`realm="${setting.realm}"`)
298
+ if (setting.realm) exts.push(`realm="${req.t('protectedArea')}"`)
268
299
  if (setting.useUtf8) exts.push('charset="UTF-8"')
269
300
  if (exts.length > 0) header += ` ${exts.join(', ')}`
270
301
  reply.header('WWW-Authenticate', header)
@@ -278,7 +309,7 @@ async function factory (pkgName) {
278
309
  if (isEmpty(authInfo)) {
279
310
  if (setting.realm) {
280
311
  await setHeader(setting, reply)
281
- throw this.error(setting.warningMessage)
312
+ throw this.error(req.t('pleaseAuthenticate'), { statusCode: 403 })
282
313
  } else return false
283
314
  }
284
315
  const decoded = Buffer.from(authInfo, 'base64').toString()
@@ -398,10 +429,10 @@ async function factory (pkgName) {
398
429
  const { generateId } = this.app.lib.aneka
399
430
  const cfg = get(req, 'site.setting.sumba.userPassword', this.config.siteSetting.userPassword)
400
431
  let passwd = generateId()
401
- if (cfg.minLowercase) passwd += generateId({ pattern: 'abcdefghijklmnopqrstuvwxyz', length: cfg.minLowercase })
402
- if (cfg.minUppercase) passwd += generateId({ pattern: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', length: cfg.minUppercase })
403
- if (cfg.minSpecialChar) passwd += generateId({ pattern: '!@#$%*', length: cfg.minSpecialChar })
404
- if (cfg.minNumeric) passwd += generateId({ pattern: '0123456789', length: cfg.minNumeric })
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 })
405
436
  return passwd
406
437
  }
407
438
 
@@ -473,6 +504,34 @@ async function factory (pkgName) {
473
504
  await this.app.masohiMail.send({ payload, source: source ?? this.ns, conn })
474
505
  }
475
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
+
476
535
  createNewSite = createNewSite
477
536
  removeSite = removeSite
478
537
  getSite = getSite
@@ -1,10 +1,11 @@
1
1
  import path from 'path'
2
2
 
3
- async function createNewSite (alias, hostname, verbose) {
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
- if (verbose) spin.stop()
56
- // create site first
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)) throw this.error('aliasOrHOstnameExists%s%s', alias, hostname ?? '')
85
+ if (!isEmpty(site)) {
86
+ if (verbose) spin.stop()
87
+ throw this.error('aliasOrHOstnameExists%s%s', alias, hostname ?? '')
88
+ }
62
89
  // lets go
63
- data.SumbaSite.alias = alias
64
- if (!isEmpty(hostname)) data.SumbaSite.hostname = hostname
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 options = { trx }
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
- for (const m in data) {
70
- if (m === 'SumbaSite') continue
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
- const multiSite = this.config.multiSite === true ? { enabled: true, catchAll: 'default' } : this.config.multiSite
33
- if (!multiSite.enabled) {
32
+ if (!this.config.multiSite.enabled) {
34
33
  const filter = { query: { alias: 'default' } }
35
- const key = 'getSite:default'
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:multiSite:${input}`
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:${id}`
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:${token}`
60
+ const key = `getUserByToken|${token}`
60
61
  let user
61
62
  if (getCache) {
62
63
  user = await getCache({ key })
@@ -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, orderBy } = this.app.lib._
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
- for (const m of models) {
20
- const mdl = getModel(m)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sumba",
3
- "version": "2.14.0",
3
+ "version": "2.15.1",
4
4
  "description": "Biz Suite for Bajo Framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/wiki/CHANGES.md CHANGED
@@ -1,5 +1,13 @@
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
+ - [2.15.1] Code cleanups
10
+
3
11
  ## 2026-03-27
4
12
 
5
13
  - [2.14.0] Add support for cache storage management
@@ -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
- }
@@ -1,8 +0,0 @@
1
- async function hook (body, options) {
2
- const { isSet } = this.app.lib.aneka
3
- const { round } = this.app.lib.aneka
4
- if (!isSet(body[options.fieldName])) return
5
- body[options.fieldName] = round(body[options.fieldName], options.scale)
6
- }
7
-
8
- export default hook
@@ -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
@@ -1,9 +0,0 @@
1
- async function resetToken (salt) {
2
- const { generateId } = this.app.lib.aneka
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