diff --git a/packages/api/lib/controllers/boxesController.js b/packages/api/lib/controllers/boxesController.js index 70f35aef..470ace31 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 * * @apiUse LocationBody * @apiUse SensorBody @@ -424,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, @@ -434,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.access_token) { + params.access_token = box.access_token; + } + + res.send(box.getSketch(params)); } catch (err) { handleError(err, next); } @@ -442,7 +451,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 @@ -506,7 +515,9 @@ module.exports = { { name: 'ttn', dataType: 'object' }, { name: 'sensors', dataType: ['object'] }, { name: 'addons', dataType: 'object' }, - { predef: 'location' } + { predef: 'location' }, + { name: 'useAuth', allowedValues: ['true', 'false'] }, + { name: 'generate_access_token', allowedValues: ['true', 'false'] } ]), checkPrivilege, updateBox @@ -537,6 +548,7 @@ module.exports = { { name: 'windSpeedPort', dataType: 'String', defaultValue: 'C', allowedValues: ['A', 'B', 'C'] }, { name: 'mqtt', dataType: 'object' }, { name: 'ttn', dataType: 'object' }, + { 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 7f56bc24..28cad558 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; @@ -92,15 +101,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) { @@ -208,12 +219,17 @@ 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; 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, @@ -261,6 +277,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", @@ -317,10 +334,15 @@ 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('Box access token not valid!'); + // } - if (contentType === 'hackair' && box.access_token !== req.headers.authorization) { - throw new UnauthorizedError('Access token not valid!'); + // authorization for all boxes that have not opt out + 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 }); @@ -389,6 +411,7 @@ module.exports = { retrieveParameters([ { predef: 'boxId', required: true }, { predef: 'sensorId' }, + { name: 'onlyValue', required: false } ]), getLatestMeasurements ] 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 0b76a451..8f53d5bc 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -1,12 +1,12 @@ { "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": { "@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", diff --git a/packages/models/src/box/box.js b/packages/models/src/box/box.js index 6cfd82f8..ce863f2c 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: false, } }, { usePushEach: true }); boxSchema.plugin(timestamp); @@ -167,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; @@ -225,7 +231,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 @@ -244,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'].indexOf(model) != -1){ + useAuth = true + } else { + useAuth = false + } + } const integrations = { mqtt: { enabled, url, topic, decodeOptions: mqttDecodeOptions, connectionOptions, messageFormat }, @@ -271,7 +286,8 @@ boxSchema.statics.initNew = function ({ model, sensors, integrations, - access_token + access_token, + useAuth }); }; @@ -801,7 +817,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; } @@ -820,6 +836,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 }); }; @@ -860,12 +877,21 @@ 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])); } } + // 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) { diff --git a/packages/models/src/user/mails.js b/packages/models/src/user/mails.js index 8650b6ef..06099518 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.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) } }; }, @@ -112,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 }) } }; }, 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/007-download-data-test.js b/tests/tests/007-download-data-test.js index a11f1b1c..f8bcfed2 100644 --- a/tests/tests/007-download-data-test.js +++ b/tests/tests/007-download-data-test.js @@ -205,6 +205,18 @@ 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) { + 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 () { @@ -226,7 +238,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; 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!'); }); }); }); 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"