diff --git a/client/index.html b/client/index.html
index 4a32be51..d8d39618 100644
--- a/client/index.html
+++ b/client/index.html
@@ -99,7 +99,7 @@
diff --git a/client/js/lounge.js b/client/js/lounge.js
index 8b4845e2..90666f0d 100644
--- a/client/js/lounge.js
+++ b/client/js/lounge.js
@@ -562,8 +562,12 @@ $(function() {
});
sidebar.on("click", "#sign-out", function() {
+ socket.emit("sign-out", storage.get("token"));
storage.remove("token");
- location.reload();
+
+ if (!socket.connected) {
+ location.reload();
+ }
});
sidebar.on("click", ".close", function() {
diff --git a/client/js/socket-events/auth.js b/client/js/socket-events/auth.js
index 41e8cfd5..ad3539da 100644
--- a/client/js/socket-events/auth.js
+++ b/client/js/socket-events/auth.js
@@ -7,6 +7,7 @@ const storage = require("../localStorage");
socket.on("auth", function(data) {
const login = $("#sign-in");
let token;
+ const user = storage.get("user");
login.find(".btn").prop("disabled", false);
@@ -17,22 +18,23 @@ socket.on("auth", function(data) {
error.show().closest("form").one("submit", function() {
error.hide();
});
- } else {
+ } else if (user) {
token = storage.get("token");
if (token) {
$("#loading-page-message").text("Authorizing…");
- socket.emit("auth", {token: token});
+ socket.emit("auth", {user: user, token: token});
}
}
- const input = login.find("input[name='user']");
- if (input.val() === "") {
- input.val(storage.get("user") || "");
+ if (user) {
+ login.find("input[name='user']").val(user);
}
+
if (token) {
return;
}
- $("#sidebar, #footer").find(".sign-in")
+
+ $("#footer").find(".sign-in")
.trigger("click", {
pushState: false,
})
diff --git a/client/js/socket-events/change_password.js b/client/js/socket-events/change_password.js
index 3d248ea1..732aecd4 100644
--- a/client/js/socket-events/change_password.js
+++ b/client/js/socket-events/change_password.js
@@ -2,7 +2,6 @@
const $ = require("jquery");
const socket = require("../socket");
-const storage = require("../localStorage");
socket.on("change-password", function(data) {
const passwordForm = $("#change-password");
@@ -22,10 +21,6 @@ socket.on("change-password", function(data) {
});
}
- if (data.token && storage.get("token") !== null) {
- storage.set("token", data.token);
- }
-
passwordForm
.find("input")
.val("")
diff --git a/client/js/socket-events/index.js b/client/js/socket-events/index.js
index b6545192..23a5e961 100644
--- a/client/js/socket-events/index.js
+++ b/client/js/socket-events/index.js
@@ -16,3 +16,4 @@ require("./quit");
require("./sync_sort");
require("./topic");
require("./users");
+require("./sign_out");
diff --git a/client/js/socket-events/init.js b/client/js/socket-events/init.js
index 31c1eb2c..cf8b464b 100644
--- a/client/js/socket-events/init.js
+++ b/client/js/socket-events/init.js
@@ -17,10 +17,8 @@ socket.on("init", function(data) {
render.renderNetworks(data);
}
- if (data.token && $("#sign-in-remember").is(":checked")) {
+ if (data.token) {
storage.set("token", data.token);
- } else {
- storage.remove("token");
}
$("body").removeClass("signed-out");
diff --git a/client/js/socket-events/sign_out.js b/client/js/socket-events/sign_out.js
new file mode 100644
index 00000000..df16dbf5
--- /dev/null
+++ b/client/js/socket-events/sign_out.js
@@ -0,0 +1,9 @@
+"use strict";
+
+const socket = require("../socket");
+const storage = require("../localStorage");
+
+socket.on("sign-out", function() {
+ storage.remove("token");
+ location.reload();
+});
diff --git a/package.json b/package.json
index 5cac4679..559e1d12 100644
--- a/package.json
+++ b/package.json
@@ -57,6 +57,7 @@
"semver": "5.4.1",
"socket.io": "1.7.4",
"spdy": "3.4.7",
+ "ua-parser-js": "0.7.12",
"urijs": "1.18.10"
},
"devDependencies": {
diff --git a/src/client.js b/src/client.js
index 56b92dec..de8fe7ff 100644
--- a/src/client.js
+++ b/src/client.js
@@ -10,6 +10,7 @@ var Msg = require("./models/msg");
var Network = require("./models/network");
var ircFramework = require("irc-framework");
var Helper = require("./helper");
+const UAParser = require("ua-parser-js");
module.exports = Client;
@@ -81,12 +82,6 @@ function Client(manager, name, config) {
var client = this;
- if (client.name && !client.config.token) {
- client.updateToken(function(token) {
- client.manager.updateUser(client.name, {token: token});
- });
- }
-
var delay = 0;
(client.config.networks || []).forEach((n) => {
setTimeout(function() {
@@ -95,6 +90,10 @@ function Client(manager, name, config) {
delay += 1000;
});
+ if (typeof client.config.sessions !== "object") {
+ client.config.sessions = {};
+ }
+
if (client.name) {
log.info(`User ${colors.bold(client.name)} loaded`);
}
@@ -286,34 +285,51 @@ Client.prototype.connect = function(args) {
client.save();
};
-Client.prototype.updateToken = function(callback) {
- var client = this;
-
- crypto.randomBytes(48, function(err, buf) {
+Client.prototype.generateToken = function(callback) {
+ crypto.randomBytes(48, (err, buf) => {
if (err) {
throw err;
}
- callback(client.config.token = buf.toString("hex"));
+ callback(buf.toString("hex"));
});
};
+Client.prototype.updateSession = function(token, ip, request) {
+ const client = this;
+ const agent = UAParser(request.headers["user-agent"] || "");
+ let friendlyAgent = "";
+
+ if (agent.browser.name.length) {
+ friendlyAgent = `${agent.browser.name} ${agent.browser.major}`;
+ } else {
+ friendlyAgent = "Unknown browser";
+ }
+
+ if (agent.os.name.length) {
+ friendlyAgent = ` on ${agent.os.name} ${agent.os.version}`;
+ }
+
+ client.config.sessions[token] = {
+ lastUse: Date.now(),
+ ip: ip,
+ agent: friendlyAgent,
+ };
+};
+
Client.prototype.setPassword = function(hash, callback) {
var client = this;
- client.updateToken(function(token) {
- client.manager.updateUser(client.name, {
- token: token,
- password: hash
- }, function(err) {
- if (err) {
- log.error("Failed to update password of", client.name, err);
- return callback(false);
- }
+ client.manager.updateUser(client.name, {
+ password: hash
+ }, function(err) {
+ if (err) {
+ log.error("Failed to update password of", client.name, err);
+ return callback(false);
+ }
- client.config.password = hash;
- return callback(true);
- });
+ client.config.password = hash;
+ return callback(true);
});
};
diff --git a/src/clientManager.js b/src/clientManager.js
index 95fa7054..c50ccf92 100644
--- a/src/clientManager.js
+++ b/src/clientManager.js
@@ -25,14 +25,8 @@ ClientManager.prototype.init = function(identHandler, sockets) {
}
};
-ClientManager.prototype.findClient = function(name, token) {
- for (var i in this.clients) {
- var client = this.clients[i];
- if (client.name === name || (token && token === client.config.token)) {
- return client;
- }
- }
- return false;
+ClientManager.prototype.findClient = function(name) {
+ return this.clients.find((u) => u.name === name);
};
ClientManager.prototype.autoloadUsers = function() {
@@ -105,7 +99,8 @@ ClientManager.prototype.addUser = function(name, password, enableLog) {
password: password || "",
log: enableLog,
awayMessage: "",
- networks: []
+ networks: [],
+ sessions: {},
};
fs.writeFileSync(
Helper.getUserConfigPath(name),
diff --git a/src/command-line/reset.js b/src/command-line/reset.js
index 8be036e7..018b73d5 100644
--- a/src/command-line/reset.js
+++ b/src/command-line/reset.js
@@ -25,7 +25,7 @@ program
return;
}
user.password = Helper.password.hash(password);
- user.token = null; // Will be regenerated when the user is loaded
+ user.sessions = {};
fs.writeFileSync(
file,
JSON.stringify(user, null, "\t")
diff --git a/src/server.js b/src/server.js
index 2d78192a..c029789e 100644
--- a/src/server.js
+++ b/src/server.js
@@ -17,7 +17,6 @@ const net = require("net");
const Identification = require("./identification");
var manager = null;
-var authFunction = localAuth;
module.exports = function() {
log.info(`The Lounge ${colors.green(Helper.getVersion())} \
@@ -94,10 +93,6 @@ module.exports = function() {
in ${config.public ? "public" : "private"} mode`);
});
- if (!config.public && (config.ldap || {}).enable) {
- authFunction = ldapAuth;
- }
-
var sockets = io(server, {
serveClient: false,
transports: config.transports
@@ -105,7 +100,7 @@ in ${config.public ? "public" : "private"} mode`);
sockets.on("connect", function(socket) {
if (config.public) {
- auth.call(socket);
+ auth.call(socket, {});
} else {
init(socket);
}
@@ -178,7 +173,7 @@ function index(req, res, next) {
res.render("index", data);
}
-function init(socket, client) {
+function init(socket, client, generateToken) {
if (!client) {
socket.emit("auth", {success: true});
socket.on("auth", auth);
@@ -248,8 +243,7 @@ function init(socket, client) {
const obj = {};
if (success) {
- obj.success = "Successfully updated your password, all your other sessions were logged out";
- obj.token = client.config.token;
+ obj.success = "Successfully updated your password";
} else {
obj.error = "Failed to update your password";
}
@@ -300,12 +294,47 @@ function init(socket, client) {
}
});
- socket.join(client.id);
- socket.emit("init", {
- active: client.lastActiveChannel,
- networks: client.networks,
- token: client.config.token || null
+ socket.on("sign-out", (token) => {
+ delete client.config.sessions[token];
+
+ client.manager.updateUser(client.name, {
+ sessions: client.config.sessions
+ }, (err) => {
+ if (err) {
+ log.error("Failed to update sessions for", client.name, err);
+ }
+ });
+
+ socket.emit("sign-out");
});
+
+ socket.join(client.id);
+
+ const sendInitEvent = (token) => {
+ socket.emit("init", {
+ active: client.lastActiveChannel,
+ networks: client.networks,
+ token: token
+ });
+ };
+
+ if (generateToken) {
+ client.generateToken((token) => {
+ client.updateSession(token, getClientIp(socket.request), socket.request);
+
+ client.manager.updateUser(client.name, {
+ sessions: client.config.sessions
+ }, (err) => {
+ if (err) {
+ log.error("Failed to update sessions for", client.name, err);
+ }
+ });
+
+ sendInitEvent(token);
+ });
+ } else {
+ sendInitEvent(null);
+ }
}
}
@@ -324,10 +353,13 @@ function reverseDnsLookup(socket, client) {
}
function localAuth(client, user, password, callback) {
+ // If no user is found, or if the client has not provided a password,
+ // fail the authentication straight away
if (!client || !password) {
return callback(false);
}
+ // If this user has no password set, fail the authentication
if (!client.config.password) {
log.error(`User ${colors.bold(user)} with no local password set tried to sign in. (Probably a LDAP user)`);
return callback(false);
@@ -345,6 +377,7 @@ function localAuth(client, user, password, callback) {
}
});
}
+
callback(matching);
}).catch((error) => {
log.error(`Error while checking users password. Error: ${error}`);
@@ -376,50 +409,64 @@ function ldapAuth(client, user, password, callback) {
}
function auth(data) {
- var socket = this;
- var client;
+ const socket = this;
+ let client;
+
+ const initClient = () => {
+ // If webirc is enabled and we do not know this users IP address,
+ // perform reverse dns lookup
+ if (Helper.config.webirc !== null && !client.config.ip) {
+ reverseDnsLookup(socket, client);
+ } else {
+ init(socket, client, data.remember === "on");
+ }
+ };
+
if (Helper.config.public) {
client = new Client(manager);
manager.clients.push(client);
+
socket.on("disconnect", function() {
manager.clients = _.without(manager.clients, client);
client.quit();
});
- if (Helper.config.webirc) {
- reverseDnsLookup(socket, client);
- } else {
- init(socket, client);
+
+ initClient();
+
+ return;
+ }
+
+ const authCallback = (success) => {
+ // Authorization failed
+ if (!success) {
+ socket.emit("auth", {success: false});
+ return;
}
+
+ // If authorization succeeded but there is no loaded user,
+ // load it and find the user again (this happens with LDAP)
+ if (!client) {
+ manager.loadUser(data.user);
+ client = manager.findClient(data.user);
+ }
+
+ initClient();
+ };
+
+ client = manager.findClient(data.user);
+
+ // We have found an existing user and client has provided a token
+ if (client && data.token && typeof client.config.sessions[data.token] !== "undefined") {
+ client.updateSession(data.token, getClientIp(socket.request), socket.request);
+
+ authCallback(true);
+ return;
+ }
+
+ // Perform password checking
+ if (!Helper.config.public && Helper.config.ldap.enable) {
+ ldapAuth(client, data.user, data.password, authCallback);
} else {
- client = manager.findClient(data.user, data.token);
- var signedIn = data.token && client && data.token === client.config.token;
- var token;
-
- if (client && (data.remember || data.token)) {
- token = client.config.token;
- }
-
- var authCallback = function(success) {
- if (success) {
- if (!client) {
- // LDAP just created a user
- manager.loadUser(data.user);
- client = manager.findClient(data.user);
- }
- if (Helper.config.webirc !== null && !client.config.ip) {
- reverseDnsLookup(socket, client);
- } else {
- init(socket, client, token);
- }
- } else {
- socket.emit("auth", {success: success});
- }
- };
-
- if (signedIn) {
- authCallback(true);
- } else {
- authFunction(client, data.user, data.password, authCallback);
- }
+ localAuth(client, data.user, data.password, authCallback);
}
}