@kalisio/kdk 2.6.4 → 2.7.0
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/core/api/application.js +2 -4
- package/core/api/authentication.js +2 -3
- package/core/api/db.js +10 -2
- package/core/api/hooks/hooks.authorisations.js +4 -2
- package/core/api/hooks/hooks.push.js +6 -2
- package/core/api/hooks/hooks.query.js +29 -12
- package/core/api/hooks/hooks.users.js +30 -17
- package/core/api/models/configurations.model.mongodb.js +4 -0
- package/core/api/services/authorisations/authorisations.service.js +1 -1
- package/core/api/services/configurations/configurations.hooks.js +33 -0
- package/core/api/services/index.js +41 -7
- package/core/api/services/messages/messages.hooks.js +9 -3
- package/core/client/api.js +14 -1
- package/core/client/capabilities.js +1 -6
- package/core/client/components/KAvatar.vue +24 -20
- package/core/client/components/account/KProfile.vue +10 -71
- package/core/client/components/account/index.js +0 -2
- package/core/client/components/app/KSettings.vue +1 -0
- package/core/client/components/collection/KBoard.vue +4 -3
- package/core/client/components/collection/KCardSection.vue +1 -0
- package/core/client/components/collection/KGrid.vue +2 -0
- package/core/client/components/collection/KTable.vue +5 -1
- package/core/client/components/collection/KTimeLine.vue +9 -1
- package/core/client/components/collection/index.js +0 -2
- package/core/client/components/form/KChipsField.vue +2 -1
- package/core/client/components/form/KEmailField.vue +1 -0
- package/core/client/components/form/KFileField.vue +22 -1
- package/core/client/components/form/KForm.vue +2 -0
- package/core/client/components/form/KItemField.vue +1 -0
- package/core/client/components/form/KNumberField.vue +1 -0
- package/core/client/components/form/KPasswordField.vue +1 -0
- package/core/client/components/form/KPhoneField.vue +1 -0
- package/core/client/components/form/KPropertyItemField.vue +1 -0
- package/core/client/components/form/KResolutionField.vue +1 -0
- package/core/client/components/form/KSelectField.vue +31 -0
- package/core/client/components/form/KTagField.vue +1 -0
- package/core/client/components/form/KTextField.vue +1 -0
- package/core/client/components/form/KTokenField.vue +1 -0
- package/core/client/components/form/KUnitField.vue +1 -0
- package/core/client/components/form/KUrlField.vue +1 -0
- package/core/client/components/graphics/KIcon.vue +3 -4
- package/core/client/components/layout/KPage.vue +1 -0
- package/core/client/components/layout/KWindow.vue +6 -3
- package/core/client/components/messages/KMessageComposer.vue +2 -1
- package/core/client/components/messages/KMessagesTimeLine.vue +1 -1
- package/core/client/components/time/KDate.vue +1 -2
- package/core/client/components/time/KDateTime.vue +11 -11
- package/core/client/components/time/KTime.vue +1 -1
- package/core/client/composables/collection.js +33 -8
- package/core/client/composables/errors.js +1 -1
- package/core/client/composables/layout.js +9 -9
- package/core/client/configurations.js +50 -0
- package/core/client/exporter.js +1 -1
- package/core/client/i18n/core_en.json +6 -39
- package/core/client/i18n/core_fr.json +6 -39
- package/core/client/index.js +2 -0
- package/core/client/layout.js +8 -8
- package/core/client/mixins/mixin.base-activity.js +5 -2
- package/core/client/mixins/mixin.base-field.js +3 -3
- package/core/client/search.js +2 -1
- package/core/client/utils/utils.collection.js +8 -8
- package/core/client/utils/utils.items.js +4 -0
- package/core/client/utils/utils.push.js +3 -3
- package/core/client/utils/utils.session.js +7 -5
- package/core/client/utils/utils.shapes.js +38 -7
- package/core/client/utils/utils.time.js +21 -22
- package/core/common/schemas/users.update-profile.json +3 -2
- package/coverage/core/api/application.js.html +392 -398
- package/coverage/core/api/authentication.js.html +352 -187
- package/coverage/core/api/db.js.html +165 -126
- package/coverage/core/api/hooks/hooks.authentication.js.html +22 -196
- package/coverage/core/api/hooks/hooks.authorisations.js.html +383 -662
- package/coverage/core/api/hooks/hooks.logger.js.html +41 -41
- package/coverage/core/api/hooks/hooks.model.js.html +113 -101
- package/coverage/core/api/hooks/hooks.push.js.html +124 -97
- package/coverage/core/api/hooks/hooks.query.js.html +292 -217
- package/coverage/core/api/hooks/hooks.schemas.js.html +123 -123
- package/coverage/core/api/hooks/hooks.service.js.html +1 -1
- package/coverage/core/api/hooks/hooks.storage.js.html +1 -1
- package/coverage/core/api/hooks/{hooks.groups.js.html → hooks.tags.js.html} +100 -76
- package/coverage/core/api/hooks/hooks.users.js.html +255 -447
- package/coverage/core/api/hooks/index.html +107 -122
- package/coverage/core/api/hooks/index.js.html +4 -10
- package/coverage/core/api/index.html +46 -61
- package/coverage/core/api/index.js.html +9 -9
- package/coverage/core/api/marshall.js.html +9 -9
- package/coverage/core/api/models/{organisations.model.mongodb.js.html → configurations.model.mongodb.js.html} +10 -7
- package/coverage/core/api/models/index.html +35 -50
- package/coverage/core/api/models/messages.model.mongodb.js.html +39 -27
- package/coverage/core/api/models/tags.model.mongodb.js.html +26 -32
- package/coverage/core/api/models/users.model.mongodb.js.html +10 -10
- package/coverage/core/api/services/account/account.hooks.js.html +5 -5
- package/coverage/core/api/services/account/account.service.js.html +127 -127
- package/coverage/core/api/services/account/index.html +22 -22
- package/coverage/core/api/services/authorisations/authorisations.hooks.js.html +1 -1
- package/coverage/core/api/services/authorisations/authorisations.service.js.html +213 -222
- package/coverage/core/api/services/authorisations/index.html +21 -21
- package/coverage/core/api/services/{organisations/organisations.hooks.js.html → configurations/configurations.hooks.js.html} +16 -10
- package/coverage/core/api/services/{groups → configurations}/index.html +8 -8
- package/coverage/core/api/services/databases/databases.hooks.js.html +1 -1
- package/coverage/core/api/services/databases/databases.service.js.html +1 -1
- package/coverage/core/api/services/databases/index.html +1 -1
- package/coverage/core/api/services/import-export/import-export.hooks.js.html +76 -76
- package/coverage/core/api/services/import-export/import-export.service.js.html +32 -32
- package/coverage/core/api/services/import-export/index.html +32 -32
- package/coverage/core/api/services/index.html +21 -21
- package/coverage/core/api/services/index.js.html +313 -142
- package/coverage/core/api/services/mailer/index.html +32 -32
- package/coverage/core/api/services/mailer/mailer.hooks.js.html +80 -80
- package/coverage/core/api/services/mailer/mailer.service.js.html +32 -32
- package/coverage/core/api/services/messages/index.html +21 -21
- package/coverage/core/api/services/messages/messages.hooks.js.html +112 -76
- package/coverage/core/api/services/push/index.html +32 -32
- package/coverage/core/api/services/push/push.hooks.js.html +80 -80
- package/coverage/core/api/services/push/push.service.js.html +34 -34
- package/coverage/core/api/services/storage/index.html +29 -29
- package/coverage/core/api/services/storage/storage.hooks.js.html +80 -80
- package/coverage/core/api/services/storage/storage.service.js.html +29 -29
- package/coverage/core/api/services/tags/index.html +21 -21
- package/coverage/core/api/services/tags/tags.hooks.js.html +119 -71
- package/coverage/core/api/services/users/index.html +27 -12
- package/coverage/core/api/services/users/users.hooks.js.html +14 -11
- package/coverage/core/api/services/users/users.service.js.html +100 -0
- package/coverage/core/common/errors.js.html +1 -1
- package/coverage/core/common/index.html +42 -27
- package/coverage/core/common/index.js.html +1 -1
- package/coverage/core/common/permissions.js.html +166 -472
- package/coverage/core/common/schema.js.html +4 -4
- package/coverage/core/common/utils.js.html +31 -25
- package/coverage/core/common/utils.offline.js.html +199 -0
- package/coverage/index.html +192 -192
- package/coverage/lcov-report/core/api/application.js.html +392 -398
- package/coverage/lcov-report/core/api/authentication.js.html +352 -187
- package/coverage/lcov-report/core/api/db.js.html +165 -126
- package/coverage/lcov-report/core/api/hooks/hooks.authentication.js.html +22 -196
- package/coverage/lcov-report/core/api/hooks/hooks.authorisations.js.html +383 -662
- package/coverage/lcov-report/core/api/hooks/hooks.logger.js.html +41 -41
- package/coverage/lcov-report/core/api/hooks/hooks.model.js.html +113 -101
- package/coverage/lcov-report/core/api/hooks/hooks.push.js.html +124 -97
- package/coverage/lcov-report/core/api/hooks/hooks.query.js.html +292 -217
- package/coverage/lcov-report/core/api/hooks/hooks.schemas.js.html +123 -123
- package/coverage/lcov-report/core/api/hooks/hooks.service.js.html +1 -1
- package/coverage/lcov-report/core/api/hooks/hooks.storage.js.html +1 -1
- package/coverage/lcov-report/core/api/hooks/{hooks.groups.js.html → hooks.tags.js.html} +100 -76
- package/coverage/lcov-report/core/api/hooks/hooks.users.js.html +255 -447
- package/coverage/lcov-report/core/api/hooks/index.html +107 -122
- package/coverage/lcov-report/core/api/hooks/index.js.html +4 -10
- package/coverage/lcov-report/core/api/index.html +46 -61
- package/coverage/lcov-report/core/api/index.js.html +9 -9
- package/coverage/lcov-report/core/api/marshall.js.html +9 -9
- package/coverage/lcov-report/core/api/models/{organisations.model.mongodb.js.html → configurations.model.mongodb.js.html} +10 -7
- package/coverage/lcov-report/core/api/models/index.html +35 -50
- package/coverage/lcov-report/core/api/models/messages.model.mongodb.js.html +39 -27
- package/coverage/lcov-report/core/api/models/tags.model.mongodb.js.html +26 -32
- package/coverage/lcov-report/core/api/models/users.model.mongodb.js.html +10 -10
- package/coverage/lcov-report/core/api/services/account/account.hooks.js.html +5 -5
- package/coverage/lcov-report/core/api/services/account/account.service.js.html +127 -127
- package/coverage/lcov-report/core/api/services/account/index.html +22 -22
- package/coverage/lcov-report/core/api/services/authorisations/authorisations.hooks.js.html +1 -1
- package/coverage/lcov-report/core/api/services/authorisations/authorisations.service.js.html +213 -222
- package/coverage/lcov-report/core/api/services/authorisations/index.html +21 -21
- package/coverage/lcov-report/core/api/services/{groups/groups.hooks.js.html → configurations/configurations.hooks.js.html} +16 -10
- package/coverage/lcov-report/core/api/services/{groups → configurations}/index.html +8 -8
- package/coverage/lcov-report/core/api/services/databases/databases.hooks.js.html +1 -1
- package/coverage/lcov-report/core/api/services/databases/databases.service.js.html +1 -1
- package/coverage/lcov-report/core/api/services/databases/index.html +1 -1
- package/coverage/lcov-report/core/api/services/import-export/import-export.hooks.js.html +76 -76
- package/coverage/lcov-report/core/api/services/import-export/import-export.service.js.html +32 -32
- package/coverage/lcov-report/core/api/services/import-export/index.html +32 -32
- package/coverage/lcov-report/core/api/services/index.html +21 -21
- package/coverage/lcov-report/core/api/services/index.js.html +313 -142
- package/coverage/lcov-report/core/api/services/mailer/index.html +32 -32
- package/coverage/lcov-report/core/api/services/mailer/mailer.hooks.js.html +80 -80
- package/coverage/lcov-report/core/api/services/mailer/mailer.service.js.html +32 -32
- package/coverage/lcov-report/core/api/services/messages/index.html +21 -21
- package/coverage/lcov-report/core/api/services/messages/messages.hooks.js.html +112 -76
- package/coverage/lcov-report/core/api/services/push/index.html +32 -32
- package/coverage/lcov-report/core/api/services/push/push.hooks.js.html +80 -80
- package/coverage/lcov-report/core/api/services/push/push.service.js.html +34 -34
- package/coverage/lcov-report/core/api/services/storage/index.html +29 -29
- package/coverage/lcov-report/core/api/services/storage/storage.hooks.js.html +80 -80
- package/coverage/lcov-report/core/api/services/storage/storage.service.js.html +29 -29
- package/coverage/lcov-report/core/api/services/tags/index.html +21 -21
- package/coverage/lcov-report/core/api/services/tags/tags.hooks.js.html +119 -71
- package/coverage/lcov-report/core/api/services/users/index.html +27 -12
- package/coverage/lcov-report/core/api/services/users/users.hooks.js.html +14 -11
- package/coverage/lcov-report/core/api/services/users/users.service.js.html +100 -0
- package/coverage/lcov-report/core/common/errors.js.html +1 -1
- package/coverage/lcov-report/core/common/index.html +42 -27
- package/coverage/lcov-report/core/common/index.js.html +1 -1
- package/coverage/lcov-report/core/common/permissions.js.html +166 -472
- package/coverage/lcov-report/core/common/schema.js.html +4 -4
- package/coverage/lcov-report/core/common/utils.js.html +31 -25
- package/coverage/lcov-report/core/common/utils.offline.js.html +199 -0
- package/coverage/lcov-report/index.html +192 -192
- package/coverage/lcov-report/map/api/hooks/hooks.catalog.js.html +169 -31
- package/coverage/lcov-report/map/api/hooks/hooks.features.js.html +1 -1
- package/coverage/lcov-report/map/api/hooks/hooks.query.js.html +215 -35
- package/coverage/lcov-report/map/api/hooks/index.html +7 -7
- package/coverage/lcov-report/map/api/hooks/index.js.html +1 -1
- package/coverage/lcov-report/map/api/index.html +1 -1
- package/coverage/lcov-report/map/api/index.js.html +1 -1
- package/coverage/lcov-report/map/api/marshall.js.html +1 -1
- package/coverage/lcov-report/map/api/models/alerts.model.mongodb.js.html +1 -1
- package/coverage/lcov-report/map/api/models/catalog.model.mongodb.js.html +82 -7
- package/coverage/lcov-report/map/api/models/features.model.mongodb.js.html +1 -1
- package/coverage/lcov-report/map/api/models/index.html +22 -7
- package/coverage/lcov-report/map/api/models/projects.model.mongodb.js.html +1 -1
- package/coverage/lcov-report/{core/api/models/groups.model.mongodb.js.html → map/api/models/styles.model.mongodb.js.html} +10 -7
- package/coverage/lcov-report/map/api/services/alerts/alerts.hooks.js.html +1 -1
- package/coverage/lcov-report/map/api/services/alerts/alerts.service.js.html +1 -1
- package/coverage/lcov-report/map/api/services/alerts/index.html +1 -1
- package/coverage/lcov-report/map/api/services/catalog/catalog.hooks.js.html +39 -12
- package/coverage/lcov-report/map/api/services/catalog/index.html +5 -5
- package/coverage/lcov-report/map/api/services/daptiles/daptiles.service.js.html +1 -1
- package/coverage/lcov-report/map/api/services/daptiles/index.html +1 -1
- package/coverage/lcov-report/map/api/services/features/features.hooks.js.html +86 -11
- package/coverage/lcov-report/map/api/services/features/features.service.js.html +307 -4
- package/coverage/lcov-report/map/api/services/features/index.html +7 -7
- package/coverage/lcov-report/map/api/services/index.html +5 -5
- package/coverage/lcov-report/map/api/services/index.js.html +326 -50
- package/coverage/lcov-report/map/api/services/projects/index.html +1 -1
- package/coverage/lcov-report/map/api/services/projects/projects.hooks.js.html +1 -1
- package/coverage/{core/api/services/organisations → lcov-report/map/api/services/styles}/index.html +10 -25
- package/coverage/lcov-report/{core/api/services/organisations/organisations.hooks.js.html → map/api/services/styles/styles.hooks.js.html} +45 -12
- package/coverage/lcov-report/map/common/dynamic-grid-source.js.html +1 -1
- package/coverage/lcov-report/map/common/errors.js.html +1 -1
- package/coverage/lcov-report/map/common/geotiff-grid-source.js.html +7 -10
- package/coverage/lcov-report/map/common/grid.js.html +1 -1
- package/coverage/lcov-report/map/common/index.html +19 -19
- package/coverage/lcov-report/map/common/index.js.html +1 -1
- package/coverage/lcov-report/map/common/meteo-model-grid-source.js.html +1 -1
- package/coverage/lcov-report/map/common/moment-utils.js.html +1 -1
- package/coverage/lcov-report/map/common/opendap-grid-source.js.html +1 -1
- package/coverage/lcov-report/map/common/opendap-utils.js.html +4 -7
- package/coverage/lcov-report/map/common/permissions.js.html +10 -4
- package/coverage/lcov-report/map/common/time-based-grid-source.js.html +1 -1
- package/coverage/lcov-report/map/common/tms-utils.js.html +9 -12
- package/coverage/lcov-report/map/common/wcs-grid-source.js.html +3 -3
- package/coverage/lcov-report/map/common/wcs-utils.js.html +12 -15
- package/coverage/lcov-report/map/common/weacast-grid-source.js.html +2 -2
- package/coverage/lcov-report/map/common/wfs-utils.js.html +14 -17
- package/coverage/lcov-report/map/common/wms-utils.js.html +30 -12
- package/coverage/lcov-report/map/common/wmts-utils.js.html +10 -13
- package/coverage/lcov.info +4157 -3816
- package/coverage/map/api/hooks/hooks.catalog.js.html +169 -31
- package/coverage/map/api/hooks/hooks.features.js.html +1 -1
- package/coverage/map/api/hooks/hooks.query.js.html +215 -35
- package/coverage/map/api/hooks/index.html +7 -7
- package/coverage/map/api/hooks/index.js.html +1 -1
- package/coverage/map/api/index.html +1 -1
- package/coverage/map/api/index.js.html +1 -1
- package/coverage/map/api/marshall.js.html +1 -1
- package/coverage/map/api/models/alerts.model.mongodb.js.html +1 -1
- package/coverage/map/api/models/catalog.model.mongodb.js.html +82 -7
- package/coverage/map/api/models/features.model.mongodb.js.html +1 -1
- package/coverage/map/api/models/index.html +22 -7
- package/coverage/map/api/models/projects.model.mongodb.js.html +1 -1
- package/coverage/{core/api/models/groups.model.mongodb.js.html → map/api/models/styles.model.mongodb.js.html} +10 -7
- package/coverage/map/api/services/alerts/alerts.hooks.js.html +1 -1
- package/coverage/map/api/services/alerts/alerts.service.js.html +1 -1
- package/coverage/map/api/services/alerts/index.html +1 -1
- package/coverage/map/api/services/catalog/catalog.hooks.js.html +39 -12
- package/coverage/map/api/services/catalog/index.html +5 -5
- package/coverage/map/api/services/daptiles/daptiles.service.js.html +1 -1
- package/coverage/map/api/services/daptiles/index.html +1 -1
- package/coverage/map/api/services/features/features.hooks.js.html +86 -11
- package/coverage/map/api/services/features/features.service.js.html +307 -4
- package/coverage/map/api/services/features/index.html +7 -7
- package/coverage/map/api/services/index.html +5 -5
- package/coverage/map/api/services/index.js.html +326 -50
- package/coverage/map/api/services/projects/index.html +1 -1
- package/coverage/map/api/services/projects/projects.hooks.js.html +1 -1
- package/coverage/{lcov-report/core/api/services/organisations → map/api/services/styles}/index.html +10 -25
- package/coverage/{core/api/services/groups/groups.hooks.js.html → map/api/services/styles/styles.hooks.js.html} +45 -12
- package/coverage/map/common/dynamic-grid-source.js.html +1 -1
- package/coverage/map/common/errors.js.html +1 -1
- package/coverage/map/common/geotiff-grid-source.js.html +7 -10
- package/coverage/map/common/grid.js.html +1 -1
- package/coverage/map/common/index.html +19 -19
- package/coverage/map/common/index.js.html +1 -1
- package/coverage/map/common/meteo-model-grid-source.js.html +1 -1
- package/coverage/map/common/moment-utils.js.html +1 -1
- package/coverage/map/common/opendap-grid-source.js.html +1 -1
- package/coverage/map/common/opendap-utils.js.html +4 -7
- package/coverage/map/common/permissions.js.html +10 -4
- package/coverage/map/common/time-based-grid-source.js.html +1 -1
- package/coverage/map/common/tms-utils.js.html +9 -12
- package/coverage/map/common/wcs-grid-source.js.html +3 -3
- package/coverage/map/common/wcs-utils.js.html +12 -15
- package/coverage/map/common/weacast-grid-source.js.html +2 -2
- package/coverage/map/common/wfs-utils.js.html +14 -17
- package/coverage/map/common/wms-utils.js.html +30 -12
- package/coverage/map/common/wmts-utils.js.html +10 -13
- package/coverage/tmp/coverage-1028514-1773134124472-0.json +1 -0
- package/coverage/tmp/coverage-1028526-1773134124448-0.json +1 -0
- package/coverage/tmp/coverage-1028537-1773134124431-0.json +1 -0
- package/coverage/tmp/coverage-1028549-1773134124401-0.json +1 -0
- package/coverage/tmp/coverage-1028556-1773134124353-0.json +1 -0
- package/extras/configs/widgets.top.js +3 -3
- package/extras/tests/core/collection.mjs +2 -9
- package/extras/tours/pane.top.js +0 -9
- package/map/api/hooks/hooks.catalog.js +18 -4
- package/map/api/services/catalog/catalog.hooks.js +3 -0
- package/map/api/services/features/features.hooks.js +3 -1
- package/map/api/services/index.js +2 -6
- package/map/api/services/styles/styles.hooks.js +6 -1
- package/map/client/components/KFeatureActionButton.vue +9 -3
- package/map/client/components/KFeaturesFilterManager.vue +5 -5
- package/map/client/components/KFilterCondition.vue +17 -10
- package/map/client/components/KLayerEditor.vue +49 -39
- package/map/client/components/KMeasureTool.vue +7 -1
- package/map/client/components/KTimezoneMap.vue +29 -9
- package/map/client/components/catalog/KLayersPanel.vue +26 -16
- package/map/client/components/catalog/KLayersSelector.vue +13 -2
- package/map/client/components/catalog/KViewsPanel.vue +5 -4
- package/map/client/components/form/KSelectLayersField.vue +28 -17
- package/map/client/components/form/KSelectViewsField.vue +18 -9
- package/map/client/components/form/KTimezoneField.vue +1 -2
- package/map/client/components/legend/KVariablesLegend.vue +10 -1
- package/map/client/components/location/KLocationCardSection.vue +7 -2
- package/map/client/components/location/KLocationMap.vue +31 -7
- package/map/client/components/selection/KSelectedLayerFeatures.vue +2 -2
- package/map/client/components/stickies/KZoomControl.vue +1 -1
- package/map/client/components/styles/KStyleManager.vue +4 -1
- package/map/client/components/widget/KTimeSeries.vue +174 -497
- package/map/client/components/widget/KTimeSeriesSelector.vue +72 -0
- package/map/client/components/widget/KTimeSeriesToolbar.vue +83 -0
- package/map/client/composables/catalog.js +6 -10
- package/map/client/composables/highlight.js +12 -9
- package/map/client/composables/project.js +1 -1
- package/map/client/composables/selection.js +8 -7
- package/map/client/composables/weather.js +9 -2
- package/map/client/geolocation.js +8 -5
- package/map/client/i18n/map_en.json +11 -10
- package/map/client/i18n/map_fr.json +10 -9
- package/map/client/leaflet/TiledFeatureLayer.js +85 -82
- package/map/client/leaflet/utils/utils.geojson.js +3 -3
- package/map/client/mixins/globe/mixin.base-globe.js +15 -6
- package/map/client/mixins/globe/mixin.geojson-layers.js +27 -18
- package/map/client/mixins/map/mixin.edit-layers.js +9 -1
- package/map/client/mixins/map/mixin.pmtiles-layers.js +118 -29
- package/map/client/mixins/map/mixin.tiled-mesh-layers.js +12 -5
- package/map/client/mixins/map/mixin.tiled-wind-layers.js +19 -10
- package/map/client/mixins/mixin.activity.js +23 -30
- package/map/client/mixins/mixin.feature-selection.js +41 -5
- package/map/client/planets.js +1 -1
- package/map/client/readers/reader.kml.js +2 -3
- package/map/client/utils/utils.catalog.js +36 -10
- package/map/client/utils/utils.layers.js +39 -8
- package/map/client/utils/utils.project.js +4 -0
- package/map/client/utils/utils.style.js +37 -7
- package/map/client/utils/utils.time-series.js +215 -6
- package/map/common/schemas/catalog.update.json +1 -1
- package/map/common/weacast-grid-source.js +1 -1
- package/package.json +3 -3
- package/scripts/kash/CHANGELOG.md +0 -4
- package/scripts/kash/README.md +0 -9
- package/scripts/kash/kash.sh +45 -40
- package/scripts/kash/scripts/run_tests.sh +1 -4
- package/test/api/core/authentication.test.js +9 -4
- package/test/api/core/config/default.cjs +1 -0
- package/test/api/core/hooks.test.js +6 -0
- package/test/api/core/index.test.js +43 -18
- package/test/api/core/push.test.js +8 -8
- package/test/api/core/test-log-2026-03-10.log +60 -0
- package/test/api/core/users.test.js +384 -0
- package/test/api/map/grid-sources.test.js +1 -1
- package/test/api/map/test-log-2026-03-10.log +56 -0
- package/vite/package.json +11 -2
- package/vite/test/core/composables.test.js +77 -0
- package/vite/vitest.config.js +13 -0
- package/vite/yarn.lock +1096 -18
- package/.vscode/settings.json +0 -5
- package/core/client/components/account/KAccount.vue +0 -68
- package/core/client/components/account/KDeleteAccountManager.vue +0 -62
- package/core/client/components/account/KEmailManager.vue +0 -128
- package/core/client/components/account/KPasswordManager.vue +0 -90
- package/core/client/components/account/KVerifyEmailManager.vue +0 -105
- package/core/client/components/collection/KColumn.vue +0 -227
- package/core/client/components/collection/KHistory.vue +0 -113
- package/core/client/components/collection/KHistoryEntry.vue +0 -109
- package/coverage/core/api/hooks/hooks.organisations.js.html +0 -541
- package/coverage/core/api/services/organisations/organisations.service.js.html +0 -343
- package/coverage/core/api/utils.js.html +0 -118
- package/coverage/lcov-report/core/api/hooks/hooks.organisations.js.html +0 -541
- package/coverage/lcov-report/core/api/services/organisations/organisations.service.js.html +0 -343
- package/coverage/lcov-report/core/api/utils.js.html +0 -118
- package/coverage/tmp/coverage-151166-1723543324307-0.json +0 -1
- package/coverage/tmp/coverage-151178-1723543324283-0.json +0 -1
- package/coverage/tmp/coverage-151189-1723543324271-0.json +0 -1
- package/coverage/tmp/coverage-151201-1723543324248-0.json +0 -1
- package/coverage/tmp/coverage-151208-1723543324227-0.json +0 -1
- package/scripts/kash/LICENSE +0 -21
- package/test/api/core/test-log-2024-04-22.log +0 -84
- package/test/api/core/test-log-2024-04-23.log +0 -23
- package/test/api/core/test-log-2024-08-13.log +0 -3
- package/test/api/map/test-log-2025-03-08.log +0 -0
|
@@ -6,6 +6,7 @@ import request from 'superagent'
|
|
|
6
6
|
import chai from 'chai'
|
|
7
7
|
import chailint from 'chai-lint'
|
|
8
8
|
import spies from 'chai-spies'
|
|
9
|
+
import fuzzySearch from 'feathers-mongodb-fuzzy-search'
|
|
9
10
|
import core, { kdk, hooks, permissions, createMessagesService } from '../../../core/api/index.js'
|
|
10
11
|
import { fileURLToPath } from 'url'
|
|
11
12
|
|
|
@@ -15,7 +16,7 @@ const { util, expect } = chai
|
|
|
15
16
|
|
|
16
17
|
describe('core:services', () => {
|
|
17
18
|
let app, server, port, baseUrl, accessToken,
|
|
18
|
-
|
|
19
|
+
usersService, userObject, authorisationService, messagesService, messageObject,
|
|
19
20
|
spyUpdateAbilities
|
|
20
21
|
|
|
21
22
|
before(async () => {
|
|
@@ -44,8 +45,12 @@ describe('core:services', () => {
|
|
|
44
45
|
it('registers the services', async () => {
|
|
45
46
|
await app.configure(core)
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
expect(
|
|
48
|
+
usersService = app.getService('users')
|
|
49
|
+
expect(usersService).toExist()
|
|
50
|
+
// Register search hooks
|
|
51
|
+
usersService.hooks({
|
|
52
|
+
before: { find: [fuzzySearch({ fields: ['profile.name'] }), hooks.diacriticSearch()] }
|
|
53
|
+
})
|
|
49
54
|
// Create a global messages service for tests
|
|
50
55
|
await createMessagesService.call(app)
|
|
51
56
|
messagesService = app.getService('messages')
|
|
@@ -108,11 +113,11 @@ describe('core:services', () => {
|
|
|
108
113
|
const [localStrategy] = app.service('api/authentication').getStrategies('local')
|
|
109
114
|
const previousPassword = await localStrategy.hashPassword('weak;')
|
|
110
115
|
|
|
111
|
-
await assert.rejects(() =>
|
|
116
|
+
await assert.rejects(() => usersService.create({
|
|
112
117
|
email: 'test@test.org',
|
|
113
118
|
password: 'weak;',
|
|
114
119
|
previousPasswords: [previousPassword],
|
|
115
|
-
name: '
|
|
120
|
+
name: 'maëlis'
|
|
116
121
|
}), error => {
|
|
117
122
|
expect(error).toExist()
|
|
118
123
|
expect(error.name).to.equal('BadRequest')
|
|
@@ -120,10 +125,10 @@ describe('core:services', () => {
|
|
|
120
125
|
return true
|
|
121
126
|
})
|
|
122
127
|
|
|
123
|
-
await assert.rejects(() =>
|
|
128
|
+
await assert.rejects(() => usersService.create({
|
|
124
129
|
email: 'test@test.org',
|
|
125
130
|
password: '12345678',
|
|
126
|
-
name: '
|
|
131
|
+
name: 'maëlis'
|
|
127
132
|
}), error => {
|
|
128
133
|
expect(error).toExist()
|
|
129
134
|
expect(error.name).to.equal('BadRequest')
|
|
@@ -137,17 +142,17 @@ describe('core:services', () => {
|
|
|
137
142
|
it('creates a user', async () => {
|
|
138
143
|
// Test password generation
|
|
139
144
|
const hook = hooks.generatePassword()({ type: 'before', data: {}, params: {}, app })
|
|
140
|
-
userObject = await
|
|
145
|
+
userObject = await usersService.create({
|
|
141
146
|
email: 'test@test.org',
|
|
142
147
|
password: hook.data.password,
|
|
143
|
-
name: '
|
|
148
|
+
name: 'maëlis',
|
|
144
149
|
profile: { phone: '0623256968' }
|
|
145
150
|
}, { checkAuthorisation: true })
|
|
146
151
|
expect(spyUpdateAbilities).to.have.been.called.once
|
|
147
152
|
spyUpdateAbilities.reset()
|
|
148
153
|
// Keep track of clear password
|
|
149
154
|
userObject.clearPassword = hook.data.password
|
|
150
|
-
const users = await
|
|
155
|
+
const users = await usersService.find({ query: { 'profile.name': 'maëlis' } })
|
|
151
156
|
expect(users.data.length > 0).beTrue()
|
|
152
157
|
expect(users.data[0].email).toExist()
|
|
153
158
|
expect(users.data[0].clearPassword).beUndefined()
|
|
@@ -159,10 +164,10 @@ describe('core:services', () => {
|
|
|
159
164
|
.timeout(10000)
|
|
160
165
|
|
|
161
166
|
it('changing user password keeps password history', async () => {
|
|
162
|
-
await
|
|
167
|
+
await usersService.patch(userObject._id.toString(), { password: userObject.password })
|
|
163
168
|
expect(spyUpdateAbilities).to.have.been.called.once
|
|
164
169
|
spyUpdateAbilities.reset()
|
|
165
|
-
const user = await
|
|
170
|
+
const user = await usersService.get(userObject._id.toString())
|
|
166
171
|
expect(user.previousPasswords).toExist()
|
|
167
172
|
expect(user.previousPasswords).to.deep.equal([userObject.password])
|
|
168
173
|
})
|
|
@@ -226,15 +231,16 @@ describe('core:services', () => {
|
|
|
226
231
|
.timeout(5000)
|
|
227
232
|
|
|
228
233
|
it('authenticated user can access services', () => {
|
|
229
|
-
return
|
|
234
|
+
return usersService.find({ query: {}, params: { user: userObject, checkAuthorisation: true } })
|
|
230
235
|
.then(users => {
|
|
231
236
|
expect(users.data.length === 1).beTrue()
|
|
232
237
|
})
|
|
233
238
|
})
|
|
234
239
|
|
|
235
240
|
it('get user profile', () => {
|
|
236
|
-
return
|
|
241
|
+
return usersService.find({ query: { $select: ['profile'] } })
|
|
237
242
|
.then(users => {
|
|
243
|
+
expect(users.data.length > 0).beTrue()
|
|
238
244
|
expect(users.data[0].name).beUndefined()
|
|
239
245
|
expect(users.data[0].profile.name).toExist()
|
|
240
246
|
expect(users.data[0].profile.description).toExist()
|
|
@@ -242,6 +248,25 @@ describe('core:services', () => {
|
|
|
242
248
|
})
|
|
243
249
|
})
|
|
244
250
|
|
|
251
|
+
it('search user profile', async () => {
|
|
252
|
+
const hook = hooks.generatePassword()({ type: 'before', data: {}, params: {}, app })
|
|
253
|
+
const user = await usersService.create({
|
|
254
|
+
email: 'anothertest@test.org',
|
|
255
|
+
password: hook.data.password,
|
|
256
|
+
name: 'maelis',
|
|
257
|
+
profile: { phone: '0623256968' }
|
|
258
|
+
})
|
|
259
|
+
spyUpdateAbilities.reset()
|
|
260
|
+
const allUsers = await usersService.find({ query: { 'profile.name': { $search: 'Mae' } } })
|
|
261
|
+
// Diacritic should be more specific
|
|
262
|
+
const singleUsers = await usersService.find({ query: { 'profile.name': { $search: 'Maë' } } })
|
|
263
|
+
await usersService.remove(user._id)
|
|
264
|
+
expect(allUsers.data.length === 2).beTrue()
|
|
265
|
+
expect(singleUsers.data.length === 1).beTrue()
|
|
266
|
+
})
|
|
267
|
+
// Let enough time to process
|
|
268
|
+
.timeout(10000)
|
|
269
|
+
|
|
245
270
|
it('creates a user message', async () => {
|
|
246
271
|
const message = await messagesService.create({
|
|
247
272
|
title: 'Title',
|
|
@@ -271,7 +296,7 @@ describe('core:services', () => {
|
|
|
271
296
|
expect(authorisation).toExist()
|
|
272
297
|
expect(spyUpdateAbilities).to.have.been.called.once
|
|
273
298
|
spyUpdateAbilities.reset()
|
|
274
|
-
userObject = await
|
|
299
|
+
userObject = await usersService.get(userObject._id.toString())
|
|
275
300
|
expect(userObject.authorisations).toExist()
|
|
276
301
|
expect(userObject.authorisations.length > 0).beTrue()
|
|
277
302
|
expect(userObject.authorisations[0].permissions).to.deep.equal('manager')
|
|
@@ -334,7 +359,7 @@ describe('core:services', () => {
|
|
|
334
359
|
expect(authorisation).toExist()
|
|
335
360
|
expect(spyUpdateAbilities).to.have.been.called.once
|
|
336
361
|
spyUpdateAbilities.reset()
|
|
337
|
-
const user = await
|
|
362
|
+
const user = await usersService.get(userObject._id.toString())
|
|
338
363
|
expect(user.authorisations).toExist()
|
|
339
364
|
expect(user.authorisations.length === 0).beTrue()
|
|
340
365
|
})
|
|
@@ -361,11 +386,11 @@ describe('core:services', () => {
|
|
|
361
386
|
})
|
|
362
387
|
|
|
363
388
|
it('removes a user', async () => {
|
|
364
|
-
await
|
|
389
|
+
await usersService.remove(userObject._id, {
|
|
365
390
|
user: userObject,
|
|
366
391
|
checkAuthorisation: true
|
|
367
392
|
})
|
|
368
|
-
const users = await
|
|
393
|
+
const users = await usersService.find({ query: { name: 'maëlis' } })
|
|
369
394
|
expect(users.data.length === 0).beTrue()
|
|
370
395
|
const messages = await messagesService.find({ query: { title: 'Title' } })
|
|
371
396
|
expect(messages.data.length === 0).beTrue()
|
|
@@ -14,7 +14,7 @@ const { util, expect } = chai
|
|
|
14
14
|
|
|
15
15
|
describe('core:push', () => {
|
|
16
16
|
let app, server, port, mailerStub, gmailUser, user,
|
|
17
|
-
mailerService,
|
|
17
|
+
mailerService, usersService, pushService
|
|
18
18
|
|
|
19
19
|
const subscription = {
|
|
20
20
|
endpoint: process.env.SUBSCRIPTION_ENDPOINT,
|
|
@@ -71,9 +71,9 @@ describe('core:push', () => {
|
|
|
71
71
|
|
|
72
72
|
it('registers the services', async () => {
|
|
73
73
|
await app.configure(core)
|
|
74
|
-
|
|
75
|
-
expect(
|
|
76
|
-
|
|
74
|
+
usersService = app.getService('users')
|
|
75
|
+
expect(usersService).toExist()
|
|
76
|
+
usersService.hooks({
|
|
77
77
|
before: {
|
|
78
78
|
remove: [hooks.unregisterDevices]
|
|
79
79
|
},
|
|
@@ -101,7 +101,7 @@ describe('core:push', () => {
|
|
|
101
101
|
.timeout(5000)
|
|
102
102
|
|
|
103
103
|
it('create a user', () => {
|
|
104
|
-
return
|
|
104
|
+
return usersService.create({
|
|
105
105
|
email: gmailUser,
|
|
106
106
|
password: 'Pass;word1',
|
|
107
107
|
name: 'user'
|
|
@@ -117,7 +117,7 @@ describe('core:push', () => {
|
|
|
117
117
|
const previousUser = _.cloneDeep(user)
|
|
118
118
|
await addSubscription(user, subscription, 'subscriptions')
|
|
119
119
|
// Subscriptions change detection requires the previous user to be set
|
|
120
|
-
await
|
|
120
|
+
await usersService.patch(user._id, { subscriptions: user.subscriptions }, { user: previousUser })
|
|
121
121
|
expect(user.subscriptions).toExist()
|
|
122
122
|
expect(user.subscriptions.length === 1).beTrue()
|
|
123
123
|
expect(user.subscriptions[0].endpoint).to.equal(subscription.endpoint)
|
|
@@ -156,7 +156,7 @@ describe('core:push', () => {
|
|
|
156
156
|
it('delete expired subscriptions', async () => {
|
|
157
157
|
// Add expired subscription
|
|
158
158
|
await addSubscription(user, expiredSubscription, 'subscriptions')
|
|
159
|
-
await
|
|
159
|
+
await usersService.patch(user._id, { subscriptions: user.subscriptions })
|
|
160
160
|
expect(user.subscriptions).toExist()
|
|
161
161
|
expect(user.subscriptions.length === 2).beTrue()
|
|
162
162
|
// Send push notification
|
|
@@ -166,7 +166,7 @@ describe('core:push', () => {
|
|
|
166
166
|
subscriptionProperty: 'subscriptions',
|
|
167
167
|
subscriptionFilter: { _id: user._id }
|
|
168
168
|
})
|
|
169
|
-
const users = await
|
|
169
|
+
const users = await usersService.find({ query: { email: gmailUser } })
|
|
170
170
|
expect(users.data.length > 0).beTrue()
|
|
171
171
|
user = users.data[0]
|
|
172
172
|
// Check that expired subscriptions have been deleted
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
|
|
2
|
+
{"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
|
|
3
|
+
{"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
|
|
4
|
+
{"level":"error","message":"error: api/messages - Method: create: You are not allowed to access service messages"}
|
|
5
|
+
{"level":"error","message":"error: api/users - Method: create: The provided password does not comply to the password policy"}
|
|
6
|
+
{"level":"error","message":"error: api/users - Method: create: The provided password does not comply to the password policy"}
|
|
7
|
+
{"level":"error","message":"error: api/authorisations - Method: create: You are not allowed to change authorisation on resource"}
|
|
8
|
+
{"level":"error","message":"error: api/authorisations - Method: remove: You are not allowed to change authorisation on subject(s)"}
|
|
9
|
+
{"level":"info","message":"This is a log test"}
|
|
10
|
+
{"level":"error","message":"error: api/service - Method: create: validation failed"}
|
|
11
|
+
{"level":"error","message":"error: api/service - Method: create: validation failed"}
|
|
12
|
+
{"level":"error","message":"error: api/service - Method: create: validation failed"}
|
|
13
|
+
{"level":"error","message":"error: api/service - Method: create: validation failed"}
|
|
14
|
+
{"level":"error","message":"error: api/storage - Method: get: NoSuchKey"}
|
|
15
|
+
{"level":"error","message":"error: api/storage - Method: get: NoSuchKey"}
|
|
16
|
+
{"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
|
|
17
|
+
{"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
|
|
18
|
+
{"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
|
|
19
|
+
{"level":"error","message":"error: api/messages - Method: create: You are not allowed to access service messages"}
|
|
20
|
+
{"level":"error","message":"error: api/users - Method: create: The provided password does not comply to the password policy"}
|
|
21
|
+
{"level":"error","message":"error: api/users - Method: create: The provided password does not comply to the password policy"}
|
|
22
|
+
{"level":"error","message":"error: api/authorisations - Method: create: You are not allowed to change authorisation on resource"}
|
|
23
|
+
{"level":"error","message":"error: api/authorisations - Method: remove: You are not allowed to change authorisation on subject(s)"}
|
|
24
|
+
{"level":"info","message":"This is a log test"}
|
|
25
|
+
{"level":"error","message":"error: api/service - Method: create: validation failed"}
|
|
26
|
+
{"level":"error","message":"error: api/service - Method: create: validation failed"}
|
|
27
|
+
{"level":"error","message":"error: api/service - Method: create: validation failed"}
|
|
28
|
+
{"level":"error","message":"error: api/service - Method: create: validation failed"}
|
|
29
|
+
{"level":"error","message":"error: api/storage - Method: get: NoSuchKey"}
|
|
30
|
+
{"level":"error","message":"error: api/storage - Method: get: NoSuchKey"}
|
|
31
|
+
{"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
|
|
32
|
+
{"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
|
|
33
|
+
{"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
|
|
34
|
+
{"level":"error","message":"error: api/messages - Method: create: You are not allowed to access service messages"}
|
|
35
|
+
{"level":"error","message":"error: api/users - Method: create: The provided password does not comply to the password policy"}
|
|
36
|
+
{"level":"error","message":"error: api/users - Method: create: The provided password does not comply to the password policy"}
|
|
37
|
+
{"level":"error","message":"error: api/authorisations - Method: create: You are not allowed to change authorisation on resource"}
|
|
38
|
+
{"level":"error","message":"error: api/authorisations - Method: remove: You are not allowed to change authorisation on subject(s)"}
|
|
39
|
+
{"level":"info","message":"This is a log test"}
|
|
40
|
+
{"level":"error","message":"error: api/service - Method: create: validation failed"}
|
|
41
|
+
{"level":"error","message":"error: api/service - Method: create: validation failed"}
|
|
42
|
+
{"level":"error","message":"error: api/service - Method: create: validation failed"}
|
|
43
|
+
{"level":"error","message":"error: api/service - Method: create: validation failed"}
|
|
44
|
+
{"level":"error","message":"error: api/storage - Method: get: NoSuchKey"}
|
|
45
|
+
{"level":"error","message":"error: api/storage - Method: get: NoSuchKey"}
|
|
46
|
+
{"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
|
|
47
|
+
{"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
|
|
48
|
+
{"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
|
|
49
|
+
{"level":"error","message":"error: api/messages - Method: create: You are not allowed to access service messages"}
|
|
50
|
+
{"level":"error","message":"error: api/users - Method: create: The provided password does not comply to the password policy"}
|
|
51
|
+
{"level":"error","message":"error: api/users - Method: create: The provided password does not comply to the password policy"}
|
|
52
|
+
{"level":"error","message":"error: api/authorisations - Method: create: You are not allowed to change authorisation on resource"}
|
|
53
|
+
{"level":"error","message":"error: api/authorisations - Method: remove: You are not allowed to change authorisation on subject(s)"}
|
|
54
|
+
{"level":"info","message":"This is a log test"}
|
|
55
|
+
{"level":"error","message":"error: api/service - Method: create: validation failed"}
|
|
56
|
+
{"level":"error","message":"error: api/service - Method: create: validation failed"}
|
|
57
|
+
{"level":"error","message":"error: api/service - Method: create: validation failed"}
|
|
58
|
+
{"level":"error","message":"error: api/service - Method: create: validation failed"}
|
|
59
|
+
{"level":"error","message":"error: api/storage - Method: get: NoSuchKey"}
|
|
60
|
+
{"level":"error","message":"error: api/storage - Method: get: NoSuchKey"}
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import authentication from '@feathersjs/authentication'
|
|
3
|
+
import commonHooks from 'feathers-hooks-common'
|
|
4
|
+
import request from 'superagent'
|
|
5
|
+
import chai from 'chai'
|
|
6
|
+
import chailint from 'chai-lint'
|
|
7
|
+
import core, { kdk, hooks } from '../../../core/api/index.js'
|
|
8
|
+
|
|
9
|
+
const { authenticate } = authentication.hooks
|
|
10
|
+
const { util, expect } = chai
|
|
11
|
+
const { iff, disallow, isProvider, keep, discard } = commonHooks
|
|
12
|
+
const { isNotMe, onlyMe, preventChanges } = hooks
|
|
13
|
+
|
|
14
|
+
describe('core:users', () => {
|
|
15
|
+
let app, server, port, baseUrl, userIdAccessToken, emailAccessToken, phoneAccessToken, statelessAccessToken, adminAccessToken,
|
|
16
|
+
userService, userObject, anotherUserObject, authenticationService
|
|
17
|
+
|
|
18
|
+
before(async () => {
|
|
19
|
+
chailint(chai, util)
|
|
20
|
+
|
|
21
|
+
app = kdk()
|
|
22
|
+
port = app.get('port')
|
|
23
|
+
baseUrl = `http://localhost:${port}${app.get('apiPath')}`
|
|
24
|
+
await app.db.connect()
|
|
25
|
+
await app.db.instance.dropDatabase()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('registers the services', async () => {
|
|
29
|
+
await app.configure(core)
|
|
30
|
+
authenticationService = app.getService('authentication')
|
|
31
|
+
expect(authenticationService).toExist()
|
|
32
|
+
userService = app.getService('users')
|
|
33
|
+
expect(userService).toExist()
|
|
34
|
+
// Register hooks, what we'd like is a configuration so that:
|
|
35
|
+
// - information disclosure about internal user secrets like password is not permitted
|
|
36
|
+
// - information disclosure about others users is not permitted for a given user unless it has 'administrator' permissions
|
|
37
|
+
// - privilege escalation is not permitted for a given user
|
|
38
|
+
// - user with 'administrator' permissions can change others user permissions
|
|
39
|
+
// - changing others users information is not permitted for a given user unless it has 'administrator' permissions
|
|
40
|
+
// - external calls can only target myself except if I have administrator permissions
|
|
41
|
+
const isNotAdministrator = (context) => {
|
|
42
|
+
const userPermissions = _.get(context.params, 'user.permissions')
|
|
43
|
+
return userPermissions !== 'administrator'
|
|
44
|
+
}
|
|
45
|
+
userService.hooks({
|
|
46
|
+
before: {
|
|
47
|
+
all: authenticate('jwt'),
|
|
48
|
+
get: [iff(isNotMe(), disallow('external'))],
|
|
49
|
+
find: [iff(isProvider('external'), iff(isNotAdministrator, onlyMe()))],
|
|
50
|
+
create: [iff(isProvider('external'), keep('name', 'email', 'profile', 'password'))],
|
|
51
|
+
update: [disallow('external')],
|
|
52
|
+
patch: [iff(isProvider('external'), iff(isNotAdministrator, onlyMe(), preventChanges(true, ['permissions'])))],
|
|
53
|
+
remove: [iff(isProvider('external'), iff(isNotAdministrator, onlyMe()))]
|
|
54
|
+
},
|
|
55
|
+
after: {
|
|
56
|
+
all: [iff(isProvider('external'), iff(isNotMe(), iff(isNotAdministrator, discard('permissions'))))]
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
// Now app is configured launch the server
|
|
60
|
+
server = await app.listen(port)
|
|
61
|
+
await new Promise(resolve => server.once('listening', resolve))
|
|
62
|
+
})
|
|
63
|
+
// Let enough time to process
|
|
64
|
+
.timeout(10000)
|
|
65
|
+
|
|
66
|
+
it('creates users and tokens with different subject identifiers', async () => {
|
|
67
|
+
userObject = await userService.create({
|
|
68
|
+
email: 'test@test.org',
|
|
69
|
+
name: 'test user',
|
|
70
|
+
permissions: 'user',
|
|
71
|
+
profile: { phone: '0623256968' }
|
|
72
|
+
})
|
|
73
|
+
userIdAccessToken = await authenticationService.createAccessToken({
|
|
74
|
+
sub: userObject._id
|
|
75
|
+
})
|
|
76
|
+
emailAccessToken = await authenticationService.createAccessToken({
|
|
77
|
+
sub: userObject.email
|
|
78
|
+
})
|
|
79
|
+
phoneAccessToken = await authenticationService.createAccessToken({
|
|
80
|
+
sub: userObject.profile.phone
|
|
81
|
+
})
|
|
82
|
+
statelessAccessToken = await authenticationService.createAccessToken({
|
|
83
|
+
property: 'mycustomproperty'
|
|
84
|
+
}, {
|
|
85
|
+
subject: 'mycustomapp'
|
|
86
|
+
})
|
|
87
|
+
anotherUserObject = await userService.create({
|
|
88
|
+
email: 'another_test@test.org',
|
|
89
|
+
name: 'another test user',
|
|
90
|
+
permissions: 'administrator',
|
|
91
|
+
profile: { phone: '0623256969' }
|
|
92
|
+
})
|
|
93
|
+
adminAccessToken = await authenticationService.createAccessToken({
|
|
94
|
+
sub: anotherUserObject._id
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('checks all user tokens are recognized', async () => {
|
|
99
|
+
let response = await request
|
|
100
|
+
.post(`${baseUrl}/authentication`)
|
|
101
|
+
.send({ accessToken: userIdAccessToken, strategy: 'jwt' })
|
|
102
|
+
let accessToken = response.body.accessToken
|
|
103
|
+
let user = response.body.user
|
|
104
|
+
expect(accessToken).toExist()
|
|
105
|
+
expect(accessToken).not.to.equal(userIdAccessToken)
|
|
106
|
+
expect(user).toExist()
|
|
107
|
+
response = await request
|
|
108
|
+
.post(`${baseUrl}/authentication`)
|
|
109
|
+
.send({ accessToken: emailAccessToken, strategy: 'jwt' })
|
|
110
|
+
accessToken = response.body.accessToken
|
|
111
|
+
user = response.body.user
|
|
112
|
+
expect(accessToken).toExist()
|
|
113
|
+
expect(accessToken).not.to.equal(emailAccessToken)
|
|
114
|
+
expect(user).toExist()
|
|
115
|
+
response = await request
|
|
116
|
+
.post(`${baseUrl}/authentication`)
|
|
117
|
+
.send({ accessToken: phoneAccessToken, strategy: 'jwt' })
|
|
118
|
+
accessToken = response.body.accessToken
|
|
119
|
+
user = response.body.user
|
|
120
|
+
expect(accessToken).toExist()
|
|
121
|
+
expect(accessToken).not.to.equal(phoneAccessToken)
|
|
122
|
+
expect(user).toExist()
|
|
123
|
+
response = await request
|
|
124
|
+
.post(`${baseUrl}/authentication`)
|
|
125
|
+
.send({ accessToken: statelessAccessToken, strategy: 'jwt' })
|
|
126
|
+
accessToken = response.body.accessToken
|
|
127
|
+
user = response.body.user
|
|
128
|
+
expect(accessToken).toExist()
|
|
129
|
+
expect(accessToken).not.to.equal(statelessAccessToken)
|
|
130
|
+
expect(user).beUndefined()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('checks for user information disclosure', async () => {
|
|
134
|
+
// Should not retrieve internal user secret information like password in any case
|
|
135
|
+
// Should not list others users in case of requests with identified user
|
|
136
|
+
let response = await request
|
|
137
|
+
.get(`${baseUrl}/users`)
|
|
138
|
+
.set('Authorization', 'Bearer ' + userIdAccessToken)
|
|
139
|
+
let users = response.body.data
|
|
140
|
+
expect(users.length).to.equal(1)
|
|
141
|
+
let user = users[0]
|
|
142
|
+
expect(user._id).to.equal(userObject._id.toString())
|
|
143
|
+
expect(user.password).beUndefined()
|
|
144
|
+
expect(user.previousPasswords).beUndefined()
|
|
145
|
+
expect(user.permissions).toExist()
|
|
146
|
+
response = await request
|
|
147
|
+
.get(`${baseUrl}/users`)
|
|
148
|
+
.set('Authorization', 'Bearer ' + emailAccessToken)
|
|
149
|
+
users = response.body.data
|
|
150
|
+
expect(users.length).to.equal(1)
|
|
151
|
+
user = users[0]
|
|
152
|
+
expect(user._id).to.equal(userObject._id.toString())
|
|
153
|
+
expect(user.password).beUndefined()
|
|
154
|
+
expect(user.previousPasswords).beUndefined()
|
|
155
|
+
expect(user.permissions).toExist()
|
|
156
|
+
response = await request
|
|
157
|
+
.get(`${baseUrl}/users`)
|
|
158
|
+
.set('Authorization', 'Bearer ' + phoneAccessToken)
|
|
159
|
+
users = response.body.data
|
|
160
|
+
expect(users.length).to.equal(1)
|
|
161
|
+
user = users[0]
|
|
162
|
+
expect(user._id).to.equal(userObject._id.toString())
|
|
163
|
+
expect(user.password).beUndefined()
|
|
164
|
+
expect(user.previousPasswords).beUndefined()
|
|
165
|
+
expect(user.permissions).toExist()
|
|
166
|
+
// Should not list users in case of request without identified user
|
|
167
|
+
try {
|
|
168
|
+
response = await request
|
|
169
|
+
.get(`${baseUrl}/users/${anotherUserObject._id}`)
|
|
170
|
+
.set('Authorization', 'Bearer ' + statelessAccessToken)
|
|
171
|
+
} catch (error) {
|
|
172
|
+
// Not sure why but in this case the raised error is in text/html format
|
|
173
|
+
expect(error.status).to.equal(500)
|
|
174
|
+
expect(error.response.text.includes('MethodNotAllowed')).beTrue()
|
|
175
|
+
}
|
|
176
|
+
/*
|
|
177
|
+
response = await request
|
|
178
|
+
.get(`${baseUrl}/users`)
|
|
179
|
+
.set('Authorization', 'Bearer ' + statelessAccessToken)
|
|
180
|
+
users = response.body.data
|
|
181
|
+
expect(users.length).to.equal(2)
|
|
182
|
+
user = users[0]
|
|
183
|
+
expect(user._id).to.equal(userObject._id.toString())
|
|
184
|
+
expect(user.password).beUndefined()
|
|
185
|
+
expect(user.previousPasswords).beUndefined()
|
|
186
|
+
expect(user.permissions).beUndefined()
|
|
187
|
+
user = users[1]
|
|
188
|
+
expect(user._id).to.equal(anotherUserObject._id.toString())
|
|
189
|
+
expect(user.password).beUndefined()
|
|
190
|
+
expect(user.previousPasswords).beUndefined()
|
|
191
|
+
expect(user.permissions).beUndefined()
|
|
192
|
+
*/
|
|
193
|
+
// Should not get others users in case of requests with identified user
|
|
194
|
+
try {
|
|
195
|
+
response = await request
|
|
196
|
+
.get(`${baseUrl}/users/${anotherUserObject._id}`)
|
|
197
|
+
.set('Authorization', 'Bearer ' + userIdAccessToken)
|
|
198
|
+
} catch (error) {
|
|
199
|
+
// Not sure why but in this case the raised error is in text/html format
|
|
200
|
+
expect(error.status).to.equal(500)
|
|
201
|
+
expect(error.response.text.includes('MethodNotAllowed')).beTrue()
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
response = await request
|
|
205
|
+
.get(`${baseUrl}/users/${anotherUserObject._id}`)
|
|
206
|
+
.set('Authorization', 'Bearer ' + emailAccessToken)
|
|
207
|
+
} catch (error) {
|
|
208
|
+
// Not sure why but in this case the raised error is in text/html format
|
|
209
|
+
expect(error.status).to.equal(500)
|
|
210
|
+
expect(error.response.text.includes('MethodNotAllowed')).beTrue()
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
response = await request
|
|
214
|
+
.get(`${baseUrl}/users/${anotherUserObject._id}`)
|
|
215
|
+
.set('Authorization', 'Bearer ' + phoneAccessToken)
|
|
216
|
+
} catch (error) {
|
|
217
|
+
// Not sure why but in this case the raised error is in text/html format
|
|
218
|
+
expect(error.status).to.equal(500)
|
|
219
|
+
expect(error.response.text.includes('MethodNotAllowed')).beTrue()
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
response = await request
|
|
223
|
+
.get(`${baseUrl}/users/${anotherUserObject._id}`)
|
|
224
|
+
.set('Authorization', 'Bearer ' + statelessAccessToken)
|
|
225
|
+
} catch (error) {
|
|
226
|
+
// Not sure why but in this case the raised error is in text/html format
|
|
227
|
+
expect(error.status).to.equal(500)
|
|
228
|
+
expect(error.response.text.includes('MethodNotAllowed')).beTrue()
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('checks for user information integrity', async () => {
|
|
233
|
+
// Should not be able to update information of others users if not administrator
|
|
234
|
+
try {
|
|
235
|
+
await request
|
|
236
|
+
.patch(`${baseUrl}/users/${anotherUserObject._id}`)
|
|
237
|
+
.set('Authorization', 'Bearer ' + userIdAccessToken)
|
|
238
|
+
.send({ name: 'new name' })
|
|
239
|
+
} catch (error) {
|
|
240
|
+
// Not sure why but in this case the raised error is in text/html format
|
|
241
|
+
expect(error.status).to.equal(500)
|
|
242
|
+
expect(error.response.text.includes('NotFound')).beTrue()
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
await request
|
|
246
|
+
.patch(`${baseUrl}/users/${anotherUserObject._id}`)
|
|
247
|
+
.set('Authorization', 'Bearer ' + emailAccessToken)
|
|
248
|
+
.send({ name: 'new name' })
|
|
249
|
+
} catch (error) {
|
|
250
|
+
// Not sure why but in this case the raised error is in text/html format
|
|
251
|
+
expect(error.status).to.equal(500)
|
|
252
|
+
expect(error.response.text.includes('NotFound')).beTrue()
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
await request
|
|
256
|
+
.patch(`${baseUrl}/users/${anotherUserObject._id}`)
|
|
257
|
+
.set('Authorization', 'Bearer ' + phoneAccessToken)
|
|
258
|
+
.send({ name: 'new name' })
|
|
259
|
+
} catch (error) {
|
|
260
|
+
// Not sure why but in this case the raised error is in text/html format
|
|
261
|
+
expect(error.status).to.equal(500)
|
|
262
|
+
expect(error.response.text.includes('NotFound')).beTrue()
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
await request
|
|
266
|
+
.patch(`${baseUrl}/users/${anotherUserObject._id}`)
|
|
267
|
+
.set('Authorization', 'Bearer ' + statelessAccessToken)
|
|
268
|
+
.send({ name: 'new name' })
|
|
269
|
+
} catch (error) {
|
|
270
|
+
// Not sure why but in this case the raised error is in text/html format
|
|
271
|
+
expect(error.status).to.equal(500)
|
|
272
|
+
expect(error.response.text.includes('Forbidden')).beTrue()
|
|
273
|
+
}
|
|
274
|
+
// Should be possible otherwise
|
|
275
|
+
const response = await request
|
|
276
|
+
.patch(`${baseUrl}/users/${userObject._id}`)
|
|
277
|
+
.set('Authorization', 'Bearer ' + adminAccessToken)
|
|
278
|
+
.send({ name: 'new name' })
|
|
279
|
+
const user = response.body
|
|
280
|
+
expect(user.name).to.equal('new name')
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
it('checks for user privilege escalation', async () => {
|
|
284
|
+
// Should not be able to upgrade user permissions when not administrator
|
|
285
|
+
try {
|
|
286
|
+
await request
|
|
287
|
+
.patch(`${baseUrl}/users/${userObject._id}`)
|
|
288
|
+
.set('Authorization', 'Bearer ' + userIdAccessToken)
|
|
289
|
+
.send({ permissions: 'administrator' })
|
|
290
|
+
} catch (error) {
|
|
291
|
+
// Not sure why but in this case the raised error is in text/html format
|
|
292
|
+
expect(error.status).to.equal(500)
|
|
293
|
+
expect(error.response.text.includes('BadRequest')).beTrue()
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
await request
|
|
297
|
+
.patch(`${baseUrl}/users/${userObject._id}`)
|
|
298
|
+
.set('Authorization', 'Bearer ' + emailAccessToken)
|
|
299
|
+
.send({ permissions: 'administrator' })
|
|
300
|
+
} catch (error) {
|
|
301
|
+
// Not sure why but in this case the raised error is in text/html format
|
|
302
|
+
expect(error.status).to.equal(500)
|
|
303
|
+
expect(error.response.text.includes('BadRequest')).beTrue()
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
await request
|
|
307
|
+
.patch(`${baseUrl}/users/${userObject._id}`)
|
|
308
|
+
.set('Authorization', 'Bearer ' + phoneAccessToken)
|
|
309
|
+
.send({ permissions: 'administrator' })
|
|
310
|
+
} catch (error) {
|
|
311
|
+
// Not sure why but in this case the raised error is in text/html format
|
|
312
|
+
expect(error.status).to.equal(500)
|
|
313
|
+
expect(error.response.text.includes('BadRequest')).beTrue()
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
await request
|
|
317
|
+
.patch(`${baseUrl}/users/${userObject._id}`)
|
|
318
|
+
.set('Authorization', 'Bearer ' + statelessAccessToken)
|
|
319
|
+
.send({ permissions: 'administrator' })
|
|
320
|
+
} catch (error) {
|
|
321
|
+
// Not sure why but in this case the raised error is in text/html format
|
|
322
|
+
expect(error.status).to.equal(500)
|
|
323
|
+
expect(error.response.text.includes('Forbidden')).beTrue()
|
|
324
|
+
}
|
|
325
|
+
// Should be possible otherwise
|
|
326
|
+
const response = await request
|
|
327
|
+
.patch(`${baseUrl}/users/${userObject._id}`)
|
|
328
|
+
.set('Authorization', 'Bearer ' + adminAccessToken)
|
|
329
|
+
.send({ permissions: 'manager' })
|
|
330
|
+
const user = response.body
|
|
331
|
+
expect(user.permissions).to.equal('manager')
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
it('checks users removal', async () => {
|
|
335
|
+
// Should not be able to remove others users if not administrator
|
|
336
|
+
try {
|
|
337
|
+
await request
|
|
338
|
+
.delete(`${baseUrl}/users/${anotherUserObject._id}`)
|
|
339
|
+
.set('Authorization', 'Bearer ' + userIdAccessToken)
|
|
340
|
+
} catch (error) {
|
|
341
|
+
// Not sure why but in this case the raised error is in text/html format
|
|
342
|
+
expect(error.status).to.equal(500)
|
|
343
|
+
expect(error.response.text.includes('NotFound')).beTrue()
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
await request
|
|
347
|
+
.delete(`${baseUrl}/users/${anotherUserObject._id}`)
|
|
348
|
+
.set('Authorization', 'Bearer ' + emailAccessToken)
|
|
349
|
+
} catch (error) {
|
|
350
|
+
// Not sure why but in this case the raised error is in text/html format
|
|
351
|
+
expect(error.status).to.equal(500)
|
|
352
|
+
expect(error.response.text.includes('NotFound')).beTrue()
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
await request
|
|
356
|
+
.delete(`${baseUrl}/users/${anotherUserObject._id}`)
|
|
357
|
+
.set('Authorization', 'Bearer ' + phoneAccessToken)
|
|
358
|
+
} catch (error) {
|
|
359
|
+
// Not sure why but in this case the raised error is in text/html format
|
|
360
|
+
expect(error.status).to.equal(500)
|
|
361
|
+
expect(error.response.text.includes('NotFound')).beTrue()
|
|
362
|
+
}
|
|
363
|
+
try {
|
|
364
|
+
await request
|
|
365
|
+
.delete(`${baseUrl}/users/${anotherUserObject._id}`)
|
|
366
|
+
.set('Authorization', 'Bearer ' + statelessAccessToken)
|
|
367
|
+
} catch (error) {
|
|
368
|
+
// Not sure why but in this case the raised error is in text/html format
|
|
369
|
+
expect(error.status).to.equal(500)
|
|
370
|
+
expect(error.response.text.includes('Forbidden')).beTrue()
|
|
371
|
+
}
|
|
372
|
+
await userService.remove(userObject._id)
|
|
373
|
+
await userService.remove(anotherUserObject._id)
|
|
374
|
+
})
|
|
375
|
+
// Let enough time to process
|
|
376
|
+
.timeout(5000)
|
|
377
|
+
|
|
378
|
+
// Cleanup
|
|
379
|
+
after(async () => {
|
|
380
|
+
if (server) await server.close()
|
|
381
|
+
await app.db.instance.dropDatabase()
|
|
382
|
+
await app.db.disconnect()
|
|
383
|
+
})
|
|
384
|
+
})
|