expressa 2.0.18 → 2.0.20

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 (258) 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 -260
  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 +30 -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/support/commands.js +69 -54
  19. package/cypress/support/index.js +20 -20
  20. package/cypress/support/util.js +10 -10
  21. package/cypress.config.js +18 -0
  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 +19 -19
  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 +46 -45
  57. package/modules/admin/build/check-versions.js +64 -64
  58. package/modules/admin/build/utils.js +109 -108
  59. package/modules/admin/build/vue-loader.conf.js +5 -5
  60. package/modules/admin/build/webpack.base.conf.js +112 -112
  61. package/modules/admin/build/webpack.dev.conf.js +95 -95
  62. package/modules/admin/build/webpack.prod.conf.js +112 -112
  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/css/197.7a90435e.css +7 -0
  67. package/modules/admin/dist/css/197.7a90435e.css.gz +0 -0
  68. package/modules/admin/dist/css/430.8c9b0482.css +8 -0
  69. package/modules/admin/dist/css/430.8c9b0482.css.gz +0 -0
  70. package/modules/admin/dist/css/451.c6646d20.css +1 -0
  71. package/modules/admin/dist/css/451.c6646d20.css.gz +0 -0
  72. package/modules/admin/dist/css/566.7a90435e.css +7 -0
  73. package/modules/admin/dist/css/566.7a90435e.css.gz +0 -0
  74. package/modules/admin/dist/css/569.f7d5c7d5.css +37 -0
  75. package/modules/admin/dist/css/569.f7d5c7d5.css.gz +0 -0
  76. package/modules/admin/dist/css/572.74d8e965.css +34 -0
  77. package/modules/admin/dist/css/572.74d8e965.css.gz +0 -0
  78. package/modules/admin/dist/css/651.e946bda7.css +6 -0
  79. package/modules/admin/dist/css/651.e946bda7.css.gz +0 -0
  80. package/modules/admin/dist/css/750.4086915d.css +2 -0
  81. package/modules/admin/dist/css/750.4086915d.css.gz +0 -0
  82. package/modules/admin/dist/css/950.1fd9d57c.css +7 -0
  83. package/modules/admin/dist/css/950.1fd9d57c.css.gz +0 -0
  84. package/modules/admin/dist/css/app.eba5f5e2.css +592 -0
  85. package/modules/admin/dist/css/app.eba5f5e2.css.gz +0 -0
  86. package/modules/admin/dist/index.html +1 -1
  87. package/modules/admin/dist/static/fonts/element-icons.313f7da.woff +0 -0
  88. package/modules/admin/dist/static/fonts/element-icons.4520188.ttf +0 -0
  89. package/modules/admin/dist/static/js/197.e511d91e.js +1 -0
  90. package/modules/admin/dist/static/js/197.e511d91e.js.gz +0 -0
  91. package/modules/admin/dist/static/js/430.b3941f30.js +1 -0
  92. package/modules/admin/dist/static/js/430.b3941f30.js.gz +0 -0
  93. package/modules/admin/dist/static/js/451.645974a8.js +1 -0
  94. package/modules/admin/dist/static/js/451.645974a8.js.gz +0 -0
  95. package/modules/admin/dist/static/js/460.962d5bc0.js +1 -0
  96. package/modules/admin/dist/static/js/460.962d5bc0.js.gz +0 -0
  97. package/modules/admin/dist/static/js/550.f27feb23.js +1 -0
  98. package/modules/admin/dist/static/js/550.f27feb23.js.gz +0 -0
  99. package/modules/admin/dist/static/js/556.1e08866a.js +1 -0
  100. package/modules/admin/dist/static/js/556.1e08866a.js.gz +0 -0
  101. package/modules/admin/dist/static/js/569.0fecfbee.js +1 -0
  102. package/modules/admin/dist/static/js/569.0fecfbee.js.gz +0 -0
  103. package/modules/admin/dist/static/js/572.13392e1d.js +1 -0
  104. package/modules/admin/dist/static/js/572.13392e1d.js.gz +0 -0
  105. package/modules/admin/dist/static/js/603.7b34a650.js +1 -0
  106. package/modules/admin/dist/static/js/603.7b34a650.js.gz +0 -0
  107. package/modules/admin/dist/static/js/651.564b24b6.js +1 -0
  108. package/modules/admin/dist/static/js/651.564b24b6.js.gz +0 -0
  109. package/modules/admin/dist/static/js/653.02ec1098.js +1 -0
  110. package/modules/admin/dist/static/js/653.02ec1098.js.gz +0 -0
  111. package/modules/admin/dist/static/js/750.50c950da.js +1 -0
  112. package/modules/admin/dist/static/js/750.50c950da.js.gz +0 -0
  113. package/modules/admin/dist/static/js/950.d4ff0a18.js +1 -0
  114. package/modules/admin/dist/static/js/950.d4ff0a18.js.gz +0 -0
  115. package/modules/admin/dist/static/js/app.ea86ae11.js +2 -0
  116. package/modules/admin/dist/static/js/{chunk-libs.06c38828.js.LICENSE.txt → app.ea86ae11.js.LICENSE.txt} +15 -18
  117. package/modules/admin/dist/static/js/app.ea86ae11.js.LICENSE.txt.gz +0 -0
  118. package/modules/admin/dist/static/js/app.ea86ae11.js.gz +0 -0
  119. package/modules/admin/index.html +16 -16
  120. package/modules/admin/package.json +101 -101
  121. package/modules/admin/src/App.vue +11 -11
  122. package/modules/admin/src/api/login.js +26 -26
  123. package/modules/admin/src/api/table.js +9 -9
  124. package/modules/admin/src/components/Breadcrumb/index.vue +79 -79
  125. package/modules/admin/src/components/Hamburger/index.vue +62 -62
  126. package/modules/admin/src/components/JSONEditor.vue +180 -180
  127. package/modules/admin/src/components/SvgIcon/index.vue +43 -43
  128. package/modules/admin/src/icons/index.js +9 -9
  129. package/modules/admin/src/icons/svgo.yml +22 -22
  130. package/modules/admin/src/main.js +42 -42
  131. package/modules/admin/src/permission.js +49 -49
  132. package/modules/admin/src/router/index.js +200 -200
  133. package/modules/admin/src/store/getters.js +11 -11
  134. package/modules/admin/src/store/index.js +17 -17
  135. package/modules/admin/src/store/modules/app.js +43 -43
  136. package/modules/admin/src/store/modules/user.js +104 -104
  137. package/modules/admin/src/styles/element-ui.scss +29 -29
  138. package/modules/admin/src/styles/index.scss +78 -78
  139. package/modules/admin/src/styles/mixin.scss +27 -27
  140. package/modules/admin/src/styles/sidebar.scss +133 -133
  141. package/modules/admin/src/styles/transition.scss +46 -46
  142. package/modules/admin/src/styles/variables.scss +4 -4
  143. package/modules/admin/src/utils/auth.js +15 -15
  144. package/modules/admin/src/utils/index.js +74 -74
  145. package/modules/admin/src/utils/request.js +48 -48
  146. package/modules/admin/src/utils/validate.js +31 -31
  147. package/modules/admin/src/views/404.vue +235 -235
  148. package/modules/admin/src/views/Dev.vue +33 -33
  149. package/modules/admin/src/views/EditDocument.vue +121 -116
  150. package/modules/admin/src/views/Endpoints.vue +279 -279
  151. package/modules/admin/src/views/Home.vue +84 -84
  152. package/modules/admin/src/views/Install.vue +79 -79
  153. package/modules/admin/src/views/ListDocuments.vue +72 -72
  154. package/modules/admin/src/views/ListDocuments2.vue +467 -462
  155. package/modules/admin/src/views/ManageListeners.vue +67 -67
  156. package/modules/admin/src/views/ManageMiddleware.vue +43 -43
  157. package/modules/admin/src/views/ManagePermissions.vue +75 -75
  158. package/modules/admin/src/views/ViewRequest.vue +77 -77
  159. package/modules/admin/src/views/form/index.vue +91 -91
  160. package/modules/admin/src/views/layout/Layout.vue +69 -69
  161. package/modules/admin/src/views/layout/components/AppMain.vue +41 -41
  162. package/modules/admin/src/views/layout/components/Navbar.vue +97 -97
  163. package/modules/admin/src/views/layout/components/Sidebar/Item.vue +29 -29
  164. package/modules/admin/src/views/layout/components/Sidebar/Link.vue +39 -39
  165. package/modules/admin/src/views/layout/components/Sidebar/SidebarItem.vue +103 -103
  166. package/modules/admin/src/views/layout/components/Sidebar/index.vue +62 -62
  167. package/modules/admin/src/views/layout/components/index.js +3 -3
  168. package/modules/admin/src/views/layout/mixin/ResizeHandler.js +41 -41
  169. package/modules/admin/src/views/login/index.vue +195 -195
  170. package/modules/admin/src/views/tree/index.vue +77 -77
  171. package/modules/collections/collections.js +39 -39
  172. package/modules/core/core.js +197 -197
  173. package/modules/logging/logging.js +88 -88
  174. package/modules/permissions/permissions.js +106 -106
  175. package/package.json +59 -59
  176. package/scripts/expressa +12 -12
  177. package/scripts/run_cypress_tests.sh +36 -6
  178. package/scripts/run_db_tests.sh +8 -8
  179. package/test/0-install.js +96 -96
  180. package/test/collections.js +431 -431
  181. package/test/collections.querying.js +288 -288
  182. package/test/db.js +258 -258
  183. package/test/db.strings.js +73 -73
  184. package/test/db.updating.js +144 -144
  185. package/test/logging.js +29 -29
  186. package/test/test.js +93 -93
  187. package/test/testserver.js +17 -17
  188. package/test/testutils.js +136 -136
  189. package/test/users.js +425 -425
  190. package/util.js +522 -522
  191. package/modules/admin/dist/index.html.gz +0 -0
  192. package/modules/admin/dist/static/css/app.9dd57eea.css +0 -1
  193. package/modules/admin/dist/static/css/app.9dd57eea.css.gz +0 -0
  194. package/modules/admin/dist/static/css/chunk-5061.ce0e4bdb.css +0 -1
  195. package/modules/admin/dist/static/css/chunk-5061.ce0e4bdb.css.gz +0 -0
  196. package/modules/admin/dist/static/css/chunk-5b97.7f9cb53e.css +0 -1
  197. package/modules/admin/dist/static/css/chunk-5b97.7f9cb53e.css.gz +0 -0
  198. package/modules/admin/dist/static/css/chunk-621e.b826be8d.css +0 -1
  199. package/modules/admin/dist/static/css/chunk-621e.b826be8d.css.gz +0 -0
  200. package/modules/admin/dist/static/css/chunk-656b.9c828b00.css +0 -1
  201. package/modules/admin/dist/static/css/chunk-656b.9c828b00.css.gz +0 -0
  202. package/modules/admin/dist/static/css/chunk-7291.13514e45.css +0 -0
  203. package/modules/admin/dist/static/css/chunk-7291.13514e45.css.gz +0 -0
  204. package/modules/admin/dist/static/css/chunk-7f29.9c828b00.css +0 -1
  205. package/modules/admin/dist/static/css/chunk-7f29.9c828b00.css.gz +0 -0
  206. package/modules/admin/dist/static/css/chunk-9a95.c647a2ef.css +0 -1
  207. package/modules/admin/dist/static/css/chunk-9a95.c647a2ef.css.gz +0 -0
  208. package/modules/admin/dist/static/css/chunk-b423.5f239333.css +0 -0
  209. package/modules/admin/dist/static/css/chunk-b423.5f239333.css.gz +0 -0
  210. package/modules/admin/dist/static/css/chunk-bd5f.aefa49d7.css +0 -1
  211. package/modules/admin/dist/static/css/chunk-bd5f.aefa49d7.css.gz +0 -0
  212. package/modules/admin/dist/static/css/chunk-e374.025d58bb.css +0 -1
  213. package/modules/admin/dist/static/css/chunk-e374.025d58bb.css.gz +0 -0
  214. package/modules/admin/dist/static/css/chunk-elementUI.3d004d9f.css +0 -1
  215. package/modules/admin/dist/static/css/chunk-elementUI.3d004d9f.css.gz +0 -0
  216. package/modules/admin/dist/static/css/chunk-f075.3a895a1e.css +0 -1
  217. package/modules/admin/dist/static/css/chunk-f075.3a895a1e.css.gz +0 -0
  218. package/modules/admin/dist/static/css/chunk-libs.327ad89e.css +0 -14
  219. package/modules/admin/dist/static/css/chunk-libs.327ad89e.css.gz +0 -0
  220. package/modules/admin/dist/static/fonts/element-icons.6f0a763.ttf +0 -0
  221. package/modules/admin/dist/static/js/app.e9461593.js +0 -1
  222. package/modules/admin/dist/static/js/app.e9461593.js.gz +0 -0
  223. package/modules/admin/dist/static/js/chunk-09d8.561880e0.js +0 -2
  224. package/modules/admin/dist/static/js/chunk-09d8.561880e0.js.LICENSE.txt +0 -13
  225. package/modules/admin/dist/static/js/chunk-09d8.561880e0.js.LICENSE.txt.gz +0 -0
  226. package/modules/admin/dist/static/js/chunk-09d8.561880e0.js.gz +0 -0
  227. package/modules/admin/dist/static/js/chunk-5061.83e6ccfa.js +0 -1
  228. package/modules/admin/dist/static/js/chunk-5061.83e6ccfa.js.gz +0 -0
  229. package/modules/admin/dist/static/js/chunk-5b97.ac360086.js +0 -1
  230. package/modules/admin/dist/static/js/chunk-5b97.ac360086.js.gz +0 -0
  231. package/modules/admin/dist/static/js/chunk-5e17.a2b69611.js +0 -1
  232. package/modules/admin/dist/static/js/chunk-5e17.a2b69611.js.gz +0 -0
  233. package/modules/admin/dist/static/js/chunk-621e.ccf42393.js +0 -1
  234. package/modules/admin/dist/static/js/chunk-621e.ccf42393.js.gz +0 -0
  235. package/modules/admin/dist/static/js/chunk-656b.9d5b03c0.js +0 -1
  236. package/modules/admin/dist/static/js/chunk-656b.9d5b03c0.js.gz +0 -0
  237. package/modules/admin/dist/static/js/chunk-7291.590524e9.js +0 -1
  238. package/modules/admin/dist/static/js/chunk-7291.590524e9.js.gz +0 -0
  239. package/modules/admin/dist/static/js/chunk-7f29.6ffc142e.js +0 -1
  240. package/modules/admin/dist/static/js/chunk-7f29.6ffc142e.js.gz +0 -0
  241. package/modules/admin/dist/static/js/chunk-9a95.bdd76583.js +0 -1
  242. package/modules/admin/dist/static/js/chunk-9a95.bdd76583.js.gz +0 -0
  243. package/modules/admin/dist/static/js/chunk-b423.387c504c.js +0 -1
  244. package/modules/admin/dist/static/js/chunk-b423.387c504c.js.gz +0 -0
  245. package/modules/admin/dist/static/js/chunk-bd5f.e9509f10.js +0 -1
  246. package/modules/admin/dist/static/js/chunk-bd5f.e9509f10.js.gz +0 -0
  247. package/modules/admin/dist/static/js/chunk-e374.7c87a0dd.js +0 -1
  248. package/modules/admin/dist/static/js/chunk-e374.7c87a0dd.js.gz +0 -0
  249. package/modules/admin/dist/static/js/chunk-e7c3.e5df8ceb.js +0 -1
  250. package/modules/admin/dist/static/js/chunk-e7c3.e5df8ceb.js.gz +0 -0
  251. package/modules/admin/dist/static/js/chunk-elementUI.b0c99c08.js +0 -1
  252. package/modules/admin/dist/static/js/chunk-elementUI.b0c99c08.js.gz +0 -0
  253. package/modules/admin/dist/static/js/chunk-f075.315ceec1.js +0 -1
  254. package/modules/admin/dist/static/js/chunk-f075.315ceec1.js.gz +0 -0
  255. package/modules/admin/dist/static/js/chunk-libs.06c38828.js +0 -2
  256. package/modules/admin/dist/static/js/chunk-libs.06c38828.js.LICENSE.txt.gz +0 -0
  257. package/modules/admin/dist/static/js/chunk-libs.06c38828.js.gz +0 -0
  258. /package/modules/admin/dist/static/img/{404.a57b6f3.png → 404.4cf6930.png} +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
+ }