Generate unique tokens for each login and session
This commit is contained in:
parent
30e167c42c
commit
c14f7da1b2
@ -99,7 +99,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<label class="remember">
|
<label class="remember">
|
||||||
<input type="checkbox" name="remember" id="sign-in-remember" checked>
|
<input type="checkbox" name="remember" value="on" id="sign-in-remember" checked>
|
||||||
Stay signed in
|
Stay signed in
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -562,8 +562,12 @@ $(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
sidebar.on("click", "#sign-out", function() {
|
sidebar.on("click", "#sign-out", function() {
|
||||||
|
socket.emit("sign-out", storage.get("token"));
|
||||||
storage.remove("token");
|
storage.remove("token");
|
||||||
location.reload();
|
|
||||||
|
if (!socket.connected) {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
sidebar.on("click", ".close", function() {
|
sidebar.on("click", ".close", function() {
|
||||||
|
@ -7,6 +7,7 @@ const storage = require("../localStorage");
|
|||||||
socket.on("auth", function(data) {
|
socket.on("auth", function(data) {
|
||||||
const login = $("#sign-in");
|
const login = $("#sign-in");
|
||||||
let token;
|
let token;
|
||||||
|
const user = storage.get("user");
|
||||||
|
|
||||||
login.find(".btn").prop("disabled", false);
|
login.find(".btn").prop("disabled", false);
|
||||||
|
|
||||||
@ -17,22 +18,23 @@ socket.on("auth", function(data) {
|
|||||||
error.show().closest("form").one("submit", function() {
|
error.show().closest("form").one("submit", function() {
|
||||||
error.hide();
|
error.hide();
|
||||||
});
|
});
|
||||||
} else {
|
} else if (user) {
|
||||||
token = storage.get("token");
|
token = storage.get("token");
|
||||||
if (token) {
|
if (token) {
|
||||||
$("#loading-page-message").text("Authorizing…");
|
$("#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 (user) {
|
||||||
if (input.val() === "") {
|
login.find("input[name='user']").val(user);
|
||||||
input.val(storage.get("user") || "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$("#sidebar, #footer").find(".sign-in")
|
|
||||||
|
$("#footer").find(".sign-in")
|
||||||
.trigger("click", {
|
.trigger("click", {
|
||||||
pushState: false,
|
pushState: false,
|
||||||
})
|
})
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
const $ = require("jquery");
|
const $ = require("jquery");
|
||||||
const socket = require("../socket");
|
const socket = require("../socket");
|
||||||
const storage = require("../localStorage");
|
|
||||||
|
|
||||||
socket.on("change-password", function(data) {
|
socket.on("change-password", function(data) {
|
||||||
const passwordForm = $("#change-password");
|
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
|
passwordForm
|
||||||
.find("input")
|
.find("input")
|
||||||
.val("")
|
.val("")
|
||||||
|
@ -16,3 +16,4 @@ require("./quit");
|
|||||||
require("./sync_sort");
|
require("./sync_sort");
|
||||||
require("./topic");
|
require("./topic");
|
||||||
require("./users");
|
require("./users");
|
||||||
|
require("./sign_out");
|
||||||
|
@ -17,10 +17,8 @@ socket.on("init", function(data) {
|
|||||||
render.renderNetworks(data);
|
render.renderNetworks(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.token && $("#sign-in-remember").is(":checked")) {
|
if (data.token) {
|
||||||
storage.set("token", data.token);
|
storage.set("token", data.token);
|
||||||
} else {
|
|
||||||
storage.remove("token");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$("body").removeClass("signed-out");
|
$("body").removeClass("signed-out");
|
||||||
|
9
client/js/socket-events/sign_out.js
Normal file
9
client/js/socket-events/sign_out.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const socket = require("../socket");
|
||||||
|
const storage = require("../localStorage");
|
||||||
|
|
||||||
|
socket.on("sign-out", function() {
|
||||||
|
storage.remove("token");
|
||||||
|
location.reload();
|
||||||
|
});
|
@ -57,6 +57,7 @@
|
|||||||
"semver": "5.4.1",
|
"semver": "5.4.1",
|
||||||
"socket.io": "1.7.4",
|
"socket.io": "1.7.4",
|
||||||
"spdy": "3.4.7",
|
"spdy": "3.4.7",
|
||||||
|
"ua-parser-js": "0.7.12",
|
||||||
"urijs": "1.18.10"
|
"urijs": "1.18.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -10,6 +10,7 @@ var Msg = require("./models/msg");
|
|||||||
var Network = require("./models/network");
|
var Network = require("./models/network");
|
||||||
var ircFramework = require("irc-framework");
|
var ircFramework = require("irc-framework");
|
||||||
var Helper = require("./helper");
|
var Helper = require("./helper");
|
||||||
|
const UAParser = require("ua-parser-js");
|
||||||
|
|
||||||
module.exports = Client;
|
module.exports = Client;
|
||||||
|
|
||||||
@ -81,12 +82,6 @@ function Client(manager, name, config) {
|
|||||||
|
|
||||||
var client = this;
|
var client = this;
|
||||||
|
|
||||||
if (client.name && !client.config.token) {
|
|
||||||
client.updateToken(function(token) {
|
|
||||||
client.manager.updateUser(client.name, {token: token});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var delay = 0;
|
var delay = 0;
|
||||||
(client.config.networks || []).forEach((n) => {
|
(client.config.networks || []).forEach((n) => {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
@ -95,6 +90,10 @@ function Client(manager, name, config) {
|
|||||||
delay += 1000;
|
delay += 1000;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (typeof client.config.sessions !== "object") {
|
||||||
|
client.config.sessions = {};
|
||||||
|
}
|
||||||
|
|
||||||
if (client.name) {
|
if (client.name) {
|
||||||
log.info(`User ${colors.bold(client.name)} loaded`);
|
log.info(`User ${colors.bold(client.name)} loaded`);
|
||||||
}
|
}
|
||||||
@ -286,34 +285,51 @@ Client.prototype.connect = function(args) {
|
|||||||
client.save();
|
client.save();
|
||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.updateToken = function(callback) {
|
Client.prototype.generateToken = function(callback) {
|
||||||
var client = this;
|
crypto.randomBytes(48, (err, buf) => {
|
||||||
|
|
||||||
crypto.randomBytes(48, function(err, buf) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
throw 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) {
|
Client.prototype.setPassword = function(hash, callback) {
|
||||||
var client = this;
|
var client = this;
|
||||||
|
|
||||||
client.updateToken(function(token) {
|
client.manager.updateUser(client.name, {
|
||||||
client.manager.updateUser(client.name, {
|
password: hash
|
||||||
token: token,
|
}, function(err) {
|
||||||
password: hash
|
if (err) {
|
||||||
}, function(err) {
|
log.error("Failed to update password of", client.name, err);
|
||||||
if (err) {
|
return callback(false);
|
||||||
log.error("Failed to update password of", client.name, err);
|
}
|
||||||
return callback(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
client.config.password = hash;
|
client.config.password = hash;
|
||||||
return callback(true);
|
return callback(true);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,14 +25,8 @@ ClientManager.prototype.init = function(identHandler, sockets) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ClientManager.prototype.findClient = function(name, token) {
|
ClientManager.prototype.findClient = function(name) {
|
||||||
for (var i in this.clients) {
|
return this.clients.find((u) => u.name === name);
|
||||||
var client = this.clients[i];
|
|
||||||
if (client.name === name || (token && token === client.config.token)) {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ClientManager.prototype.autoloadUsers = function() {
|
ClientManager.prototype.autoloadUsers = function() {
|
||||||
@ -105,7 +99,8 @@ ClientManager.prototype.addUser = function(name, password, enableLog) {
|
|||||||
password: password || "",
|
password: password || "",
|
||||||
log: enableLog,
|
log: enableLog,
|
||||||
awayMessage: "",
|
awayMessage: "",
|
||||||
networks: []
|
networks: [],
|
||||||
|
sessions: {},
|
||||||
};
|
};
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
Helper.getUserConfigPath(name),
|
Helper.getUserConfigPath(name),
|
||||||
|
@ -25,7 +25,7 @@ program
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
user.password = Helper.password.hash(password);
|
user.password = Helper.password.hash(password);
|
||||||
user.token = null; // Will be regenerated when the user is loaded
|
user.sessions = {};
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
file,
|
file,
|
||||||
JSON.stringify(user, null, "\t")
|
JSON.stringify(user, null, "\t")
|
||||||
|
147
src/server.js
147
src/server.js
@ -17,7 +17,6 @@ const net = require("net");
|
|||||||
const Identification = require("./identification");
|
const Identification = require("./identification");
|
||||||
|
|
||||||
var manager = null;
|
var manager = null;
|
||||||
var authFunction = localAuth;
|
|
||||||
|
|
||||||
module.exports = function() {
|
module.exports = function() {
|
||||||
log.info(`The Lounge ${colors.green(Helper.getVersion())} \
|
log.info(`The Lounge ${colors.green(Helper.getVersion())} \
|
||||||
@ -94,10 +93,6 @@ module.exports = function() {
|
|||||||
in ${config.public ? "public" : "private"} mode`);
|
in ${config.public ? "public" : "private"} mode`);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!config.public && (config.ldap || {}).enable) {
|
|
||||||
authFunction = ldapAuth;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sockets = io(server, {
|
var sockets = io(server, {
|
||||||
serveClient: false,
|
serveClient: false,
|
||||||
transports: config.transports
|
transports: config.transports
|
||||||
@ -105,7 +100,7 @@ in ${config.public ? "public" : "private"} mode`);
|
|||||||
|
|
||||||
sockets.on("connect", function(socket) {
|
sockets.on("connect", function(socket) {
|
||||||
if (config.public) {
|
if (config.public) {
|
||||||
auth.call(socket);
|
auth.call(socket, {});
|
||||||
} else {
|
} else {
|
||||||
init(socket);
|
init(socket);
|
||||||
}
|
}
|
||||||
@ -178,7 +173,7 @@ function index(req, res, next) {
|
|||||||
res.render("index", data);
|
res.render("index", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function init(socket, client) {
|
function init(socket, client, generateToken) {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
socket.emit("auth", {success: true});
|
socket.emit("auth", {success: true});
|
||||||
socket.on("auth", auth);
|
socket.on("auth", auth);
|
||||||
@ -248,8 +243,7 @@ function init(socket, client) {
|
|||||||
const obj = {};
|
const obj = {};
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
obj.success = "Successfully updated your password, all your other sessions were logged out";
|
obj.success = "Successfully updated your password";
|
||||||
obj.token = client.config.token;
|
|
||||||
} else {
|
} else {
|
||||||
obj.error = "Failed to update your password";
|
obj.error = "Failed to update your password";
|
||||||
}
|
}
|
||||||
@ -300,12 +294,47 @@ function init(socket, client) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.join(client.id);
|
socket.on("sign-out", (token) => {
|
||||||
socket.emit("init", {
|
delete client.config.sessions[token];
|
||||||
active: client.lastActiveChannel,
|
|
||||||
networks: client.networks,
|
client.manager.updateUser(client.name, {
|
||||||
token: client.config.token || null
|
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) {
|
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) {
|
if (!client || !password) {
|
||||||
return callback(false);
|
return callback(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this user has no password set, fail the authentication
|
||||||
if (!client.config.password) {
|
if (!client.config.password) {
|
||||||
log.error(`User ${colors.bold(user)} with no local password set tried to sign in. (Probably a LDAP user)`);
|
log.error(`User ${colors.bold(user)} with no local password set tried to sign in. (Probably a LDAP user)`);
|
||||||
return callback(false);
|
return callback(false);
|
||||||
@ -345,6 +377,7 @@ function localAuth(client, user, password, callback) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(matching);
|
callback(matching);
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
log.error(`Error while checking users password. Error: ${error}`);
|
log.error(`Error while checking users password. Error: ${error}`);
|
||||||
@ -376,50 +409,64 @@ function ldapAuth(client, user, password, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function auth(data) {
|
function auth(data) {
|
||||||
var socket = this;
|
const socket = this;
|
||||||
var client;
|
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) {
|
if (Helper.config.public) {
|
||||||
client = new Client(manager);
|
client = new Client(manager);
|
||||||
manager.clients.push(client);
|
manager.clients.push(client);
|
||||||
|
|
||||||
socket.on("disconnect", function() {
|
socket.on("disconnect", function() {
|
||||||
manager.clients = _.without(manager.clients, client);
|
manager.clients = _.without(manager.clients, client);
|
||||||
client.quit();
|
client.quit();
|
||||||
});
|
});
|
||||||
if (Helper.config.webirc) {
|
|
||||||
reverseDnsLookup(socket, client);
|
initClient();
|
||||||
} else {
|
|
||||||
init(socket, client);
|
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 {
|
} else {
|
||||||
client = manager.findClient(data.user, data.token);
|
localAuth(client, data.user, data.password, authCallback);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user