Files
Ben West 9f2f98b76a allow client to load successfully without requiring API SECRET
This patch allows the operator to control whether or not the API Secret
authentication dialog will display by default or not.  Historically Nightscout
allows a configuration to be readable and to send alarm information to all
displays.
A change in in dev in February to support authentication and api v3 introduced
a new configuration where websocket communication is authenticated, and the API
Secret modal will display if it authenticates in error.  The side-effect is
that all new instances, even readable instances with glucose data, can't be
seen or monitored without putting in the API Secreet or explicitly dismissing
it.
This patch returns to the default behavior while allowing to select the new one
using authenticationPromptOnLoad in the settings communicated from server to
client. If people strongly prefer, we can reverse to the new behavior over
time.
To select the old behavior with no API Secret needed for readabable data, do nothing.
To select the new behavior where the API Secret prompts occur on load, set
`AUTHENTICATION_PROMPT_ON_LOAD=1`.
2023-09-02 17:54:11 -07:00

419 lines
13 KiB
JavaScript

'use strict';
var _ = require('lodash');
var constants = require('./constants.json');
function init () {
var settings = {
units: 'mg/dl'
, timeFormat: 12
, dayStart: 7.0
, dayEnd: 21.0
, nightMode: false
, editMode: true
, showRawbg: 'never'
, customTitle: 'Nightscout'
, theme: 'default'
, alarmUrgentHigh: true
, alarmUrgentHighMins: [30, 60, 90, 120]
, alarmHigh: true
, alarmHighMins: [30, 60, 90, 120]
, alarmLow: true
, alarmLowMins: [15, 30, 45, 60]
, alarmUrgentLow: true
, alarmUrgentLowMins: [15, 30, 45]
, alarmUrgentMins: [30, 60, 90, 120]
, alarmWarnMins: [30, 60, 90, 120]
, alarmTimeagoWarn: true
, alarmTimeagoWarnMins: 15
, alarmTimeagoUrgent: true
, alarmTimeagoUrgentMins: 30
, alarmPumpBatteryLow: false
, language: 'en'
, scaleY: 'log'
, showPlugins: 'dbsize'
, showForecast: 'ar2'
, focusHours: 3
, heartbeat: 60
, baseURL: ''
, authDefaultRoles: 'readable'
, thresholds: {
bgHigh: 260
, bgTargetTop: 180
, bgTargetBottom: 80
, bgLow: 55
}
, insecureUseHttp: false
, secureHstsHeader: true
, secureHstsHeaderIncludeSubdomains: false
, secureHstsHeaderPreload: false
, secureCsp: false
, deNormalizeDates: false
, showClockDelta: false
, showClockLastTime: false
, frameUrl1: ''
, frameUrl2: ''
, frameUrl3: ''
, frameUrl4: ''
, frameUrl5: ''
, frameUrl6: ''
, frameUrl7: ''
, frameUrl8: ''
, frameName1: ''
, frameName2: ''
, frameName3: ''
, frameName4: ''
, frameName5: ''
, frameName6: ''
, frameName7: ''
, frameName8: ''
, authFailDelay: 5000
, adminNotifiesEnabled: true
, obscured: ''
, obscureDeviceProvenance: ''
, authenticationPromptOnLoad: false
};
var secureSettings = [
'apnsKey'
, 'apnsKeyId'
, 'developerTeamId'
, 'userName'
, 'password'
, 'obscured'
, 'obscureDeviceProvenance'
];
var valueMappers = {
nightMode: mapTruthy
, alarmUrgentHigh: mapTruthy
, alarmUrgentHighMins: mapNumberArray
, alarmHigh: mapTruthy
, alarmHighMins: mapNumberArray
, alarmLow: mapTruthy
, alarmLowMins: mapNumberArray
, alarmUrgentLow: mapTruthy
, alarmUrgentLowMins: mapNumberArray
, alarmUrgentMins: mapNumberArray
, alarmTimeagoWarn: mapTruthy
, alarmTimeagoUrgent: mapTruthy
, alarmWarnMins: mapNumberArray
, timeFormat: mapNumber
, insecureUseHttp: mapTruthy
, secureHstsHeader: mapTruthy
, secureCsp: mapTruthy
, deNormalizeDates: mapTruthy
, showClockDelta: mapTruthy
, showClockLastTime: mapTruthy
, bgHigh: mapNumber
, bgLow: mapNumber
, bgTargetTop: mapNumber
, bgTargetBottom: mapNumber
, authFailDelay: mapNumber
, adminNotifiesEnabled: mapTruthy
, authenticationPromptOnLoad: mapTruthy
};
function filterObj(obj, secureKeys) {
if (obj && typeof obj === 'object') {
var allKeys = Object.keys(obj);
for (var i = 0 ; i < allKeys.length ; i++) {
var k = allKeys[i];
if (secureKeys.includes(k)) {
delete obj[k];
} else {
var value = obj[k];
if ( typeof value === 'object') {
filterObj(value, secureKeys);
}
}
}
}
return obj;
}
function filteredSettings(settingsObject) {
let so = _.cloneDeep(settingsObject);
if (so.obscured) {
so.enable = _.difference(so.enable, so.obscured);
}
return filterObj(so, secureSettings);
}
function mapNumberArray (value) {
if (!value || _.isArray(value)) {
return value;
}
if (isNaN(value)) {
var rawValues = value && value.split(' ') || [];
return _.map(rawValues, function(num) {
return isNaN(num) ? null : Number(num);
});
} else {
return [Number(value)];
}
}
function mapNumber (value) {
if (!value) {
return value;
}
if (typeof value === 'string' && isNaN(value)) {
const decommaed = value.replace(',','.');
if (!isNaN(decommaed)) { value = decommaed; }
}
if (isNaN(value)) {
return value;
} else {
return Number(value);
}
}
function mapTruthy (value) {
if (typeof value === 'string' && (value.toLowerCase() === 'on' || value.toLowerCase() === 'true')) { value = true; }
if (typeof value === 'string' && (value.toLowerCase() === 'off' || value.toLowerCase() === 'false')) { value = false; }
return value;
}
//TODO: getting sent in status.json, shouldn't be
settings.DEFAULT_FEATURES = ['bgnow', 'delta', 'direction', 'timeago', 'devicestatus', 'upbat', 'errorcodes', 'profile', 'bolus', 'dbsize', 'runtimestate', 'basal', 'careportal'];
var wasSet = [];
function isSimple (value) {
return _.isArray(value) || (typeof value !== 'function' && typeof value !== 'object');
}
function nameFromKey (key, nameType) {
return nameType === 'env' ? _.snakeCase(key).toUpperCase() : key;
}
function eachSettingAs (nameType) {
function mapKeys (accessor, keys) {
_.forIn(keys, function each (value, key) {
if (isSimple(value)) {
var newValue = accessor(nameFromKey(key, nameType));
if (newValue !== undefined) {
var mapper = valueMappers[key];
wasSet.push(key);
keys[key] = mapper ? mapper(newValue) : newValue;
}
}
});
}
return function allKeys (accessor) {
mapKeys(accessor, settings);
mapKeys(accessor, settings.thresholds);
enableAndDisableFeatures(accessor, nameType);
};
}
function enableAndDisableFeatures (accessor, nameType) {
function getAndPrepare (key) {
var raw = accessor(nameFromKey(key, nameType)) || '';
var cleaned = decodeURIComponent(raw).toLowerCase();
cleaned = cleaned ? cleaned.split(' ') : [];
cleaned = _.filter(cleaned, function(e) { return e !== ""; } );
return cleaned;
}
function enableIf (feature, condition) {
if (condition) {
enable.push(feature);
}
}
function anyEnabled (features) {
return _.findIndex(features, function(feature) {
return enable.indexOf(feature) > -1;
}) > -1;
}
function prepareAlarmTypes () {
var alarmTypes = _.filter(getAndPrepare('alarmTypes'), function onlyKnownTypes (type) {
return type === 'predict' || type === 'simple';
});
if (alarmTypes.length === 0) {
var thresholdWasSet = _.findIndex(wasSet, function(name) {
return name.indexOf('bg') === 0;
}) > -1;
alarmTypes = thresholdWasSet ? ['simple'] : ['predict'];
}
return alarmTypes;
}
var enable = getAndPrepare('enable');
var disable = getAndPrepare('disable');
var obscured = getAndPrepare('obscured');
settings.alarmTypes = prepareAlarmTypes();
//don't require pushover to be enabled to preserve backwards compatibility if there are extendedSettings for it
enableIf('pushover', accessor(nameFromKey('pushoverApiToken', nameType)));
enableIf('treatmentnotify', anyEnabled(['careportal', 'pushover', 'maker']));
_.each(settings.DEFAULT_FEATURES, function eachDefault (feature) {
enableIf(feature, enable.indexOf(feature) < 0);
});
//TODO: maybe get rid of ALARM_TYPES and only use enable?
enableIf('simplealarms', settings.alarmTypes.indexOf('simple') > -1);
enableIf('ar2', settings.alarmTypes.indexOf('predict') > -1);
if (disable.length > 0) {
console.info('disabling', disable);
}
//all enabled feature, without any that have been disabled
settings.enable = _.difference(enable, disable);
settings.obscured = obscured;
var thresholds = settings.thresholds;
thresholds.bgHigh = Number(thresholds.bgHigh);
thresholds.bgTargetTop = Number(thresholds.bgTargetTop);
thresholds.bgTargetBottom = Number(thresholds.bgTargetBottom);
thresholds.bgLow = Number(thresholds.bgLow);
// Do not convert for old installs that have these set in mg/dl
if (settings.units.toLowerCase().includes('mmol') && thresholds.bgHigh < 50) {
thresholds.bgHigh = Math.round(thresholds.bgHigh * constants.MMOL_TO_MGDL);
thresholds.bgTargetTop = Math.round(thresholds.bgTargetTop * constants.MMOL_TO_MGDL);
thresholds.bgTargetBottom = Math.round(thresholds.bgTargetBottom * constants.MMOL_TO_MGDL);
thresholds.bgLow = Math.round(thresholds.bgLow * constants.MMOL_TO_MGDL);
}
verifyThresholds();
adjustShownPlugins();
}
function verifyThresholds () {
var thresholds = settings.thresholds;
if (thresholds.bgTargetBottom >= thresholds.bgTargetTop) {
console.warn('BG_TARGET_BOTTOM(' + thresholds.bgTargetBottom + ') was >= BG_TARGET_TOP(' + thresholds.bgTargetTop + ')');
thresholds.bgTargetBottom = thresholds.bgTargetTop - 1;
console.warn('BG_TARGET_BOTTOM is now ' + thresholds.bgTargetBottom);
}
if (thresholds.bgTargetTop <= thresholds.bgTargetBottom) {
console.warn('BG_TARGET_TOP(' + thresholds.bgTargetTop + ') was <= BG_TARGET_BOTTOM(' + thresholds.bgTargetBottom + ')');
thresholds.bgTargetTop = thresholds.bgTargetBottom + 1;
console.warn('BG_TARGET_TOP is now ' + thresholds.bgTargetTop);
}
if (thresholds.bgLow >= thresholds.bgTargetBottom) {
console.warn('BG_LOW(' + thresholds.bgLow + ') was >= BG_TARGET_BOTTOM(' + thresholds.bgTargetBottom + ')');
thresholds.bgLow = thresholds.bgTargetBottom - 1;
console.warn('BG_LOW is now ' + thresholds.bgLow);
}
if (thresholds.bgHigh <= thresholds.bgTargetTop) {
console.warn('BG_HIGH(' + thresholds.bgHigh + ') was <= BG_TARGET_TOP(' + thresholds.bgTargetTop + ')');
thresholds.bgHigh = thresholds.bgTargetTop + 1;
console.warn('BG_HIGH is now ' + thresholds.bgHigh);
}
}
function adjustShownPlugins () {
var showPluginsUnset = settings.showPlugins && 0 === settings.showPlugins.length;
settings.showPlugins += ' delta direction upbat';
if (settings.showRawbg === 'always' || settings.showRawbg === 'noise') {
settings.showPlugins += ' rawbg';
}
if (showPluginsUnset) {
//assume all enabled features are plugins and they should be shown for now
//it would be better to use the registered plugins, but it's not loaded yet...
_.forEach(settings.enable, function showFeature (feature) {
if (isEnabled(feature)) {
settings.showPlugins += ' ' + feature;
}
});
}
}
function isEnabled (feature) {
var enabled = false;
if (settings.enable && typeof feature === 'object' && feature.length !== undefined) {
enabled = _.find(feature, function eachFeature (f) {
return settings.enable.indexOf(f) > -1;
}) !== undefined;
} else {
enabled = settings.enable && settings.enable.indexOf(feature) > -1;
}
return enabled;
}
function isUrgentHighAlarmEnabled(notify) {
return notify.eventName === 'high' && notify.level === constants.LEVEL_URGENT && settings.alarmUrgentHigh;
}
function isHighAlarmEnabled(notify) {
return notify.eventName === 'high' && settings.alarmHigh;
}
function isUrgentLowAlarmEnabled(notify) {
return notify.eventName === 'low' && notify.level === constants.LEVEL_URGENT && settings.alarmUrgentLow;
}
function isLowAlarmEnabled(notify) {
return notify.eventName === 'low' && settings.alarmLow;
}
function isAlarmEventEnabled (notify) {
return ('high' !== notify.eventName && 'low' !== notify.eventName)
|| isUrgentHighAlarmEnabled(notify)
|| isHighAlarmEnabled(notify)
|| isUrgentLowAlarmEnabled(notify)
|| isLowAlarmEnabled(notify);
}
function snoozeMinsForAlarmEvent (notify) {
var snoozeTime;
if (isUrgentHighAlarmEnabled(notify)) {
snoozeTime = settings.alarmUrgentHighMins;
} else if (isHighAlarmEnabled(notify)) {
snoozeTime = settings.alarmHighMins;
} else if (isUrgentLowAlarmEnabled(notify)) {
snoozeTime = settings.alarmUrgentLowMins;
} else if (isLowAlarmEnabled(notify)) {
snoozeTime = settings.alarmLowMins;
} else if (notify.level === constants.LEVEL_URGENT) {
snoozeTime = settings.alarmUrgentMins;
} else {
snoozeTime = settings.alarmWarnMins;
}
return snoozeTime;
}
function snoozeFirstMinsForAlarmEvent (notify) {
return _.first(snoozeMinsForAlarmEvent(notify));
}
settings.eachSetting = eachSettingAs();
settings.eachSettingAsEnv = eachSettingAs('env');
settings.isEnabled = isEnabled;
settings.isAlarmEventEnabled = isAlarmEventEnabled;
settings.snoozeMinsForAlarmEvent = snoozeMinsForAlarmEvent;
settings.snoozeFirstMinsForAlarmEvent = snoozeFirstMinsForAlarmEvent;
settings.filteredSettings = filteredSettings;
return settings;
}
module.exports = init;