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:
parent
ea4d65ceee
commit
8c561e92c8
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,21 +75,30 @@ 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 {
|
||||||
|
// Otherwise, 'message' is an array of actual comms messages
|
||||||
|
for (var m = 0; m < message.length; m++) {
|
||||||
|
var msg = message[m];
|
||||||
|
if (msg.topic) {
|
||||||
for (var t in subscriptions) {
|
for (var t in subscriptions) {
|
||||||
if (subscriptions.hasOwnProperty(t)) {
|
if (subscriptions.hasOwnProperty(t)) {
|
||||||
var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
|
var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
|
||||||
@ -105,6 +114,7 @@ RED.comms = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
ws.onclose = function() {
|
ws.onclose = function() {
|
||||||
if (!active) {
|
if (!active) {
|
||||||
|
Loading…
Reference in New Issue
Block a user