frontend password change functionality

- refactor clientManager.js to allow configuration parsing as a serparate
  function.
  - refactor clientManager.js to add configuration writing function.
  - add server.js changes to allow for new password-change functionality
  - add password change ui to "settings" screen
  - refactor client.js to use new clientManager functionality for saving
    the configuration files
This commit is contained in:
Daniel Llewellyn 2016-02-17 00:14:43 +00:00
parent 1e8ca51d47
commit b79a918be8
6 changed files with 177 additions and 38 deletions

View file

@ -1116,6 +1116,19 @@ button,
line-height: 1.8; line-height: 1.8;
} }
#settings #change-password .error,
#settings #change-password .success {
margin-bottom: 1em;
}
#settings #change-password .error {
color: #e74c3c;
}
#settings #change-password .success {
color: #2ecc40;
}
#form { #form {
background: #eee; background: #eee;
border-top: 1px solid #ddd; border-top: 1px solid #ddd;

View file

@ -265,6 +265,31 @@
Enable notification for all messages Enable notification for all messages
</label> </label>
</div> </div>
<% if (!public) { %>
<div id="change-password">
<form action="" method="post">
<div class="col-sm-12">
<h2>Change password</h2>
</div>
<div class="col-sm-12">
<label for="old_password" class="sr-only">Enter current password</label>
<input type="password" name="old_password" class="input" placeholder="Enter current password">
</div>
<div class="col-sm-12">
<label for="new_password" class="sr-only">Enter desired new password</label>
<input type="password" name="new_password" class="input" placeholder="Enter desired new password">
</div>
<div class="col-sm-12">
<label for="verify_password" class="sr-only">Repeat new password</label>
<input type="password" name="verify_password" class="input" placeholder="Repeat new password">
</div>
<div class="col-sm-12 feedback"></div>
<div class="col-sm-12">
<button type="submit" class="btn">Change password</button>
</div>
</form>
</div>
<% } %>
<div class="col-sm-12"> <div class="col-sm-12">
<h2>About The Lounge</h2> <h2>About The Lounge</h2>
</div> </div>

View file

@ -112,6 +112,31 @@ $(function() {
.show(); .show();
}); });
socket.on("change-password", function(data) {
var passwordForm = $("#change-password");
if (data.error || data.success) {
var message = data.success ? data.success : data.error;
var feedback = passwordForm.find(".feedback");
if (data.success) {
feedback.addClass("success").removeClass("error");
} else {
feedback.addClass("error").removeClass("success");
}
feedback.text(message).show();
feedback.closest("form").one("submit", function() {
feedback.hide();
});
}
passwordForm
.find("input")
.val("")
.end()
.find(".btn")
.prop("disabled", false);
});
socket.on("init", function(data) { socket.on("init", function(data) {
if (data.networks.length === 0) { if (data.networks.length === 0) {
$("#footer").find(".connect").trigger("click"); $("#footer").find(".connect").trigger("click");
@ -734,7 +759,7 @@ $(function() {
}); });
var windows = $("#windows"); var windows = $("#windows");
var forms = $("#sign-in, #connect"); var forms = $("#sign-in, #connect, #change-password");
windows.on("show", "#sign-in", function() { windows.on("show", "#sign-in", function() {
var self = $(this); var self = $(this);
@ -761,6 +786,8 @@ $(function() {
.end(); .end();
if (form.closest(".window").attr("id") === "connect") { if (form.closest(".window").attr("id") === "connect") {
event = "conn"; event = "conn";
} else if (form.closest("div").attr("id") === "change-password") {
event = "change-password";
} }
var values = {}; var values = {};
$.each(form.serializeArray(), function(i, obj) { $.each(form.serializeArray(), function(i, obj) {

View file

@ -1,7 +1,6 @@
var _ = require("lodash"); var _ = require("lodash");
var Chan = require("./models/chan"); var Chan = require("./models/chan");
var crypto = require("crypto"); var crypto = require("crypto");
var fs = require("fs");
var identd = require("./identd"); var identd = require("./identd");
var log = require("./log"); var log = require("./log");
var net = require("net"); var net = require("net");
@ -51,14 +50,15 @@ var inputs = [
"whois" "whois"
]; ];
function Client(sockets, name, config) { function Client(manager, name, config) {
_.merge(this, { _.merge(this, {
activeChannel: -1, activeChannel: -1,
config: config, config: config,
id: id++, id: id++,
name: name, name: name,
networks: [], networks: [],
sockets: sockets sockets: manager.sockets,
manager: manager
}); });
var client = this; var client = this;
crypto.randomBytes(48, function(err, buf) { crypto.randomBytes(48, function(err, buf) {
@ -221,6 +221,21 @@ Client.prototype.connect = function(args) {
}); });
}; };
Client.prototype.setPassword = function(hash) {
var client = this;
client.manager.updateUser(client.name, {password:hash});
// re-read the hash off disk to ensure we use whatever is saved. this will
// prevent situations where the password failed to save properly and so
// a restart of the server would forget the change and use the old
// password again.
var user = client.manager.readUserConfig(client.name);
if (user.password === hash) {
client.config.password = hash;
return true;
}
return false;
};
Client.prototype.input = function(data) { Client.prototype.input = function(data) {
var client = this; var client = this;
var text = data.text.trim(); var text = data.text.trim();
@ -353,9 +368,6 @@ Client.prototype.save = function(force) {
return; return;
} }
var name = this.name;
var path = Helper.HOME + "/users/" + name + ".json";
var networks = _.map( var networks = _.map(
this.networks, this.networks,
function(n) { function(n) {
@ -364,29 +376,6 @@ Client.prototype.save = function(force) {
); );
var json = {}; var json = {};
fs.readFile(path, "utf-8", function(err, data) {
if (err) {
console.log(err);
return;
}
try {
json = JSON.parse(data);
json.networks = networks; json.networks = networks;
} catch (e) { client.manager.updateUser(client.name, json);
console.log(e);
return;
}
fs.writeFile(
path,
JSON.stringify(json, null, " "),
{mode: "0777"},
function(err) {
if (err) {
console.log(err);
}
}
);
});
}; };

View file

@ -29,18 +29,14 @@ ClientManager.prototype.loadUsers = function() {
ClientManager.prototype.loadUser = function(name) { ClientManager.prototype.loadUser = function(name) {
try { try {
var json = fs.readFileSync( var json = this.readUserConfig(name);
Helper.HOME + "/users/" + name + ".json",
"utf-8"
);
json = JSON.parse(json);
} catch (e) { } catch (e) {
console.log(e); console.log(e);
return; return;
} }
if (!this.findClient(name)) { if (!this.findClient(name)) {
this.clients.push(new Client( this.clients.push(new Client(
this.sockets, this,
name, name,
json json
)); ));
@ -93,6 +89,50 @@ ClientManager.prototype.addUser = function(name, password) {
return true; return true;
}; };
ClientManager.prototype.updateUser = function(name, opts) {
var users = this.getUsers();
if (users.indexOf(name) === -1) {
return false;
}
if (typeof opts === "undefined") {
return false;
}
var path = Helper.HOME + "/users/" + name + ".json";
var user = {};
try {
user = this.readUserConfig(name);
_.merge(user, opts);
} catch (e) {
console.log(e);
return;
}
fs.writeFileSync(
path,
JSON.stringify(user, null, " "),
{mode: "0777"},
function(err) {
if (err) {
console.log(err);
}
}
);
return true;
};
ClientManager.prototype.readUserConfig = function(name) {
var users = this.getUsers();
if (users.indexOf(name) === -1) {
return false;
}
var path = Helper.HOME + "/users/" + name + ".json";
var user = {};
var data = fs.readFileSync(path, "utf-8");
user = JSON.parse(data);
return user;
};
ClientManager.prototype.removeUser = function(name) { ClientManager.prototype.removeUser = function(name) {
var users = this.getUsers(); var users = this.getUsers();
if (users.indexOf(name) === -1) { if (users.indexOf(name) === -1) {

View file

@ -107,6 +107,51 @@ function init(socket, client, token) {
client.connect(data); client.connect(data);
} }
); );
if (!config.public) {
socket.on(
"change-password",
function(data) {
var old = data.old_password;
var p1 = data.new_password;
var p2 = data.verify_password;
if (typeof old === "undefined" || old === "") {
socket.emit("change-password", {
error: "Please enter your current password"
});
return;
}
if (typeof p1 === "undefined" || p1 === "") {
socket.emit("change-password", {
error: "Please enter a new password"
});
return;
}
if (p1 !== p2) {
socket.emit("change-password", {
error: "Both new password fields must match"
});
return;
}
if (!bcrypt.compareSync(old || "", client.config.password)) {
socket.emit("change-password", {
error: "The current password field does not match your account password"
});
return;
}
var salt = bcrypt.genSaltSync(8);
var hash = bcrypt.hashSync(p1, salt);
if (client.setPassword(hash)) {
socket.emit("change-password", {
success: "Successfully updated your password"
});
return;
}
socket.emit("change-password", {
error: "Failed to update your password"
});
}
);
}
socket.on( socket.on(
"open", "open",
function(data) { function(data) {