sumba 2.0.0 → 2.0.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 (82) hide show
  1. package/.github/FUNDING.yml +13 -0
  2. package/.github/workflows/repo-lockdown.yml +24 -0
  3. package/.jsdoc.conf.json +45 -0
  4. package/LICENSE +1 -1
  5. package/README.md +40 -7
  6. package/docs/Sumba.html +3 -0
  7. package/docs/data/search.json +1 -0
  8. package/docs/fonts/Inconsolata-Regular.ttf +0 -0
  9. package/docs/fonts/OpenSans-Regular.ttf +0 -0
  10. package/docs/fonts/WorkSans-Bold.ttf +0 -0
  11. package/docs/global.html +3 -0
  12. package/docs/index.html +3 -0
  13. package/docs/index.js.html +526 -0
  14. package/docs/scripts/core.js +726 -0
  15. package/docs/scripts/core.min.js +23 -0
  16. package/docs/scripts/resize.js +90 -0
  17. package/docs/scripts/search.js +265 -0
  18. package/docs/scripts/search.min.js +6 -0
  19. package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
  20. package/docs/scripts/third-party/fuse.js +9 -0
  21. package/docs/scripts/third-party/hljs-line-num-original.js +369 -0
  22. package/docs/scripts/third-party/hljs-line-num.js +1 -0
  23. package/docs/scripts/third-party/hljs-original.js +5171 -0
  24. package/docs/scripts/third-party/hljs.js +1 -0
  25. package/docs/scripts/third-party/popper.js +5 -0
  26. package/docs/scripts/third-party/tippy.js +1 -0
  27. package/docs/scripts/third-party/tocbot.js +672 -0
  28. package/docs/scripts/third-party/tocbot.min.js +1 -0
  29. package/docs/static/bitcoin.jpeg +0 -0
  30. package/docs/static/home.md +25 -0
  31. package/docs/static/logo-ecosystem.png +0 -0
  32. package/docs/static/logo.png +0 -0
  33. package/docs/styles/clean-jsdoc-theme-base.css +1159 -0
  34. package/docs/styles/clean-jsdoc-theme-dark.css +412 -0
  35. package/docs/styles/clean-jsdoc-theme-light.css +482 -0
  36. package/docs/styles/clean-jsdoc-theme-scrollbar.css +30 -0
  37. package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
  38. package/docs/styles/clean-jsdoc-theme.min.css +1 -0
  39. package/extend/bajo/hook/dobo.sumba-contact-form@after-record-create.js +1 -1
  40. package/extend/bajo/hook/dobo.sumba-contact-form@before-record-create.js +1 -1
  41. package/extend/bajo/hook/dobo.sumba-user@after-record-create.js +1 -1
  42. package/extend/bajo/hook/dobo.sumba-user@after-record-update.js +3 -3
  43. package/extend/bajo/hook/dobo.sumba-user@after-record-validation.js +1 -1
  44. package/extend/bajo/hook/dobo.sumba-user@before-record-validation.js +1 -1
  45. package/extend/bajo/hook/dobo@before-record-create.js +1 -1
  46. package/extend/bajo/hook/dobo@before-record-find.js +1 -1
  47. package/extend/bajo/hook/waibu@after-app-boot.js +2 -2
  48. package/extend/dobo/feature/lat-lng.js +1 -1
  49. package/extend/dobo/feature/slug.js +2 -2
  50. package/extend/dobo/feature/status.js +1 -1
  51. package/extend/dobo/feature/url.js +1 -1
  52. package/extend/masohiSocketIo/middleware/server/auth.js +1 -1
  53. package/extend/waibuBootstrap/theme/component/factory/nav-dropdown-user.js +1 -1
  54. package/extend/waibuMpa/extend/waibuAdmin/route/reset-user-password.js +2 -2
  55. package/extend/waibuMpa/route/help/contact-form.js +2 -2
  56. package/extend/waibuMpa/route/help/trouble-tickets/add.js +1 -1
  57. package/extend/waibuMpa/route/help/trouble-tickets/details/@id.js +1 -1
  58. package/extend/waibuMpa/route/signout.js +2 -2
  59. package/extend/waibuMpa/route/user/activation.js +1 -1
  60. package/extend/waibuMpa/route/user/forgot-password/@fpl.js +5 -5
  61. package/extend/waibuMpa/route/user/forgot-password.js +3 -3
  62. package/extend/waibuMpa/route/user/signup.js +1 -1
  63. package/extend/waibuMpa/route/your-stuff/change-password.js +2 -2
  64. package/extend/waibuMpa/route/your-stuff/profile/edit.js +2 -2
  65. package/extend/waibuMpa/route/your-stuff/reset-api-key.js +2 -2
  66. package/extend/waibuRestApi/route/your-stuff/profile/update.js +1 -1
  67. package/extend/waibuSocketIo/middleware/server/auth.js +1 -1
  68. package/index.js +43 -28
  69. package/lib/check-iconset.js +1 -1
  70. package/lib/check-team.js +1 -1
  71. package/lib/check-theme.js +1 -1
  72. package/lib/check-user-id.js +3 -3
  73. package/lib/collect-routes.js +4 -4
  74. package/lib/collect-team.js +3 -3
  75. package/lib/lat-lng-hook.js +2 -2
  76. package/package.json +39 -34
  77. package/wiki/CONFIG.md +2 -0
  78. package/wiki/CONTRIBUTING.md +5 -0
  79. package/wiki/DEV-GUIDE.md +1 -0
  80. package/wiki/ECOSYSTEM.md +28 -0
  81. package/wiki/GETTING-STARTED.md +1 -0
  82. package/wiki/USER-GUIDE.md +1 -0
@@ -0,0 +1,526 @@
1
+ <!DOCTYPE html><html lang="en" style="font-size:16px"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Source: index.js</title><!--[if lt IE 9]>
2
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
3
+ <![endif]--><script src="scripts/third-party/hljs.js" defer="defer"></script><script src="scripts/third-party/hljs-line-num.js" defer="defer"></script><script src="scripts/third-party/popper.js" defer="defer"></script><script src="scripts/third-party/tippy.js" defer="defer"></script><script src="scripts/third-party/tocbot.min.js"></script><script>var baseURL="/",locationPathname="";baseURL=(locationPathname=document.location.pathname).substr(0,locationPathname.lastIndexOf("/")+1)</script><link rel="stylesheet" href="styles/clean-jsdoc-theme.min.css"><svg aria-hidden="true" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="display:none"><defs><symbol id="copy-icon" viewbox="0 0 488.3 488.3"><g><path d="M314.25,85.4h-227c-21.3,0-38.6,17.3-38.6,38.6v325.7c0,21.3,17.3,38.6,38.6,38.6h227c21.3,0,38.6-17.3,38.6-38.6V124 C352.75,102.7,335.45,85.4,314.25,85.4z M325.75,449.6c0,6.4-5.2,11.6-11.6,11.6h-227c-6.4,0-11.6-5.2-11.6-11.6V124 c0-6.4,5.2-11.6,11.6-11.6h227c6.4,0,11.6,5.2,11.6,11.6V449.6z"/><path d="M401.05,0h-227c-21.3,0-38.6,17.3-38.6,38.6c0,7.5,6,13.5,13.5,13.5s13.5-6,13.5-13.5c0-6.4,5.2-11.6,11.6-11.6h227 c6.4,0,11.6,5.2,11.6,11.6v325.7c0,6.4-5.2,11.6-11.6,11.6c-7.5,0-13.5,6-13.5,13.5s6,13.5,13.5,13.5c21.3,0,38.6-17.3,38.6-38.6 V38.6C439.65,17.3,422.35,0,401.05,0z"/></g></symbol><symbol id="search-icon" viewBox="0 0 512 512"><g><g><path d="M225.474,0C101.151,0,0,101.151,0,225.474c0,124.33,101.151,225.474,225.474,225.474 c124.33,0,225.474-101.144,225.474-225.474C450.948,101.151,349.804,0,225.474,0z M225.474,409.323 c-101.373,0-183.848-82.475-183.848-183.848S124.101,41.626,225.474,41.626s183.848,82.475,183.848,183.848 S326.847,409.323,225.474,409.323z"/></g></g><g><g><path d="M505.902,476.472L386.574,357.144c-8.131-8.131-21.299-8.131-29.43,0c-8.131,8.124-8.131,21.306,0,29.43l119.328,119.328 c4.065,4.065,9.387,6.098,14.715,6.098c5.321,0,10.649-2.033,14.715-6.098C514.033,497.778,514.033,484.596,505.902,476.472z"/></g></g></symbol><symbol id="font-size-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.246 15H4.754l-2 5H.6L7 4h2l6.4 16h-2.154l-2-5zm-.8-2L8 6.885 5.554 13h4.892zM21 12.535V12h2v8h-2v-.535a4 4 0 1 1 0-6.93zM19 18a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></symbol><symbol id="add-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"/></symbol><symbol id="minus-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5 11h14v2H5z"/></symbol><symbol id="dark-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2h.1A6.979 6.979 0 0 0 10 7zm-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938 7.999 7.999 0 0 0 4 12z"/></symbol><symbol id="light-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85l1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></symbol><symbol id="reset-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M18.537 19.567A9.961 9.961 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10c0 2.136-.67 4.116-1.81 5.74L17 12h3a8 8 0 1 0-2.46 5.772l.997 1.795z"/></symbol><symbol id="down-icon" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.7803 6.21967C13.0732 6.51256 13.0732 6.98744 12.7803 7.28033L8.53033 11.5303C8.23744 11.8232 7.76256 11.8232 7.46967 11.5303L3.21967 7.28033C2.92678 6.98744 2.92678 6.51256 3.21967 6.21967C3.51256 5.92678 3.98744 5.92678 4.28033 6.21967L8 9.93934L11.7197 6.21967C12.0126 5.92678 12.4874 5.92678 12.7803 6.21967Z"></path></symbol><symbol id="codepen-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M16.5 13.202L13 15.535v3.596L19.197 15 16.5 13.202zM14.697 12L12 10.202 9.303 12 12 13.798 14.697 12zM20 10.869L18.303 12 20 13.131V10.87zM19.197 9L13 4.869v3.596l3.5 2.333L19.197 9zM7.5 10.798L11 8.465V4.869L4.803 9 7.5 10.798zM4.803 15L11 19.131v-3.596l-3.5-2.333L4.803 15zM4 13.131L5.697 12 4 10.869v2.262zM2 9a1 1 0 0 1 .445-.832l9-6a1 1 0 0 1 1.11 0l9 6A1 1 0 0 1 22 9v6a1 1 0 0 1-.445.832l-9 6a1 1 0 0 1-1.11 0l-9-6A1 1 0 0 1 2 15V9z"/></symbol><symbol id="close-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"/></symbol><symbol id="menu-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M3 4h18v2H3V4zm0 7h18v2H3v-2zm0 7h18v2H3v-2z"/></symbol></defs></svg></head><body data-theme="light"><div class="sidebar-container"><div class="sidebar" id="sidebar"><a href="/" class="sidebar-title sidebar-title-anchor">Sumba API</a><div class="sidebar-items-container"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="Sumba.html">Sumba</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-global"><div>Global</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="global.html#factory">factory</a></div></div></div></div></div><div class="navbar-container" id="VuAckcnZhf"><nav class="navbar"><div class="navbar-left-items"><div class="navbar-item"><a id="" href="https://www.npmjs.com/package/sumba" target="">NPM</a></div><div class="navbar-item"><a id="" href="https://github.com/ardhi/sumba" target="">Github</a></div><div class="navbar-item"><a id="" href="https://sumba.bajo.app/" target="">Sumba</a></div><div class="navbar-item"><a id="" href="https://bajo.app/" target="">Bajo</a></div></div><div class="navbar-right-items"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#dark-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div><nav></nav></nav></div><div class="toc-container"><div class="toc-content"><span class="bold">On this page</span><div id="eed4d2a0bfd64539bb9df78095dec881"></div></div></div><div class="body-wrapper"><div class="main-content"><div class="main-wrapper"><section id="source-page" class="source-page"><header><h1 id="title" class="has-anchor">index.js</h1></header><article><pre class="prettyprint source lang-js"><code>import path from 'path'
4
+
5
+ /**
6
+ * Plugin factory
7
+ *
8
+ * @param {string} pkgName - NPM package name
9
+ * @returns {class}
10
+ */
11
+ async function factory (pkgName) {
12
+ const me = this
13
+
14
+ /**
15
+ * Sumba class
16
+ *
17
+ * @class
18
+ */
19
+ class Sumba extends this.app.pluginClass.base {
20
+ static alias = 'sumba'
21
+ static dependencies = ['bajo-extra', 'bajo-common-db', 'bajo-config']
22
+
23
+ constructor () {
24
+ super(pkgName, me.app)
25
+ this.config = {
26
+ multiSite: false,
27
+ waibu: {
28
+ title: 'site',
29
+ prefix: 'site'
30
+ },
31
+ waibuMpa: {
32
+ home: 'sumba:/your-stuff/profile',
33
+ icon: 'globe',
34
+ redirect: {
35
+ '/': 'sumba:/your-stuff/profile',
36
+ '/your-stuff': 'sumba:/your-stuff/profile',
37
+ '/info': 'sumba:/info/about-us',
38
+ '/user': 'sumba:/your-stuff/profile',
39
+ '/db/export': 'sumba:/db/export/list',
40
+ '/help': 'sumba:/help/contact-form',
41
+ '/help/trouble-tickets': 'sumba:/help/trouble-tickets/list'
42
+ },
43
+ menuHandler: [{
44
+ title: 'account',
45
+ level: 9998,
46
+ children: [
47
+ // anonymous only
48
+ { title: 'signin', href: 'sumba:/signin', visible: 'anon' },
49
+ { title: 'forgotPassword', href: 'sumba:/user/forgot-password', visible: 'anon' },
50
+ { title: 'newUserSignup', href: 'sumba:/user/signup', visible: 'anon' },
51
+ { title: '-', visible: 'anon' },
52
+ { title: 'activation', href: 'sumba:/user/activation', visible: 'anon' },
53
+ // authenticated only
54
+ { title: 'yourProfile', href: 'sumba:/your-stuff/profile', visible: 'auth' },
55
+ { title: 'changePassword', href: 'sumba:/your-stuff/change-password', visible: 'auth' },
56
+ { title: 'downloadList', href: 'sumba:/your-stuff/download/list', visible: 'auth' },
57
+ { title: '-', visible: 'auth' },
58
+ { title: 'signout', href: 'sumba:/signout', visible: 'auth' }
59
+ ]
60
+ }, {
61
+ title: 'help',
62
+ level: 9999,
63
+ children: [
64
+ { title: 'contactForm', href: 'sumba:/help/contact-form' },
65
+ { title: 'troubleTickets', href: 'sumba:/help/trouble-tickets', visible: 'auth' },
66
+ { title: '-' },
67
+ { title: 'cookiePolicy', href: 'sumba:/info/cookie-policy' },
68
+ { title: 'privacy', href: 'sumba:/info/privacy' },
69
+ { title: 'termsConditions', href: 'sumba:/info/terms-conditions' }
70
+ ]
71
+ }]
72
+ },
73
+ waibuAdmin: {
74
+ menuHandler: 'sumba:adminMenu',
75
+ menuCollapsible: true,
76
+ modelDisabled: 'all'
77
+ },
78
+ auth: {
79
+ common: {
80
+ apiKey: {
81
+ type: 'Bearer',
82
+ qsKey: 'apiKey',
83
+ headerKey: 'X-Sumba-ApiKey'
84
+ },
85
+ basic: {
86
+ },
87
+ jwt: {
88
+ type: 'Bearer',
89
+ qsKey: 'token',
90
+ headerKey: 'X-Sumba-Token',
91
+ secret: '668de9cf57316c7dbf52f7ff7611c299',
92
+ expiresIn: 604800000
93
+ }
94
+ },
95
+ waibuRestApi: {
96
+ methods: ['basic', 'apiKey', 'jwt'],
97
+ silentOnError: false
98
+ },
99
+ waibuMpa: {
100
+ methods: ['session'],
101
+ silentOnError: false
102
+ },
103
+ waibuStatic: {
104
+ methods: ['basic', 'apiKey', 'jwt'],
105
+ basic: {
106
+ useUtf8: true,
107
+ realm: 'Protected Area',
108
+ warningMessage: 'Please authenticate yourself, thank you!'
109
+ },
110
+ silentOnError: false
111
+ }
112
+ },
113
+ redirect: {
114
+ signin: 'sumba:/signin',
115
+ afterSignin: '/',
116
+ signout: 'sumba:/signout',
117
+ afterSignout: '/'
118
+ },
119
+ siteSetting: {
120
+ forgotPasswordExpDur: '5m',
121
+ userPassword: {
122
+ minUppercase: 1,
123
+ minLowercase: 1,
124
+ minSpecialChar: 1,
125
+ minNumeric: 1,
126
+ noWhitespace: false,
127
+ latinOnlyChars: false
128
+ }
129
+ }
130
+ }
131
+ this.unsafeUserFields = ['password']
132
+ }
133
+
134
+ init = async () => {
135
+ const { getPluginDataDir } = this.app.bajo
136
+ this.downloadDir = `${getPluginDataDir(this.ns)}/download`
137
+ this.app.lib.fs.ensureDirSync(this.downloadDir)
138
+ for (const type of ['secure', 'anonymous', 'team']) {
139
+ this[`${type}Routes`] = this[`${type}Routes`] ?? []
140
+ this[`${type}NegRoutes`] = this[`${type}NegRoutes`] ?? []
141
+ }
142
+ }
143
+
144
+ _getSetting = async (type, source) => {
145
+ const { defaultsDeep } = this.app.lib.aneka
146
+ const { get } = this.app.lib._
147
+
148
+ const setting = defaultsDeep(get(this.config, `auth.${source}.${type}`, {}), get(this.config, `auth.common.${type}`, {}))
149
+ if (type === 'basic') setting.type = 'Basic'
150
+ return setting
151
+ }
152
+
153
+ _getToken = async (type, req, source) => {
154
+ const { isEmpty } = this.app.lib._
155
+
156
+ const setting = await this._getSetting(type, source)
157
+ let token = req.headers[setting.headerKey.toLowerCase()]
158
+ if (!['basic'].includes(type) &amp;&amp; isEmpty(token)) token = req.query[setting.qsKey]
159
+ if (isEmpty(token)) {
160
+ const parts = (req.headers.authorization || '').split(' ')
161
+ if (parts[0] === setting.type) token = parts[1]
162
+ }
163
+ if (isEmpty(token)) return false
164
+ return token
165
+ }
166
+
167
+ adminMenu = async (locals, req) => {
168
+ if (!this.app.waibuAdmin) return
169
+ const { getPluginPrefix } = this.app.waibu
170
+ const prefix = getPluginPrefix(this.ns)
171
+ return [{
172
+ title: 'supportSystem',
173
+ children: [
174
+ { title: 'contactForm', href: `waibuAdmin:/${prefix}/contact-form/list` },
175
+ { title: 'contactFormCat', href: `waibuAdmin:/${prefix}/contact-form-cat/list` },
176
+ { title: 'ticket', href: `waibuAdmin:/${prefix}/ticket/list` },
177
+ { title: 'ticketCat', href: `waibuAdmin:/${prefix}/ticket-cat/list` }
178
+ ]
179
+ }, {
180
+ title: 'management',
181
+ children: [
182
+ { title: 'manageSite', href: `waibuAdmin:/${prefix}/site` },
183
+ { title: 'manageUser', href: `waibuAdmin:/${prefix}/user/list` },
184
+ { title: 'manageTeam', href: `waibuAdmin:/${prefix}/team/list` },
185
+ { title: 'manageTeamUser', href: `waibuAdmin:/${prefix}/team-user/list` },
186
+ { title: 'manageDownload', href: `waibuAdmin:/${prefix}/download/list` },
187
+ { title: 'resetUserPassword', href: `waibuAdmin:/${prefix}/reset-user-password` }
188
+ ]
189
+ }, {
190
+ title: 'misc',
191
+ children: [
192
+ { title: 'userSession', href: `waibuAdmin:/${prefix}/session/list` }
193
+ ]
194
+ }]
195
+ }
196
+
197
+ getUser = async (rec, safe = true) => {
198
+ const { recordGet } = this.app.dobo
199
+ const { omit, isPlainObject } = this.app.lib._
200
+ let user
201
+ if (isPlainObject(rec)) user = rec
202
+ else user = await recordGet('SumbaUser', rec, { noHook: true })
203
+ return safe ? omit(user, this.unsafeUserFields) : user
204
+ }
205
+
206
+ mergeTeam = async (user, site) => {
207
+ if (!user) return
208
+ const { map, pick } = this.app.lib._
209
+ const { recordFindAll } = this.app.dobo
210
+ user.teams = []
211
+ const query = { userId: user.id, siteId: site.id }
212
+ const userTeam = await recordFindAll('SumbaTeamUser', { query })
213
+ if (userTeam.length === 0) return
214
+ delete query.userId
215
+ query.id = { $in: map(userTeam, 'id'), status: 'ENABLED' }
216
+ const team = await recordFindAll('SumbaTeam', { query })
217
+ if (team.length > 0) user.teams.push(...map(team, t => pick(t, ['id', 'alias'])))
218
+ }
219
+
220
+ getUserFromUsernamePassword = async (username = '', password = '', req) => {
221
+ const { importPkg } = this.app.bajo
222
+ const { recordFind, validate } = this.app.dobo
223
+ const model = 'SumbaUser'
224
+ await validate({ username, password }, model, { ns: ['sumba', 'dobo'], fields: ['username', 'password'] })
225
+ const bcrypt = await importPkg('bajoExtra:bcrypt')
226
+
227
+ const query = { username, provider: 'local' }
228
+ const rows = await recordFind(model, { query }, { req, forceNoHidden: true, noHook: true })
229
+ if (rows.length === 0) throw this.error('validationError', { details: [{ field: 'username', error: 'Unknown username' }], statusCode: 401 })
230
+ const rec = rows[0]
231
+ if (rec.status !== 'ACTIVE') throw this.error('validationError', { details: ['User is inactive or temporarily disabled'], statusCode: 401 })
232
+ const verified = await bcrypt.compare(password, rec.password)
233
+ if (!verified) throw this.error('validationError', { details: [{ field: 'password', error: 'invalidPassword' }], statusCode: 401 })
234
+ return rec
235
+ }
236
+
237
+ createJwtFromUserRecord = async (rec) => {
238
+ const { importPkg } = this.app.bajo
239
+ const { dayjs } = this.app.lib
240
+ const { hash } = this.app.bajoExtra
241
+ const { get, pick } = this.app.lib._
242
+
243
+ const fastJwt = await importPkg('bajoExtra:fast-jwt')
244
+ const { createSigner } = fastJwt
245
+
246
+ const opts = pick(this.config.auth.common.jwt, ['expiresIn'])
247
+ opts.key = get(this.config, 'auth.common.jwt.secret')
248
+ const sign = createSigner(opts)
249
+ const apiKey = await hash(rec.password)
250
+ const payload = { uid: rec.id, apiKey }
251
+ const token = await sign(payload)
252
+ const expiresAt = dayjs().add(opts.expiresIn).toDate()
253
+ return { token, expiresAt }
254
+ }
255
+
256
+ verifySession = async (req, reply, source, payload) => {
257
+ const { getUser } = this
258
+ const { routePath } = this.app.waibu
259
+
260
+ if (!req.session) return false
261
+ if (req.session.userId) {
262
+ req.user = await getUser(req.session.userId)
263
+ return true
264
+ }
265
+ const redir = routePath(this.config.redirect.signin, req)
266
+ req.session.ref = req.url
267
+ throw this.error('_redirect', { redirect: redir })
268
+ }
269
+
270
+ verifyApiKey = async (req, reply, source, payload) => {
271
+ const { merge } = this.app.lib._
272
+ const { isMd5, hash } = this.app.bajoExtra
273
+ const { getUser } = this
274
+ const { recordFind } = this.app.dobo
275
+
276
+ let token = await this._getToken('apiKey', req, source)
277
+ if (!isMd5(token)) return false
278
+ token = await hash(token)
279
+ const query = { token }
280
+ const rows = await recordFind('SumbaUser', { query }, { req, noHook: true })
281
+ if (rows.length === 0) throw this.error('invalidKey', merge({ statusCode: 401 }, payload))
282
+ if (rows[0].status !== 'ACTIVE') throw this.error('userInactive', merge({ details: [{ field: 'status', error: 'inactive' }], statusCode: 401 }, payload))
283
+ req.user = await getUser(rows[0])
284
+ return true
285
+ }
286
+
287
+ verifyBasic = async (req, reply, source, payload) => {
288
+ const { getUserFromUsernamePassword } = this
289
+ const { getUser } = this
290
+ const { isEmpty, merge } = this.app.lib._
291
+
292
+ const setHeader = async (setting, reply) => {
293
+ const { isString } = this.app.lib._
294
+
295
+ let header = setting.type
296
+ const exts = []
297
+ if (isString(setting.realm)) exts.push(`realm="${setting.realm}"`)
298
+ if (setting.useUtf8) exts.push('charset="UTF-8"')
299
+ if (exts.length > 0) header += ` ${exts.join(', ')}`
300
+ reply.header('WWW-Authenticate', header)
301
+ reply.code(401)
302
+ }
303
+
304
+ const setting = await this._getSetting('basic', source)
305
+ let authInfo
306
+ const parts = (req.headers.authorization ?? '').split(' ')
307
+ if (parts[0] === setting.type) authInfo = parts[1]
308
+ if (isEmpty(authInfo)) {
309
+ if (setting.realm) {
310
+ await setHeader(setting, reply)
311
+ throw this.error(setting.warningMessage)
312
+ } else return false
313
+ }
314
+ const decoded = Buffer.from(authInfo, 'base64').toString()
315
+ const [username, password] = decoded.split(':')
316
+ try {
317
+ const user = await getUserFromUsernamePassword(username, password, req)
318
+ req.user = await getUser(user)
319
+ } catch (err) {
320
+ if (err.statusCode === 401 &amp;&amp; setting.realm) {
321
+ await setHeader(setting, reply)
322
+ return err.message
323
+ }
324
+ throw merge(err, payload)
325
+ }
326
+ return true
327
+ }
328
+
329
+ verifyJwt = async (req, reply, source, payload) => {
330
+ const { importPkg } = this.app.bajo
331
+ const { recordGet } = this.app.dobo
332
+ const { getUser } = this
333
+ const { isEmpty, merge } = this.app.lib._
334
+
335
+ const fastJwt = await importPkg('bajoExtra:fast-jwt')
336
+ const { createVerifier } = fastJwt
337
+ const setting = await this._getSetting('jwt', source)
338
+ const token = await this._getToken('jwt', req, source)
339
+ if (isEmpty(token)) return false
340
+ const verifier = createVerifier({
341
+ key: setting.secret,
342
+ complete: true
343
+ })
344
+ const decoded = await verifier(token)
345
+ const id = decoded.payload.uid
346
+ try {
347
+ const rec = await recordGet('SumbaUser', id, { req, noHook: true })
348
+ if (!rec) throw this.error('invalidToken', { statusCode: 401 })
349
+ if (rec.status !== 'ACTIVE') throw this.error('userInactive', { details: [{ field: 'status', error: 'inactive' }], statusCode: 401 })
350
+ req.user = await getUser(rec)
351
+ } catch (err) {
352
+ merge(err, payload)
353
+ throw err
354
+ }
355
+ return true
356
+ }
357
+
358
+ checkPathsByTeam = ({ paths = [], method = 'GET', teams = [], guards = [] }) => {
359
+ const { includes } = this.app.lib.aneka
360
+ const { outmatch } = this.app.lib
361
+
362
+ for (const item of guards) {
363
+ const matchPath = outmatch(item.path)
364
+ for (const path of paths) {
365
+ if (matchPath(path)) {
366
+ const matchMethods = outmatch(item.methods, { separator: false })
367
+ if (matchMethods(method)) {
368
+ if (item.teams.length === 0) return item
369
+ if (includes(teams, item.teams)) return item
370
+ }
371
+ }
372
+ }
373
+ }
374
+ }
375
+
376
+ checkPathsByRoute = ({ paths = [], method = 'GET', guards = [] }) => {
377
+ const { outmatch } = this.app.lib
378
+
379
+ for (const item of guards) {
380
+ const matchPath = outmatch(item.path)
381
+ for (const path of paths) {
382
+ if (matchPath(path)) {
383
+ const matchMethods = outmatch(item.methods, { separator: false })
384
+ if (matchMethods(method)) return item
385
+ }
386
+ }
387
+ }
388
+ }
389
+
390
+ checkPathsByGuard = ({ guards, paths }) => {
391
+ const { outmatch } = this.app.lib
392
+ const matcher = outmatch(guards)
393
+ let guarded
394
+ for (const path of paths) {
395
+ if (!guarded) guarded = matcher(path)
396
+ }
397
+ return guarded
398
+ }
399
+
400
+ getSite = async (hostname, useId) => {
401
+ const { omit } = this.app.lib._
402
+ const { recordFind } = this.app.dobo
403
+ const omitted = ['status']
404
+
405
+ const mergeSetting = async (site) => {
406
+ const { defaultsDeep } = this.app.lib.aneka
407
+ const { parseObject } = this.app.bajo
408
+ const { trim, get, filter } = this.app.lib._
409
+ const { recordFind, recordGet } = this.app.dobo
410
+ const defSetting = {}
411
+ const nsSetting = {}
412
+ const names = this.app.getAllNs()
413
+ const query = {
414
+ ns: { $in: names },
415
+ siteId: site.id
416
+ }
417
+ const all = await recordFind('SumbaSiteSetting', { query, limit: -1 })
418
+ for (const ns of names) {
419
+ nsSetting[ns] = {}
420
+ defSetting[ns] = get(this, `app.${ns}.config.siteSetting`, {})
421
+ const items = filter(all, { ns })
422
+ for (const item of items) {
423
+ let value = trim([item.value] ?? '')
424
+ if (['[', '{'].includes(value[0])) value = JSON.parse(value)
425
+ else if (Number(value)) value = Number(value)
426
+ else if (['true', 'false'].includes(value)) value = value === 'true'
427
+ nsSetting[ns][item.key] = value
428
+ }
429
+ }
430
+ site.setting = parseObject(defaultsDeep({}, nsSetting, defSetting))
431
+ // additional fields
432
+ const country = await recordGet('CdbCountry', site.country, { noHook: true })
433
+ site.countryName = (country ?? {}).name ?? site.country
434
+ }
435
+
436
+ let site = {}
437
+
438
+ if (!this.config.multiSite) {
439
+ const resp = await recordFind('SumbaSite', { query: { alias: 'default' } }, { noHook: true })
440
+ site = omit(resp[0], omitted)
441
+ await mergeSetting(site)
442
+ return site
443
+ }
444
+ let query
445
+ if (useId) query = { id: hostname }
446
+ else {
447
+ query = {
448
+ $or: [
449
+ { hostname },
450
+ { alias: hostname }
451
+ ]
452
+ }
453
+ }
454
+ const filter = { query, limit: 1 }
455
+ const rows = await recordFind('SumbaSite', filter, { noHook: true })
456
+ if (rows.length === 0) throw this.error('unknownSite')
457
+ const row = omit(rows[0], omitted)
458
+ if (row.status !== 'ACTIVE') throw this.error('siteInactiveInfo')
459
+ site = row
460
+ await mergeSetting(site)
461
+ return site
462
+ }
463
+
464
+ signin = async ({ user, req, reply }) => {
465
+ const { getSessionId } = this.app.waibuMpa
466
+ const { runHook } = this.app.bajo
467
+ const { isEmpty, omit } = this.app.lib._
468
+ let { referer } = req.body || {}
469
+ if (req.session.ref) referer = req.session.ref
470
+ req.session.ref = null
471
+ const _user = omit(user, ['password', 'token'])
472
+ req.session.userId = _user.id
473
+ const sid = await getSessionId(req.headers.cookie)
474
+ await runHook(`${this.ns}:afterSignin`, _user, sid, req)
475
+ const { query, params } = req
476
+ const url = !isEmpty(referer) ? referer : this.config.redirect.afterSignin
477
+ req.flash('notify', req.t('signinSuccessfully'))
478
+ return reply.redirectTo(url, { query, params })
479
+ }
480
+
481
+ generatePassword = (req) => {
482
+ const { generateId } = this.app.bajo
483
+ const cfg = req ? req.site.setting.sumba.userPassword : this.config.siteSetting.userPassword
484
+ let passwd = generateId()
485
+ if (cfg.minLowercase) passwd += generateId({ pattern: 'abcdefghijklmnopqrstuvwxyz', length: cfg.minLowercase })
486
+ if (cfg.minUppercase) passwd += generateId({ pattern: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', length: cfg.minUppercase })
487
+ if (cfg.minSpecialChar) passwd += generateId({ pattern: '!@#$%*', length: cfg.minSpecialChar })
488
+ if (cfg.minNumeric) passwd += generateId({ pattern: '0123456789', length: cfg.minNumeric })
489
+ return passwd
490
+ }
491
+
492
+ pushDownload = async ({ description, worker, data, source, req, file, type }) => {
493
+ const { getPlugin } = this.app.bajo
494
+ const { recordCreate } = getPlugin('waibuDb')
495
+ const { push } = getPlugin('bajoQueue')
496
+ description = description ?? file
497
+ const jobQueue = {
498
+ worker,
499
+ source,
500
+ payload: {
501
+ type: 'object',
502
+ data
503
+ }
504
+ }
505
+ if (!type) type = path.extname(file)
506
+ if (type[0] === '.') type = type.slice(1)
507
+ const body = { file, description, jobQueue, type }
508
+ const rec = await recordCreate({ model: 'SumbaDownload', body, req, options: { noFlash: true } })
509
+ jobQueue.payload.data.download = { id: rec.data.id, file }
510
+ await push(jobQueue)
511
+ }
512
+
513
+ getApiKeyFromUserId = async id => {
514
+ const { hash } = this.app.bajoExtra
515
+ const { recordGet } = this.app.dobo
516
+ const options = { forceNoHidden: true, noHook: true, noCache: true, attachment: true, mimeType: true }
517
+ const resp = await recordGet('SumbaUser', id, options)
518
+ return await hash(resp.salt)
519
+ }
520
+ }
521
+
522
+ return Sumba
523
+ }
524
+
525
+ export default factory
526
+ </code></pre></article></section></div></div></div><div class="search-container" id="PkfLWpAbet" style="display:none"><div class="wrapper" id="iCxFxjkHbP"><button class="icon-button search-close-button" id="VjLlGakifb" aria-label="close search"><svg><use xlink:href="#close-icon"></use></svg></button><div class="search-box-c"><svg><use xlink:href="#search-icon"></use></svg> <input type="text" id="vpcKVYIppa" class="search-input" placeholder="Search..." autofocus></div><div class="search-result-c" id="fWwVHRuDuN"><span class="search-result-c-text">Type anything to view search result</span></div></div></div><div class="mobile-menu-icon-container"><button class="icon-button" id="mobile-menu" data-isopen="false" aria-label="menu"><svg><use xlink:href="#menu-icon"></use></svg></button></div><div id="mobile-sidebar" class="mobile-sidebar-container"><div class="mobile-sidebar-wrapper"><a href="/" class="sidebar-title sidebar-title-anchor">Sumba API</a><div class="mobile-nav-links"><div class="navbar-item"><a id="" href="https://www.npmjs.com/package/sumba" target="">NPM</a></div><div class="navbar-item"><a id="" href="https://github.com/ardhi/sumba" target="">Github</a></div><div class="navbar-item"><a id="" href="https://sumba.bajo.app/" target="">Sumba</a></div><div class="navbar-item"><a id="" href="https://bajo.app/" target="">Bajo</a></div></div><div class="mobile-sidebar-items-c"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="Sumba.html">Sumba</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-global"><div>Global</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="global.html#factory">factory</a></div></div></div><div class="mobile-navbar-actions"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#dark-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div></div></div><script type="text/javascript" src="scripts/core.min.js"></script><script src="scripts/search.min.js" defer="defer"></script><script src="scripts/third-party/fuse.js" defer="defer"></script><script type="text/javascript">var tocbotInstance=tocbot.init({tocSelector:"#eed4d2a0bfd64539bb9df78095dec881",contentSelector:".main-content",headingSelector:"h1, h2, h3",hasInnerContainers:!0,scrollContainer:".main-content",headingsOffset:130,onClick:bringLinkToView})</script></body></html>