expressa 2.0.20 → 2.0.22
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.
- package/.circleci/config.yml +21 -21
- package/.eslintrc.json +29 -29
- package/.vscode/launch.json +19 -0
- package/LICENSE +21 -21
- package/README.md +137 -137
- package/auth/index.js +76 -76
- package/auth/jwt.js +31 -31
- package/controllers/collections.js +260 -260
- package/controllers/install.js +105 -105
- package/controllers/status.js +43 -43
- package/controllers/users.js +52 -52
- package/cypress/fixtures/example.json +4 -4
- package/cypress/integration/0_install_spec.js +8 -8
- package/cypress/integration/custom_collections.js +30 -30
- package/cypress/integration/home.js +9 -9
- package/cypress/integration/settings.js +34 -34
- package/cypress/integration/users.js +61 -61
- package/cypress/plugins/index.js +17 -17
- package/cypress/support/commands.js +69 -69
- package/cypress/support/index.js +20 -20
- package/cypress/support/util.js +10 -10
- package/cypress.config.js +18 -18
- package/cypress.json +4 -4
- package/db/cached.js +44 -40
- package/db/file.js +131 -112
- package/db/memory.js +92 -79
- package/db/mongo.js +93 -86
- package/db/postgres.js +100 -93
- package/doc/authentication.md +38 -38
- package/doc/automatic-fields.md +12 -12
- package/doc/blogexample.md +16 -16
- package/doc/custom-endpoints.md +20 -20
- package/doc/database.md +23 -23
- package/doc/development.md +33 -33
- package/doc/listeners.md +103 -103
- package/doc/modules.md +8 -8
- package/doc/permissions.md +14 -14
- package/doc/relationships.md +65 -65
- package/doc/testing.md +47 -47
- package/doc/uploading-files.md +53 -53
- package/index.js +297 -297
- package/listeners.js +65 -65
- package/listeners_collection_permissions.js +29 -29
- package/listeners_users.js +96 -96
- package/listeners_validation.js +43 -43
- package/middleware/logging.js +30 -30
- package/middleware/permissions.js +41 -41
- package/modules/access_keys/access_keys.js +32 -32
- package/modules/admin/.babelrc +19 -19
- package/modules/admin/.editorconfig +14 -14
- package/modules/admin/.eslintignore +5 -5
- package/modules/admin/.eslintrc.js +197 -197
- package/modules/admin/.postcssrc.js +10 -10
- package/modules/admin/.travis.yml +5 -5
- package/modules/admin/LICENSE +21 -21
- package/modules/admin/README.md +88 -88
- package/modules/admin/build/build.js +46 -46
- package/modules/admin/build/check-versions.js +64 -64
- package/modules/admin/build/utils.js +109 -109
- package/modules/admin/build/vue-loader.conf.js +5 -5
- package/modules/admin/build/webpack.base.conf.js +112 -112
- package/modules/admin/build/webpack.dev.conf.js +95 -95
- package/modules/admin/build/webpack.prod.conf.js +112 -112
- package/modules/admin/config/dev.env.js +8 -8
- package/modules/admin/config/index.js +86 -86
- package/modules/admin/config/prod.env.js +5 -5
- package/modules/admin/dist/css/136.1055b646.css +69 -0
- package/modules/admin/dist/css/136.1055b646.css.gz +0 -0
- package/modules/admin/dist/css/{430.8c9b0482.css → 165.e83d8cfe.css} +4 -4
- package/modules/admin/dist/css/165.e83d8cfe.css.gz +0 -0
- package/modules/admin/dist/css/220.e946bda7.css.gz +0 -0
- package/modules/admin/dist/css/{197.7a90435e.css → 236.e66ce8d8.css} +4 -4
- package/modules/admin/dist/css/236.e66ce8d8.css.gz +0 -0
- package/modules/admin/dist/css/338.1fd9d57c.css.gz +0 -0
- package/modules/admin/dist/css/{566.7a90435e.css → 566.e66ce8d8.css} +4 -4
- package/modules/admin/dist/css/566.e66ce8d8.css.gz +0 -0
- package/modules/admin/dist/css/76.46fe6799.css +66 -0
- package/modules/admin/dist/css/76.46fe6799.css.gz +0 -0
- package/modules/admin/dist/css/807.f450521e.css +1 -0
- package/modules/admin/dist/css/807.f450521e.css.gz +0 -0
- package/modules/admin/dist/css/{750.4086915d.css.gz → 913.4086915d.css.gz} +0 -0
- package/modules/admin/dist/css/{app.eba5f5e2.css → app.1c3232a3.css} +25 -25
- package/modules/admin/dist/css/app.1c3232a3.css.gz +0 -0
- package/modules/admin/dist/index.html +1 -1
- package/modules/admin/dist/static/js/115.108d0dd5.js +1 -0
- package/modules/admin/dist/static/js/115.108d0dd5.js.gz +0 -0
- package/modules/admin/dist/static/js/136.1f9f4cb0.js +1 -0
- package/modules/admin/dist/static/js/136.1f9f4cb0.js.gz +0 -0
- package/modules/admin/dist/static/js/165.b3d51941.js +1 -0
- package/modules/admin/dist/static/js/165.b3d51941.js.gz +0 -0
- package/modules/admin/dist/static/js/220.02b2cd49.js +1 -0
- package/modules/admin/dist/static/js/220.02b2cd49.js.gz +0 -0
- package/modules/admin/dist/static/js/236.98c79868.js +1 -0
- package/modules/admin/dist/static/js/236.98c79868.js.gz +0 -0
- package/modules/admin/dist/static/js/338.4ba49501.js +1 -0
- package/modules/admin/dist/static/js/338.4ba49501.js.gz +0 -0
- package/modules/admin/dist/static/js/381.a05421de.js +1 -0
- package/modules/admin/dist/static/js/381.a05421de.js.gz +0 -0
- package/modules/admin/dist/static/js/412.0914ff23.js +1 -0
- package/modules/admin/dist/static/js/412.0914ff23.js.gz +0 -0
- package/modules/admin/dist/static/js/533.507df4cb.js +1 -0
- package/modules/admin/dist/static/js/533.507df4cb.js.gz +0 -0
- package/modules/admin/dist/static/js/556.d7586b89.js +1 -0
- package/modules/admin/dist/static/js/556.d7586b89.js.gz +0 -0
- package/modules/admin/dist/static/js/76.811d0e96.js +1 -0
- package/modules/admin/dist/static/js/76.811d0e96.js.gz +0 -0
- package/modules/admin/dist/static/js/807.8b7455a7.js +1 -0
- package/modules/admin/dist/static/js/807.8b7455a7.js.gz +0 -0
- package/modules/admin/dist/static/js/913.8b376386.js +1 -0
- package/modules/admin/dist/static/js/913.8b376386.js.gz +0 -0
- package/modules/admin/dist/static/js/app.834abee4.js +2 -0
- package/modules/admin/dist/static/js/{app.ea86ae11.js.LICENSE.txt → app.834abee4.js.LICENSE.txt} +3 -3
- package/modules/admin/dist/static/js/app.834abee4.js.LICENSE.txt.gz +0 -0
- package/modules/admin/dist/static/js/app.834abee4.js.gz +0 -0
- package/modules/admin/index.html +16 -16
- package/modules/admin/package.json +102 -101
- package/modules/admin/src/App.vue +11 -11
- package/modules/admin/src/api/login.js +26 -26
- package/modules/admin/src/api/table.js +9 -9
- package/modules/admin/src/components/Breadcrumb/index.vue +79 -79
- package/modules/admin/src/components/Hamburger/index.vue +62 -62
- package/modules/admin/src/components/JSONEditor.vue +206 -180
- package/modules/admin/src/components/SvgIcon/index.vue +43 -43
- package/modules/admin/src/icons/index.js +9 -9
- package/modules/admin/src/icons/svgo.yml +22 -22
- package/modules/admin/src/main.js +42 -42
- package/modules/admin/src/permission.js +49 -49
- package/modules/admin/src/router/index.js +200 -200
- package/modules/admin/src/store/getters.js +11 -11
- package/modules/admin/src/store/index.js +17 -17
- package/modules/admin/src/store/modules/app.js +43 -43
- package/modules/admin/src/store/modules/user.js +104 -104
- package/modules/admin/src/styles/element-ui.scss +29 -29
- package/modules/admin/src/styles/index.scss +78 -78
- package/modules/admin/src/styles/mixin.scss +27 -27
- package/modules/admin/src/styles/sidebar.scss +133 -133
- package/modules/admin/src/styles/transition.scss +46 -46
- package/modules/admin/src/styles/variables.scss +4 -4
- package/modules/admin/src/utils/auth.js +15 -15
- package/modules/admin/src/utils/index.js +74 -74
- package/modules/admin/src/utils/request.js +47 -48
- package/modules/admin/src/utils/validate.js +31 -31
- package/modules/admin/src/views/404.vue +235 -235
- package/modules/admin/src/views/Dev.vue +33 -33
- package/modules/admin/src/views/EditDocument.vue +121 -121
- package/modules/admin/src/views/Endpoints.vue +279 -279
- package/modules/admin/src/views/Home.vue +84 -84
- package/modules/admin/src/views/Install.vue +79 -79
- package/modules/admin/src/views/ListDocuments.vue +72 -72
- package/modules/admin/src/views/ListDocuments2.vue +467 -467
- package/modules/admin/src/views/ManageListeners.vue +67 -67
- package/modules/admin/src/views/ManageMiddleware.vue +43 -43
- package/modules/admin/src/views/ManagePermissions.vue +75 -75
- package/modules/admin/src/views/ViewRequest.vue +77 -77
- package/modules/admin/src/views/form/index.vue +91 -91
- package/modules/admin/src/views/layout/Layout.vue +69 -69
- package/modules/admin/src/views/layout/components/AppMain.vue +41 -41
- package/modules/admin/src/views/layout/components/Navbar.vue +97 -97
- package/modules/admin/src/views/layout/components/Sidebar/Item.vue +29 -29
- package/modules/admin/src/views/layout/components/Sidebar/Link.vue +39 -39
- package/modules/admin/src/views/layout/components/Sidebar/SidebarItem.vue +103 -103
- package/modules/admin/src/views/layout/components/Sidebar/index.vue +62 -62
- package/modules/admin/src/views/layout/components/index.js +3 -3
- package/modules/admin/src/views/layout/mixin/ResizeHandler.js +41 -41
- package/modules/admin/src/views/login/index.vue +195 -195
- package/modules/admin/src/views/tree/index.vue +77 -77
- package/modules/collections/collections.js +39 -39
- package/modules/core/core.js +197 -197
- package/modules/logging/logging.js +88 -88
- package/modules/permissions/permissions.js +106 -106
- package/package.json +59 -59
- package/scripts/expressa +12 -12
- package/scripts/run_cypress_tests.sh +35 -35
- package/scripts/run_db_tests.sh +8 -8
- package/test/0-install.js +96 -96
- package/test/collections.js +431 -431
- package/test/collections.querying.js +288 -288
- package/test/db.js +258 -258
- package/test/db.strings.js +73 -73
- package/test/db.updating.js +186 -144
- package/test/logging.js +29 -29
- package/test/test.js +93 -93
- package/test/testserver.js +30 -17
- package/test/testutils.js +136 -136
- package/test/users.js +425 -425
- package/util.js +522 -522
- package/modules/admin/dist/css/197.7a90435e.css.gz +0 -0
- package/modules/admin/dist/css/430.8c9b0482.css.gz +0 -0
- package/modules/admin/dist/css/451.c6646d20.css +0 -1
- package/modules/admin/dist/css/451.c6646d20.css.gz +0 -0
- package/modules/admin/dist/css/566.7a90435e.css.gz +0 -0
- package/modules/admin/dist/css/569.f7d5c7d5.css +0 -37
- package/modules/admin/dist/css/569.f7d5c7d5.css.gz +0 -0
- package/modules/admin/dist/css/572.74d8e965.css +0 -34
- package/modules/admin/dist/css/572.74d8e965.css.gz +0 -0
- package/modules/admin/dist/css/651.e946bda7.css.gz +0 -0
- package/modules/admin/dist/css/950.1fd9d57c.css.gz +0 -0
- package/modules/admin/dist/css/app.eba5f5e2.css.gz +0 -0
- package/modules/admin/dist/static/js/197.e511d91e.js +0 -1
- package/modules/admin/dist/static/js/197.e511d91e.js.gz +0 -0
- package/modules/admin/dist/static/js/430.b3941f30.js +0 -1
- package/modules/admin/dist/static/js/430.b3941f30.js.gz +0 -0
- package/modules/admin/dist/static/js/451.645974a8.js +0 -1
- package/modules/admin/dist/static/js/451.645974a8.js.gz +0 -0
- package/modules/admin/dist/static/js/460.962d5bc0.js +0 -1
- package/modules/admin/dist/static/js/460.962d5bc0.js.gz +0 -0
- package/modules/admin/dist/static/js/550.f27feb23.js +0 -1
- package/modules/admin/dist/static/js/550.f27feb23.js.gz +0 -0
- package/modules/admin/dist/static/js/556.1e08866a.js +0 -1
- package/modules/admin/dist/static/js/556.1e08866a.js.gz +0 -0
- package/modules/admin/dist/static/js/569.0fecfbee.js +0 -1
- package/modules/admin/dist/static/js/569.0fecfbee.js.gz +0 -0
- package/modules/admin/dist/static/js/572.13392e1d.js +0 -1
- package/modules/admin/dist/static/js/572.13392e1d.js.gz +0 -0
- package/modules/admin/dist/static/js/603.7b34a650.js +0 -1
- package/modules/admin/dist/static/js/603.7b34a650.js.gz +0 -0
- package/modules/admin/dist/static/js/651.564b24b6.js +0 -1
- package/modules/admin/dist/static/js/651.564b24b6.js.gz +0 -0
- package/modules/admin/dist/static/js/653.02ec1098.js +0 -1
- package/modules/admin/dist/static/js/653.02ec1098.js.gz +0 -0
- package/modules/admin/dist/static/js/750.50c950da.js +0 -1
- package/modules/admin/dist/static/js/750.50c950da.js.gz +0 -0
- package/modules/admin/dist/static/js/950.d4ff0a18.js +0 -1
- package/modules/admin/dist/static/js/950.d4ff0a18.js.gz +0 -0
- package/modules/admin/dist/static/js/app.ea86ae11.js +0 -2
- package/modules/admin/dist/static/js/app.ea86ae11.js.LICENSE.txt.gz +0 -0
- package/modules/admin/dist/static/js/app.ea86ae11.js.gz +0 -0
- /package/modules/admin/dist/css/{651.e946bda7.css → 220.e946bda7.css} +0 -0
- /package/modules/admin/dist/css/{950.1fd9d57c.css → 338.1fd9d57c.css} +0 -0
- /package/modules/admin/dist/css/{750.4086915d.css → 913.4086915d.css} +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
|
+
}
|
package/listeners_users.js
CHANGED
|
@@ -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
|
+
}
|
package/listeners_validation.js
CHANGED
|
@@ -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
|
+
}
|
package/middleware/logging.js
CHANGED
|
@@ -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
|
+
}
|