expressa 2.0.21 → 2.0.23

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 (222) hide show
  1. package/.circleci/config.yml +21 -21
  2. package/.eslintrc.json +29 -29
  3. package/.vscode/launch.json +19 -0
  4. package/LICENSE +21 -21
  5. package/README.md +137 -137
  6. package/auth/index.js +76 -76
  7. package/auth/jwt.js +31 -31
  8. package/controllers/collections.js +260 -260
  9. package/controllers/install.js +105 -105
  10. package/controllers/status.js +43 -43
  11. package/controllers/users.js +52 -52
  12. package/cypress/fixtures/example.json +4 -4
  13. package/cypress/integration/0_install_spec.js +8 -8
  14. package/cypress/integration/custom_collections.js +30 -30
  15. package/cypress/integration/home.js +9 -9
  16. package/cypress/integration/settings.js +34 -34
  17. package/cypress/integration/users.js +61 -61
  18. package/cypress/plugins/index.js +17 -17
  19. package/cypress/support/commands.js +69 -69
  20. package/cypress/support/index.js +20 -20
  21. package/cypress/support/util.js +10 -10
  22. package/cypress.config.js +18 -18
  23. package/cypress.json +4 -4
  24. package/db/cached.js +44 -40
  25. package/db/file.js +131 -112
  26. package/db/memory.js +92 -79
  27. package/db/mongo.js +93 -86
  28. package/db/postgres.js +100 -93
  29. package/doc/authentication.md +38 -38
  30. package/doc/automatic-fields.md +12 -12
  31. package/doc/blogexample.md +16 -16
  32. package/doc/custom-endpoints.md +20 -20
  33. package/doc/database.md +23 -23
  34. package/doc/development.md +33 -33
  35. package/doc/listeners.md +103 -103
  36. package/doc/modules.md +8 -8
  37. package/doc/permissions.md +14 -14
  38. package/doc/relationships.md +65 -65
  39. package/doc/testing.md +47 -47
  40. package/doc/uploading-files.md +53 -53
  41. package/index.js +297 -297
  42. package/listeners.js +65 -65
  43. package/listeners_collection_permissions.js +29 -29
  44. package/listeners_users.js +96 -96
  45. package/listeners_validation.js +43 -43
  46. package/middleware/logging.js +30 -30
  47. package/middleware/permissions.js +41 -41
  48. package/modules/access_keys/access_keys.js +32 -32
  49. package/modules/admin/.babelrc +8 -19
  50. package/modules/admin/.editorconfig +14 -14
  51. package/modules/admin/.eslintignore +5 -5
  52. package/modules/admin/.eslintrc.js +197 -197
  53. package/modules/admin/.postcssrc.js +10 -10
  54. package/modules/admin/.travis.yml +5 -5
  55. package/modules/admin/LICENSE +21 -21
  56. package/modules/admin/README.md +88 -88
  57. package/modules/admin/build/build.js +46 -46
  58. package/modules/admin/build/check-versions.js +64 -64
  59. package/modules/admin/build/utils.js +109 -109
  60. package/modules/admin/build/vue-loader.conf.js +5 -5
  61. package/modules/admin/build/webpack.base.conf.js +112 -112
  62. package/modules/admin/build/webpack.dev.conf.js +95 -95
  63. package/modules/admin/build/webpack.prod.conf.js +112 -112
  64. package/modules/admin/config/dev.env.js +8 -8
  65. package/modules/admin/config/index.js +86 -86
  66. package/modules/admin/config/prod.env.js +5 -5
  67. package/modules/admin/dist/css/{197.7a90435e.css → 197.e66ce8d8.css} +4 -4
  68. package/modules/admin/dist/css/197.e66ce8d8.css.gz +0 -0
  69. package/modules/admin/dist/css/{430.8c9b0482.css → 430.e83d8cfe.css} +4 -4
  70. package/modules/admin/dist/css/430.e83d8cfe.css.gz +0 -0
  71. package/modules/admin/dist/css/451.c6646d20.css.gz +0 -0
  72. package/modules/admin/dist/css/{566.7a90435e.css → 566.e66ce8d8.css} +4 -4
  73. package/modules/admin/dist/css/566.e66ce8d8.css.gz +0 -0
  74. package/modules/admin/dist/css/569.db9a10c2.css +213 -0
  75. package/modules/admin/dist/css/569.db9a10c2.css.gz +0 -0
  76. package/modules/admin/dist/css/572.513a323e.css +210 -0
  77. package/modules/admin/dist/css/572.513a323e.css.gz +0 -0
  78. package/modules/admin/dist/css/651.e946bda7.css.gz +0 -0
  79. package/modules/admin/dist/css/750.4086915d.css.gz +0 -0
  80. package/modules/admin/dist/css/950.1fd9d57c.css.gz +0 -0
  81. package/modules/admin/dist/css/{app.eba5f5e2.css → app.1c3232a3.css} +25 -25
  82. package/modules/admin/dist/css/app.1c3232a3.css.gz +0 -0
  83. package/modules/admin/dist/index.html +1 -1
  84. package/modules/admin/dist/static/js/197.36982650.js +1 -0
  85. package/modules/admin/dist/static/js/197.36982650.js.gz +0 -0
  86. package/modules/admin/dist/static/js/311.25e842dc.js +1 -0
  87. package/modules/admin/dist/static/js/311.25e842dc.js.gz +0 -0
  88. package/modules/admin/dist/static/js/430.53aea612.js +1 -0
  89. package/modules/admin/dist/static/js/430.53aea612.js.gz +0 -0
  90. package/modules/admin/dist/static/js/451.9f6dcdb1.js +1 -0
  91. package/modules/admin/dist/static/js/451.9f6dcdb1.js.gz +0 -0
  92. package/modules/admin/dist/static/js/460.351dad91.js +1 -0
  93. package/modules/admin/dist/static/js/460.351dad91.js.gz +0 -0
  94. package/modules/admin/dist/static/js/550.87e54b29.js +1 -0
  95. package/modules/admin/dist/static/js/550.87e54b29.js.gz +0 -0
  96. package/modules/admin/dist/static/js/569.52dba3a7.js +1 -0
  97. package/modules/admin/dist/static/js/569.52dba3a7.js.gz +0 -0
  98. package/modules/admin/dist/static/js/572.296cfd3b.js +1 -0
  99. package/modules/admin/dist/static/js/572.296cfd3b.js.gz +0 -0
  100. package/modules/admin/dist/static/js/603.98d5f5d9.js +1 -0
  101. package/modules/admin/dist/static/js/603.98d5f5d9.js.gz +0 -0
  102. package/modules/admin/dist/static/js/651.6dca6fbe.js +1 -0
  103. package/modules/admin/dist/static/js/651.6dca6fbe.js.gz +0 -0
  104. package/modules/admin/dist/static/js/653.0dff78dc.js +1 -0
  105. package/modules/admin/dist/static/js/653.0dff78dc.js.gz +0 -0
  106. package/modules/admin/dist/static/js/750.6877a6b8.js +1 -0
  107. package/modules/admin/dist/static/js/750.6877a6b8.js.gz +0 -0
  108. package/modules/admin/dist/static/js/950.4f40455a.js +1 -0
  109. package/modules/admin/dist/static/js/950.4f40455a.js.gz +0 -0
  110. package/modules/admin/dist/static/js/app.2dd732d9.js +2 -0
  111. package/modules/admin/dist/static/js/{app.083328f8.js.LICENSE.txt → app.2dd732d9.js.LICENSE.txt} +0 -2
  112. package/modules/admin/dist/static/js/app.2dd732d9.js.LICENSE.txt.gz +0 -0
  113. package/modules/admin/dist/static/js/app.2dd732d9.js.gz +0 -0
  114. package/modules/admin/index.html +16 -16
  115. package/modules/admin/package.json +101 -101
  116. package/modules/admin/src/App.vue +11 -11
  117. package/modules/admin/src/api/login.js +26 -26
  118. package/modules/admin/src/api/table.js +9 -9
  119. package/modules/admin/src/components/Breadcrumb/index.vue +79 -79
  120. package/modules/admin/src/components/Hamburger/index.vue +62 -62
  121. package/modules/admin/src/components/JSONEditor.vue +206 -180
  122. package/modules/admin/src/components/SvgIcon/index.vue +43 -43
  123. package/modules/admin/src/icons/index.js +9 -9
  124. package/modules/admin/src/icons/svgo.yml +22 -22
  125. package/modules/admin/src/main.js +42 -42
  126. package/modules/admin/src/permission.js +49 -49
  127. package/modules/admin/src/router/index.js +200 -200
  128. package/modules/admin/src/store/getters.js +11 -11
  129. package/modules/admin/src/store/index.js +17 -17
  130. package/modules/admin/src/store/modules/app.js +43 -43
  131. package/modules/admin/src/store/modules/user.js +104 -104
  132. package/modules/admin/src/styles/element-ui.scss +29 -29
  133. package/modules/admin/src/styles/index.scss +78 -78
  134. package/modules/admin/src/styles/mixin.scss +27 -27
  135. package/modules/admin/src/styles/sidebar.scss +133 -133
  136. package/modules/admin/src/styles/transition.scss +46 -46
  137. package/modules/admin/src/styles/variables.scss +4 -4
  138. package/modules/admin/src/utils/auth.js +15 -15
  139. package/modules/admin/src/utils/index.js +74 -74
  140. package/modules/admin/src/utils/request.js +47 -47
  141. package/modules/admin/src/utils/validate.js +31 -31
  142. package/modules/admin/src/views/404.vue +235 -235
  143. package/modules/admin/src/views/Dev.vue +33 -33
  144. package/modules/admin/src/views/EditDocument.vue +121 -121
  145. package/modules/admin/src/views/Endpoints.vue +279 -279
  146. package/modules/admin/src/views/Home.vue +84 -84
  147. package/modules/admin/src/views/Install.vue +79 -79
  148. package/modules/admin/src/views/ListDocuments.vue +72 -72
  149. package/modules/admin/src/views/ListDocuments2.vue +467 -467
  150. package/modules/admin/src/views/ManageListeners.vue +67 -67
  151. package/modules/admin/src/views/ManageMiddleware.vue +43 -43
  152. package/modules/admin/src/views/ManagePermissions.vue +75 -75
  153. package/modules/admin/src/views/ViewRequest.vue +77 -77
  154. package/modules/admin/src/views/form/index.vue +91 -91
  155. package/modules/admin/src/views/layout/Layout.vue +69 -69
  156. package/modules/admin/src/views/layout/components/AppMain.vue +41 -41
  157. package/modules/admin/src/views/layout/components/Navbar.vue +97 -97
  158. package/modules/admin/src/views/layout/components/Sidebar/Item.vue +29 -29
  159. package/modules/admin/src/views/layout/components/Sidebar/Link.vue +39 -39
  160. package/modules/admin/src/views/layout/components/Sidebar/SidebarItem.vue +103 -103
  161. package/modules/admin/src/views/layout/components/Sidebar/index.vue +62 -62
  162. package/modules/admin/src/views/layout/components/index.js +3 -3
  163. package/modules/admin/src/views/layout/mixin/ResizeHandler.js +41 -41
  164. package/modules/admin/src/views/login/index.vue +195 -195
  165. package/modules/admin/src/views/tree/index.vue +77 -77
  166. package/modules/collections/collections.js +39 -39
  167. package/modules/core/core.js +197 -197
  168. package/modules/logging/logging.js +88 -88
  169. package/modules/permissions/permissions.js +106 -106
  170. package/package.json +59 -59
  171. package/scripts/expressa +12 -12
  172. package/scripts/run_cypress_tests.sh +35 -35
  173. package/scripts/run_db_tests.sh +8 -8
  174. package/test/0-install.js +96 -96
  175. package/test/collections.js +431 -431
  176. package/test/collections.querying.js +288 -288
  177. package/test/db.js +258 -258
  178. package/test/db.strings.js +73 -73
  179. package/test/db.updating.js +186 -144
  180. package/test/logging.js +29 -29
  181. package/test/test.js +93 -93
  182. package/test/testserver.js +30 -30
  183. package/test/testutils.js +136 -136
  184. package/test/users.js +425 -425
  185. package/util.js +522 -522
  186. package/modules/admin/dist/css/197.7a90435e.css.gz +0 -0
  187. package/modules/admin/dist/css/430.8c9b0482.css.gz +0 -0
  188. package/modules/admin/dist/css/566.7a90435e.css.gz +0 -0
  189. package/modules/admin/dist/css/569.f7d5c7d5.css +0 -37
  190. package/modules/admin/dist/css/569.f7d5c7d5.css.gz +0 -0
  191. package/modules/admin/dist/css/572.74d8e965.css +0 -34
  192. package/modules/admin/dist/css/572.74d8e965.css.gz +0 -0
  193. package/modules/admin/dist/css/app.eba5f5e2.css.gz +0 -0
  194. package/modules/admin/dist/static/js/197.e511d91e.js +0 -1
  195. package/modules/admin/dist/static/js/197.e511d91e.js.gz +0 -0
  196. package/modules/admin/dist/static/js/430.b3941f30.js +0 -1
  197. package/modules/admin/dist/static/js/430.b3941f30.js.gz +0 -0
  198. package/modules/admin/dist/static/js/451.645974a8.js +0 -1
  199. package/modules/admin/dist/static/js/451.645974a8.js.gz +0 -0
  200. package/modules/admin/dist/static/js/460.962d5bc0.js +0 -1
  201. package/modules/admin/dist/static/js/460.962d5bc0.js.gz +0 -0
  202. package/modules/admin/dist/static/js/550.f27feb23.js +0 -1
  203. package/modules/admin/dist/static/js/550.f27feb23.js.gz +0 -0
  204. package/modules/admin/dist/static/js/556.1e08866a.js +0 -1
  205. package/modules/admin/dist/static/js/556.1e08866a.js.gz +0 -0
  206. package/modules/admin/dist/static/js/569.0fecfbee.js +0 -1
  207. package/modules/admin/dist/static/js/569.0fecfbee.js.gz +0 -0
  208. package/modules/admin/dist/static/js/572.13392e1d.js +0 -1
  209. package/modules/admin/dist/static/js/572.13392e1d.js.gz +0 -0
  210. package/modules/admin/dist/static/js/603.7b34a650.js +0 -1
  211. package/modules/admin/dist/static/js/603.7b34a650.js.gz +0 -0
  212. package/modules/admin/dist/static/js/651.564b24b6.js +0 -1
  213. package/modules/admin/dist/static/js/651.564b24b6.js.gz +0 -0
  214. package/modules/admin/dist/static/js/653.02ec1098.js +0 -1
  215. package/modules/admin/dist/static/js/653.02ec1098.js.gz +0 -0
  216. package/modules/admin/dist/static/js/750.50c950da.js +0 -1
  217. package/modules/admin/dist/static/js/750.50c950da.js.gz +0 -0
  218. package/modules/admin/dist/static/js/950.d4ff0a18.js +0 -1
  219. package/modules/admin/dist/static/js/950.d4ff0a18.js.gz +0 -0
  220. package/modules/admin/dist/static/js/app.083328f8.js +0 -2
  221. package/modules/admin/dist/static/js/app.083328f8.js.LICENSE.txt.gz +0 -0
  222. package/modules/admin/dist/static/js/app.083328f8.js.gz +0 -0
package/listeners.js CHANGED
@@ -1,65 +1,65 @@
1
- const debug = require('debug')('expressa')
2
- const util = require('./util')
3
-
4
- module.exports = function (api) {
5
- /* Listeners to avoid the need for a server restart */
6
- api.addCollectionListener('changed', 'collection', async function setupCollectionStorage (req, collection, data) {
7
- if (!(data.storage === 'memory' && util.getPath(api.db[data._id], 'type') === 'memory')) {
8
- await api.setupCollectionDb(data)
9
- }
10
- debug('updated ' + data._id + ' collection storage')
11
- })
12
- api.addCollectionListener('delete', 'collection', function cleanupCollectionStorage (req, collection, data) {
13
- delete api.db[data._id]
14
- debug('removed ' + data._id + ' collection storage')
15
- })
16
- api.addCollectionListener('changed', 'settings', function updateInMemorySettings (req, collection, data) {
17
- // TODO: only reload if current environment is updated
18
- Object.assign(req.settings, data)
19
- })
20
-
21
- api.addListener(['put', 'post'], async function updateMetadata (req, collection, data, { event }) {
22
- data.meta ??= {}
23
- data.meta.updated = new Date().toISOString()
24
- if (event === 'post') {
25
- data.meta.created = new Date().toISOString()
26
- }
27
- })
28
-
29
- api.addCollectionListener('get', 'schemas', function addMetaToSchema (req, collection, data) {
30
- const schema = data.schema
31
- if (!schema.properties.meta) {
32
- schema.properties.meta = {
33
- type: 'object',
34
- properties: {
35
- created: {
36
- type: 'string'
37
- },
38
- updated: {
39
- type: 'string'
40
- }
41
- }
42
- }
43
- }
44
- if (schema.properties.meta.propertyOrder === undefined) {
45
- schema.properties.meta.propertyOrder = 2000
46
- }
47
- if (data.documentsHaveOwners) {
48
- if (!schema.properties.meta.properties.owner) {
49
- schema.properties.meta.properties.owner = {
50
- type: 'string',
51
- links: [
52
- {
53
- rel: '» view owner user',
54
- href: '/admin/#/edit/users/{{self}}',
55
- class: 'comment-link'
56
- }
57
- ]
58
- },
59
- schema.properties.meta.properties.owner_collection = {
60
- type: 'string'
61
- }
62
- }
63
- }
64
- })
65
- }
1
+ const debug = require('debug')('expressa')
2
+ const util = require('./util')
3
+
4
+ module.exports = function (api) {
5
+ /* Listeners to avoid the need for a server restart */
6
+ api.addCollectionListener('changed', 'collection', async function setupCollectionStorage (req, collection, data) {
7
+ if (!(data.storage === 'memory' && util.getPath(api.db[data._id], 'type') === 'memory')) {
8
+ await api.setupCollectionDb(data)
9
+ }
10
+ debug('updated ' + data._id + ' collection storage')
11
+ })
12
+ api.addCollectionListener('delete', 'collection', function cleanupCollectionStorage (req, collection, data) {
13
+ delete api.db[data._id]
14
+ debug('removed ' + data._id + ' collection storage')
15
+ })
16
+ api.addCollectionListener('changed', 'settings', function updateInMemorySettings (req, collection, data) {
17
+ // TODO: only reload if current environment is updated
18
+ Object.assign(req.settings, data)
19
+ })
20
+
21
+ api.addListener(['put', 'post'], async function updateMetadata (req, collection, data, { event }) {
22
+ data.meta ??= {}
23
+ data.meta.updated = new Date().toISOString()
24
+ if (event === 'post') {
25
+ data.meta.created = new Date().toISOString()
26
+ }
27
+ })
28
+
29
+ api.addCollectionListener('get', 'schemas', function addMetaToSchema (req, collection, data) {
30
+ const schema = data.schema
31
+ if (!schema.properties.meta) {
32
+ schema.properties.meta = {
33
+ type: 'object',
34
+ properties: {
35
+ created: {
36
+ type: 'string'
37
+ },
38
+ updated: {
39
+ type: 'string'
40
+ }
41
+ }
42
+ }
43
+ }
44
+ if (schema.properties.meta.propertyOrder === undefined) {
45
+ schema.properties.meta.propertyOrder = 2000
46
+ }
47
+ if (data.documentsHaveOwners) {
48
+ if (!schema.properties.meta.properties.owner) {
49
+ schema.properties.meta.properties.owner = {
50
+ type: 'string',
51
+ links: [
52
+ {
53
+ rel: '» view owner user',
54
+ href: '/admin/#/edit/users/{{self}}',
55
+ class: 'comment-link'
56
+ }
57
+ ]
58
+ },
59
+ schema.properties.meta.properties.owner_collection = {
60
+ type: 'string'
61
+ }
62
+ }
63
+ }
64
+ })
65
+ }
@@ -1,29 +1,29 @@
1
- const util = require('./util')
2
- const debug = require('debug')('expressa')
3
-
4
- const eventToPermissionMapping = {
5
- get: 'view',
6
- put: 'edit',
7
- post: 'create',
8
- delete: 'delete'
9
- }
10
-
11
- module.exports = function (api) {
12
- api.addCollectionListener('get', 'collection', function viewRelevantCollections (req, collection, data) {
13
- if (req.hasPermission('collection: view relevant')) {
14
- if (req.hasPermission(data._id + ': view') || req.hasPermission(data._id + ': view own')) {
15
- return true // force allow
16
- }
17
- }
18
- })
19
-
20
- api.addListener(['get', 'put', 'post', 'delete'], function collectionPermissionCheck (req, collection, data, info) {
21
- const permission = eventToPermissionMapping[info.event]
22
- const editingOwnDoc = data.meta && data.meta.owner && data.meta.owner === req.uid
23
- const editingOwn = editingOwnDoc && req.hasPermission(collection + ': ' + permission + ' own')
24
- if (!editingOwn && !req.hasPermission(collection + ': ' + permission)) {
25
- debug(`cancelling, missing permission "${collection}: ${permission}"`)
26
- throw new util.ApiError(401, 'You do not have permission to perform this action.')
27
- }
28
- })
29
- }
1
+ const util = require('./util')
2
+ const debug = require('debug')('expressa')
3
+
4
+ const eventToPermissionMapping = {
5
+ get: 'view',
6
+ put: 'edit',
7
+ post: 'create',
8
+ delete: 'delete'
9
+ }
10
+
11
+ module.exports = function (api) {
12
+ api.addCollectionListener('get', 'collection', function viewRelevantCollections (req, collection, data) {
13
+ if (req.hasPermission('collection: view relevant')) {
14
+ if (req.hasPermission(data._id + ': view') || req.hasPermission(data._id + ': view own')) {
15
+ return true // force allow
16
+ }
17
+ }
18
+ })
19
+
20
+ api.addListener(['get', 'put', 'post', 'delete'], function collectionPermissionCheck (req, collection, data, info) {
21
+ const permission = eventToPermissionMapping[info.event]
22
+ const editingOwnDoc = data.meta && data.meta.owner && data.meta.owner === req.uid
23
+ const editingOwn = editingOwnDoc && req.hasPermission(collection + ': ' + permission + ' own')
24
+ if (!editingOwn && !req.hasPermission(collection + ': ' + permission)) {
25
+ debug(`cancelling, missing permission "${collection}: ${permission}"`)
26
+ throw new util.ApiError(401, 'You do not have permission to perform this action.')
27
+ }
28
+ })
29
+ }
@@ -1,96 +1,96 @@
1
- const auth = require('./auth')
2
- const util = require('./util.js')
3
- const debug = require('debug')('expressa')
4
-
5
- module.exports = async function(api) {
6
-
7
- const loginCollections = (await util.getLoginCollections(api)).map((coll) => coll._id)
8
-
9
- api.addCollectionListener(['post', 'put'], loginCollections, async function updatePassword(req, collection, data) {
10
- if (req.method === 'PUT') {
11
- if (!data.password) {
12
- const oldData = await api.db[collection].get(data._id)
13
- data.password = oldData.password // preserve password if not explicitly set
14
- }
15
- }
16
- if (!auth.isHashed(data.password)) {
17
- debug('hashing and replacing password in the user document.')
18
- data.password = auth.createHash(data.password)
19
- data.meta.password_last_updated_at = new Date().toISOString()
20
- }
21
- })
22
-
23
- api.addCollectionListener('post', loginCollections, async function roleCreateCheck(req, collection, data) {
24
- if (!data.roles) {
25
- data.roles = []
26
- }
27
- // user special case to allow first user to be admin
28
- if (collection === 'users') {
29
- const hasUsers = (await api.db.users.find({}, 0, 1)).length === 1
30
- if (!hasUsers && !data.roles.includes('Admin')) {
31
- throw new util.ApiError(400, 'first user must have role Admin')
32
- }
33
- if (hasUsers && data.roles.length > 0 && !req.hasPermission('users: modify roles')) {
34
- throw new util.ApiError(400, 'insufficient permissions to create user with roles')
35
- }
36
- }
37
- else {
38
- if (data.roles.length > 0 && !req.hasPermission(`${collection}: modify roles`)) {
39
- throw new util.ApiError(400, `insufficient permissions to create ${collection} with roles`)
40
- }
41
- }
42
- })
43
-
44
- api.addLateCollectionListener('put', loginCollections, async function roleChangeCheck(req, collection, data) {
45
- if (!req.hasPermission(`${collection}: modify roles`)) {
46
- const oldData = await api.db[collection].get(data._id)
47
- data.roles = oldData.roles
48
- }
49
- })
50
-
51
- api.addCollectionListenerWithPriority(['get', 'changed', 'deleted'], loginCollections, 100, function hidePasswordHashes(req, collection, data) {
52
- if (!req.hasPermission(`${collection}: view hashed passwords`)) {
53
- debug(`deleting password because "${collection}: view hashed passwords"-permission is not set`)
54
- delete data.password
55
- }
56
- })
57
-
58
- api.addCollectionListener(['post', 'put'], loginCollections, async function userEmailLowerCase(req, collection, data) {
59
- if (data.email) {
60
- data.email = data.email.toLowerCase()
61
- }
62
- })
63
-
64
- api.addCollectionListener('post', loginCollections, async function userUniquenessCheck(req, collection, data) {
65
- if (data.email) {
66
- const result = await api.db[collection].find({email: data.email})
67
- if (result.length > 0) {
68
- throw new util.ApiError(409, 'This email is already registered.')
69
- }
70
- }
71
- })
72
-
73
- /* Mantain users-roles reference */
74
- api.addCollectionListener('changed', 'role', async function addRoleToUserSchema(req, collection, data) {
75
- for (const coll of loginCollections) {
76
- const doc = await api.db.collection.get(coll)
77
- if (!doc.schema.properties.roles.items.enum.includes(data._id)) {
78
- doc.schema.properties.roles.items.enum.push(data._id)
79
- await api.db.collection.update(coll, doc)
80
- await api.notify('changed', req, 'collection', doc)
81
- }
82
- }
83
- })
84
-
85
- api.addCollectionListener('deleted', 'role', async function removeRoleToUserSchema(req, collection, data) {
86
- for (const coll of loginCollections) {
87
- const doc = await api.db.collection.get(coll)
88
- const roles = doc.schema.properties.roles.items.enum
89
- if (roles.includes(data._id)) {
90
- roles.splice(roles.indexOf(data._id), 1)
91
- await api.db.collection.update(coll, doc)
92
- await api.notify('changed', req, 'collection', doc)
93
- }
94
- }
95
- })
96
- }
1
+ const auth = require('./auth')
2
+ const util = require('./util.js')
3
+ const debug = require('debug')('expressa')
4
+
5
+ module.exports = async function(api) {
6
+
7
+ const loginCollections = (await util.getLoginCollections(api)).map((coll) => coll._id)
8
+
9
+ api.addCollectionListener(['post', 'put'], loginCollections, async function updatePassword(req, collection, data) {
10
+ if (req.method === 'PUT') {
11
+ if (!data.password) {
12
+ const oldData = await api.db[collection].get(data._id)
13
+ data.password = oldData.password // preserve password if not explicitly set
14
+ }
15
+ }
16
+ if (!auth.isHashed(data.password)) {
17
+ debug('hashing and replacing password in the user document.')
18
+ data.password = auth.createHash(data.password)
19
+ data.meta.password_last_updated_at = new Date().toISOString()
20
+ }
21
+ })
22
+
23
+ api.addCollectionListener('post', loginCollections, async function roleCreateCheck(req, collection, data) {
24
+ if (!data.roles) {
25
+ data.roles = []
26
+ }
27
+ // user special case to allow first user to be admin
28
+ if (collection === 'users') {
29
+ const hasUsers = (await api.db.users.find({}, 0, 1)).length === 1
30
+ if (!hasUsers && !data.roles.includes('Admin')) {
31
+ throw new util.ApiError(400, 'first user must have role Admin')
32
+ }
33
+ if (hasUsers && data.roles.length > 0 && !req.hasPermission('users: modify roles')) {
34
+ throw new util.ApiError(400, 'insufficient permissions to create user with roles')
35
+ }
36
+ }
37
+ else {
38
+ if (data.roles.length > 0 && !req.hasPermission(`${collection}: modify roles`)) {
39
+ throw new util.ApiError(400, `insufficient permissions to create ${collection} with roles`)
40
+ }
41
+ }
42
+ })
43
+
44
+ api.addLateCollectionListener('put', loginCollections, async function roleChangeCheck(req, collection, data) {
45
+ if (!req.hasPermission(`${collection}: modify roles`)) {
46
+ const oldData = await api.db[collection].get(data._id)
47
+ data.roles = oldData.roles
48
+ }
49
+ })
50
+
51
+ api.addCollectionListenerWithPriority(['get', 'changed', 'deleted'], loginCollections, 100, function hidePasswordHashes(req, collection, data) {
52
+ if (!req.hasPermission(`${collection}: view hashed passwords`)) {
53
+ debug(`deleting password because "${collection}: view hashed passwords"-permission is not set`)
54
+ delete data.password
55
+ }
56
+ })
57
+
58
+ api.addCollectionListener(['post', 'put'], loginCollections, async function userEmailLowerCase(req, collection, data) {
59
+ if (data.email) {
60
+ data.email = data.email.toLowerCase()
61
+ }
62
+ })
63
+
64
+ api.addCollectionListener('post', loginCollections, async function userUniquenessCheck(req, collection, data) {
65
+ if (data.email) {
66
+ const result = await api.db[collection].find({email: data.email})
67
+ if (result.length > 0) {
68
+ throw new util.ApiError(409, 'This email is already registered.')
69
+ }
70
+ }
71
+ })
72
+
73
+ /* Mantain users-roles reference */
74
+ api.addCollectionListener('changed', 'role', async function addRoleToUserSchema(req, collection, data) {
75
+ for (const coll of loginCollections) {
76
+ const doc = await api.db.collection.get(coll)
77
+ if (!doc.schema.properties.roles.items.enum.includes(data._id)) {
78
+ doc.schema.properties.roles.items.enum.push(data._id)
79
+ await api.db.collection.update(coll, doc)
80
+ await api.notify('changed', req, 'collection', doc)
81
+ }
82
+ }
83
+ })
84
+
85
+ api.addCollectionListener('deleted', 'role', async function removeRoleToUserSchema(req, collection, data) {
86
+ for (const coll of loginCollections) {
87
+ const doc = await api.db.collection.get(coll)
88
+ const roles = doc.schema.properties.roles.items.enum
89
+ if (roles.includes(data._id)) {
90
+ roles.splice(roles.indexOf(data._id), 1)
91
+ await api.db.collection.update(coll, doc)
92
+ await api.notify('changed', req, 'collection', doc)
93
+ }
94
+ }
95
+ })
96
+ }
@@ -1,43 +1,43 @@
1
- const util = require('./util')
2
-
3
- function addImplicitFields (schema) {
4
- schema['properties'] ??= {}
5
- schema['properties']['meta'] ??= { type: 'object' }
6
- schema['properties']['meta']['properties'] ??= {}
7
- schema['properties']['meta']['properties']['created'] ??= { type: 'string' }
8
- schema['properties']['meta']['properties']['updated'] ??= { type: 'string' }
9
- schema['properties']['meta']['properties']['owner'] ??= { type: 'string' }
10
- schema['properties']['meta']['properties']['owner_collection'] ??= { type: 'string' }
11
- schema['properties']['_id'] ??= { type: 'string' }
12
- return schema
13
- }
14
-
15
- module.exports = async function (api) {
16
-
17
- // Load all validators
18
- const collections = await api.db.collection.all()
19
- collections.forEach((collection) => {
20
- const schema = addImplicitFields(collection.schema)
21
- util.addSchema(collection._id, schema)
22
- })
23
-
24
- // Load new and update validators as necessary
25
- api.addCollectionListener('changed', 'collection', function updateSchemaValidators (req, collection, data) {
26
- const schema = addImplicitFields(data.schema)
27
- util.addSchema(data._id, schema)
28
- })
29
-
30
- api.addCollectionListener('get', ['collection', 'schemas'], function ensureIdAdded (req, collection, data) {
31
- if (data.schema) {
32
- data.schema = addImplicitFields(data.schema)
33
- }
34
- })
35
-
36
- api.addListener(['put', 'post'], function matchesSchema (req, collection, data) {
37
- // TODO (switch to check if "installed" after install tests are done.
38
- if (!req.settings.enforce_permissions) {
39
- return
40
- }
41
- util.validateSchema(collection, data)
42
- })
43
- }
1
+ const util = require('./util')
2
+
3
+ function addImplicitFields (schema) {
4
+ schema['properties'] ??= {}
5
+ schema['properties']['meta'] ??= { type: 'object' }
6
+ schema['properties']['meta']['properties'] ??= {}
7
+ schema['properties']['meta']['properties']['created'] ??= { type: 'string' }
8
+ schema['properties']['meta']['properties']['updated'] ??= { type: 'string' }
9
+ schema['properties']['meta']['properties']['owner'] ??= { type: 'string' }
10
+ schema['properties']['meta']['properties']['owner_collection'] ??= { type: 'string' }
11
+ schema['properties']['_id'] ??= { type: 'string' }
12
+ return schema
13
+ }
14
+
15
+ module.exports = async function (api) {
16
+
17
+ // Load all validators
18
+ const collections = await api.db.collection.all()
19
+ collections.forEach((collection) => {
20
+ const schema = addImplicitFields(collection.schema)
21
+ util.addSchema(collection._id, schema)
22
+ })
23
+
24
+ // Load new and update validators as necessary
25
+ api.addCollectionListener('changed', 'collection', function updateSchemaValidators (req, collection, data) {
26
+ const schema = addImplicitFields(data.schema)
27
+ util.addSchema(data._id, schema)
28
+ })
29
+
30
+ api.addCollectionListener('get', ['collection', 'schemas'], function ensureIdAdded (req, collection, data) {
31
+ if (data.schema) {
32
+ data.schema = addImplicitFields(data.schema)
33
+ }
34
+ })
35
+
36
+ api.addListener(['put', 'post'], function matchesSchema (req, collection, data) {
37
+ // TODO (switch to check if "installed" after install tests are done.
38
+ if (!req.settings.enforce_permissions) {
39
+ return
40
+ }
41
+ util.validateSchema(collection, data)
42
+ })
43
+ }
@@ -1,30 +1,30 @@
1
- const onFinished = require('on-finished')
2
-
3
- const util = require('../util')
4
-
5
- module.exports = function logRequests(req, res, next) {
6
- if (req.db.requestlog) {
7
- const oldSend = res.send
8
- res.send = function(...params) {
9
- const data = params[0]
10
- // ensure requests using custom middleware log error messages
11
- if (typeof data?.error === 'string') {
12
- req.returnedError ??= new util.ApiError(res.statusCode, data.error)
13
- }
14
- res.send = oldSend // set function back to avoid the 'double-send'
15
- return res.send(...params) // just call as normal with data
16
- }
17
- onFinished(res, async function (err) {
18
- if (err) {
19
- console.error('error found while logging.')
20
- console.error(err)
21
- return
22
- }
23
- if (util.shouldLogRequest(req, res)) {
24
- const entry = util.createLogEntry(req, res)
25
- await req.db.requestlog.create(entry)
26
- }
27
- })
28
- }
29
- next()
30
- }
1
+ const onFinished = require('on-finished')
2
+
3
+ const util = require('../util')
4
+
5
+ module.exports = function logRequests(req, res, next) {
6
+ if (req.db.requestlog) {
7
+ const oldSend = res.send
8
+ res.send = function(...params) {
9
+ const data = params[0]
10
+ // ensure requests using custom middleware log error messages
11
+ if (typeof data?.error === 'string') {
12
+ req.returnedError ??= new util.ApiError(res.statusCode, data.error)
13
+ }
14
+ res.send = oldSend // set function back to avoid the 'double-send'
15
+ return res.send(...params) // just call as normal with data
16
+ }
17
+ onFinished(res, async function (err) {
18
+ if (err) {
19
+ console.error('error found while logging.')
20
+ console.error(err)
21
+ return
22
+ }
23
+ if (util.shouldLogRequest(req, res)) {
24
+ const entry = util.createLogEntry(req, res)
25
+ await req.db.requestlog.create(entry)
26
+ }
27
+ })
28
+ }
29
+ next()
30
+ }
@@ -1,41 +1,41 @@
1
- const util = require('../util')
2
- let authenticatedRoleExists
3
-
4
- async function addRolePermissions (req, roles) {
5
- const rolePermissions = await util.getEffectivePermissionsForRoles(roles, req.db)
6
- req.permissions ??= {}
7
- req.permissions = { ...req.permissions, ...rolePermissions }
8
- }
9
-
10
- async function doesAuthenticatedRoleExist(req) {
11
- if (typeof authenticatedRoleExists === 'undefined') {
12
- try {
13
- await req.db.role.get('Authenticated')
14
- authenticatedRoleExists = true
15
- } catch (e) {
16
- authenticatedRoleExists = false
17
- }
18
- }
19
- return authenticatedRoleExists
20
- }
21
-
22
- module.exports.addRolePermissionsAsync = async function addRolePermissionsMiddlewareAsync(req) {
23
- if (!req.settings || !req.settings.enforce_permissions) {
24
- // Use a dummy permission getter
25
- req.hasPermission = () => true
26
- return
27
- }
28
- req.hasPermission = (permission) => util.hasPermission(req.permissions, permission)
29
- let roles = ['Anonymous']
30
- const isAuthenticatedRole = await doesAuthenticatedRoleExist(req)
31
- if (req.uid) {
32
- roles = (req.user.roles || []).concat(isAuthenticatedRole ? ['Authenticated'] : [])
33
- } else {
34
- req.user = {}
35
- }
36
- await addRolePermissions(req, roles)
37
- }
38
-
39
- module.exports.middleware = function addRolePermissionsMiddleware (req, res, next) {
40
- module.exports.addRolePermissionsAsync(req).then(next).catch(next)
41
- }
1
+ const util = require('../util')
2
+ let authenticatedRoleExists
3
+
4
+ async function addRolePermissions (req, roles) {
5
+ const rolePermissions = await util.getEffectivePermissionsForRoles(roles, req.db)
6
+ req.permissions ??= {}
7
+ req.permissions = { ...req.permissions, ...rolePermissions }
8
+ }
9
+
10
+ async function doesAuthenticatedRoleExist(req) {
11
+ if (typeof authenticatedRoleExists === 'undefined') {
12
+ try {
13
+ await req.db.role.get('Authenticated')
14
+ authenticatedRoleExists = true
15
+ } catch (e) {
16
+ authenticatedRoleExists = false
17
+ }
18
+ }
19
+ return authenticatedRoleExists
20
+ }
21
+
22
+ module.exports.addRolePermissionsAsync = async function addRolePermissionsMiddlewareAsync(req) {
23
+ if (!req.settings || !req.settings.enforce_permissions) {
24
+ // Use a dummy permission getter
25
+ req.hasPermission = () => true
26
+ return
27
+ }
28
+ req.hasPermission = (permission) => util.hasPermission(req.permissions, permission)
29
+ let roles = ['Anonymous']
30
+ const isAuthenticatedRole = await doesAuthenticatedRoleExist(req)
31
+ if (req.uid) {
32
+ roles = (req.user.roles || []).concat(isAuthenticatedRole ? ['Authenticated'] : [])
33
+ } else {
34
+ req.user = {}
35
+ }
36
+ await addRolePermissions(req, roles)
37
+ }
38
+
39
+ module.exports.middleware = function addRolePermissionsMiddleware (req, res, next) {
40
+ module.exports.addRolePermissionsAsync(req).then(next).catch(next)
41
+ }