Skip to content

Commit bb7d5e3

Browse files
felixerdyumut0
andauthored
Auth (#362)
* fix download in getData closes #325 (#349) (#350) * initial auth for boxes * auth for boxes that have not opt out * fix spelling * useAuth in updateBox * update access_token via generate_access_token * pass access_token to sketch templater * bump node-sketch-templater version (beta) * add authorization header anyways * auth beta * include useAuth in includeSecret requests * remove default true for useAuth, set true for new Boxes * update tests for auth feature, fix errors in auth code * add access_token to getSketch of newSketch email * update documentation 📜 * remove custom boxes from useAuth=true * add onlyValue in measurement controller * test for onlyValue feature * remove console.log Co-authored-by: Umut Tas <[email protected]>
1 parent ed66f00 commit bb7d5e3

20 files changed

+203
-89
lines changed

packages/api/lib/controllers/boxesController.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ const getBox = async function getBox (req, res, next) {
384384
* @apiParam (RequestBody) {String="hdc1080","bmp280","tsl45315","veml6070","sds011","bme680","smt50","soundlevelmeter", "windspeed"} [sensorTemplates] Specify which sensors should be included.
385385
* @apiParam (RequestBody) {Object} [mqtt] specify parameters of the MQTT integration for external measurement upload. Please see below for the accepted parameters
386386
* @apiParam (RequestBody) {Object} [ttn] specify parameters for the TTN integration for measurement from TheThingsNetwork.org upload. Please see below for the accepted parameters
387+
* @apiParam (RequestBody) {Boolean="true","false"} [useAuth] whether to use access_token or not for authentication
387388
*
388389
* @apiUse LocationBody
389390
* @apiUse SensorBody
@@ -424,7 +425,8 @@ const getSketch = async function getSketch (req, res, next) {
424425
res.header('Content-Type', 'text/plain; charset=utf-8');
425426
try {
426427
const box = await Box.findBoxById(req._userParams.boxId, { populate: false, lean: false });
427-
res.send(box.getSketch({
428+
429+
const params = {
428430
serialPort: req._userParams.serialPort,
429431
soilDigitalPort: req._userParams.soilDigitalPort,
430432
soundMeterPort: req._userParams.soundMeterPort,
@@ -434,15 +436,22 @@ const getSketch = async function getSketch (req, res, next) {
434436
devEUI: req._userParams.devEUI,
435437
appEUI: req._userParams.appEUI,
436438
appKey: req._userParams.appKey
437-
}));
439+
};
440+
441+
// pass access token only if useAuth is true and access_token is available
442+
if (box.access_token) {
443+
params.access_token = box.access_token;
444+
}
445+
446+
res.send(box.getSketch(params));
438447
} catch (err) {
439448
handleError(err, next);
440449
}
441450
};
442451

443452
/**
444453
* @api {delete} /boxes/:senseBoxId Mark a senseBox and its measurements for deletion
445-
* @apiDescription This will delete all the measurements of the senseBox. Please not that the deletion isn't happening immediately.
454+
* @apiDescription This will delete all the measurements of the senseBox. Please note that the deletion isn't happening immediately.
446455
* @apiName deleteBox
447456
* @apiGroup Boxes
448457
* @apiUse ContentTypeJSON
@@ -506,7 +515,9 @@ module.exports = {
506515
{ name: 'ttn', dataType: 'object' },
507516
{ name: 'sensors', dataType: ['object'] },
508517
{ name: 'addons', dataType: 'object' },
509-
{ predef: 'location' }
518+
{ predef: 'location' },
519+
{ name: 'useAuth', allowedValues: ['true', 'false'] },
520+
{ name: 'generate_access_token', allowedValues: ['true', 'false'] }
510521
]),
511522
checkPrivilege,
512523
updateBox
@@ -537,6 +548,7 @@ module.exports = {
537548
{ name: 'windSpeedPort', dataType: 'String', defaultValue: 'C', allowedValues: ['A', 'B', 'C'] },
538549
{ name: 'mqtt', dataType: 'object' },
539550
{ name: 'ttn', dataType: 'object' },
551+
{ name: 'useAuth', allowedValues: ['true', 'false'] },
540552
{ predef: 'location', required: true }
541553
]),
542554
postNewBox

packages/api/lib/controllers/measurementsController.js

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const
2727
* @apiName getLatestMeasurementOfSensor
2828
* @apiUse BoxIdParam
2929
* @apiUse SensorIdParam
30+
* @apiParam {Boolean="true","false"} [onlyValue] If set to true only returns the measured value without information about the sensor. Requires a sensorId.
3031
*/
3132
const getLatestMeasurements = async function getLatestMeasurements (req, res, next) {
3233
const { _userParams: params } = req;
@@ -44,6 +45,14 @@ const getLatestMeasurements = async function getLatestMeasurements (req, res, ne
4445
if (params.sensorId) {
4546
const sensor = box.sensors.find(s => s._id.equals(params.sensorId));
4647
if (sensor) {
48+
if(params.onlyValue){
49+
if(!sensor.lastMeasurement){
50+
res.send(undefined);
51+
return;
52+
}
53+
res.send(sensor.lastMeasurement.value);
54+
return;
55+
}
4756
res.send(sensor);
4857

4958
return;
@@ -210,12 +219,17 @@ const getDataMulti = async function getDataMulti (req, res, next) {
210219
* @apiParam (RequestBody) {String} value the measured value of the sensor. Also accepts JSON float numbers.
211220
* @apiParam (RequestBody) {RFC3339Date} [createdAt] the timestamp of the measurement. Should conform to RFC 3339. Is needed when posting with Location Values!
212221
* @apiParam (RequestBody) {Location} [location] the WGS84-coordinates of the measurement.
222+
* @apiHeader {String} access_token Box' unique access_token. Will be used as authorization token if box has auth enabled (e.g. useAuth: true)
213223
*/
214224
const postNewMeasurement = async function postNewMeasurement (req, res, next) {
215225
const { boxId, sensorId, value, createdAt, location } = req._userParams;
216226

217227
try {
218228
const box = await Box.findBoxById(boxId, { populate: false, lean: false });
229+
if (box.useAuth && box.access_token && box.access_token !== req.headers.authorization) {
230+
throw new UnauthorizedError('Box access token not valid!');
231+
}
232+
219233
const [measurement] = await Measurement.decodeMeasurements([{
220234
sensor_id: sensorId,
221235
value,
@@ -263,6 +277,7 @@ const postNewMeasurement = async function postNewMeasurement (req, res, next) {
263277
* @apiUse LocationBody
264278
* @apiParam {String} [luftdaten] Specify whatever you want (like `luftdaten=1`. Signals the api to treat the incoming data as luftdaten.info formatted json.
265279
* * @apiParam {String} [hackair] Specify whatever you want (like `hackair=1`. Signals the api to treat the incoming data as hackair formatted json.
280+
* @apiHeader {String} access_token Box' unique access_token. Will be used as authorization token if box has auth enabled (e.g. useAuth: true)
266281
* @apiParamExample {application/json} JSON-Object:
267282
* {
268283
* "sensorID": "value",
@@ -319,10 +334,15 @@ const postNewMeasurements = async function postNewMeasurements (req, res, next)
319334

320335
if (Measurement.hasDecoder(contentType)) {
321336
try {
322-
const box = await Box.findBoxById(boxId, { populate: false, lean: false, projection: { sensors: 1, locations: 1, lastMeasurementAt: 1, currentLocation: 1, model: 1, access_token: 1 } });
337+
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 } });
338+
339+
// if (contentType === 'hackair' && box.access_token !== req.headers.authorization) {
340+
// throw new UnauthorizedError('Box access token not valid!');
341+
// }
323342

324-
if (contentType === 'hackair' && box.access_token !== req.headers.authorization) {
325-
throw new UnauthorizedError('Access token not valid!');
343+
// authorization for all boxes that have not opt out
344+
if ((box.useAuth || contentType === 'hackair') && box.access_token && box.access_token !== req.headers.authorization) {
345+
throw new UnauthorizedError('Box access token not valid!');
326346
}
327347

328348
const measurements = await Measurement.decodeMeasurements(req.body, { contentType, sensors: box.sensors });
@@ -391,6 +411,7 @@ module.exports = {
391411
retrieveParameters([
392412
{ predef: 'boxId', required: true },
393413
{ predef: 'sensorId' },
414+
{ name: 'onlyValue', required: false }
394415
]),
395416
getLatestMeasurements
396417
]

packages/api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"Norwin Roosen"
1111
],
1212
"dependencies": {
13-
"@sensebox/opensensemap-api-models": "^0.0.25",
13+
"@sensebox/opensensemap-api-models": "^0.0.26-beta.0",
1414
"@turf/area": "^6.0.1",
1515
"@turf/bbox": "^6.0.1",
1616
"@turf/centroid": "^6.0.2",

packages/models/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Unreleased
44

5+
## v0.0.26-beta.0
6+
- Authorization
7+
58
## v0.0.25
69
- Add Cayenne LPP Decoder
710

packages/models/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
{
22
"name": "@sensebox/opensensemap-api-models",
33
"description": "openSenseMap data models and database connection",
4-
"version": "0.0.25",
4+
"version": "0.0.26-beta.0",
55
"main": "index.js",
66
"license": "MIT",
77
"dependencies": {
88
"@sensebox/osem-protos": "^1.1.0",
9-
"@sensebox/sketch-templater": "^1.8.2",
9+
"@sensebox/sketch-templater": "^1.8.3-beta.0",
1010
"bcrypt": "^5.0.0",
1111
"bunyan": "^1.8.14",
1212
"config": "^3.3.2",

packages/models/src/box/box.js

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,12 @@ const boxSchema = new Schema({
125125
},
126126
access_token: {
127127
type: String,
128-
required: false
128+
required: true
129+
},
130+
useAuth: {
131+
type: Boolean,
132+
required: true,
133+
default: false,
129134
}
130135
}, { usePushEach: true });
131136
boxSchema.plugin(timestamp);
@@ -167,6 +172,7 @@ boxSchema.set('toJSON', {
167172
if (options && options.includeSecrets) {
168173
box.integrations = ret.integrations;
169174
box.access_token = ret.access_token;
175+
box.useAuth = ret.useAuth;
170176
}
171177

172178
return box;
@@ -225,7 +231,8 @@ boxSchema.statics.initNew = function ({
225231
} = { enabled: false },
226232
ttn: {
227233
app_id, dev_id, port, profile, decodeOptions: ttnDecodeOptions
228-
} = {}
234+
} = {},
235+
useAuth
229236
}) {
230237
// if model is not empty, get sensor definitions from products
231238
// otherwise, sensors should not be empty
@@ -244,6 +251,14 @@ boxSchema.statics.initNew = function ({
244251
sensors = sensorLayouts.getSensorsForModel(model);
245252
}
246253
}
254+
if(model){
255+
//activate useAuth only for certain models until all sketches are updated
256+
if(['homeV2Lora' , 'homeV2Ethernet' , 'homeV2EthernetFeinstaub' , 'homeV2Wifi' , 'homeV2WifiFeinstaub' , 'homeEthernet' , 'homeWifi' , 'homeEthernetFeinstaub' , 'homeWifiFeinstaub' , 'hackair_home_v2'].indexOf(model) != -1){
257+
useAuth = true
258+
} else {
259+
useAuth = false
260+
}
261+
}
247262

248263
const integrations = {
249264
mqtt: { enabled, url, topic, decodeOptions: mqttDecodeOptions, connectionOptions, messageFormat },
@@ -271,7 +286,8 @@ boxSchema.statics.initNew = function ({
271286
model,
272287
sensors,
273288
integrations,
274-
access_token
289+
access_token,
290+
useAuth
275291
});
276292

277293
};
@@ -801,7 +817,7 @@ boxSchema.methods.updateSensors = function updateSensors (sensors) {
801817
}
802818
};
803819

804-
boxSchema.methods.getSketch = function getSketch ({ encoding, serialPort, soilDigitalPort, soundMeterPort, windSpeedPort, ssid, password, devEUI, appEUI, appKey } = {}) {
820+
boxSchema.methods.getSketch = function getSketch ({ encoding, serialPort, soilDigitalPort, soundMeterPort, windSpeedPort, ssid, password, devEUI, appEUI, appKey, access_token } = {}) {
805821
if (serialPort) {
806822
this.serialPort = serialPort;
807823
}
@@ -820,6 +836,7 @@ boxSchema.methods.getSketch = function getSketch ({ encoding, serialPort, soilDi
820836
this.devEUI = devEUI;
821837
this.appEUI = appEUI,
822838
this.appKey = appKey;
839+
this.access_token = access_token;
823840

824841
return templateSketcher.generateSketch(this, { encoding });
825842
};
@@ -860,12 +877,21 @@ boxSchema.methods.updateBox = function updateBox (args) {
860877
const box = this;
861878

862879
// only grouptag, description and weblink can removed through setting them to empty string ('')
863-
for (const prop of ['name', 'exposure', 'grouptag', 'description', 'weblink', 'image', 'integrations.mqtt', 'integrations.ttn', 'model']) {
880+
for (const prop of ['name', 'exposure', 'grouptag', 'description', 'weblink', 'image', 'integrations.mqtt', 'integrations.ttn', 'model', 'useAuth']) {
864881
if (typeof args[prop] !== 'undefined') {
865882
box.set(prop, (args[prop] === '' ? undefined : args[prop]));
866883
}
867884
}
868885

886+
// if user wants a new access_token
887+
if (typeof args['generate_access_token'] !== 'undefined') {
888+
if (args['generate_access_token'] == 'true') {
889+
// Create new acces token for box
890+
const access_token = crypto.randomBytes(32).toString('hex');
891+
box.set('access_token', access_token);
892+
}
893+
}
894+
869895
if (sensors) {
870896
box.updateSensors(sensors);
871897
} else if (addonToAdd) {

packages/models/src/user/mails.js

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,30 @@ if (config.get('mailer.url')) {
1818

1919
const mailTemplates = {
2020
'newBox' (user, box) {
21+
let sketchParams = {
22+
encoding: 'base64',
23+
ssid: '',
24+
password: '',
25+
serialPort: box.serialPort,
26+
soilDigitalPort: box.soilDigitalPort,
27+
soundMeterPort: box.soundMeterPort,
28+
windSpeedPort: box.windSpeedPort,
29+
devEUI: '',
30+
appEUI: '',
31+
appKey: ''
32+
}
33+
34+
if(box.access_token) {
35+
sketchParams.access_token = box.access_token
36+
}
37+
2138
return {
2239
payload: {
2340
box
2441
},
2542
attachment: {
2643
filename: 'senseBox.ino',
27-
contents: box.getSketch({
28-
encoding: 'base64',
29-
ssid: '',
30-
password: '',
31-
serialPort: box.serialPort,
32-
soilDigitalPort: box.soilDigitalPort,
33-
soundMeterPort: box.soundMeterPort,
34-
windSpeedPort: box.windSpeedPort,
35-
devEUI: '',
36-
appEUI: '',
37-
appKey: ''
38-
})
44+
contents: box.getSketch(sketchParams)
3945
}
4046
};
4147
},
@@ -112,7 +118,7 @@ if (config.get('mailer.url')) {
112118
},
113119
attachment: {
114120
filename: 'senseBox.ino',
115-
contents: box.getSketch({ encoding: 'base64' })
121+
contents: box.getSketch({ encoding: 'base64', access_token: box.access_token })
116122
}
117123
};
118124
},

tests/data/getUserBoxesSchema.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ module.exports = {
3030
'updatedAt': {
3131
'type': 'string'
3232
},
33+
'useAuth': {
34+
'type': 'boolean'
35+
},
36+
'access_token': {
37+
'type': 'string'
38+
},
3339
'currentLocation': {
3440
'type': 'object',
3541
'properties': {

tests/data/senseBoxSchema.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ module.exports = {
3131
'image': {
3232
'type': 'string'
3333
},
34+
'useAuth': {
35+
'type': "boolean"
36+
},
37+
'access_token': {
38+
'type': "string"
39+
},
3440
'sensors': {
3541
'type': 'array',
3642
'items': {
@@ -115,6 +121,8 @@ module.exports = {
115121
'exposure',
116122
'sensors',
117123
'currentLocation',
118-
'loc'
124+
'loc',
125+
// 'useAuth',
126+
// 'access_token'
119127
]
120128
};

tests/data/valid_sensebox.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ module.exports = function ({ bbox, model, sensors, lonlat, name = '', sensorTemp
3535
'connectionOptions': ''
3636
}
3737
};
38-
3938
if (sensors) {
4039
box.sensors = sensors;
4140
}

0 commit comments

Comments
 (0)