From ef7348cf5ef21352c680c55ad9e0494024e860fe Mon Sep 17 00:00:00 2001 From: Felix Erdmann Date: Fri, 4 Sep 2020 09:27:08 +0000 Subject: [PATCH 01/19] fix download in getData closes #325 (#349) (#350) --- packages/api/lib/controllers/measurementsController.js | 6 ++++-- tests/tests/007-download-data-test.js | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/api/lib/controllers/measurementsController.js b/packages/api/lib/controllers/measurementsController.js index 7f56bc24..7d7b6ef7 100644 --- a/packages/api/lib/controllers/measurementsController.js +++ b/packages/api/lib/controllers/measurementsController.js @@ -92,15 +92,17 @@ const getData = function getData (req, res, next) { let stringifier; // IDEA: add geojson point featurecollection format - if (format === 'csv' || (download === 'true')) { + if (format === 'csv') { res.header('Content-Type', 'text/csv'); - res.header('Content-Disposition', `attachment; filename=${sensorId}.${format}`); stringifier = csvStringifier(['createdAt', 'value'], delimiter); } else if (format === 'json') { res.header('Content-Type', 'application/json; charset=utf-8'); // IDEA: add geojson point featurecollection format stringifier = jsonstringify({ open: '[', close: ']' }, jsonLocationReplacer); } + if (download === 'true') { + res.header('Content-Disposition', `attachment; filename=${sensorId}.${format}`); + } let measurementsStream = Measurement.getMeasurementsStream(req._userParams) .on('error', function (err) { diff --git a/tests/tests/007-download-data-test.js b/tests/tests/007-download-data-test.js index a11f1b1c..914a337f 100644 --- a/tests/tests/007-download-data-test.js +++ b/tests/tests/007-download-data-test.js @@ -226,7 +226,7 @@ describe('downloading data', function () { }); it('should allow download data through /boxes/:boxid/data/:sensorid as csv', function () { - return chakram.get(`${BASE_URL}/boxes/${boxIds[0]}/data/${boxes[0].sensors[1]._id}?format=csv`) + return chakram.get(`${BASE_URL}/boxes/${boxIds[0]}/data/${boxes[0].sensors[1]._id}?format=csv&download=true`) .then(function (response) { expect(response).to.have.status(200); expect(response.body).not.to.be.empty; From d5a1475de4a3eb8070c6eb8deb8600b7590fd52c Mon Sep 17 00:00:00 2001 From: felixerdy Date: Mon, 12 Oct 2020 11:23:05 +0200 Subject: [PATCH 02/19] initial auth for boxes --- packages/api/lib/controllers/boxesController.js | 2 ++ .../api/lib/controllers/measurementsController.js | 5 +++++ packages/models/src/box/box.js | 13 ++++++++++--- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/api/lib/controllers/boxesController.js b/packages/api/lib/controllers/boxesController.js index 70f35aef..df4d9b9b 100644 --- a/packages/api/lib/controllers/boxesController.js +++ b/packages/api/lib/controllers/boxesController.js @@ -384,6 +384,7 @@ const getBox = async function getBox (req, res, next) { * @apiParam (RequestBody) {String="hdc1080","bmp280","tsl45315","veml6070","sds011","bme680","smt50","soundlevelmeter", "windspeed"} [sensorTemplates] Specify which sensors should be included. * @apiParam (RequestBody) {Object} [mqtt] specify parameters of the MQTT integration for external measurement upload. Please see below for the accepted parameters * @apiParam (RequestBody) {Object} [ttn] specify parameters for the TTN integration for measurement from TheThingsNetwork.org upload. Please see below for the accepted parameters + * @apiParam (RequestBody) {Boolean="true","false"} [useAuth] whether to use access_token or not for authentication (default: true) * * @apiUse LocationBody * @apiUse SensorBody @@ -537,6 +538,7 @@ module.exports = { { name: 'windSpeedPort', dataType: 'String', defaultValue: 'C', allowedValues: ['A', 'B', 'C'] }, { name: 'mqtt', dataType: 'object' }, { name: 'ttn', dataType: 'object' }, + { name: 'useAuth', defaultValue: 'true', allowedValues: ['true', 'false'] }, { predef: 'location', required: true } ]), postNewBox diff --git a/packages/api/lib/controllers/measurementsController.js b/packages/api/lib/controllers/measurementsController.js index 7d7b6ef7..4468857d 100644 --- a/packages/api/lib/controllers/measurementsController.js +++ b/packages/api/lib/controllers/measurementsController.js @@ -325,6 +325,11 @@ const postNewMeasurements = async function postNewMeasurements (req, res, next) throw new UnauthorizedError('Access token not valid!'); } + // authorization for all boxes + if (box.access_token && box.access_token !== req.headers.authorization) { + throw new UnauthorizedError('Access token not valid!'); + } + const measurements = await Measurement.decodeMeasurements(req.body, { contentType, sensors: box.sensors }); await box.saveMeasurementsArray(measurements); res.send(201, 'Measurements saved in box'); diff --git a/packages/models/src/box/box.js b/packages/models/src/box/box.js index 6cfd82f8..7be94f0c 100644 --- a/packages/models/src/box/box.js +++ b/packages/models/src/box/box.js @@ -125,7 +125,12 @@ const boxSchema = new Schema({ }, access_token: { type: String, - required: false + required: true + }, + useAuth: { + type: Boolean, + required: true, + default: true, } }, { usePushEach: true }); boxSchema.plugin(timestamp); @@ -225,7 +230,8 @@ boxSchema.statics.initNew = function ({ } = { enabled: false }, ttn: { app_id, dev_id, port, profile, decodeOptions: ttnDecodeOptions - } = {} + } = {}, + useAuth }) { // if model is not empty, get sensor definitions from products // otherwise, sensors should not be empty @@ -271,7 +277,8 @@ boxSchema.statics.initNew = function ({ model, sensors, integrations, - access_token + access_token, + useAuth }); }; From 0c80d3c036044c72ed313ee7ebf663e0a92ca28b Mon Sep 17 00:00:00 2001 From: felixerdy Date: Mon, 12 Oct 2020 11:27:54 +0200 Subject: [PATCH 03/19] auth for boxes that have not opt out --- packages/api/lib/controllers/measurementsController.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/api/lib/controllers/measurementsController.js b/packages/api/lib/controllers/measurementsController.js index 4468857d..0e63e37b 100644 --- a/packages/api/lib/controllers/measurementsController.js +++ b/packages/api/lib/controllers/measurementsController.js @@ -319,14 +319,14 @@ const postNewMeasurements = async function postNewMeasurements (req, res, next) if (Measurement.hasDecoder(contentType)) { try { - const box = await Box.findBoxById(boxId, { populate: false, lean: false, projection: { sensors: 1, locations: 1, lastMeasurementAt: 1, currentLocation: 1, model: 1, access_token: 1 } }); + const box = await Box.findBoxById(boxId, { populate: false, lean: false, projection: { sensors: 1, locations: 1, lastMeasurementAt: 1, currentLocation: 1, model: 1, access_token: 1, useAuth: 1 } }); if (contentType === 'hackair' && box.access_token !== req.headers.authorization) { throw new UnauthorizedError('Access token not valid!'); } - // authorization for all boxes - if (box.access_token && box.access_token !== req.headers.authorization) { + // authorization for all boxes that have not opt out + if (box.useAuth && box.access_token && box.access_token !== req.headers.authorization) { throw new UnauthorizedError('Access token not valid!'); } From 125eda7d81fafc23ff063d70ee3dcdc6f4a8fd56 Mon Sep 17 00:00:00 2001 From: felixerdy Date: Mon, 12 Oct 2020 12:15:20 +0200 Subject: [PATCH 04/19] fix spelling --- packages/api/lib/controllers/boxesController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/lib/controllers/boxesController.js b/packages/api/lib/controllers/boxesController.js index df4d9b9b..57a17595 100644 --- a/packages/api/lib/controllers/boxesController.js +++ b/packages/api/lib/controllers/boxesController.js @@ -443,7 +443,7 @@ const getSketch = async function getSketch (req, res, next) { /** * @api {delete} /boxes/:senseBoxId Mark a senseBox and its measurements for deletion - * @apiDescription This will delete all the measurements of the senseBox. Please not that the deletion isn't happening immediately. + * @apiDescription This will delete all the measurements of the senseBox. Please note that the deletion isn't happening immediately. * @apiName deleteBox * @apiGroup Boxes * @apiUse ContentTypeJSON From 174f78fa364b466405c0b215bd4093051c37ee84 Mon Sep 17 00:00:00 2001 From: felixerdy Date: Mon, 12 Oct 2020 13:08:18 +0200 Subject: [PATCH 05/19] useAuth in updateBox --- packages/api/lib/controllers/boxesController.js | 3 ++- packages/models/src/box/box.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/api/lib/controllers/boxesController.js b/packages/api/lib/controllers/boxesController.js index 57a17595..19826ca7 100644 --- a/packages/api/lib/controllers/boxesController.js +++ b/packages/api/lib/controllers/boxesController.js @@ -507,7 +507,8 @@ module.exports = { { name: 'ttn', dataType: 'object' }, { name: 'sensors', dataType: ['object'] }, { name: 'addons', dataType: 'object' }, - { predef: 'location' } + { predef: 'location' }, + { name: 'useAuth', allowedValues: ['true', 'false'] } ]), checkPrivilege, updateBox diff --git a/packages/models/src/box/box.js b/packages/models/src/box/box.js index 7be94f0c..aa35aa00 100644 --- a/packages/models/src/box/box.js +++ b/packages/models/src/box/box.js @@ -867,7 +867,7 @@ boxSchema.methods.updateBox = function updateBox (args) { const box = this; // only grouptag, description and weblink can removed through setting them to empty string ('') - for (const prop of ['name', 'exposure', 'grouptag', 'description', 'weblink', 'image', 'integrations.mqtt', 'integrations.ttn', 'model']) { + for (const prop of ['name', 'exposure', 'grouptag', 'description', 'weblink', 'image', 'integrations.mqtt', 'integrations.ttn', 'model', 'useAuth']) { if (typeof args[prop] !== 'undefined') { box.set(prop, (args[prop] === '' ? undefined : args[prop])); } From 5e157cf2aaebf0c642dc02c6ebdec94be088f66a Mon Sep 17 00:00:00 2001 From: felixerdy Date: Mon, 12 Oct 2020 13:19:48 +0200 Subject: [PATCH 06/19] update access_token via generate_access_token --- packages/api/lib/controllers/boxesController.js | 3 ++- packages/models/src/box/box.js | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/api/lib/controllers/boxesController.js b/packages/api/lib/controllers/boxesController.js index 19826ca7..961c8a9f 100644 --- a/packages/api/lib/controllers/boxesController.js +++ b/packages/api/lib/controllers/boxesController.js @@ -508,7 +508,8 @@ module.exports = { { name: 'sensors', dataType: ['object'] }, { name: 'addons', dataType: 'object' }, { predef: 'location' }, - { name: 'useAuth', allowedValues: ['true', 'false'] } + { name: 'useAuth', allowedValues: ['true', 'false'] }, + { name: 'generate_access_token', allowedValues: ['true', 'false'] } ]), checkPrivilege, updateBox diff --git a/packages/models/src/box/box.js b/packages/models/src/box/box.js index aa35aa00..86147579 100644 --- a/packages/models/src/box/box.js +++ b/packages/models/src/box/box.js @@ -873,6 +873,15 @@ boxSchema.methods.updateBox = function updateBox (args) { } } + // if user wants a new access_token + if (typeof args['generate_access_token'] !== 'undefined') { + if (args['generate_access_token'] == 'true') { + // Create new acces token for box + const access_token = crypto.randomBytes(32).toString('hex'); + box.set('access_token', access_token); + } + } + if (sensors) { box.updateSensors(sensors); } else if (addonToAdd) { From 051bca8ebdd99855c6b421468915be3b1f1ab9a8 Mon Sep 17 00:00:00 2001 From: felixerdy Date: Mon, 12 Oct 2020 13:51:11 +0200 Subject: [PATCH 07/19] pass access_token to sketch templater --- .../api/lib/controllers/boxesController.js | 12 ++++++-- packages/models/src/box/box.js | 3 +- packages/models/src/user/mails.js | 30 +++++++++++-------- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/packages/api/lib/controllers/boxesController.js b/packages/api/lib/controllers/boxesController.js index 961c8a9f..a19edc9e 100644 --- a/packages/api/lib/controllers/boxesController.js +++ b/packages/api/lib/controllers/boxesController.js @@ -425,7 +425,8 @@ const getSketch = async function getSketch (req, res, next) { res.header('Content-Type', 'text/plain; charset=utf-8'); try { const box = await Box.findBoxById(req._userParams.boxId, { populate: false, lean: false }); - res.send(box.getSketch({ + + const params = { serialPort: req._userParams.serialPort, soilDigitalPort: req._userParams.soilDigitalPort, soundMeterPort: req._userParams.soundMeterPort, @@ -435,7 +436,14 @@ const getSketch = async function getSketch (req, res, next) { devEUI: req._userParams.devEUI, appEUI: req._userParams.appEUI, appKey: req._userParams.appKey - })); + }; + + // pass access token only if useAuth is true and access_token is available + if (box.useAuth && box.access_token) { + params.access_token = box.access_token; + } + + res.send(box.getSketch(params)); } catch (err) { handleError(err, next); } diff --git a/packages/models/src/box/box.js b/packages/models/src/box/box.js index 86147579..f4fb55c3 100644 --- a/packages/models/src/box/box.js +++ b/packages/models/src/box/box.js @@ -808,7 +808,7 @@ boxSchema.methods.updateSensors = function updateSensors (sensors) { } }; -boxSchema.methods.getSketch = function getSketch ({ encoding, serialPort, soilDigitalPort, soundMeterPort, windSpeedPort, ssid, password, devEUI, appEUI, appKey } = {}) { +boxSchema.methods.getSketch = function getSketch ({ encoding, serialPort, soilDigitalPort, soundMeterPort, windSpeedPort, ssid, password, devEUI, appEUI, appKey, access_token } = {}) { if (serialPort) { this.serialPort = serialPort; } @@ -827,6 +827,7 @@ boxSchema.methods.getSketch = function getSketch ({ encoding, serialPort, soilDi this.devEUI = devEUI; this.appEUI = appEUI, this.appKey = appKey; + this.access_token = access_token; return templateSketcher.generateSketch(this, { encoding }); }; diff --git a/packages/models/src/user/mails.js b/packages/models/src/user/mails.js index 8650b6ef..92badbf4 100644 --- a/packages/models/src/user/mails.js +++ b/packages/models/src/user/mails.js @@ -18,24 +18,30 @@ if (config.get('mailer.url')) { const mailTemplates = { 'newBox' (user, box) { + let sketchParams = { + encoding: 'base64', + ssid: '', + password: '', + serialPort: box.serialPort, + soilDigitalPort: box.soilDigitalPort, + soundMeterPort: box.soundMeterPort, + windSpeedPort: box.windSpeedPort, + devEUI: '', + appEUI: '', + appKey: '' + } + + if(box.useAuth && box.access_token) { + sketchParams.access_token = box.access_token + } + return { payload: { box }, attachment: { filename: 'senseBox.ino', - contents: box.getSketch({ - encoding: 'base64', - ssid: '', - password: '', - serialPort: box.serialPort, - soilDigitalPort: box.soilDigitalPort, - soundMeterPort: box.soundMeterPort, - windSpeedPort: box.windSpeedPort, - devEUI: '', - appEUI: '', - appKey: '' - }) + contents: box.getSketch(sketchParams) } }; }, From e69ca2c4d016795f6fdf9a68b1f7955930b03224 Mon Sep 17 00:00:00 2001 From: felixerdy Date: Mon, 12 Oct 2020 15:35:07 +0200 Subject: [PATCH 08/19] bump node-sketch-templater version (beta) --- packages/models/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/models/package.json b/packages/models/package.json index 0b76a451..49a6a0b4 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -6,7 +6,7 @@ "license": "MIT", "dependencies": { "@sensebox/osem-protos": "^1.1.0", - "@sensebox/sketch-templater": "^1.8.2", + "@sensebox/sketch-templater": "^1.8.3-beta.0", "bcrypt": "^5.0.0", "bunyan": "^1.8.14", "config": "^3.3.1", From 8d6e1ff4075c952f468bb6c272beac590b3e1db8 Mon Sep 17 00:00:00 2001 From: felixerdy Date: Mon, 12 Oct 2020 15:46:04 +0200 Subject: [PATCH 09/19] add authorization header anyways --- .../api/lib/controllers/boxesController.js | 2 +- packages/models/src/user/mails.js | 4 +- yarn.lock | 56 ++----------------- 3 files changed, 8 insertions(+), 54 deletions(-) diff --git a/packages/api/lib/controllers/boxesController.js b/packages/api/lib/controllers/boxesController.js index a19edc9e..3b886a2f 100644 --- a/packages/api/lib/controllers/boxesController.js +++ b/packages/api/lib/controllers/boxesController.js @@ -439,7 +439,7 @@ const getSketch = async function getSketch (req, res, next) { }; // pass access token only if useAuth is true and access_token is available - if (box.useAuth && box.access_token) { + if (box.access_token) { params.access_token = box.access_token; } diff --git a/packages/models/src/user/mails.js b/packages/models/src/user/mails.js index 92badbf4..d51a216d 100644 --- a/packages/models/src/user/mails.js +++ b/packages/models/src/user/mails.js @@ -31,10 +31,10 @@ if (config.get('mailer.url')) { appKey: '' } - if(box.useAuth && box.access_token) { + if(box.access_token) { sketchParams.access_token = box.access_token } - + return { payload: { box diff --git a/yarn.lock b/yarn.lock index 20803f04..618d42e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -42,10 +42,10 @@ resolved "https://registry.yarnpkg.com/@sensebox/osem-protos/-/osem-protos-1.1.0.tgz#a7de8bc6be867953f1309181a012063c23299e79" integrity sha512-H+nUVcWlT0dvIqfJnYHuX9JBcCkP1ZKGE5YTRNWPbAEdZ11h+srpQsmeI58wK5hJcdukaZAjc4Dy96IeGM77aA== -"@sensebox/sketch-templater@^1.8.2": - version "1.8.2" - resolved "https://registry.yarnpkg.com/@sensebox/sketch-templater/-/sketch-templater-1.8.2.tgz#70ee33479b6ee03402e6e9575b19b9388b7e947f" - integrity sha512-gqW8ltzu/CYLVtiC7FnpU775LMPr8lO05TcZfQfFoLVg7JRsAX0xicFTc03NW7C3e813w16tyxqZInzPYf6RaQ== +"@sensebox/sketch-templater@^1.8.3-beta.0": + version "1.8.3-beta.0" + resolved "https://registry.yarnpkg.com/@sensebox/sketch-templater/-/sketch-templater-1.8.3-beta.0.tgz#7eccf67469725741dc4a55fcf43f872dd37e1409" + integrity sha512-nsVkMmqr6tDJ4x48qiLyFOAJL1LLhavtg5dksqJa7qeFXQ2AHMmUC8uiU9r2PstANFDDRUDqFZ2rN69rl+NYog== dependencies: config "^1.29.2" dedent "^0.7.0" @@ -452,24 +452,6 @@ bcrypt@^5.0.0: node-addon-api "^3.0.0" node-pre-gyp "0.15.0" -bcrypt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-4.0.1.tgz#06e21e749a061020e4ff1283c1faa93187ac57fe" - integrity sha512-hSIZHkUxIDS5zA2o00Kf2O5RfVbQ888n54xQoF/eIaquU4uaLxK8vhhBdktd0B3n2MjkcAWzv4mnhogykBKOUQ== - dependencies: - node-addon-api "^2.0.0" - node-pre-gyp "0.14.0" - -binary-extensions@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" - integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== - -binary-extensions@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" - integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== - binary-extensions@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" @@ -533,7 +515,7 @@ buffer-shims@^1.0.0, buffer-shims@~1.0.0: resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" integrity sha1-mXjOMXOIxkmth5MCjDR37wRKi1E= -bunyan@^1.8.1, bunyan@^1.8.12, bunyan@^1.8.14: +bunyan@^1.8.1, bunyan@^1.8.14: version "1.8.14" resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.14.tgz#3d8c1afea7de158a5238c7cb8a66ab6b38dd45b4" integrity sha512-LlahJUxXzZLuw/hetUQJmRgZ1LF6+cr5TPpRj6jf327AsiIq2jhYEH4oqUUkVKTor+9w2BT3oxVwhzE5lw9tcg== @@ -2365,16 +2347,6 @@ minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== - -minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== - minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" @@ -2585,24 +2557,6 @@ needle@^2.5.0: iconv-lite "^0.4.4" sax "^1.2.4" -needle@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.5.0.tgz#e6fc4b3cc6c25caed7554bd613a5cf0bac8c31c0" - integrity sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - -needle@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.5.0.tgz#e6fc4b3cc6c25caed7554bd613a5cf0bac8c31c0" - integrity sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - negotiator@^0.6.1: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" From dae11056d5ffe8a560dd5c89912ed757bc843738 Mon Sep 17 00:00:00 2001 From: felixerdy Date: Mon, 12 Oct 2020 15:50:35 +0200 Subject: [PATCH 10/19] auth beta --- packages/api/package.json | 2 +- packages/models/CHANGELOG.md | 3 +++ packages/models/package.json | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index 40e6d2d0..4830c665 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -10,7 +10,7 @@ "Norwin Roosen" ], "dependencies": { - "@sensebox/opensensemap-api-models": "^0.0.25", + "@sensebox/opensensemap-api-models": "^0.0.26-beta.0", "@turf/area": "^6.0.1", "@turf/bbox": "^6.0.1", "@turf/centroid": "^6.0.2", diff --git a/packages/models/CHANGELOG.md b/packages/models/CHANGELOG.md index f4098f85..a7fd73cb 100644 --- a/packages/models/CHANGELOG.md +++ b/packages/models/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +## v0.0.26-beta.0 +- Authorization + ## v0.0.25 - Add Cayenne LPP Decoder diff --git a/packages/models/package.json b/packages/models/package.json index 49a6a0b4..8f53d5bc 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -1,7 +1,7 @@ { "name": "@sensebox/opensensemap-api-models", "description": "openSenseMap data models and database connection", - "version": "0.0.25", + "version": "0.0.26-beta.0", "main": "index.js", "license": "MIT", "dependencies": { From 3cb6ccc2755945d8b796675b0ee9ad92654b83ef Mon Sep 17 00:00:00 2001 From: felixerdy Date: Tue, 13 Oct 2020 14:25:19 +0200 Subject: [PATCH 11/19] include useAuth in includeSecret requests --- packages/models/src/box/box.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/models/src/box/box.js b/packages/models/src/box/box.js index f4fb55c3..62f1d306 100644 --- a/packages/models/src/box/box.js +++ b/packages/models/src/box/box.js @@ -172,6 +172,7 @@ boxSchema.set('toJSON', { if (options && options.includeSecrets) { box.integrations = ret.integrations; box.access_token = ret.access_token; + box.useAuth = ret.useAuth; } return box; From 92c7c8267002525f00cbeecc3a0d2e92e65c05e5 Mon Sep 17 00:00:00 2001 From: Umut Tas Date: Tue, 13 Oct 2020 18:17:16 +0200 Subject: [PATCH 12/19] remove default true for useAuth, set true for new Boxes --- packages/models/src/box/box.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/models/src/box/box.js b/packages/models/src/box/box.js index 62f1d306..d1669d3a 100644 --- a/packages/models/src/box/box.js +++ b/packages/models/src/box/box.js @@ -130,7 +130,7 @@ const boxSchema = new Schema({ useAuth: { type: Boolean, required: true, - default: true, + default: false, } }, { usePushEach: true }); boxSchema.plugin(timestamp); @@ -232,7 +232,7 @@ boxSchema.statics.initNew = function ({ ttn: { app_id, dev_id, port, profile, decodeOptions: ttnDecodeOptions } = {}, - useAuth + useAuth = true }) { // if model is not empty, get sensor definitions from products // otherwise, sensors should not be empty From 1f562b6dad44bcbff9cf1b25c3ba5784630e2c29 Mon Sep 17 00:00:00 2001 From: umut Date: Thu, 15 Oct 2020 14:29:38 +0200 Subject: [PATCH 13/19] update tests for auth feature, fix errors in auth code --- .../api/lib/controllers/boxesController.js | 2 +- .../lib/controllers/measurementsController.js | 14 +++-- packages/models/src/box/box.js | 10 +++- tests/data/getUserBoxesSchema.js | 6 ++ tests/data/senseBoxSchema.js | 10 +++- tests/data/valid_sensebox.js | 1 - tests/tests/002-location_tests.js | 40 ++++++------- tests/tests/003-idw-test.js | 15 +++-- tests/tests/005-create-boxes-test.js | 1 - tests/tests/006-submit-measurements-test.js | 56 +++++++++++++------ tests/tests/010-luftdaten-test.js | 1 - tests/tests/013-classify-test.js | 8 +-- .../tests/014-descriptive-statistics-test.js | 8 +-- tests/tests/016-hackair-test.js | 2 +- 14 files changed, 110 insertions(+), 64 deletions(-) diff --git a/packages/api/lib/controllers/boxesController.js b/packages/api/lib/controllers/boxesController.js index 3b886a2f..cb758fc5 100644 --- a/packages/api/lib/controllers/boxesController.js +++ b/packages/api/lib/controllers/boxesController.js @@ -548,7 +548,7 @@ module.exports = { { name: 'windSpeedPort', dataType: 'String', defaultValue: 'C', allowedValues: ['A', 'B', 'C'] }, { name: 'mqtt', dataType: 'object' }, { name: 'ttn', dataType: 'object' }, - { name: 'useAuth', defaultValue: 'true', allowedValues: ['true', 'false'] }, + { name: 'useAuth', allowedValues: ['true', 'false'] }, { predef: 'location', required: true } ]), postNewBox diff --git a/packages/api/lib/controllers/measurementsController.js b/packages/api/lib/controllers/measurementsController.js index 0e63e37b..7631987e 100644 --- a/packages/api/lib/controllers/measurementsController.js +++ b/packages/api/lib/controllers/measurementsController.js @@ -216,6 +216,10 @@ const postNewMeasurement = async function postNewMeasurement (req, res, next) { try { const box = await Box.findBoxById(boxId, { populate: false, lean: false }); + if (box.useAuth && box.access_token && box.access_token !== req.headers.authorization) { + throw new UnauthorizedError('Box access token not valid!'); + } + const [measurement] = await Measurement.decodeMeasurements([{ sensor_id: sensorId, value, @@ -321,13 +325,13 @@ const postNewMeasurements = async function postNewMeasurements (req, res, next) try { const box = await Box.findBoxById(boxId, { populate: false, lean: false, projection: { sensors: 1, locations: 1, lastMeasurementAt: 1, currentLocation: 1, model: 1, access_token: 1, useAuth: 1 } }); - if (contentType === 'hackair' && box.access_token !== req.headers.authorization) { - throw new UnauthorizedError('Access token not valid!'); - } + // if (contentType === 'hackair' && box.access_token !== req.headers.authorization) { + // throw new UnauthorizedError('Box access token not valid!'); + // } // authorization for all boxes that have not opt out - if (box.useAuth && box.access_token && box.access_token !== req.headers.authorization) { - throw new UnauthorizedError('Access token not valid!'); + if ((box.useAuth || contentType === 'hackair') && box.access_token && box.access_token !== req.headers.authorization) { + throw new UnauthorizedError('Box access token not valid!'); } const measurements = await Measurement.decodeMeasurements(req.body, { contentType, sensors: box.sensors }); diff --git a/packages/models/src/box/box.js b/packages/models/src/box/box.js index d1669d3a..7b00e9af 100644 --- a/packages/models/src/box/box.js +++ b/packages/models/src/box/box.js @@ -232,7 +232,7 @@ boxSchema.statics.initNew = function ({ ttn: { app_id, dev_id, port, profile, decodeOptions: ttnDecodeOptions } = {}, - useAuth = true + useAuth }) { // if model is not empty, get sensor definitions from products // otherwise, sensors should not be empty @@ -251,6 +251,14 @@ boxSchema.statics.initNew = function ({ sensors = sensorLayouts.getSensorsForModel(model); } } + if(model){ + //activate useAuth only for certain models until all sketches are updated + if(['homeV2Lora' , 'homeV2Ethernet' , 'homeV2EthernetFeinstaub' , 'homeV2Wifi' , 'homeV2WifiFeinstaub' , 'homeEthernet' , 'homeWifi' , 'homeEthernetFeinstaub' , 'homeWifiFeinstaub' , 'hackair_home_v2' , 'custom'].indexOf(model) != -1){ + useAuth = true + } else { + useAuth = false + } + } const integrations = { mqtt: { enabled, url, topic, decodeOptions: mqttDecodeOptions, connectionOptions, messageFormat }, diff --git a/tests/data/getUserBoxesSchema.js b/tests/data/getUserBoxesSchema.js index 9a506e85..9770b6ea 100644 --- a/tests/data/getUserBoxesSchema.js +++ b/tests/data/getUserBoxesSchema.js @@ -30,6 +30,12 @@ module.exports = { 'updatedAt': { 'type': 'string' }, + 'useAuth': { + 'type': 'boolean' + }, + 'access_token': { + 'type': 'string' + }, 'currentLocation': { 'type': 'object', 'properties': { diff --git a/tests/data/senseBoxSchema.js b/tests/data/senseBoxSchema.js index fdfc5be3..fd578d99 100644 --- a/tests/data/senseBoxSchema.js +++ b/tests/data/senseBoxSchema.js @@ -31,6 +31,12 @@ module.exports = { 'image': { 'type': 'string' }, + 'useAuth': { + 'type': "boolean" + }, + 'access_token': { + 'type': "string" + }, 'sensors': { 'type': 'array', 'items': { @@ -115,6 +121,8 @@ module.exports = { 'exposure', 'sensors', 'currentLocation', - 'loc' + 'loc', + // 'useAuth', + // 'access_token' ] }; diff --git a/tests/data/valid_sensebox.js b/tests/data/valid_sensebox.js index 3c2173be..9082a72b 100644 --- a/tests/data/valid_sensebox.js +++ b/tests/data/valid_sensebox.js @@ -35,7 +35,6 @@ module.exports = function ({ bbox, model, sensors, lonlat, name = '', sensorTemp 'connectionOptions': '' } }; - if (sensors) { box.sensors = sensors; } diff --git a/tests/tests/002-location_tests.js b/tests/tests/002-location_tests.js index 33298a3a..271e6809 100644 --- a/tests/tests/002-location_tests.js +++ b/tests/tests/002-location_tests.js @@ -24,7 +24,7 @@ const minimalSensebox = function minimalSensebox (location = [123, 12, 34], expo }; describe('openSenseMap API locations tests', function () { - let authHeader, box, submitTimeLoc1; + let authHeader, authHeaderBox, csvAndAuthHeader, box, submitTimeLoc1; before('add test user', function (done) { const user = { name: 'locationtestuser', email: 'locationtestuser@test.test', password: '12345678' }; @@ -110,6 +110,8 @@ describe('openSenseMap API locations tests', function () { expect(moment().diff(response.body.data.currentLocation.timestamp)).to.be.below(300); box = response.body.data; + authHeaderBox = { headers: { 'Authorization': `${response.body.data.access_token}` } }; + csvAndAuthHeader = { json: false, headers: { 'Content-Type': 'text/csv', 'Authorization': response.body.data.access_token } }; return chakram.wait(); }); @@ -290,7 +292,7 @@ describe('openSenseMap API locations tests', function () { it('should allow updating a boxes location via new measurement (array)', function () { const measurement = { value: 3, location: [3, 3, 3] }; - return chakram.post(POST_MEASUREMENT_URL, measurement, authHeader) + return chakram.post(POST_MEASUREMENT_URL, measurement, authHeaderBox) .then(logResponseIfError) .then(function (response) { expect(response).to.have.status(201); @@ -310,7 +312,7 @@ describe('openSenseMap API locations tests', function () { it('should allow updating a boxes location via new measurement (latLng)', function () { const measurement = { value: 4, location: { lat: 4, lng: 4, height: 4 } }; - return chakram.post(POST_MEASUREMENT_URL, measurement, authHeader) + return chakram.post(POST_MEASUREMENT_URL, measurement, authHeaderBox) .then(logResponseIfError) .then(function (response) { expect(response).to.have.status(201); @@ -338,7 +340,7 @@ describe('openSenseMap API locations tests', function () { createdAt: moment().subtract(1, 'm'), }; - return chakram.post(POST_MEASUREMENT_URL, measurement, authHeader) + return chakram.post(POST_MEASUREMENT_URL, measurement, authHeaderBox) .then(logResponseIfError) .then(function (response) { expect(response).to.have.status(201); @@ -359,7 +361,7 @@ describe('openSenseMap API locations tests', function () { const createdAt = moment().subtract(10, 'm'); const measurement = { value: -1, createdAt }; - return chakram.post(POST_MEASUREMENT_URL, measurement, authHeader) + return chakram.post(POST_MEASUREMENT_URL, measurement, authHeaderBox) .then(logResponseIfError) .then(function (response) { expect(response).to.have.status(201); @@ -382,12 +384,12 @@ describe('openSenseMap API locations tests', function () { // timestamp exactly at time of location set through PUT /boxes/:boxID const measurement2 = { value: 1, createdAt: submitTimeLoc1 }; - return chakram.post(POST_MEASUREMENT_URL, measurement1, authHeader) + return chakram.post(POST_MEASUREMENT_URL, measurement1, authHeaderBox) .then(logResponseIfError) .then(function (response) { expect(response).to.have.status(201); - return chakram.post(POST_MEASUREMENT_URL, measurement2, authHeader); + return chakram.post(POST_MEASUREMENT_URL, measurement2, authHeaderBox); }) .then(function (response) { expect(response).to.have.status(201); @@ -428,18 +430,18 @@ describe('openSenseMap API locations tests', function () { createdAt: measurement2.createdAt.clone().subtract(2, 'ms') }; - return chakram.post(POST_MEASUREMENT_URL, measurement3, authHeader) + return chakram.post(POST_MEASUREMENT_URL, measurement3, authHeaderBox) .then(logResponseIfError) .then(function (response) { expect(response).to.have.status(201); - return chakram.post(POST_MEASUREMENT_URL, measurement2, authHeader); + return chakram.post(POST_MEASUREMENT_URL, measurement2, authHeaderBox); }) .then(logResponseIfError) .then(function (response) { expect(response).to.have.status(201); - return chakram.post(POST_MEASUREMENT_URL, measurement1, authHeader); + return chakram.post(POST_MEASUREMENT_URL, measurement1, authHeaderBox); }) .then(logResponseIfError) .then(function (response) { @@ -482,8 +484,7 @@ describe('openSenseMap API locations tests', function () { const measurements = {}; measurements[box.sensors[1]._id] = [7, moment().subtract(2, 'ms'), [7, 7, 7]]; measurements[box.sensors[2]._id] = [8, moment(), { lat: 8, lng: 8, height: 8 }]; - - return chakram.post(BASE_URL, measurements, authHeader) + return chakram.post(BASE_URL, measurements, authHeaderBox) .then(logResponseIfError) .then(function (response) { expect(response).to.have.status(201); @@ -510,7 +511,7 @@ describe('openSenseMap API locations tests', function () { { sensor_id: sensor, value: 10.5 }, ]; - return chakram.post(BASE_URL, measurements, authHeader) + return chakram.post(BASE_URL, measurements, authHeaderBox) .then(logResponseIfError) .then(function (response) { expect(response).to.have.status(201); @@ -547,13 +548,12 @@ describe('openSenseMap API locations tests', function () { describe('text/csv', function () { - // const csvHeader = Object.assign({ json: false, headers: { 'Content-Type': 'text/csv' } }, authHeader); - const csvHeader = { json: false, headers: { 'Content-Type': 'text/csv' } }; - + // const csvAndAuthHeader = Object.assign({ json: false, headers: { 'Content-Type': 'text/csv' } }, authHeaderBox); + it('should accept 2D locations', function () { const measurements = `${box.sensors[3]._id},11,${moment().toISOString()},11,11`; - return chakram.post(BASE_URL, measurements, csvHeader) + return chakram.post(BASE_URL, measurements, csvAndAuthHeader) .then(logResponseIfError) .then(function (response) { expect(response).to.have.status(201); @@ -578,7 +578,7 @@ describe('openSenseMap API locations tests', function () { [sensor, 12.6, moment().subtract(2, 'ms').toISOString(), 12, 12, 12].join(','), // eslint-disable-line newline-per-chained-call ].join('\n'); - return chakram.post(BASE_URL, measurements, csvHeader) + return chakram.post(BASE_URL, measurements, csvAndAuthHeader) .then(logResponseIfError) .then(function (response) { expect(response).to.have.status(201); @@ -598,7 +598,7 @@ describe('openSenseMap API locations tests', function () { it('should reject measurements with location & w/out createdAt', function () { const measurements = `${box.sensors[3]._id},13,13,13,13`; // id,value,lng,lat,height - return chakram.post(BASE_URL, measurements, csvHeader) + return chakram.post(BASE_URL, measurements, csvAndAuthHeader) .then(function (response) { expect(response).to.have.status(422); @@ -912,7 +912,7 @@ describe('openSenseMap API locations tests', function () { return chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${box._id}/data`, [ { sensor_id: box.sensors[4]._id, value: 123, location: [-3, -3, -3], createdAt: daysAgo3 }, { sensor_id: box.sensors[4]._id, value: 321, location: [-4, -4, -4], createdAt: daysAgo6 }, - ], authHeader) + ], authHeaderBox) .then(logResponseIfError) .then(function (response) { expect(response).to.have.status(201); diff --git a/tests/tests/003-idw-test.js b/tests/tests/003-idw-test.js index d240778e..794ee166 100644 --- a/tests/tests/003-idw-test.js +++ b/tests/tests/003-idw-test.js @@ -33,7 +33,7 @@ describe('openSenseMap API Routes: /statistics/idw', function () { ]); }) .then(function (responses) { - const boxes = responses.map(r => { return { _id: r.body.data._id, sensorid: r.body.data.sensors[0]._id }; }); + const boxes = responses.map(r => { return { _id: r.body.data._id, sensorid: r.body.data.sensors[0]._id, access_token: r.body.data.access_token }; }); for (const [ i, box ] of boxes.entries()) { box.measurements = []; @@ -50,14 +50,13 @@ describe('openSenseMap API Routes: /statistics/idw', function () { } const [ first, second, third, fourth, fifth, sixth ] = boxes; - return chakram.all([ - chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${first._id}/data`, first.measurements, { headers: { 'content-type': 'application/json' } }), - chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${second._id}/data`, second.measurements, { headers: { 'content-type': 'application/json' } }), - chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${third._id}/data`, third.measurements, { headers: { 'content-type': 'application/json' } }), - chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${fourth._id}/data`, fourth.measurements, { headers: { 'content-type': 'application/json' } }), - chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${fifth._id}/data`, fifth.measurements, { headers: { 'content-type': 'application/json' } }), - chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${sixth._id}/data`, sixth.measurements, { headers: { 'content-type': 'application/json' } }), + chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${first._id}/data`, first.measurements, { headers: { 'content-type': 'application/json', 'Authorization': first.access_token} }), + chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${second._id}/data`, second.measurements, { headers: { 'content-type': 'application/json', 'Authorization': second.access_token } }), + chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${third._id}/data`, third.measurements, { headers: { 'content-type': 'application/json', 'Authorization': third.access_token } }), + chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${fourth._id}/data`, fourth.measurements, { headers: { 'content-type': 'application/json', 'Authorization': fourth.access_token } }), + chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${fifth._id}/data`, fifth.measurements, { headers: { 'content-type': 'application/json', 'Authorization': fifth.access_token } }), + chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${sixth._id}/data`, sixth.measurements, { headers: { 'content-type': 'application/json', 'Authorization': sixth.access_token } }), ]); }) .then(function () { diff --git a/tests/tests/005-create-boxes-test.js b/tests/tests/005-create-boxes-test.js index 07b4d4f1..a1129a62 100644 --- a/tests/tests/005-create-boxes-test.js +++ b/tests/tests/005-create-boxes-test.js @@ -83,7 +83,6 @@ describe('openSenseMap API Routes: /boxes', function () { .then(function (response) { expect(response).to.have.status(201); expect(response).to.have.header('content-type', 'application/json; charset=utf-8'); - boxId = response.body.data._id; boxCount = boxCount + 1; boxIds.push(boxId); diff --git a/tests/tests/006-submit-measurements-test.js b/tests/tests/006-submit-measurements-test.js index cae31dcf..c7661ffb 100644 --- a/tests/tests/006-submit-measurements-test.js +++ b/tests/tests/006-submit-measurements-test.js @@ -43,7 +43,7 @@ describe('submitting measurements', function () { it('should accept a single measurement via POST', function () { let submitTime; - return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/${boxes[0].sensors[0]._id}`, { 'value': 312.1 }) + return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/${boxes[0].sensors[0]._id}`, { 'value': 312.1 }, {headers: {'Authorization' : boxes[0].access_token} }) .then(function (response) { submitTime = moment.utc(response.response.headers.date, 'ddd, DD MMM YYYY HH:mm:ss GMT'); expect(response).to.have.status(201); @@ -69,10 +69,22 @@ describe('submitting measurements', function () { }); }); + it('should reject a single measurement via POST with wrong access_token', function () { + + return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/${boxes[0].sensors[0]._id}`, { 'value': 312.1 }, {headers: {'Authorization' : 'wrongAccessToken'} }) + .then(function (response) { + expect(response).to.have.status(401); + expect(response.body.message).to.equal('Box access token not valid!'); + return chakram.wait(); + + // return chakram.get(`${BASE_URL}/boxes/${boxIds[0]}`); + }) + }); + it('should accept a single measurement with timestamp via POST', function () { const submitTime = moment.utc().toISOString(); - return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/${boxes[0].sensors[1]._id}`, { 'value': 123.4, 'createdAt': submitTime }) + return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/${boxes[0].sensors[1]._id}`, { 'value': 123.4, 'createdAt': submitTime }, {headers: {'Authorization' : boxes[0].access_token} }) .then(function (response) { expect(response).to.have.status(201); expect(response.body).to.equal('Measurement saved in box'); @@ -101,7 +113,7 @@ describe('submitting measurements', function () { const submitTime = moment.utc().add(1.5, 'minutes') .toISOString(); - return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/${boxes[0].sensors[1]._id}`, { 'value': 123.4, 'createdAt': submitTime }) + return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/${boxes[0].sensors[1]._id}`, { 'value': 123.4, 'createdAt': submitTime }, {headers: {'Authorization' : boxes[0].access_token} }) .then(function (response) { expect(response).to.have.status(422); @@ -116,7 +128,7 @@ describe('submitting measurements', function () { it('should accept multiple measurements as csv via POST', function () { let submitTime; - return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, csv_example_data.no_timestamps(boxes[0].sensors), { json: false, headers: { 'content-type': 'text/csv' } }) + return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, csv_example_data.no_timestamps(boxes[0].sensors), { json: false, headers: { 'content-type': 'text/csv', 'Authorization' : boxes[0].access_token} }) .then(function (response) { submitTime = moment.utc(response.response.headers.date, 'ddd, DD MMM YYYY HH:mm:ss GMT'); expect(response).to.have.status(201); @@ -138,10 +150,22 @@ describe('submitting measurements', function () { }); }); + it('should reject multiple measurements as csv via POST with wrong access_token', function () { + + return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, csv_example_data.no_timestamps(boxes[0].sensors), { json: false, headers: { 'content-type': 'text/csv', 'Authorization' : 'WRONGAUTHTOKEN'} }) + .then(function (response) { + expect(response).to.have.status(401); + expect(JSON.parse(response.body).message).to.equal('Box access token not valid!'); + + return chakram.wait(); + + }) + }); + it('should accept multiple measurements with timestamps as csv via POST', function () { let submitTime; - return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, csv_example_data.with_timestamps(boxes[0].sensors), { json: false, headers: { 'content-type': 'text/csv' } }) + return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, csv_example_data.with_timestamps(boxes[0].sensors), { json: false, headers: { 'content-type': 'text/csv', 'Authorization' : boxes[0].access_token } }) .then(function (response) { submitTime = moment.utc(response.response.headers.date, 'ddd, DD MMM YYYY HH:mm:ss GMT'); expect(response).to.have.status(201); @@ -164,7 +188,7 @@ describe('submitting measurements', function () { }); it('should reject multiple measurements with timestamps too far into the future as csv via POST', function () { - return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, csv_example_data.with_timestamps_future(boxes[0].sensors), { json: false, headers: { 'content-type': 'text/csv' } }) + return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, csv_example_data.with_timestamps_future(boxes[0].sensors), { json: false, headers: { 'content-type': 'text/csv', 'Authorization' : boxes[0].access_token } }) .then(function (response) { expect(response).to.have.status(422); @@ -173,7 +197,7 @@ describe('submitting measurements', function () { }); it('should reject multiple measurements with too many fields as csv via POST', function () { - return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, csv_example_data.with_too_many(boxes[0].sensors), { json: false, headers: { 'content-type': 'text/csv' } }) + return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, csv_example_data.with_too_many(boxes[0].sensors), { json: false, headers: { 'content-type': 'text/csv', 'Authorization' : boxes[0].access_token } }) .then(function (response) { expect(response).to.have.status(422); @@ -182,7 +206,7 @@ describe('submitting measurements', function () { }); it('should accept multiple csv measurements from ten days ago', function () { - return chakram.post(`${BASE_URL}/boxes/${boxIds[1]}/data`, csv_example_data.ten_days_ago_many(boxes[1].sensors), { json: false, headers: { 'content-type': 'text/csv' } }) + return chakram.post(`${BASE_URL}/boxes/${boxIds[1]}/data`, csv_example_data.ten_days_ago_many(boxes[1].sensors), { json: false, headers: { 'content-type': 'text/csv', 'Authorization' : boxes[1].access_token } }) .then(function (response) { expect(response).to.have.status(201); @@ -207,7 +231,7 @@ describe('submitting measurements', function () { it('should accept multiple measurements with timestamps as json object via POST and Content-type: json', function () { let submitTime; - return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, JSON.stringify(json_submit_data.json_obj(boxes[0].sensors)), { json: false, headers: { 'content-type': 'json' } }) + return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, JSON.stringify(json_submit_data.json_obj(boxes[0].sensors)), { json: false, headers: { 'content-type': 'json', 'Authorization' : boxes[0].access_token } }) .then(function (response) { submitTime = moment.utc(response.response.headers.date, 'ddd, DD MMM YYYY HH:mm:ss GMT'); expect(response).to.have.status(201); @@ -233,7 +257,7 @@ describe('submitting measurements', function () { it('should accept multiple measurements with timestamps as json object via POST', function () { let submitTime; - return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, json_submit_data.json_obj(boxes[0].sensors)) + return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, json_submit_data.json_obj(boxes[0].sensors), {headers: {'Authorization' : boxes[0].access_token} }) .then(function (response) { submitTime = moment.utc(response.response.headers.date, 'ddd, DD MMM YYYY HH:mm:ss GMT'); expect(response).to.have.status(201); @@ -262,7 +286,7 @@ describe('submitting measurements', function () { it('should accept multiple measurements with timestamps as json array via POST', function () { let submitTime; - return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, json_submit_data.json_arr(boxes[0].sensors)) + return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, json_submit_data.json_arr(boxes[0].sensors), {headers: {'Authorization' : boxes[0].access_token} }) .then(function (response) { submitTime = moment.utc(response.response.headers.date, 'ddd, DD MMM YYYY HH:mm:ss GMT'); expect(response).to.have.status(201); @@ -298,7 +322,7 @@ describe('submitting measurements', function () { { sensor_id, value: 0.7, createdAt: '2016-01-23T08:37:23.000Z' } ]; - return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, payload) + return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, payload, {headers: {'Authorization' : boxes[0].access_token} }) .then(function (response) { expect(response).to.have.status(201); expect(response.body).to.equal('Measurements saved in box'); @@ -332,7 +356,7 @@ describe('submitting measurements', function () { { sensor: boxes[0].sensors[3]._id, value: 0.3, createdAt: '2010-01-02T01:00:22.000Z' }, ]; - return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, payload) + return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, payload, {headers: {'Authorization' : boxes[0].access_token} }) .then(function (response) { expect(response).to.have.status(201); expect(response.body).to.equal('Measurements saved in box'); @@ -368,7 +392,7 @@ describe('submitting measurements', function () { { sensor: boxes[0].sensors[3]._id, value: 0.3, createdAt: '2010-01-02T01:00:22.000Z' }, ]; - return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, payload) + return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, payload, {headers: {'Authorization' : boxes[0].access_token} }) .then(function (response) { expect(response).to.have.status(201); expect(response.body).to.equal('Measurements saved in box'); @@ -395,7 +419,7 @@ describe('submitting measurements', function () { it('should accept multiple measurements as bytes via POST', function () { let submitTime; - return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, byte_submit_data(boxes[0].sensors), { json: false, headers: { 'content-type': 'application/sbx-bytes' } }) + return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, byte_submit_data(boxes[0].sensors), { json: false, headers: { 'content-type': 'application/sbx-bytes', 'Authorization' : boxes[0].access_token} }) .then(function (response) { submitTime = moment.utc(response.response.headers.date, 'ddd, DD MMM YYYY HH:mm:ss GMT'); expect(response).to.have.status(201); @@ -421,7 +445,7 @@ describe('submitting measurements', function () { it('should accept multiple measurements as bytes with timestamp via POST', function () { let submitTime; - return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, byte_submit_data(boxes[0].sensors, true), { json: false, headers: { 'content-type': 'application/sbx-bytes-ts' } }) + return chakram.post(`${BASE_URL}/boxes/${boxIds[0]}/data`, byte_submit_data(boxes[0].sensors, true), { json: false, headers: { 'content-type': 'application/sbx-bytes-ts', 'Authorization' : boxes[0].access_token } }) .then(function (response) { submitTime = moment.utc(response.response.headers.date, 'ddd, DD MMM YYYY HH:mm:ss GMT'); expect(response).to.have.status(201); diff --git a/tests/tests/010-luftdaten-test.js b/tests/tests/010-luftdaten-test.js index 1bde225f..2adbb0c8 100644 --- a/tests/tests/010-luftdaten-test.js +++ b/tests/tests/010-luftdaten-test.js @@ -27,7 +27,6 @@ describe('openSenseMap API luftdaten.info devices', function () { .then(function (response) { expect(response).to.have.status(201); expect(response).to.have.header('content-type', 'application/json; charset=utf-8'); - const boxId = response.body.data._id; dht11_id = boxId; diff --git a/tests/tests/013-classify-test.js b/tests/tests/013-classify-test.js index 48d0f104..7f366741 100644 --- a/tests/tests/013-classify-test.js +++ b/tests/tests/013-classify-test.js @@ -25,7 +25,7 @@ describe('openSenseMap API Routes: getBoxes Classification', function () { ]); }) .then(function (responses) { - const boxes = responses.map(r => { return { _id: r.body.data._id, sensorid: r.body.data.sensors[0]._id, name: r.body.data.name }; }); + const boxes = responses.map(r => { return { _id: r.body.data._id, sensorid: r.body.data.sensors[0]._id, name: r.body.data.name, access_token: r.body.data.access_token }; }); for (const [i, box] of boxes.entries()) { box.measurements = []; @@ -54,9 +54,9 @@ describe('openSenseMap API Routes: getBoxes Classification', function () { const [first, second, third] = boxes; return chakram.all([ - chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${first._id}/data`, first.measurements, { headers: { 'content-type': 'application/json' } }), - chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${second._id}/data`, second.measurements, { headers: { 'content-type': 'application/json' } }), - chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${third._id}/data`, third.measurements, { headers: { 'content-type': 'application/json' } }), + chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${first._id}/data`, first.measurements, { headers: { 'content-type': 'application/json', 'Authorization': first.access_token } }), + chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${second._id}/data`, second.measurements, { headers: { 'content-type': 'application/json', 'Authorization': second.access_token } }), + chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${third._id}/data`, third.measurements, { headers: { 'content-type': 'application/json', 'Authorization': third.access_token } }), ]); }); }); diff --git a/tests/tests/014-descriptive-statistics-test.js b/tests/tests/014-descriptive-statistics-test.js index 109ca1c7..b7580e1c 100644 --- a/tests/tests/014-descriptive-statistics-test.js +++ b/tests/tests/014-descriptive-statistics-test.js @@ -25,7 +25,7 @@ describe('openSenseMap API Routes: basic descriptive statistics', function () { ]); }) .then(function (responses) { - const boxes = responses.map(r => { return { _id: r.body.data._id, sensorid: r.body.data.sensors[0]._id }; }); + const boxes = responses.map(r => { return { _id: r.body.data._id, sensorid: r.body.data.sensors[0]._id, access_token: r.body.data.access_token }; }); boxIds = responses.map(r => r.body.data._id).join(','); for (const box of boxes) { @@ -108,9 +108,9 @@ ${box.sensorid},3,2018-02-05T14:06:12.620Z const [first, second, third] = boxes; return chakram.all([ - chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${first._id}/data`, first.measurements, { json: false, headers: { 'content-type': 'text/csv' } }), - chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${second._id}/data`, second.measurements, { json: false, headers: { 'content-type': 'text/csv' } }), - chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${third._id}/data`, third.measurements, { json: false, headers: { 'content-type': 'text/csv' } }), + chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${first._id}/data`, first.measurements, { json: false, headers: { 'content-type': 'text/csv', 'Authorization': first.access_token } }), + chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${second._id}/data`, second.measurements, { json: false, headers: { 'content-type': 'text/csv', 'Authorization': second.access_token } }), + chakram.post(`${process.env.OSEM_TEST_BASE_URL}/boxes/${third._id}/data`, third.measurements, { json: false, headers: { 'content-type': 'text/csv', 'Authorization': third.access_token } }), ]); }); }); diff --git a/tests/tests/016-hackair-test.js b/tests/tests/016-hackair-test.js index c4b0b1c1..86b2e0c2 100644 --- a/tests/tests/016-hackair-test.js +++ b/tests/tests/016-hackair-test.js @@ -84,7 +84,7 @@ describe('openSenseMap API hackAIR devices', function () { expect(response).to.have.status(401); expect(response.body).to.be.an('object'); expect(response.body.message).to.be.a('string'); - expect(response.body.message).to.equal('Access token not valid!'); + expect(response.body.message).to.equal('Box access token not valid!'); }); }); }); From 90da6c834f05b72e26309a342304ef03cd1fd5b8 Mon Sep 17 00:00:00 2001 From: felixerdy Date: Fri, 16 Oct 2020 11:33:42 +0200 Subject: [PATCH 14/19] add access_token to getSketch of newSketch email --- packages/models/src/user/mails.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/models/src/user/mails.js b/packages/models/src/user/mails.js index d51a216d..06099518 100644 --- a/packages/models/src/user/mails.js +++ b/packages/models/src/user/mails.js @@ -118,7 +118,7 @@ if (config.get('mailer.url')) { }, attachment: { filename: 'senseBox.ino', - contents: box.getSketch({ encoding: 'base64' }) + contents: box.getSketch({ encoding: 'base64', access_token: box.access_token }) } }; }, From 44fdcf93c1753a11d60a2046bd3111d0aeb779a8 Mon Sep 17 00:00:00 2001 From: felixerdy Date: Fri, 16 Oct 2020 14:03:48 +0200 Subject: [PATCH 15/19] =?UTF-8?q?update=20documentation=20=F0=9F=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/api/lib/controllers/boxesController.js | 2 +- packages/api/lib/controllers/measurementsController.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/api/lib/controllers/boxesController.js b/packages/api/lib/controllers/boxesController.js index cb758fc5..470ace31 100644 --- a/packages/api/lib/controllers/boxesController.js +++ b/packages/api/lib/controllers/boxesController.js @@ -384,7 +384,7 @@ const getBox = async function getBox (req, res, next) { * @apiParam (RequestBody) {String="hdc1080","bmp280","tsl45315","veml6070","sds011","bme680","smt50","soundlevelmeter", "windspeed"} [sensorTemplates] Specify which sensors should be included. * @apiParam (RequestBody) {Object} [mqtt] specify parameters of the MQTT integration for external measurement upload. Please see below for the accepted parameters * @apiParam (RequestBody) {Object} [ttn] specify parameters for the TTN integration for measurement from TheThingsNetwork.org upload. Please see below for the accepted parameters - * @apiParam (RequestBody) {Boolean="true","false"} [useAuth] whether to use access_token or not for authentication (default: true) + * @apiParam (RequestBody) {Boolean="true","false"} [useAuth] whether to use access_token or not for authentication * * @apiUse LocationBody * @apiUse SensorBody diff --git a/packages/api/lib/controllers/measurementsController.js b/packages/api/lib/controllers/measurementsController.js index 7631987e..dc40f983 100644 --- a/packages/api/lib/controllers/measurementsController.js +++ b/packages/api/lib/controllers/measurementsController.js @@ -210,6 +210,7 @@ const getDataMulti = async function getDataMulti (req, res, next) { * @apiParam (RequestBody) {String} value the measured value of the sensor. Also accepts JSON float numbers. * @apiParam (RequestBody) {RFC3339Date} [createdAt] the timestamp of the measurement. Should conform to RFC 3339. Is needed when posting with Location Values! * @apiParam (RequestBody) {Location} [location] the WGS84-coordinates of the measurement. + * @apiHeader {String} access_token Box' unique access_token. Will be used as authorization token if box has auth enabled (e.g. useAuth: true) */ const postNewMeasurement = async function postNewMeasurement (req, res, next) { const { boxId, sensorId, value, createdAt, location } = req._userParams; @@ -267,6 +268,7 @@ const postNewMeasurement = async function postNewMeasurement (req, res, next) { * @apiUse LocationBody * @apiParam {String} [luftdaten] Specify whatever you want (like `luftdaten=1`. Signals the api to treat the incoming data as luftdaten.info formatted json. * * @apiParam {String} [hackair] Specify whatever you want (like `hackair=1`. Signals the api to treat the incoming data as hackair formatted json. + * @apiHeader {String} access_token Box' unique access_token. Will be used as authorization token if box has auth enabled (e.g. useAuth: true) * @apiParamExample {application/json} JSON-Object: * { * "sensorID": "value", From c50494a4f14f63a6b1609336b1258f1e6eab1939 Mon Sep 17 00:00:00 2001 From: Umut Tas Date: Fri, 16 Oct 2020 14:51:47 +0200 Subject: [PATCH 16/19] remove custom boxes from useAuth=true --- packages/models/src/box/box.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/models/src/box/box.js b/packages/models/src/box/box.js index 7b00e9af..ce863f2c 100644 --- a/packages/models/src/box/box.js +++ b/packages/models/src/box/box.js @@ -253,7 +253,7 @@ boxSchema.statics.initNew = function ({ } if(model){ //activate useAuth only for certain models until all sketches are updated - if(['homeV2Lora' , 'homeV2Ethernet' , 'homeV2EthernetFeinstaub' , 'homeV2Wifi' , 'homeV2WifiFeinstaub' , 'homeEthernet' , 'homeWifi' , 'homeEthernetFeinstaub' , 'homeWifiFeinstaub' , 'hackair_home_v2' , 'custom'].indexOf(model) != -1){ + if(['homeV2Lora' , 'homeV2Ethernet' , 'homeV2EthernetFeinstaub' , 'homeV2Wifi' , 'homeV2WifiFeinstaub' , 'homeEthernet' , 'homeWifi' , 'homeEthernetFeinstaub' , 'homeWifiFeinstaub' , 'hackair_home_v2'].indexOf(model) != -1){ useAuth = true } else { useAuth = false From 051b0d6ae3b0389fa5abec9c114b3a9ed3e23656 Mon Sep 17 00:00:00 2001 From: umut Date: Mon, 19 Oct 2020 13:25:45 +0200 Subject: [PATCH 17/19] add onlyValue in measurement controller --- packages/api/lib/controllers/measurementsController.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/api/lib/controllers/measurementsController.js b/packages/api/lib/controllers/measurementsController.js index 7631987e..a27bf673 100644 --- a/packages/api/lib/controllers/measurementsController.js +++ b/packages/api/lib/controllers/measurementsController.js @@ -27,6 +27,7 @@ const * @apiName getLatestMeasurementOfSensor * @apiUse BoxIdParam * @apiUse SensorIdParam + * @apiParam {Boolean="true","false"} [onlyValue] If set to true only returns the measured value without information about the sensor. Requires a sensorId. */ const getLatestMeasurements = async function getLatestMeasurements (req, res, next) { const { _userParams: params } = req; @@ -44,6 +45,14 @@ const getLatestMeasurements = async function getLatestMeasurements (req, res, ne if (params.sensorId) { const sensor = box.sensors.find(s => s._id.equals(params.sensorId)); if (sensor) { + if(params.onlyValue){ + if(!sensor.lastMeasurement){ + res.send(undefined); + return; + } + res.send(sensor.lastMeasurement.value); + return; + } res.send(sensor); return; @@ -400,6 +409,7 @@ module.exports = { retrieveParameters([ { predef: 'boxId', required: true }, { predef: 'sensorId' }, + { name: 'onlyValue', required: false } ]), getLatestMeasurements ] From 5e03d5ef3d3838d9a64d3a4d5a7f2b11fbe407cf Mon Sep 17 00:00:00 2001 From: umut Date: Mon, 19 Oct 2020 13:50:43 +0200 Subject: [PATCH 18/19] test for onlyValue feature --- tests/tests/007-download-data-test.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/tests/007-download-data-test.js b/tests/tests/007-download-data-test.js index 914a337f..87809ad0 100644 --- a/tests/tests/007-download-data-test.js +++ b/tests/tests/007-download-data-test.js @@ -205,6 +205,19 @@ describe('downloading data', function () { }); }); + it('should return only value of a single sensor of a box for /boxes/:boxid/sensors/:sensorId?onlyValue=true GET', function () { + return chakram.get(`${BASE_URL}/boxes/${boxes[0]._id}/sensors/${boxes[0].sensors[0]._id}?onlyValue=true`) + .then(function (response) { + console.log("BOOODY", response.body) + expect(response).to.have.status(200); + expect(response).to.have.header('content-type', 'application/json; charset=utf-8'); + expect(parseFloat(response.body)).to.be.a('number'); + + return chakram.wait(); + }); + }); + + }); describe('/boxes/:boxid/data/:sensorid', function () { From 5636c493e04a492e69edd489b8c096c8fa356cfa Mon Sep 17 00:00:00 2001 From: umut Date: Mon, 19 Oct 2020 14:03:40 +0200 Subject: [PATCH 19/19] remove console.log --- tests/tests/007-download-data-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/tests/007-download-data-test.js b/tests/tests/007-download-data-test.js index 87809ad0..f8bcfed2 100644 --- a/tests/tests/007-download-data-test.js +++ b/tests/tests/007-download-data-test.js @@ -208,7 +208,6 @@ describe('downloading data', function () { it('should return only value of a single sensor of a box for /boxes/:boxid/sensors/:sensorId?onlyValue=true GET', function () { return chakram.get(`${BASE_URL}/boxes/${boxes[0]._id}/sensors/${boxes[0].sensors[0]._id}?onlyValue=true`) .then(function (response) { - console.log("BOOODY", response.body) expect(response).to.have.status(200); expect(response).to.have.header('content-type', 'application/json; charset=utf-8'); expect(parseFloat(response.body)).to.be.a('number');