1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Actively expire login sesssions and notify user

This commit is contained in:
Nick O'Leary 2018-12-11 11:32:12 +00:00
parent ea4d65ceee
commit 8c561e92c8
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
3 changed files with 67 additions and 25 deletions

View File

@ -25,27 +25,39 @@ function generateToken(length) {
var storage; var storage;
var sessionExpiryTime var sessionExpiryTime
var sessions = {}; var sessions = {};
var loadedSessions = null; var loadedSessions = null;
var apiAccessTokens; var apiAccessTokens;
var sessionExpiryListeners = [];
var expiryTimeout;
function expireSessions() { function expireSessions() {
if (expiryTimeout) {
clearTimeout(expiryTimeout);
expiryTimeout = null;
}
var nextExpiry = Number.MAX_SAFE_INTEGER;
var now = Date.now(); var now = Date.now();
var modified = false; var modified = false;
for (var t in sessions) { for (var t in sessions) {
if (sessions.hasOwnProperty(t)) { if (sessions.hasOwnProperty(t)) {
var session = sessions[t]; var session = sessions[t];
if (!session.hasOwnProperty("expires") || session.expires < now) { if (!session.hasOwnProperty("expires") || session.expires < now) {
sessionExpiryListeners.forEach(listener => { listener(session) })
delete sessions[t]; delete sessions[t];
modified = true; modified = true;
} else {
if (session.expires < nextExpiry) {
nextExpiry = session.expires;
}
} }
} }
} }
if (nextExpiry < Number.MAX_SAFE_INTEGER) {
// Allow 5 seconds grace
expiryTimeout = setTimeout(expireSessions,(nextExpiry - Date.now()) + 5000)
}
if (modified) { if (modified) {
return storage.saveSessions(sessions); return storage.saveSessions(sessions);
} else { } else {
@ -65,6 +77,9 @@ function loadSessions() {
module.exports = { module.exports = {
init: function(adminAuthSettings, _storage) { init: function(adminAuthSettings, _storage) {
storage = _storage; storage = _storage;
sessionExpiryListeners = [];
sessionExpiryTime = adminAuthSettings.sessionExpiryTime || 604800; // 1 week in seconds sessionExpiryTime = adminAuthSettings.sessionExpiryTime || 604800; // 1 week in seconds
// At this point, storage will not have been initialised, so defer loading // At this point, storage will not have been initialised, so defer loading
// the sessions until there's a request for them. // the sessions until there's a request for them.
@ -112,6 +127,11 @@ module.exports = {
expires: accessTokenExpiresAt expires: accessTokenExpiresAt
}; };
sessions[accessToken] = session; sessions[accessToken] = session;
if (!expiryTimeout) {
expiryTimeout = setTimeout(expireSessions,(accessTokenExpiresAt - Date.now()) + 5000)
}
return storage.saveSessions(sessions).then(function() { return storage.saveSessions(sessions).then(function() {
return { return {
accessToken: accessToken, accessToken: accessToken,
@ -125,5 +145,8 @@ module.exports = {
delete sessions[token]; delete sessions[token];
return storage.saveSessions(sessions); return storage.saveSessions(sessions);
}); });
},
onSessionExpiry: function(callback) {
sessionExpiryListeners.push(callback);
} }
} }

View File

@ -40,11 +40,19 @@ function init(_server,_settings,_runtimeAPI) {
settings = _settings; settings = _settings;
runtimeAPI = _runtimeAPI; runtimeAPI = _runtimeAPI;
Tokens = require("../auth/tokens"); Tokens = require("../auth/tokens");
Tokens.onSessionExpiry(handleSessionExpiry);
Users = require("../auth/users"); Users = require("../auth/users");
Permissions = require("../auth/permissions"); Permissions = require("../auth/permissions");
} }
function handleSessionExpiry(session) {
activeConnections.forEach(connection => {
if (connection.token === session.accessToken) {
connection.ws.send(JSON.stringify({auth:"fail"}));
connection.ws.close();
}
})
}
function generateSession(length) { function generateSession(length) {
var c = "ABCDEFGHIJKLMNOPQRSTUZWXYZabcdefghijklmnopqrstuvwxyz1234567890"; var c = "ABCDEFGHIJKLMNOPQRSTUZWXYZabcdefghijklmnopqrstuvwxyz1234567890";
var token = []; var token = [];
@ -88,7 +96,7 @@ function CommsConnection(ws) {
// handleRemoteSubscription(ws,msg.subscribe); // handleRemoteSubscription(ws,msg.subscribe);
} }
} else { } else {
var completeConnection = function(userScope,sendAck) { var completeConnection = function(userScope,session,sendAck) {
try { try {
if (!userScope || !Permissions.hasPermission(userScope,"status.read")) { if (!userScope || !Permissions.hasPermission(userScope,"status.read")) {
ws.send(JSON.stringify({auth:"fail"})); ws.send(JSON.stringify({auth:"fail"}));
@ -96,6 +104,7 @@ function CommsConnection(ws) {
} else { } else {
pendingAuth = false; pendingAuth = false;
addActiveConnection(self); addActiveConnection(self);
self.token = msg.auth;
if (sendAck) { if (sendAck) {
ws.send(JSON.stringify({auth:"ok"})); ws.send(JSON.stringify({auth:"ok"}));
} }
@ -113,29 +122,29 @@ function CommsConnection(ws) {
if (user) { if (user) {
self.user = user; self.user = user;
log.audit({event: "comms.auth",user:self.user}); log.audit({event: "comms.auth",user:self.user});
completeConnection(client.scope,true); completeConnection(client.scope,msg.auth,true);
} else { } else {
log.audit({event: "comms.auth.fail"}); log.audit({event: "comms.auth.fail"});
completeConnection(null,false); completeConnection(null,null,false);
} }
}); });
} else { } else {
log.audit({event: "comms.auth.fail"}); log.audit({event: "comms.auth.fail"});
completeConnection(null,false); completeConnection(null,null,false);
} }
}); });
} else { } else {
if (anonymousUser) { if (anonymousUser) {
log.audit({event: "comms.auth",user:anonymousUser}); log.audit({event: "comms.auth",user:anonymousUser});
self.user = anonymousUser; self.user = anonymousUser;
completeConnection(anonymousUser.permissions,false); completeConnection(anonymousUser.permissions,null,false);
//TODO: duplicated code - pull non-auth message handling out //TODO: duplicated code - pull non-auth message handling out
if (msg.subscribe) { if (msg.subscribe) {
self.subscribe(msg.subscribe); self.subscribe(msg.subscribe);
} }
} else { } else {
log.audit({event: "comms.auth.fail"}); log.audit({event: "comms.auth.fail"});
completeConnection(null,false); completeConnection(null,null,false);
} }
} }
} }

View File

@ -75,29 +75,39 @@ RED.comms = (function() {
} }
ws.onmessage = function(event) { ws.onmessage = function(event) {
var message = JSON.parse(event.data); var message = JSON.parse(event.data);
for (var m = 0; m < message.length; m++) { if (message.auth) {
var msg = message[m]; if (pendingAuth) {
if (pendingAuth && msg.auth) { if (message.auth === "ok") {
if (msg.auth === "ok") {
pendingAuth = false; pendingAuth = false;
completeConnection(); completeConnection();
} else if (msg.auth === "fail") { } else if (message.auth === "fail") {
// anything else is an error... // anything else is an error...
active = false; active = false;
RED.user.login({updateMenu:true},function() { RED.user.login({updateMenu:true},function() {
connectWS(); connectWS();
}) })
} }
} else if (message.auth === "fail") {
// Our current session has expired
active = false;
RED.user.login({updateMenu:true},function() {
connectWS();
})
} }
else if (msg.topic) { } else {
for (var t in subscriptions) { // Otherwise, 'message' is an array of actual comms messages
if (subscriptions.hasOwnProperty(t)) { for (var m = 0; m < message.length; m++) {
var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$"); var msg = message[m];
if (re.test(msg.topic)) { if (msg.topic) {
var subscribers = subscriptions[t]; for (var t in subscriptions) {
if (subscribers) { if (subscriptions.hasOwnProperty(t)) {
for (var i=0;i<subscribers.length;i++) { var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
subscribers[i](msg.topic,msg.data); if (re.test(msg.topic)) {
var subscribers = subscriptions[t];
if (subscribers) {
for (var i=0;i<subscribers.length;i++) {
subscribers[i](msg.topic,msg.data);
}
} }
} }
} }