diff --git a/lib/api3/alarmSocket.js b/lib/api3/alarmSocket.js index 5fe30a620c1..88bb699a553 100644 --- a/lib/api3/alarmSocket.js +++ b/lib/api3/alarmSocket.js @@ -46,9 +46,11 @@ function AlarmSocket (app, env, ctx) { socket.on('subscribe', function onSubscribe (message, returnCallback) { self.subscribe(socket, message, returnCallback); }); - + }); + // Turns all notifications on the event bus back into events to be + // broadcast to clients. ctx.bus.on('notification', self.emitNotification); }; @@ -65,7 +67,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 +78,14 @@ 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); - }); - - var okResponse = { success: true, message: 'Subscribed for alarms' } + // 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); + }); + + var okResponse = { success: true, message: 'Subscribed for alarms' } if (shouldCallBack) { returnCallback(okResponse); } @@ -90,10 +93,32 @@ function AlarmSocket (app, env, ctx) { } }); } - - // Web client (jwt access token or api_hash) - if (message && (message.jwtToken || message.secret)) { + + if (!message) { message = {}; } + // 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) { + shouldTry = false; + } + } + + if (message && shouldTry) { return ctx.authorization.resolve({ api_secret: message.secret, token: message.jwtToken, ip: getRemoteIP(socket.request) }, function resolveFinish (err, auth) { + if (err) { console.log(`${LOG_ERROR} Authorization failed for jwtToken:`, message.jwtToken); @@ -102,13 +127,34 @@ 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); - }); - - var okResponse = { success: true, message: 'Subscribed for alarms' } + 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) { returnCallback(okResponse); } diff --git a/lib/client/index.js b/lib/client/index.js index 0f0d17b7d64..c4c83b32a01 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -1146,15 +1146,13 @@ 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(); - } - }); - } else if (callback) { - callback(); + var shouldAuthenticationPromptOnLoad = client.settings.authenticationPromptOnLoad ; + if (!data.success) { + if (!data.read || !hasRequiredPermission() || shouldAuthenticationPromptOnLoad) { + return client.hashauth.requestAuthentication(function afterRequest () { + return client.hashauth.updateSocketAuth(); + }); + } } } ); @@ -1235,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); } diff --git a/lib/profile/profileeditor.js b/lib/profile/profileeditor.js index 9a9062603c1..71355dd194a 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 }