From c22b9e6be5d23fa640bb615f73b041ce01685b5e Mon Sep 17 00:00:00 2001 From: Ben West Date: Fri, 20 Oct 2023 08:22:15 -0700 Subject: [PATCH 01/10] create space for developing post release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b4742fc05f3..ef36cda4604 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "15.0.1", + "version": "15.0.2", "description": "Nightscout acts as a web-based CGM (Continuous Glucose Montinor) to allow multiple caregivers to remotely view a patients glucose data in realtime.", "license": "AGPL-3.0", "author": "Nightscout Team", From cfe89c867ce9c55d2c9cbb3c3083e1db51c06aa5 Mon Sep 17 00:00:00 2001 From: Ben West Date: Fri, 20 Oct 2023 09:05:41 -0700 Subject: [PATCH 02/10] bump for dev post release of 15.0.1 --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 6aa3a366cb4..933c4ed5a1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "15.0.1", + "version": "15.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { From b166afc3bcb6c82e1ac2dce8cf3ce1e37f6916cd Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 21 Oct 2023 14:58:59 -0700 Subject: [PATCH 03/10] socket.io: enable legacy v2 connections The allowEIO3, when set to true allows v2 clients to connect while the default is false. This patch should allow versions of AndroidAPS older than 3.2 to connect to NS15 and above. Thanks to Dave Carlson for researching and experimenting with this option! https://socket.io/docs/v4/migrating-from-2-x-to-3-0/#how-to-upgrade-an-existing-production-deployment https://socket.io/docs/v4/server-options/#alloweio3 --- lib/server/websocket.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/server/websocket.js b/lib/server/websocket.js index 2924db0554d..d69999ce856 100644 --- a/lib/server/websocket.js +++ b/lib/server/websocket.js @@ -72,6 +72,7 @@ function init (env, ctx, server) { function start () { io = require('socket.io')({ + allowEIO3: true, 'log level': 0 }).listen(server, { //these only effect the socket.io.js file that is sent to the client, but better than nothing From 40dc7b03c159f66a305bcafbe6b70c438fd9c745 Mon Sep 17 00:00:00 2001 From: Dave Carlson Date: Sat, 21 Oct 2023 13:11:19 -0700 Subject: [PATCH 04/10] restore compat with socket.io v2 client --- lib/server/websocket.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/server/websocket.js b/lib/server/websocket.js index d69999ce856..07e3b11edc0 100644 --- a/lib/server/websocket.js +++ b/lib/server/websocket.js @@ -76,7 +76,9 @@ function init (env, ctx, server) { 'log level': 0 }).listen(server, { //these only effect the socket.io.js file that is sent to the client, but better than nothing - 'browser client minification': true + // compat with v2 client + allowEIO3: true + , 'browser client minification': true , 'browser client etag': true , 'browser client gzip': false , 'perMessageDeflate': { From 19be01d2c0b9237ca5a37dedf1185fa72fcc386e Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 21 Oct 2023 17:41:41 -0700 Subject: [PATCH 05/10] socket.io prefer version from @thecubic This patch removes my own patch to enable allowEIO3 in favor of @thecubic's patch. They both do the same thing, but @thecubic's is tested. --- lib/server/websocket.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/server/websocket.js b/lib/server/websocket.js index 07e3b11edc0..95da7d906ce 100644 --- a/lib/server/websocket.js +++ b/lib/server/websocket.js @@ -72,7 +72,6 @@ function init (env, ctx, server) { function start () { io = require('socket.io')({ - allowEIO3: true, 'log level': 0 }).listen(server, { //these only effect the socket.io.js file that is sent to the client, but better than nothing From 0fbacfd3fcb0e26a13555269fc00f6c9966a144b Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 22 Oct 2023 09:10:35 -0700 Subject: [PATCH 06/10] alarmSocket: customizing authorization requirements This patch is intended to allow customizing the behavior for whether or not to prompt for authorization before subscribing or acknolweding alarms. There was a bug in previous attempts where the profileeditor would be double initialized, causing the profileeditor to remove some buttons from the GUI. This patch adds checking for a permission specifically related to acknolwedging alarms, as well as avoids double-initializing the editor, which causes the issue with the GUI. --- lib/api3/alarmSocket.js | 50 ++++++++++++++++++++++++------------ lib/client/index.js | 29 +++++++++++++++------ lib/profile/profileeditor.js | 2 ++ 3 files changed, 56 insertions(+), 25 deletions(-) diff --git a/lib/api3/alarmSocket.js b/lib/api3/alarmSocket.js index 5fe30a620c1..0a6968c9ac4 100644 --- a/lib/api3/alarmSocket.js +++ b/lib/api3/alarmSocket.js @@ -46,7 +46,7 @@ function AlarmSocket (app, env, ctx) { socket.on('subscribe', function onSubscribe (message, returnCallback) { self.subscribe(socket, message, returnCallback); }); - + }); ctx.bus.on('notification', self.emitNotification); @@ -65,7 +65,7 @@ function AlarmSocket (app, env, ctx) { self.subscribe = function subscribe (socket, message, returnCallback) { const shouldCallBack = typeof(returnCallback) === 'function'; - // Native client + // Native client if (message && message.accessToken) { return ctx.authorization.resolveAccessToken(message.accessToken, function resolveFinishForToken (err, auth) { if (err) { @@ -76,13 +76,13 @@ function AlarmSocket (app, env, ctx) { } return err; } else { - // Subscribe for acking alarms - socket.on('ack', function onAck (level, group, silenceTime) { - ctx.notifications.ack(level, group, silenceTime, true); - console.info(LOG + 'ack received ' + level + ' ' + group + ' ' + silenceTime); - }); + // Subscribe for acking alarms + socket.on('ack', function onAck (level, group, silenceTime) { + ctx.notifications.ack(level, group, silenceTime, true); + console.info(LOG + 'ack received ' + level + ' ' + group + ' ' + silenceTime); + }); - var okResponse = { success: true, message: 'Subscribed for alarms' } + var okResponse = { success: true, message: 'Subscribed for alarms' } if (shouldCallBack) { returnCallback(okResponse); } @@ -90,10 +90,24 @@ function AlarmSocket (app, env, ctx) { } }); } - - // Web client (jwt access token or api_hash) - if (message && (message.jwtToken || message.secret)) { + + if (!message) { message = {}; } + console.log("AUTH TEST", message, env.settings.authenticationPromptOnLoad); + // Web client (jwt access token or api_hash) + var shouldTry = true; + if (env.settings.authenticationPromptOnLoad) { + if (!message.jwtToken || !message.secret) { + shouldTry = false; + } + } + if (message && shouldTry) { return ctx.authorization.resolve({ api_secret: message.secret, token: message.jwtToken, ip: getRemoteIP(socket.request) }, function resolveFinish (err, auth) { + console.log("AUTH FOR ALARMS", err, auth); + var perms = { + read: ctx.authorization.checkMultiple('api:*:read', auth.shiros) + , ack: ctx.authorization.checkMultiple('api:*:write', auth.shiros) + }; + console.log("AUTH FOR ALARMS", err, auth, perms); if (err) { console.log(`${LOG_ERROR} Authorization failed for jwtToken:`, message.jwtToken); @@ -102,13 +116,15 @@ function AlarmSocket (app, env, ctx) { } return err; } else { - // Subscribe for acking alarms - socket.on('ack', function onAck (level, group, silenceTime) { - ctx.notifications.ack(level, group, silenceTime, true); - console.info(LOG + 'ack received ' + level + ' ' + group + ' ' + silenceTime); - }); + // Subscribe for acking alarms + if (perms.ack) { + socket.on('ack', function onAck (level, group, silenceTime) { + ctx.notifications.ack(level, group, silenceTime, true); + console.info(LOG + 'ack received ' + level + ' ' + group + ' ' + silenceTime); + }); + } - var okResponse = { success: true, message: 'Subscribed for alarms' } + var okResponse = { success: true, message: 'Subscribed for alarms', ...perms }; if (shouldCallBack) { returnCallback(okResponse); } diff --git a/lib/client/index.js b/lib/client/index.js index 0f0d17b7d64..c570f3f4af8 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -1146,15 +1146,28 @@ client.load = function load (serverSettings, callback) { } console.log('Subscribed for alarms', data); - if (client.settings.authenticationPromptOnLoad && !data.success) { - client.hashauth.requestAuthentication(function afterRequest () { - client.hashauth.updateSocketAuth(); - if (callback) { - callback(); - } - }); + var shouldAuthenticationPromptOnLoad = client.settings.authenticationPromptOnLoad ; + console.log("shouldAuthenticationPromptOnLoad", shouldAuthenticationPromptOnLoad, !shouldAuthenticationPromptOnLoad && !data.success, data, data.read, hasRequiredPermission( )); + if (!data.success) { + console.log("SHOULD REQUEST AUTHENTICATION", callback); + if (!data.read || !hasRequiredPermission() || shouldAuthenticationPromptOnLoad) { + return client.hashauth.requestAuthentication(function afterRequest () { + console.log("SHOULD REQUEST AUTHENTICATION"); + return client.hashauth.updateSocketAuth(); + if (callback) { + callback(); + } + }); + } + if (callback) { + console.log("ISSUING CALLBACK NEW BRANCH"); + callback(); + } } else if (callback) { - callback(); + console.log("HAS OTHER BRANCH", callback); + if (shouldAuthenticationPromptOnLoad) { + callback(); + } } } ); diff --git a/lib/profile/profileeditor.js b/lib/profile/profileeditor.js index 9a9062603c1..ca049573e51 100644 --- a/lib/profile/profileeditor.js +++ b/lib/profile/profileeditor.js @@ -18,6 +18,7 @@ var init = function init () { client.init(function loaded () { + console.log("LOADING CLIENT INIT"); if (c_profile !== null) { return; // already loaded so don't load again } @@ -151,6 +152,7 @@ var init = function init () { } function initeditor() { + console.log("INITEDITOR", client.settings.extendedSettings.profile, client.settings.extendedSettings.profile?.history, client.settings.extendedSettings.profile && client.settings.extendedSettings.profile.history); $('#pe_history').toggle(client.settings.extendedSettings.profile && client.settings.extendedSettings.profile.history); $('#pe_multiple').toggle(client.settings.extendedSettings.profile && client.settings.extendedSettings.profile.multiple); From d8fe025ad2a430eee21187ab70dd17970d4cd0b8 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 22 Oct 2023 10:59:54 -0700 Subject: [PATCH 07/10] alarmSocket: document and prep concept This patch eliminates debugging logging in favor of commentary to capture how, where, and why alarmSocket feature is causing different pages to demand the authentication prompt in a variety of circumstances. --- lib/api3/alarmSocket.js | 20 ++++++++++++++++---- lib/client/index.js | 6 +----- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/api3/alarmSocket.js b/lib/api3/alarmSocket.js index 0a6968c9ac4..6bba6208297 100644 --- a/lib/api3/alarmSocket.js +++ b/lib/api3/alarmSocket.js @@ -92,22 +92,34 @@ function AlarmSocket (app, env, ctx) { } if (!message) { message = {}; } - console.log("AUTH TEST", message, env.settings.authenticationPromptOnLoad); // Web client (jwt access token or api_hash) + /* + * On the web: a client may have saved a secret or using a jwtToken, or may have none. + * Some pages will automatically prompt for authorization, when needed. + * To make the main homepage require authorization as well, set + * AUTHENTICATION_PROMPT_ON_LOAD=true. + * + * If there is missing authorization when authorization is required, + * rejecting the attempt in order to trigger a prompt on the client. + * If there is no authorization required, or there are available + * credentials, attempt to resolve the available permissions. + * When processing ACK messages that dismiss alarms, Authorization should be + * required. + */ var shouldTry = true; if (env.settings.authenticationPromptOnLoad) { - if (!message.jwtToken || !message.secret) { + if (!message.jwtToken && !message.secret) { shouldTry = false; } } + if (message && shouldTry) { return ctx.authorization.resolve({ api_secret: message.secret, token: message.jwtToken, ip: getRemoteIP(socket.request) }, function resolveFinish (err, auth) { - console.log("AUTH FOR ALARMS", err, auth); + var perms = { read: ctx.authorization.checkMultiple('api:*:read', auth.shiros) , ack: ctx.authorization.checkMultiple('api:*:write', auth.shiros) }; - console.log("AUTH FOR ALARMS", err, auth, perms); if (err) { console.log(`${LOG_ERROR} Authorization failed for jwtToken:`, message.jwtToken); diff --git a/lib/client/index.js b/lib/client/index.js index c570f3f4af8..cbfd51bc20e 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -1147,12 +1147,9 @@ client.load = function load (serverSettings, callback) { console.log('Subscribed for alarms', data); var shouldAuthenticationPromptOnLoad = client.settings.authenticationPromptOnLoad ; - console.log("shouldAuthenticationPromptOnLoad", shouldAuthenticationPromptOnLoad, !shouldAuthenticationPromptOnLoad && !data.success, data, data.read, hasRequiredPermission( )); if (!data.success) { - console.log("SHOULD REQUEST AUTHENTICATION", callback); if (!data.read || !hasRequiredPermission() || shouldAuthenticationPromptOnLoad) { return client.hashauth.requestAuthentication(function afterRequest () { - console.log("SHOULD REQUEST AUTHENTICATION"); return client.hashauth.updateSocketAuth(); if (callback) { callback(); @@ -1160,11 +1157,10 @@ client.load = function load (serverSettings, callback) { }); } if (callback) { - console.log("ISSUING CALLBACK NEW BRANCH"); callback(); } } else if (callback) { - console.log("HAS OTHER BRANCH", callback); + // Callback is client.init, causing the prompt to appear. if (shouldAuthenticationPromptOnLoad) { callback(); } From 1fa6af412a9269f533b05f96c155df5c8e8e5d61 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 22 Oct 2023 12:14:28 -0700 Subject: [PATCH 08/10] alarmSocket: don't double initialize, stub out non-global-ack Create an opportunity to respond with something other than a global ack when someone that is not authorized sends an acknolwedgement to an alarm. Eliminate double-initializing when subscribing to alarms. The `callback` variable is not local to the function, anyway. --- lib/api3/alarmSocket.js | 20 ++++++++++++-------- lib/client/index.js | 11 ----------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/lib/api3/alarmSocket.js b/lib/api3/alarmSocket.js index 6bba6208297..e242aa9480d 100644 --- a/lib/api3/alarmSocket.js +++ b/lib/api3/alarmSocket.js @@ -116,10 +116,6 @@ function AlarmSocket (app, env, ctx) { if (message && shouldTry) { return ctx.authorization.resolve({ api_secret: message.secret, token: message.jwtToken, ip: getRemoteIP(socket.request) }, function resolveFinish (err, auth) { - var perms = { - read: ctx.authorization.checkMultiple('api:*:read', auth.shiros) - , ack: ctx.authorization.checkMultiple('api:*:write', auth.shiros) - }; if (err) { console.log(`${LOG_ERROR} Authorization failed for jwtToken:`, message.jwtToken); @@ -128,13 +124,21 @@ function AlarmSocket (app, env, ctx) { } return err; } else { + var perms = { + read: ctx.authorization.checkMultiple('api:*:read', auth.shiros) + , ack: ctx.authorization.checkMultiple('notifications:*:ack', auth.shiros) + + }; // Subscribe for acking alarms - if (perms.ack) { - socket.on('ack', function onAck (level, group, silenceTime) { + socket.on('ack', function onAck (level, group, silenceTime) { + if (perms.ack) { ctx.notifications.ack(level, group, silenceTime, true); console.info(LOG + 'ack received ' + level + ' ' + group + ' ' + silenceTime); - }); - } + } else { + // TODO: send a message to client to silence locally, but not + // globally, and request authorization. + } + }); var okResponse = { success: true, message: 'Subscribed for alarms', ...perms }; if (shouldCallBack) { diff --git a/lib/client/index.js b/lib/client/index.js index cbfd51bc20e..9a0bdcc68e9 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -1151,19 +1151,8 @@ client.load = function load (serverSettings, callback) { if (!data.read || !hasRequiredPermission() || shouldAuthenticationPromptOnLoad) { return client.hashauth.requestAuthentication(function afterRequest () { return client.hashauth.updateSocketAuth(); - if (callback) { - callback(); - } }); } - if (callback) { - callback(); - } - } else if (callback) { - // Callback is client.init, causing the prompt to appear. - if (shouldAuthenticationPromptOnLoad) { - callback(); - } } } ); From c0892863a2f631efd76315dae8c5fb8b7036c256 Mon Sep 17 00:00:00 2001 From: Ben West Date: Mon, 23 Oct 2023 08:46:04 -0700 Subject: [PATCH 09/10] remove spurious logging in profileeditor --- lib/profile/profileeditor.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/profile/profileeditor.js b/lib/profile/profileeditor.js index ca049573e51..71355dd194a 100644 --- a/lib/profile/profileeditor.js +++ b/lib/profile/profileeditor.js @@ -152,7 +152,6 @@ var init = function init () { } function initeditor() { - console.log("INITEDITOR", client.settings.extendedSettings.profile, client.settings.extendedSettings.profile?.history, client.settings.extendedSettings.profile && client.settings.extendedSettings.profile.history); $('#pe_history').toggle(client.settings.extendedSettings.profile && client.settings.extendedSettings.profile.history); $('#pe_multiple').toggle(client.settings.extendedSettings.profile && client.settings.extendedSettings.profile.multiple); From a7ebb301b6d52a58e18914aa7c845dfc3d186fff Mon Sep 17 00:00:00 2001 From: Ben West Date: Mon, 23 Oct 2023 09:50:00 -0700 Subject: [PATCH 10/10] add notes regarding handling unauthorized ACK request When someone is looking at Nightscout and needs the alarm silenced, it is very desirable to always silence the local UI. This patch documents some of the working code around handling the alarm notification process, as well as provides commentary on handling unauthorized scenarios. There are some open questions such as how to update the permission set after authorization. --- lib/api3/alarmSocket.js | 16 +++++++++++++++- lib/client/index.js | 22 ++++++++++++++++++++++ lib/notifications.js | 6 ++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/lib/api3/alarmSocket.js b/lib/api3/alarmSocket.js index e242aa9480d..88bb699a553 100644 --- a/lib/api3/alarmSocket.js +++ b/lib/api3/alarmSocket.js @@ -49,6 +49,8 @@ function AlarmSocket (app, env, ctx) { }); + // Turns all notifications on the event bus back into events to be + // broadcast to clients. ctx.bus.on('notification', self.emitNotification); }; @@ -77,6 +79,7 @@ function AlarmSocket (app, env, ctx) { return err; } else { // Subscribe for acking alarms + // Client sends ack, which sends a notificaiton through our internal bus socket.on('ack', function onAck (level, group, silenceTime) { ctx.notifications.ack(level, group, silenceTime, true); console.info(LOG + 'ack received ' + level + ' ' + group + ' ' + silenceTime); @@ -127,18 +130,29 @@ function AlarmSocket (app, env, ctx) { var perms = { read: ctx.authorization.checkMultiple('api:*:read', auth.shiros) , ack: ctx.authorization.checkMultiple('notifications:*:ack', auth.shiros) - }; // Subscribe for acking alarms + // TODO: does this produce double ACK after the authorizing? Only if reconnecting? + // TODO: how will perms get updated after authorizing? socket.on('ack', function onAck (level, group, silenceTime) { if (perms.ack) { + // This goes through the server-wide event bus. ctx.notifications.ack(level, group, silenceTime, true); console.info(LOG + 'ack received ' + level + ' ' + group + ' ' + silenceTime); } else { // TODO: send a message to client to silence locally, but not // globally, and request authorization. + // This won't go through th event bus. + // var acked = { silenceTime, group, level }; + // socket.emit('authorization_needed', acked); } }); + /* TODO: need to know when to update the permissions. + // Can we use + socket.on('resubscribe', function update_permissions ( ) { + // perms = { ... }; + }); + */ var okResponse = { success: true, message: 'Subscribed for alarms', ...perms }; if (shouldCallBack) { diff --git a/lib/client/index.js b/lib/client/index.js index 9a0bdcc68e9..c4c83b32a01 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -1233,6 +1233,28 @@ client.load = function load (serverSettings, callback) { stopAlarm(false, null, notify); } }); + /* + * + // TODO: When an unauthorized client attempts to silence an alarm, we should + // allow silencing locally, request for authorization, and if the + // authorization succeeds even republish the ACK notification. something like... + alarmSocket.on('authorization_needed', function(details) { + if (alarmInProgress) { + console.log('clearing alarm'); + stopAlarm(true, details.silenceTime, currentNotify); + } + client.hashauth.requestAuthentication(function afterRequest () { + console.log("SUCCESSFULLY AUTHORIZED, REPUBLISHED ACK?"); + // easiest way to update permission set on server side is to send another message. + alarmSocket.emit('resubscribe', currentNotify, details); + + if (isClient && currentNotify) { + alarmSocket.emit('ack', currentNotify.level, currentNotify.group, details.silenceTime); + } + }); + }); + + */ $('#testAlarms').click(function(event) { diff --git a/lib/notifications.js b/lib/notifications.js index 9b6adab3ac4..4bd5481ffb3 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -185,6 +185,10 @@ function init (env, ctx) { notifications.ack(1, group, time); } + /* + * TODO: modify with a local clear, this will clear all connected clients, + * globally + */ if (sendClear) { var notify = { clear: true @@ -192,6 +196,8 @@ function init (env, ctx) { , message: group + ' - ' + ctx.levels.toDisplay(level) + ' was ack\'d' , group: group }; + // When web client sends ack, this translates the websocket message into + // an event on our internal bus. ctx.bus.emit('notification', notify); logEmitEvent(notify); }