expressa 2.0.16 → 2.0.18

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 (208) hide show
  1. package/.circleci/config.yml +21 -21
  2. package/.eslintrc.json +29 -29
  3. package/LICENSE +21 -21
  4. package/README.md +137 -137
  5. package/auth/index.js +76 -76
  6. package/auth/jwt.js +31 -31
  7. package/controllers/collections.js +260 -258
  8. package/controllers/install.js +105 -105
  9. package/controllers/status.js +43 -43
  10. package/controllers/users.js +52 -52
  11. package/cypress/fixtures/example.json +4 -4
  12. package/cypress/integration/0_install_spec.js +8 -8
  13. package/cypress/integration/custom_collections.js +28 -28
  14. package/cypress/integration/home.js +9 -9
  15. package/cypress/integration/settings.js +34 -34
  16. package/cypress/integration/users.js +61 -61
  17. package/cypress/plugins/index.js +17 -17
  18. package/cypress/screenshots/users.js/User Management -- Can log in as non-admin, but cannot create users (failed).png +0 -0
  19. package/cypress/support/commands.js +54 -54
  20. package/cypress/support/index.js +20 -20
  21. package/cypress/support/util.js +10 -10
  22. package/cypress.json +4 -4
  23. package/db/cached.js +40 -40
  24. package/db/file.js +112 -112
  25. package/db/memory.js +79 -79
  26. package/db/mongo.js +86 -86
  27. package/db/postgres.js +93 -93
  28. package/doc/authentication.md +38 -38
  29. package/doc/automatic-fields.md +12 -12
  30. package/doc/blogexample.md +16 -16
  31. package/doc/custom-endpoints.md +20 -20
  32. package/doc/database.md +23 -23
  33. package/doc/development.md +33 -33
  34. package/doc/listeners.md +103 -103
  35. package/doc/modules.md +8 -8
  36. package/doc/permissions.md +14 -14
  37. package/doc/relationships.md +65 -65
  38. package/doc/testing.md +47 -47
  39. package/doc/uploading-files.md +53 -53
  40. package/index.js +297 -297
  41. package/listeners.js +65 -65
  42. package/listeners_collection_permissions.js +29 -29
  43. package/listeners_users.js +96 -96
  44. package/listeners_validation.js +43 -43
  45. package/middleware/logging.js +30 -30
  46. package/middleware/permissions.js +41 -41
  47. package/modules/access_keys/access_keys.js +32 -32
  48. package/modules/admin/.babelrc +20 -12
  49. package/modules/admin/.editorconfig +14 -14
  50. package/modules/admin/.eslintignore +5 -5
  51. package/modules/admin/.eslintrc.js +197 -197
  52. package/modules/admin/.postcssrc.js +10 -10
  53. package/modules/admin/.travis.yml +5 -5
  54. package/modules/admin/LICENSE +21 -21
  55. package/modules/admin/README.md +88 -88
  56. package/modules/admin/build/build.js +45 -45
  57. package/modules/admin/build/check-versions.js +64 -64
  58. package/modules/admin/build/utils.js +108 -108
  59. package/modules/admin/build/vue-loader.conf.js +5 -5
  60. package/modules/admin/build/webpack.base.conf.js +112 -108
  61. package/modules/admin/build/webpack.dev.conf.js +95 -95
  62. package/modules/admin/build/webpack.prod.conf.js +112 -171
  63. package/modules/admin/config/dev.env.js +8 -8
  64. package/modules/admin/config/index.js +86 -86
  65. package/modules/admin/config/prod.env.js +5 -5
  66. package/modules/admin/dist/favicon.ico +0 -0
  67. package/modules/admin/dist/index.html +1 -0
  68. package/modules/admin/dist/index.html.gz +0 -0
  69. package/modules/admin/dist/static/css/app.9dd57eea.css +1 -0
  70. package/modules/admin/dist/static/css/app.9dd57eea.css.gz +0 -0
  71. package/modules/admin/dist/static/css/chunk-5061.ce0e4bdb.css +1 -0
  72. package/modules/admin/dist/static/css/chunk-5061.ce0e4bdb.css.gz +0 -0
  73. package/modules/admin/dist/static/css/chunk-5b97.7f9cb53e.css +1 -0
  74. package/modules/admin/dist/static/css/chunk-5b97.7f9cb53e.css.gz +0 -0
  75. package/modules/admin/dist/static/css/chunk-621e.b826be8d.css +1 -0
  76. package/modules/admin/dist/static/css/chunk-621e.b826be8d.css.gz +0 -0
  77. package/modules/admin/dist/static/css/chunk-656b.9c828b00.css +1 -0
  78. package/modules/admin/dist/static/css/chunk-656b.9c828b00.css.gz +0 -0
  79. package/modules/admin/dist/static/css/chunk-7291.13514e45.css +0 -0
  80. package/modules/admin/dist/static/css/chunk-7291.13514e45.css.gz +0 -0
  81. package/modules/admin/dist/static/css/chunk-7f29.9c828b00.css +1 -0
  82. package/modules/admin/dist/static/css/chunk-7f29.9c828b00.css.gz +0 -0
  83. package/modules/admin/dist/static/css/chunk-9a95.c647a2ef.css +1 -0
  84. package/modules/admin/dist/static/css/chunk-9a95.c647a2ef.css.gz +0 -0
  85. package/modules/admin/dist/static/css/chunk-b423.5f239333.css +0 -0
  86. package/modules/admin/dist/static/css/chunk-b423.5f239333.css.gz +0 -0
  87. package/modules/admin/dist/static/css/chunk-bd5f.aefa49d7.css +1 -0
  88. package/modules/admin/dist/static/css/chunk-bd5f.aefa49d7.css.gz +0 -0
  89. package/modules/admin/dist/static/css/chunk-e374.025d58bb.css +1 -0
  90. package/modules/admin/dist/static/css/chunk-e374.025d58bb.css.gz +0 -0
  91. package/modules/admin/dist/static/css/chunk-elementUI.3d004d9f.css +1 -0
  92. package/modules/admin/dist/static/css/chunk-elementUI.3d004d9f.css.gz +0 -0
  93. package/modules/admin/dist/static/css/chunk-f075.3a895a1e.css +1 -0
  94. package/modules/admin/dist/static/css/chunk-f075.3a895a1e.css.gz +0 -0
  95. package/modules/admin/dist/static/css/chunk-libs.327ad89e.css +14 -0
  96. package/modules/admin/dist/static/css/chunk-libs.327ad89e.css.gz +0 -0
  97. package/modules/admin/dist/static/fonts/element-icons.6f0a763.ttf +0 -0
  98. package/modules/admin/dist/static/img/404.a57b6f3.png +0 -0
  99. package/modules/admin/dist/static/js/app.e9461593.js +1 -0
  100. package/modules/admin/dist/static/js/app.e9461593.js.gz +0 -0
  101. package/modules/admin/dist/static/js/chunk-09d8.561880e0.js +2 -0
  102. package/modules/admin/dist/static/js/chunk-09d8.561880e0.js.LICENSE.txt +13 -0
  103. package/modules/admin/dist/static/js/chunk-09d8.561880e0.js.LICENSE.txt.gz +0 -0
  104. package/modules/admin/dist/static/js/chunk-09d8.561880e0.js.gz +0 -0
  105. package/modules/admin/dist/static/js/chunk-5061.83e6ccfa.js +1 -0
  106. package/modules/admin/dist/static/js/chunk-5061.83e6ccfa.js.gz +0 -0
  107. package/modules/admin/dist/static/js/chunk-5b97.ac360086.js +1 -0
  108. package/modules/admin/dist/static/js/chunk-5b97.ac360086.js.gz +0 -0
  109. package/modules/admin/dist/static/js/chunk-5e17.a2b69611.js +1 -0
  110. package/modules/admin/dist/static/js/chunk-5e17.a2b69611.js.gz +0 -0
  111. package/modules/admin/dist/static/js/chunk-621e.ccf42393.js +1 -0
  112. package/modules/admin/dist/static/js/chunk-621e.ccf42393.js.gz +0 -0
  113. package/modules/admin/dist/static/js/chunk-656b.9d5b03c0.js +1 -0
  114. package/modules/admin/dist/static/js/chunk-656b.9d5b03c0.js.gz +0 -0
  115. package/modules/admin/dist/static/js/chunk-7291.590524e9.js +1 -0
  116. package/modules/admin/dist/static/js/chunk-7291.590524e9.js.gz +0 -0
  117. package/modules/admin/dist/static/js/chunk-7f29.6ffc142e.js +1 -0
  118. package/modules/admin/dist/static/js/chunk-7f29.6ffc142e.js.gz +0 -0
  119. package/modules/admin/dist/static/js/chunk-9a95.bdd76583.js +1 -0
  120. package/modules/admin/dist/static/js/chunk-9a95.bdd76583.js.gz +0 -0
  121. package/modules/admin/dist/static/js/chunk-b423.387c504c.js +1 -0
  122. package/modules/admin/dist/static/js/chunk-b423.387c504c.js.gz +0 -0
  123. package/modules/admin/dist/static/js/chunk-bd5f.e9509f10.js +1 -0
  124. package/modules/admin/dist/static/js/chunk-bd5f.e9509f10.js.gz +0 -0
  125. package/modules/admin/dist/static/js/chunk-e374.7c87a0dd.js +1 -0
  126. package/modules/admin/dist/static/js/chunk-e374.7c87a0dd.js.gz +0 -0
  127. package/modules/admin/dist/static/js/chunk-e7c3.e5df8ceb.js +1 -0
  128. package/modules/admin/dist/static/js/chunk-e7c3.e5df8ceb.js.gz +0 -0
  129. package/modules/admin/dist/static/js/chunk-elementUI.b0c99c08.js +1 -0
  130. package/modules/admin/dist/static/js/chunk-elementUI.b0c99c08.js.gz +0 -0
  131. package/modules/admin/dist/static/js/chunk-f075.315ceec1.js +1 -0
  132. package/modules/admin/dist/static/js/chunk-f075.315ceec1.js.gz +0 -0
  133. package/modules/admin/dist/static/js/chunk-libs.06c38828.js +2 -0
  134. package/modules/admin/dist/static/js/chunk-libs.06c38828.js.LICENSE.txt +126 -0
  135. package/modules/admin/dist/static/js/chunk-libs.06c38828.js.LICENSE.txt.gz +0 -0
  136. package/modules/admin/dist/static/js/chunk-libs.06c38828.js.gz +0 -0
  137. package/modules/admin/index.html +16 -16
  138. package/modules/admin/package.json +101 -98
  139. package/modules/admin/src/App.vue +11 -11
  140. package/modules/admin/src/api/login.js +26 -26
  141. package/modules/admin/src/api/table.js +9 -9
  142. package/modules/admin/src/components/Breadcrumb/index.vue +79 -79
  143. package/modules/admin/src/components/Hamburger/index.vue +62 -62
  144. package/modules/admin/src/components/JSONEditor.vue +180 -180
  145. package/modules/admin/src/components/SvgIcon/index.vue +43 -43
  146. package/modules/admin/src/icons/index.js +9 -9
  147. package/modules/admin/src/icons/svgo.yml +22 -22
  148. package/modules/admin/src/main.js +42 -41
  149. package/modules/admin/src/permission.js +49 -49
  150. package/modules/admin/src/router/index.js +200 -200
  151. package/modules/admin/src/store/getters.js +11 -11
  152. package/modules/admin/src/store/index.js +17 -17
  153. package/modules/admin/src/store/modules/app.js +43 -43
  154. package/modules/admin/src/store/modules/user.js +104 -104
  155. package/modules/admin/src/styles/element-ui.scss +29 -29
  156. package/modules/admin/src/styles/index.scss +78 -78
  157. package/modules/admin/src/styles/mixin.scss +27 -27
  158. package/modules/admin/src/styles/sidebar.scss +133 -133
  159. package/modules/admin/src/styles/transition.scss +46 -46
  160. package/modules/admin/src/styles/variables.scss +4 -4
  161. package/modules/admin/src/utils/auth.js +15 -15
  162. package/modules/admin/src/utils/index.js +74 -74
  163. package/modules/admin/src/utils/request.js +48 -48
  164. package/modules/admin/src/utils/validate.js +31 -31
  165. package/modules/admin/src/views/404.vue +235 -235
  166. package/modules/admin/src/views/Dev.vue +33 -33
  167. package/modules/admin/src/views/EditDocument.vue +116 -116
  168. package/modules/admin/src/views/Endpoints.vue +279 -279
  169. package/modules/admin/src/views/Home.vue +84 -84
  170. package/modules/admin/src/views/Install.vue +79 -79
  171. package/modules/admin/src/views/ListDocuments.vue +72 -72
  172. package/modules/admin/src/views/ListDocuments2.vue +462 -462
  173. package/modules/admin/src/views/ManageListeners.vue +67 -67
  174. package/modules/admin/src/views/ManageMiddleware.vue +43 -43
  175. package/modules/admin/src/views/ManagePermissions.vue +75 -75
  176. package/modules/admin/src/views/ViewRequest.vue +77 -77
  177. package/modules/admin/src/views/form/index.vue +91 -91
  178. package/modules/admin/src/views/layout/Layout.vue +69 -69
  179. package/modules/admin/src/views/layout/components/AppMain.vue +41 -41
  180. package/modules/admin/src/views/layout/components/Navbar.vue +97 -97
  181. package/modules/admin/src/views/layout/components/Sidebar/Item.vue +29 -29
  182. package/modules/admin/src/views/layout/components/Sidebar/Link.vue +39 -39
  183. package/modules/admin/src/views/layout/components/Sidebar/SidebarItem.vue +103 -103
  184. package/modules/admin/src/views/layout/components/Sidebar/index.vue +62 -62
  185. package/modules/admin/src/views/layout/components/index.js +3 -3
  186. package/modules/admin/src/views/layout/mixin/ResizeHandler.js +41 -41
  187. package/modules/admin/src/views/login/index.vue +195 -195
  188. package/modules/admin/src/views/tree/index.vue +77 -77
  189. package/modules/collections/collections.js +39 -39
  190. package/modules/core/core.js +197 -197
  191. package/modules/logging/logging.js +88 -88
  192. package/modules/permissions/permissions.js +106 -106
  193. package/package.json +59 -59
  194. package/scripts/expressa +12 -12
  195. package/scripts/run_cypress_tests.sh +5 -5
  196. package/scripts/run_db_tests.sh +8 -8
  197. package/test/0-install.js +96 -96
  198. package/test/collections.js +431 -431
  199. package/test/collections.querying.js +288 -289
  200. package/test/db.js +258 -258
  201. package/test/db.strings.js +73 -73
  202. package/test/db.updating.js +144 -144
  203. package/test/logging.js +29 -29
  204. package/test/test.js +93 -94
  205. package/test/testserver.js +17 -17
  206. package/test/testutils.js +136 -136
  207. package/test/users.js +425 -425
  208. package/util.js +522 -522
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
+ }