profile editor, hash authentication

This commit is contained in:
MilosKozak
2015-08-11 19:08:48 +02:00
parent 6a93394f31
commit 49d6d9fdf2
12 changed files with 1064 additions and 19 deletions
+2 -2
View File
@@ -6,8 +6,8 @@
"bootstrap": "~3.2.0",
"jquery": "2.1.0",
"jQuery-Storage-API": "~1.7.2",
"jsSHA": "~1.5.0",
"tipsy-jmalonzo": "~1.0.1"
"tipsy-jmalonzo": "~1.0.1",
"jquery-ui": "~1.11.3"
},
"resolutions": {
"jquery": "2.1.0"
+2 -1
View File
@@ -46,7 +46,8 @@ function create (env, ctx) {
app.use('/', require('./profile/')(app, wares, ctx));
app.use('/', require('./devicestatus/')(app, wares, ctx));
app.use('/', require('./notifications-api')(app, wares, ctx));
app.use('/', require('./verifyauth')(app, wares));
// Status
app.use('/', require('./status')(app, wares, env));
return app;
+25
View File
@@ -0,0 +1,25 @@
'use strict';
var consts = require('../constants');
function configure (app, wares) {
var express = require('express'),
api = express.Router( );
function config_authed (app, api, wares) {
// create new record
api.get('/verifyauth', wares.verifyAuthorization, function(req, res) {
res.sendJSONStatus(res, consts.HTTP_OK, 'OK');
});
}
if (app.enabled('api')) {
config_authed(app, api, wares);
}
return api;
}
module.exports = configure;
+3
View File
@@ -58,6 +58,9 @@ client.init = function init(serverSettings, plugins) {
language.set(client.settings.language).DOMtranslate($);
client.translate = language.translate;
client.hashauth = require('../hashauth');
client.hashauth.init(client, $).initAuthentication();
client.tooltip = d3.select('body').append('div')
.attr('class', 'tooltip')
+153
View File
@@ -0,0 +1,153 @@
'use strict';
var crypto = require('crypto');
var hashauth = {
apisecret: ''
, storeapisecret: false
, apisecrethash: null
, authenticated: false
, initialized: false
};
hashauth.init = function init(client, $) {
if (hashauth.initialized) {
return hashauth;
}
hashauth.verifyAuthentication = function verifyAuthentication(next) {
$.ajax({
method: 'GET',
url: '/api/v1/verifyauth',
beforeSend: function (request)
{
request.setRequestHeader('api-secret', hashauth.apisecrethash);
},
}).done(function verifysuccess () {
hashauth.authenticated = true;
console.log('Authentication passed.');
next(true);
}).fail(function verifyfail() {
console.log('Authentication failed.');
hashauth.removeAuthentication();
next(false);
});
};
hashauth.initAuthentication = function initAuthentication() {
hashauth.apisecrethash = $.localStorage.get('apisecrethash') || null;
hashauth.verifyAuthentication(function () {
$('#authentication_placeholder').html(hashauth.inlineCode());
});
return hashauth;
};
hashauth.removeAuthentication = function removeAuthentication(event) {
$.localStorage.remove('apisecrethash');
hashauth.apisecret = null;
hashauth.apisecrethash = null;
hashauth.authenticated = false;
$('#authentication_placeholder').html(hashauth.inlineCode());
if (event) {
event.preventDefault();
}
return false;
};
hashauth.requestAuthentication = function requestAuthentication(event) {
var translate = client.translate;
$( "#requestauthenticationdialog" ).dialog({
width: 500
, height: 240
, buttons: [
{ text: translate("Update"),
click: function() {
var dialog = this;
$( dialog ).dialog( "close" );
hashauth.processSecret($('#apisecret').val(),$('#storeapisecret').is(':checked'));
}
},
{ text: translate("Remove"),
click: function () {
$( this ).dialog( "close" );
hashauth.removeAuthentication();
}
}
]
, open : function() {
$('#apisecret').val('');
$('#apisecret').focus();
}
});
if (event) {
event.preventDefault();
}
return false;
};
hashauth.processSecret = function processSecret(apisecret,storeapisecret) {
var translate = client.translate;
hashauth.apisecret = apisecret;
hashauth.storeapisecret = storeapisecret;
if (hashauth.apisecret.length < 12) {
window.alert(translate('Too short API secret'));
} else {
var shasum = crypto.createHash('sha1');
shasum.update(hashauth.apisecret);
hashauth.apisecrethash = shasum.digest('hex');
hashauth.verifyAuthentication( function(isok) {
if (isok) {
if (hashauth.storeapisecret) {
$.localStorage.set('apisecrethash',hashauth.apisecrethash);
}
$('#authentication_placeholder').html(hashauth.inlineCode());
} else {
alert(translate('Wrong API secret'));
}
});
}
};
hashauth.inlineCode = function inlineCode() {
var translate = client.translate;
var html =
'<div id="requestauthenticationdialog" style="display:none" title="'+translate('Device authentication')+'">'+
'<label for="apisecret">'+translate('Your API secret')+': </label>'+
'<input type="password" id="apisecret" size="20" />'+
'<br>'+
'<input type="checkbox" id="storeapisecret" /> '+translate('Store hash on this computer (Use only on private computers)')+
'<div id="apisecrethash">'+
(hashauth.apisecrethash ? translate('Hash') + ' ' + hashauth.apisecrethash: '')+
'</div>'+
'</div>'+
'<div id="authorizationstatus">'+
(hashauth.isAuthenticated() ?
translate('Device authenticated') + ' <a href="#" onclick="Nightscout.client.hashauth.removeAuthentication(); return false;">(' + translate('Remove') + ')</a>'
:
translate('Device not authenticated') + ' <a href="#" onclick="Nightscout.client.hashauth.requestAuthentication(); return false;">(' + translate('Authenticate') + ')</a>'
)+
'</div>';
return html;
};
hashauth.hash = function hash() {
return hashauth.apisecrethash;
};
hashauth.isAuthenticated = function isAuthenticated() {
return hashauth.authenticated;
};
hashauth.initialized = true;
return hashauth;
};
module.exports = hashauth;
+10
View File
@@ -0,0 +1,10 @@
#profile-editor h1 {
position: absolute;
top: 0;
right: 0;
left: 50px;
margin-top: 0;
margin-right: auto;
margin-left: auto;
text-align: center;
}
+122
View File
@@ -0,0 +1,122 @@
<!DOCTYPE html>
<html>
<head>
<title>Nightscout profile editor</title>
<link href="/images/round1.png" rel="icon" id="favicon" type="image/png" />
<link rel="stylesheet" type="text/css" href="/css/main.css" />
<link rel="stylesheet" type="text/css" href="/css/drawer.css" />
<link rel="stylesheet" type="text/css" href="/css/profile.css" />
<link rel="stylesheet" type="text/css" href="/bower_components/jquery-ui/themes/ui-darkness/jquery-ui.min.css">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-ui-timepicker-addon/1.4.5/jquery-ui-timepicker-addon.css" />
</head>
<body>
<div id="toolbar">
<div id="buttonbar">
<b> Status:</b> <span id="pe_status">Not loaded</span>
</div>
<h1 class="customTitle">Nightscout</h1>
<div id="profile-editor">
<h1>Profile editor</h1>
</div>
</div>
<br>
<form id="pe_form">
<fieldset class="browserSettings">
<legend>General patient profile settings</legend>
<dl>
<b>Title: </b>
<span id="pe_title"></span>
</dl>
<dl>
<b>Units: </b>
<span id="pe_units"></span>
</dl>
<dl>
<b>Time format: </b>
<span id="pe_timeformat"></span>
</dl>
<dl>
<b>Timezone: </b>
<select id="pe_timezone">
</select>
</dl>
<dl>
<b title="Represents the typical duration over which insulin takes effect. Varies per patient and per insulin type. Typically 3-4 hours for most pumped insulin and most patients. Sometimes also called insulin lifetime.">Duration of Insulin Activity (DIA) [hours]: </b>
<input type="text" id="pe_dia" />
</dl>
<dl>
<b title="Start of profile validity">Profile valid from: </b>
<input type="date" id="pe_date" />
<input type="time" id="pe_time" />
<div id="pe_lastrecvalidfrom"></div>
</dl>
</fieldset>
<fieldset>
<legend title="g carbs per U insulin. The ratio of how many grams of carbohydrates are offset by each U of insulin.">Insulin to carb ratio (IC) [g]:</legend>
<div id="pe_ic_placeholder"></div>
</fieldset>
<fieldset>
<legend title="mg/dL or mmol/L per U insulin. The ratio of how much BG changes with each U of corrective insulin.">Insulin Sensitivity Factor (ISF) [mg/dL,mmol/L]: </legend>
<div id="pe_isf_placeholder"></div>
</fieldset>
<fieldset>
<legend>Carbs
(
<i>
<input type="checkbox" id="pe_perGIvalues" checked />
use values specific to GI of food
</i>
)
</legend>
<div id="pe_simple">
<dl>
<b title="grams per unit time. Represents both the change in COB per unit of time, as well as the amount of carbs that should take effect over that time. Carb absorption / activity curves are less well understood than insulin activity, but can be approximated using an initial delay followed by a constant rate of absorption (g/hr).">Carbs activity / absorption rate: [g/hour]</b><br>
<input type="number" id="pe_hr" />
</dl>
</div>
<div id="pe_advanced">
<dl>
<b title="grams per unit time. Represents both the change in COB per unit of time, as well as the amount of carbs that should take effect over that time. Carb absorption / activity curves are less well understood than insulin activity, but can be approximated using an initial delay followed by a constant rate of absorption (g/hr).">Carbs activity / absorption rate: [g/hour]</b><br>
<span title="Value for carbs with high glycemic index">High: </span><input type="number" id="pe_hr_high" />
<span title="Value for carbs with medium glycemic index">Medium: </span><input type="number" id="pe_hr_medium" />
<span title="Value for carbs with low glycemic index">Low: </span><input type="number" id="pe_hr_low" />
</dl>
<dl>
<b title="minutes. Initial time before carbs are being absorbed.">Carbs absorption delay: [min]</b><br>
<span title="Value for carbs with high glycemic index">High: </span><input type="number" id="pe_delay_high" />
<span title="Value for carbs with medium glycemic index">Medium: </span><input type="number" id="pe_delay_medium" />
<span title="Value for carbs with low glycemic index">Low: </span><input type="number" id="pe_delay_low" />
</dl>
</div>
</fieldset>
<fieldset class="browserSettings">
<legend>Basal rates [unit/hour]</legend>
<div id="pe_basal_placeholder"></div>
</fieldset>
<fieldset class="browserSettings">
<legend>Target BG range [mg/dL,mmol/L]</legend>
<div id="pe_targetbg_placeholder"></div>
</fieldset>
<b><span class="translate">Authentication status</span>: </b>
<span id="authentication_placeholder"></span>
<button type="submit" id="pe_submit">Update profile</button>
</form>
<script src="/api/v1/status.js"></script>
<script src="/public/js/bundle.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="/bower_components/jQuery-Storage-API/jquery.storageapi.min.js"></script>
<script src="/bower_components/jquery-ui/jquery-ui.min.js"></script>
<script src="/bower_components/tipsy-jmalonzo/src/javascripts/jquery.tipsy.js"></script>
<script src="/profile/js/profile.js"></script>
</body>
</html>
+486
View File
@@ -0,0 +1,486 @@
'use strict';
(function () {
var c_profile = null;
//var moment = require('moment-timezone');
if (serverSettings === undefined) {
console.error('server settings were not loaded, will not call init');
} else {
window.Nightscout.client.init(serverSettings, Nightscout.plugins);
}
var translate = Nightscout.client.translate;
var defaultprofile = {
//General values
"dia":3,
// Simple style values, "from" are in minutes from midnight
"carbratio": [
{
"time": "00:00",
"value": 30
}],
"carbs_hr":30,
"delay": 20,
"sens": [
{
"time": "00:00",
"value": 17
}],
"startDate": new Date(),
"timezone": "UTC",
//perGIvalues style values
"perGIvalues": false,
"carbs_hr_high": 30,
"carbs_hr_medium": 30,
"carbs_hr_low": 30,
"delay_high": 15,
"delay_medium": 20,
"delay_low": 20,
"basal":[
{
"time": "00:00",
"value": 0.1
}],
"target_low":[
{
"time": "00:00",
"value": 0
}],
"target_high":[
{
"time": "00:00",
"value": 0
}]
};
defaultprofile.startDate.setSeconds(0);
defaultprofile.startDate.setMilliseconds(0);
var icon_add = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABa0lEQVQ4T6WTzysEYRjHP+/Mrv2hHOTuqJRNOfgPSORHokg4OClHcnVzVygHF6WIcuHMnyCHVRyI3ZYxa23vzOzs7LzamaI0e5i89fTWt/f5vPV5n1cQsXLbHepvfLv5JaLORoZNwMbyFo5vYfsWB0c7xAasLa5T/vCg45Oj48P4gJWFVYxCA63L5PzkND5gfm4Jo+Chd5W5OrtsDYgS1pQ1OTuNUfTQO8tcX9xE+QugYnS/X81MzGP7MpTWkEFVZY1KxcVPV3h27zAtA+oCagIcDfWUCgEje31qfHwK06gHjaF5iXQcHCV5lHmqqgQCNEAI0IsavCVDwNBurxoeGwmaAkDDwvYsqtIh//6AJUoklP97s62BbJYeAqIcpJNZsoM+r2aVbKKOekiBL8An3BuAEiGg1SSKAYnttpFxPdR9Jv4zipxFTUuQKqsfYbFGWfTYuO06yRfxIyweoLuG+iMsFuBfvzFy7FqE33vs2BFqlfN5AAAAAElFTkSuQmCC";
var icon_remove = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACrElEQVQ4T42Ty2sTQRzHv5tmk2yyjRNtpfZhL8V6s2KoUNC2XqwgaCsVQcGiFqpHi0c9iRdR/ANE9KR40FIQX4cueKoPaKFoLdSYNtE0abKT1+5s9iW7aUMiHtzTzO7v85md+c6PA4DrHbsPCKIgOWO1pA7dT6YXnXH949SE/F63pqwZtRrO+SCKgjQ5NUV+azpmHj2krMwaJC4c8Erj+/eRyloMMwWFKgbn1nC3ervlK1evkXBLGBZT8SOewotnTylTNLdgeg/pDgZDC2cPHSR8bB22DVC9hFe0SG/H0xFXcHlykjRHRDBWgJcZSCY38Xx2lhqMnRYE34Px/sN9vlQWeoHBAx2yXsRruVAVuFsIBaSJ8+eJGPaBqQV4NROJjTzez89jLBoFn6FgybQL54wS3uTyVDFQ3cL2IYpBv3RhdJSIIQ80tQyv7gEqJvS8AmUlBs7UXPhtjtZgh3UFNYngk86NHCfNAg9dMwHVBPu+CpsVkTXKeJeVG+AGgTOZ3tt6MSKKjy+NjEBjFrR4ElZmA4pdxstMFsyyJu6tZZ7Ux9vwB6EAL50ZGiRECEPPUOixVTRxHlicgSVWxEdZpuZWfNuS2hk48NjwMIkIYZglBnV5Cbqtws/5IaAJmsfCglrEl2y2QeKmEBJ80tixKmxrFpSVr0gV0viQoxho2YUuPohmeFD22PiklLC4ma5JuBvdrfLJI0dJd0s7bM0ES8aR/BXDXGaTskqlL+D3Lwy0tZEePoAd4EA5YF4tYymdonfjmQh3s6dTPjU4SHYGwjAKecSXFyGlM1TdytntE56T+ts7SC/vhw3gm6njc2Kd3vm5Ub1IwQAvnYhGiZpYw1wiWYPrIw7wnBTt7CLOOwdmut14kQQvqt24tfK/utGR6LaF+iRqMf4N/O/8D28HiiCRYqzAAAAAAElFTkSuQmCC";
var mongoprofiles = [];
// Fetch data from mongo
$('#pe_status').hide().text('Loading profile records ...').fadeIn("slow");
$.ajax('/api/v1/profile.json', {
success: function (records) {
c_profile = {};
mongoprofiles = records;
// create new profile to be edited from last record
if (records[0] && records[0].dia) {
// Use only values(keys) defined in defaultprofile, drop the rest. Preparation for future changes.
c_profile = _.cloneDeep(defaultprofile);
for (var key in records[0]) {
if (records[0].hasOwnProperty(key)) {
if (typeof c_profile[key] !== 'undefined') {
c_profile[key] = records[0][key];
}
// copy _id of record too
if (key === '_id') {
c_profile[key] = records[0][key];
}
}
}
convertToRanges(c_profile);
$('#pe_status').hide().text('Values loaded.').fadeIn("slow");
mongoprofiles.unshift(c_profile);
} else {
c_profile = _.cloneDeep(defaultprofile);
mongoprofiles.unshift(c_profile);
$('#pe_status').hide().text('Default values used.').fadeIn("slow");
}
},
error: function () {
c_profile = _.cloneDeep(defaultprofile);
mongoprofiles.unshift(c_profile);
$('#pe_status').hide().text('Error. Default values used.').fadeIn("slow");
}
}).done(initeditor);
// convert simple values to ranges if needed
function convertToRanges(profile) {
if (typeof profile.carbratio !== 'object') { profile.carbratio = [{ 'time': '00:00', 'value': profile.carbratio }]; }
if (typeof profile.sens !== 'object') { profile.sens = [{ 'time': '00:00', 'value': profile.sens }]; }
if (typeof profile.target_low !== 'object') { profile.target_low = [{ 'time': '00:00', 'value': profile.target_low }]; }
if (typeof profile.target_high !== 'object') { profile.target_high = [{ 'time': '00:00', 'value': profile.target_high }]; }
if (profile.target_high.length !== profile.target_low.length) {
alert('Time ranges of target_low and target_high don\'t match. Values are restored to defaults.');
profile.target_low = _.cloneDeep(defaultprofile.target_low);
profile.target_high = _.cloneDeep(defaultprofile.target_high);
}
}
function initeditor() {
// Load timezones
$('#pe_timezone').empty();
moment.tz.names().forEach(function addTz(tz) {
$('#pe_timezone').append(new Option(tz,tz));
});
$('#pe_form').find('button').click(profileSubmit);
// Add handler for style switching
$('#pe_perGIvalues').change(switchStyle);
// display status
$('#pe_units').text(serverSettings.settings.units);
$('#pe_timeformat').text(serverSettings.settings.timeFormat+'h');
$('#pe_title').text(serverSettings.settings.customTitle);
var lastvalidfrom = new Date(mongoprofiles[1] && mongoprofiles[1].startDate ? mongoprofiles[1].startDate : null);
//timepicker
$('#pe_date').on('change', dateChanged);
$('#pe_time').on('change', dateChanged);
// Set values from profile to html
fillTimeRanges();
// hide unused style of ratios
switchStyle();
// show proper submit button
dateChanged();
// date of last record
if (lastvalidfrom) {
$('#pe_lastrecvalidfrom').html('Last record date: '+lastvalidfrom.toLocaleString()+' <i>(Date must be newer to create new record or the same to update current record)</i>');
} else {
$('#pe_lastrecvalidfrom').html('');
}
console.log('Done initeditor()');
}
// Handling valid from date change
function dateChanged(event) {
var newdate = new Date(Nightscout.client.utils.mergeInputTime($('#pe_time').val(), $('#pe_date').val()));
if (mongoprofiles.length<2 || !mongoprofiles[1].startDate || mongoprofiles.length>=2 && new Date(mongoprofiles[1].startDate).getTime() === newdate.getTime()) {
$('#pe_submit').text('Update record').css('display','');
$('#pe_time').css({'background-color':'white'});
$('#pe_date').css({'background-color':'white'});
$('#pe_submit').css({'background-color':'buttonface'});
} else if (mongoprofiles.length<2 || new Date(mongoprofiles[1].startDate).getTime() < newdate.getTime()) {
$('#pe_submit').text('Create new record').css('display','');
$('#pe_time').css({'background-color':'green'});
$('#pe_date').css({'background-color':'green'});
$('#pe_submit').css({'background-color':'green'});
} else {
$('#pe_submit').css('display','none');
$('#pe_time').css({'background-color':'red'});
$('#pe_date').css({'background-color':'red'});
$('#pe_submit').css({'background-color':'red'});
}
if (event) {
event.preventDefault();
}
}
// Handling html events and setting/getting values
function switchStyle(event) {
if (!$('#pe_perGIvalues').is(':checked')) {
$('#pe_simple').show("slow");
$('#pe_advanced').hide("slow");
} else {
$('#pe_simple').hide("slow");
$('#pe_advanced').show("slow");
}
if (event) {
event.preventDefault();
}
}
function fillTimeRanges(event) {
var mmoltime = [
"0:00","0:30","1:00","1:30","2:00","2:30","3:00","3:30","4:00","4:30","5:00","5:30",
"6:00","6:30","7:00","7:30","8:00","8:30","9:00","9:30","10:00","10:30","11:00","11:30",
"12:00","12:30","13:00","13:30","14:00","14:30","15:00","15:30","16:00","16:30","17:00","17:30",
"18:00","18:30","19:00","19:30","20:00","20:30","21:00","21:30","22:00","22:30","23:00","23:30"
];
var mgtime = [
"12:00AM","0:30AM","1:00AM","1:30AM","2:00AM","2:30AM","3:00AM","3:30AM","4:00AM","4:30AM","5:00AM","5:30AM",
"6:00AM","6:30AM","7:00AM","7:30AM","8:00AM","8:30AM","9:00AM","9:30AM","10:00AM","10:30AM","11:00AM","11:30AM",
"12:00PM","0:30PM","1:00PM","1:30PM","2:00PM","2:30PM","3:00PM","3:30PM","4:00PM","4:30PM","5:00PM","5:30PM",
"6:00PM","6:30PM","7:00PM","7:30PM","8:00PM","8:30PM","9:00PM","9:30PM","10:00PM","10:30PM","11:00PM","11:30PM"
];
if (event) {
GUIToObject();
}
function shouldAddTime(i,time,array) {
if (i===0 && time>0) { return false; }
if (i>0 && isNaN(toMinutesFromMidnight(c_profile[array][i-1].time))) { return false; }
if (i>0 && toMinutesFromMidnight(c_profile[array][i-1].time) >= time*30) { return false; }
return true;
}
function addSingleLine(e,i) {
var tr = $('<tr>');
var select = $('<select>').attr('class','pe_selectabletime').attr('id',e.prefix+'_from_'+i);
var lowesttime=-1;
for (var t=0;t<48;t++) {
if (shouldAddTime(i,t,e.array)) {
if (lowesttime === -1) { lowesttime = t*30; }
var selected = toMinutesFromMidnight(c_profile[e.array][i].time) === t*30;
select.append(new Option(serverSettings.settings.timeFormat==='24' ? mmoltime[t] : mgtime[t], toTimeString(t*30),selected,selected));
}
}
tr.append($('<td>').append('From: ').append(select));
tr.append($('<td>').append(e.label).append($('<input type="text">').attr('id',e.prefix+'_val_'+i).attr('value',c_profile[e.array][i].value)));
var icons_td = $('<td>').append($('<img>').attr('class','addsingle').attr('style','cursor:pointer').attr('title','Add new interval before').attr('src',icon_add).attr('array',e.array).attr('pos',i));
if (c_profile[e.array].lenght>1) {
icons_td.append($('<img>').attr('class','delsingle').attr('style','cursor:pointer').attr('title','Delete interval').attr('src',icon_remove).attr('array',e.array).attr('pos',i));
}
tr.append(icons_td);
if (lowesttime>toMinutesFromMidnight(c_profile[e.array][i].time)) {
c_profile[e.array][i].time = toTimeString(lowesttime);
}
return tr[0].outerHTML;
}
// Fill dropdown boxes
[{prefix:'pe_basal', array:'basal', label:'Rate: '},
{prefix:'pe_ic', array:'carbratio', label:'IC: '},
{prefix:'pe_isf', array:'sens', label:'ISF: '}
].forEach(function (e) {
var html = '<table>';
for (var i=0; i<c_profile[e.array].length; i++) {
html += addSingleLine(e,i);
}
html += '<tr><td></td><td></td><td><img class="addsingle" style="cursor:pointer" title="Add new interval before" src="'+icon_add+'" array="'+e.array+'" pos="'+i+'" href="#"></td></tr>';
html += '</table>';
$('#'+e.prefix+'_placeholder').html(html);
});
$('.addsingle').click(function addsingle_click() {
var array = $(this).attr('array');
var pos = $(this).attr('pos');
GUIToObject();
c_profile[array].splice(pos,0,{time:'00:00',value:0});
return fillTimeRanges();
});
$('.delsingle').click(function delsingle_click() {
var array = $(this).attr('array');
var pos = $(this).attr('pos');
GUIToObject();
c_profile[array].splice(pos,1);
c_profile[array][0].time = '00:00';
return fillTimeRanges();
});
function addBGLine(i) {
var tr = $('<tr>');
var select = $('<select>').attr('class','pe_selectabletime').attr('id','pe_targetbg_from_'+i);
var lowesttime=-1;
for (var t=0;t<48;t++) {
if (shouldAddTime(i,t,'target_low')) {
if (lowesttime === -1) { lowesttime = t*30; }
var selected = toMinutesFromMidnight(c_profile.target_low[i].time) === t*30;
select.append(new Option(serverSettings.settings.timeFormat==='24' ? mmoltime[t] : mgtime[t], toTimeString(t*30),selected,selected));
}
}
tr.append($('<td>').append('From: ').append(select));
tr.append($('<td>').append('Low : ').append($('<input type="text">').attr('id','pe_targetbg_low_'+i).attr('value',c_profile.target_low[i].value)));
tr.append($('<td>').append('High : ').append($('<input type="text">').attr('id','pe_targetbg_high_'+i).attr('value',c_profile.target_high[i].value)));
var icons_td = $('<td>').append($('<img>').attr('class','addtargetbg').attr('style','cursor:pointer').attr('title','Add new interval before').attr('src',icon_add).attr('pos',i));
if (c_profile.target_low.length>1) {
icons_td.append($('<img>').attr('class','deltargetbg').attr('style','cursor:pointer').attr('title','Delete interval').attr('src',icon_remove).attr('pos',i));
}
tr.append(icons_td);
// Fix time to correct value after add or change
if (lowesttime>toMinutesFromMidnight(c_profile.target_low[i].time)) {
c_profile.target_low[i].time = toTimeString(lowesttime);
}
return tr[0].outerHTML;
}
// target BG
var html = '<table>';
for (var i=0; i<c_profile.target_low.length; i++) {
html += addBGLine(i);
}
html += '<tr><td></td><td></td><td></td><td><img class="addtargetbg" style="cursor:pointer" title="Add new interval before" src="'+icon_add+'" pos="'+i+'" href="#"></td></tr>';
html += '</table>';
$('#pe_targetbg_placeholder').html(html);
$('.addtargetbg').click(function addtargetbg_click() {
var pos = $(this).attr('pos');
GUIToObject();
c_profile.target_low.splice(pos,0,{time:'00:00',value:0});
c_profile.target_high.splice(pos,0,{time:'00:00',value:0});
return fillTimeRanges();
});
$('.deltargetbg').click(function deltargetbg_click() {
var pos = $(this).attr('pos');
GUIToObject();
c_profile.target_low.splice(pos,1);
c_profile.target_high.splice(pos,1);
c_profile.target_low[0].time = '00:00';
c_profile.target_high[0].time = '00:00';
return fillTimeRanges();
});
$(".pe_selectabletime").change(fillTimeRanges);
objectToGUI();
if (event) {
event.preventDefault();
}
return false;
}
// fill GUI with values from c_profile object
function objectToGUI() {
$('#pe_dia').val(c_profile.dia);
$('#pe_time').val(moment(c_profile.startDate).format('HH:mm'));
$('#pe_date').val(moment(c_profile.startDate).format('YYYY-MM-DD'));
$('#pe_hr').val(c_profile.carbs_hr);
$('#pe_perGIvalues').prop('checked', c_profile.perGIvalues);
$('#pe_hr_high').val(c_profile.carbs_hr_high);
$('#pe_hr_medium').val(c_profile.carbs_hr_medium);
$('#pe_hr_low').val(c_profile.carbs_hr_low);
$('#pe_delay_high').val(c_profile.delay_high);
$('#pe_delay_medium').val(c_profile.delay_medium);
$('#pe_delay_low').val(c_profile.delay_low);
$('#pe_timezone').val(c_profile.timezone);
var index;
[ { prefix:'pe_basal', array:'basal' },
{ prefix:'pe_ic', array:'carbratio' },
{ prefix:'pe_isf', array:'sens' }
].forEach(function (e) {
for (index=0; index<c_profile[e.array].length; index++) {
$('#'+e.prefix+'_from_'+index).val(c_profile[e.array][index].time);
$('#'+e.prefix+'_val_'+index).val(c_profile[e.array][index].value);
}
});
for (index=0; index<c_profile.target_low.length; index++) {
$('#pe_targetbg_from_'+index).val(c_profile.target_low[index].time);
$('#pe_targetbg_low_'+index).val(c_profile.target_low[index].value);
$('#pe_targetbg_high_'+index).val(c_profile.target_high[index].value);
}
console.info(JSON.stringify(c_profile));
}
// Grab values from html GUI to object
function GUIToObject() {
c_profile.dia = parseFloat($('#pe_dia').val());
c_profile.startDate = new Date(Nightscout.client.utils.mergeInputTime($('#pe_time').val(), $('#pe_date').val()));
c_profile.carbs_hr = parseInt($('#pe_hr').val());
c_profile.delay = 20;
c_profile.perGIvalues = $('#pe_perGIvalues').is(':checked');
c_profile.carbs_hr_high = parseInt($('#pe_hr_high').val());
c_profile.carbs_hr_medium = parseInt($('#pe_hr_medium').val());
c_profile.carbs_hr_low = parseInt($('#pe_hr_low').val());
c_profile.delay_high = parseInt($('#pe_delay_high').val());
c_profile.delay_medium = parseInt($('#pe_delay_medium').val());
c_profile.delay_low = parseInt($('#pe_delay_low').val());
c_profile.timezone = $('#pe_timezone').val();
var index;
[ { prefix:'pe_basal', array:'basal' },
{ prefix:'pe_ic', array:'carbratio' },
{ prefix:'pe_isf', array:'sens' }
].forEach(function (e) {
for (index=0; index<c_profile[e.array].length; index++) {
c_profile[e.array][index].time = $('#'+e.prefix+'_from_'+index).val();
c_profile[e.array][index].value = parseFloat($('#'+e.prefix+'_val_'+index).val());
}
});
for (index=0; index<c_profile.target_low.length; index++) {
c_profile.target_low[index].time = $('#pe_targetbg_from_'+index).val();
c_profile.target_low[index].value = parseFloat($('#pe_targetbg_low_'+index).val());
c_profile.target_high[index].time = $('#pe_targetbg_from_'+index).val();
c_profile.target_high[index].value = parseFloat($('#pe_targetbg_high_'+index).val());
}
}
function toMinutesFromMidnight(time) {
var split = time.split(':');
return parseInt(split[0])*60 + parseInt(split[1]);
}
function toTimeString(minfrommidnight) {
var time = moment().startOf('day').add(minfrommidnight,'minutes').format('HH:mm');
return time;
}
function profileSubmit(event) {
GUIToObject();
if (new Date(c_profile.startDate) > new Date()) {
alert('Date must be set in the past');
$('#pe_status').hide().html('Wrong date').fadeIn("slow");
return false;
}
if (!Nightscout.client.hashauth.isAuthenticated()) {
alert(translate('Your device is not authenticated yet'));
return false;
}
c_profile.units = serverSettings.settings.units;
var xhr, dataJson;
if ($('#pe_submit').text().indexOf('Create new record')>-1) {
if (mongoprofiles.length > 1 && (new Date(c_profile.startDate) <= new Date(mongoprofiles[1].validfrom))) {
alert('Date must be greater than last record '+new Date(mongoprofiles[1].startDate));
$('#pe_status').hide().html('Wrong date').fadeIn("slow");
return false;
}
// remove _id when creating new record
delete c_profile._id;
dataJson = JSON.stringify(c_profile, null, ' ');
xhr = new XMLHttpRequest();
xhr.open('POST', '/api/v1/profile/', true);
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
xhr.setRequestHeader('api-secret', Nightscout.client.hashauth.hash());
xhr.onload = function () {
$('#pe_status').hide().text(xhr.statusText).fadeIn("slow");
if (xhr.statusText==='OK') {
var newprofile = _.cloneDeep(c_profile);
mongoprofiles.unshift(newprofile);
initeditor();
}
}
xhr.send(dataJson);
} else {
// Update record
dataJson = JSON.stringify(c_profile, null, ' ');
xhr = new XMLHttpRequest();
xhr.open('PUT', '/api/v1/profile/', true);
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
xhr.setRequestHeader('api-secret', Nightscout.client.hashauth.hash());
xhr.onload = function () {
$('#pe_status').hide().text(xhr.statusText).fadeIn("slow");
}
xhr.send(dataJson);
}
if (event) {
event.preventDefault();
}
return false;
}
})();
+19 -16
View File
@@ -17,13 +17,7 @@ describe('client', function ( ) {
before(function (done) {
benv.setup(function() {
self.$ = require('jquery');
self.$.localStorage = {
get: function mockGet ( ) {
return undefined;
}
, set: function mockSet ( ) {
}
};
self.$.localStorage = require('./fixtures/localstorage');
self.$.fn.tipsy = function mockTipsy ( ) { };
@@ -57,6 +51,24 @@ describe('client', function ( ) {
it ('open careportal, and enter a treatment', function (done) {
var plugins = require('../lib/plugins/')().registerClientDefaults();
var client = require('../lib/client');
self.$.ajax = function mockAjax ( ) {
return {
done: function mockDone (fn) {
fn();
done();
}
};
};
var hashauth = require('../lib/hashauth');
hashauth.init(client,$);
hashauth.verifyAuthentication = function mockVerifyAuthentication(next) {
hashauth.authenticated = true;
next(true);
}
client.init(serverSettings, plugins);
client.dataUpdate(nowData);
@@ -75,15 +87,6 @@ describe('client', function ( ) {
client.careportal.dateTimeFocus();
client.careportal.dateTimeChange();
self.$.ajax = function mockAjax ( ) {
return {
done: function mockDone (fn) {
fn();
done();
}
};
};
window.confirm = function mockConfirm (message) {
function containsLine (line) {
message.indexOf(line + '\n').should.be.greaterThan(0);
+20
View File
@@ -0,0 +1,20 @@
'use strict';
var browserStorage = [];
var localstorage = {
get: function Get(item) {
return browserStorage[item] || null;
}
, set: function Set(item, value) {
browserStorage[item] = value;
}
, remove: function Remove(item) {
delete browserStorage[item];
}
, removeAll: function RemoveAll() {
browserStorage = [];
}
};
module.exports = localstorage;
+154
View File
@@ -0,0 +1,154 @@
'use strict';
require('should');
var times = require('../lib/times');
var benv = require('benv');
var read = require('fs').readFileSync;
var serverSettings = require('./fixtures/default-server-settings');
describe('hashauth', function ( ) {
var self = this;
before(function (done) {
benv.setup(function() {
self.$ = require('jquery');
self.$.localStorage = require('./fixtures/localstorage');
self.$.fn.tipsy = function mockTipsy ( ) { };
var indexHtml = read(__dirname + '/../static/index.html', 'utf8');
self.$('body').html(indexHtml);
var d3 = require('d3');
//disable all d3 transitions so most of the other code can run with jsdom
d3.timer = function mockTimer() { };
benv.expose({
$: self.$
, jQuery: self.$
, d3: d3
, io: {
connect: function mockConnect ( ) {
return {
on: function mockOn ( ) { }
};
}
}
});
done();
});
});
after(function (done) {
benv.teardown();
done();
});
it ('should make module unauthorized', function () {
var plugins = require('../lib/plugins/')().registerClientDefaults();
var client = require('../lib/client');
var hashauth = require('../lib/hashauth');
hashauth.init(client,$);
hashauth.verifyAuthentication = function mockVerifyAuthentication(next) {
hashauth.authenticated = false;
next(true);
}
client.init(serverSettings, plugins);
hashauth.inlineCode().indexOf('Device not authenticated').should.be.greaterThan(0);
hashauth.isAuthenticated().should.equal(false);
var testnull = (hashauth.hash()===null);
testnull.should.be.true;
});
it ('should make module authorized', function () {
var plugins = require('../lib/plugins/')().registerClientDefaults();
var client = require('../lib/client');
var hashauth = require('../lib/hashauth');
hashauth.init(client,$);
hashauth.verifyAuthentication = function mockVerifyAuthentication(next) {
hashauth.authenticated = true;
next(true);
}
client.init(serverSettings, plugins);
hashauth.inlineCode().indexOf('Device authenticated').should.be.greaterThan(0);
hashauth.isAuthenticated().should.equal(true);
});
it ('should store hash and the remove authentication', function () {
var plugins = require('../lib/plugins/')().registerClientDefaults();
var client = require('../lib/client');
var hashauth = require('../lib/hashauth');
var localStorage = require('./fixtures/localstorage');
localStorage.remove('apisecrethash');
hashauth.init(client,$);
hashauth.verifyAuthentication = function mockVerifyAuthentication(next) {
hashauth.authenticated = true;
next(true);
}
client.init(serverSettings, plugins);
hashauth.processSecret('this is my long pass phrase',true);
hashauth.hash().should.equal('b723e97aa97846eb92d5264f084b2823f57c4aa1');
localStorage.get('apisecrethash').should.equal('b723e97aa97846eb92d5264f084b2823f57c4aa1');
hashauth.isAuthenticated().should.equal(true);
hashauth.removeAuthentication();
hashauth.isAuthenticated().should.equal(false);
});
it ('should not store hash', function () {
var plugins = require('../lib/plugins/')().registerClientDefaults();
var client = require('../lib/client');
var hashauth = require('../lib/hashauth');
var localStorage = require('./fixtures/localstorage');
localStorage.remove('apisecrethash');
hashauth.init(client,$);
hashauth.verifyAuthentication = function mockVerifyAuthentication(next) {
hashauth.authenticated = true;
next(true);
}
client.init(serverSettings, plugins);
hashauth.processSecret('this is my long pass phrase',false);
hashauth.hash().should.equal('b723e97aa97846eb92d5264f084b2823f57c4aa1');
var testnull = (localStorage.get('apisecrethash')===null);
testnull.should.be.true;
hashauth.isAuthenticated().should.equal(true);
});
it ('should report secret too short', function () {
var plugins = require('../lib/plugins/')().registerClientDefaults();
var client = require('../lib/client');
var hashauth = require('../lib/hashauth');
var localStorage = require('./fixtures/localstorage');
localStorage.remove('apisecrethash');
hashauth.init(client,$);
client.init(serverSettings, plugins);
window.alert = function mockConfirm (message) {
function containsLine (line) {
message.indexOf(line).should.be.greaterThan(-1);
}
containsLine('Too short API secret');
return true;
};
hashauth.processSecret('short passp',false);
});
});
+68
View File
@@ -0,0 +1,68 @@
'use strict';
var request = require('supertest');
describe('verifyauth', function ( ) {
var api = require('../lib/api/');
var scope = this;
function setup_app (env, fn) {
require('../lib/bootevent')(env).boot(function booted (ctx) {
var wares = require('../lib/middleware/')(env);
ctx.app = api(env, wares, ctx);
scope.app = ctx.app;
fn(ctx);
});
}
after(function (done) {
done();
});
it('should fail unauthorized', function (done) {
var known = 'b723e97aa97846eb92d5264f084b2823f57c4aa1';
delete process.env.API_SECRET;
process.env.API_SECRET = 'this is my long pass phrase';
var env = require('../env')( );
env.api_secret.should.equal(known);
setup_app(env, function (ctx) {
ctx.app.enabled('api').should.equal(true);
ctx.app.api_secret = '';
ping_authorized_endpoint(ctx.app, 401, done);
});
});
it('should work fine authorized', function (done) {
var known = 'b723e97aa97846eb92d5264f084b2823f57c4aa1';
delete process.env.API_SECRET;
process.env.API_SECRET = 'this is my long pass phrase';
var env = require('../env')( );
env.api_secret.should.equal(known);
setup_app(env, function (ctx) {
ctx.app.enabled('api').should.equal(true);
ctx.app.api_secret = env.api_secret;
ping_authorized_endpoint(ctx.app, 200, done);
});
});
function ping_authorized_endpoint (app, fails, fn) {
request(app)
.get('/verifyauth')
.set('api-secret', app.api_secret || '')
.expect(fails)
.end(function (err, res) {
//console.log(res.body);
if (fails < 400) {
res.body.status.should.equal(200);
}
fn( );
// console.log('err', err, 'res', res);
});
}
});