mirror of https://github.com/calzoneman/sync.git
Implement new session system
I replaced the old login system with a more secure one. Instead of storing cookies containing the username and plaintext password, the password is submitted once to obtain a session hash, which is valid for a given length of time. Registering and logging in is now done via an iframe, which prevents custom javascript from having access to the password field. Site admins need to run the following SQL before updating, or else all of your logins/registrations will fail: ALTER TABLE `registrations` ADD `session_hash` VARCHAR( 64 ) NOT NULL , ADD `expire` BIGINT NOT NULL
This commit is contained in:
parent
db2e5e20b9
commit
3a7acd0526
85
api.js
85
api.js
|
@ -16,7 +16,9 @@ var apilog = new Logger.Logger("api.log");
|
|||
|
||||
var jsonHandlers = {
|
||||
"channeldata": handleChannelData,
|
||||
"listloaded" : handleChannelList
|
||||
"listloaded" : handleChannelList,
|
||||
"login" : handleLogin,
|
||||
"register" : handleRegister
|
||||
};
|
||||
|
||||
function handle(path, req, res) {
|
||||
|
@ -59,6 +61,15 @@ function handle(path, req, res) {
|
|||
}
|
||||
exports.handle = handle;
|
||||
|
||||
function sendJSON(res, obj) {
|
||||
var response = JSON.stringify(obj, null, 4);
|
||||
var len = unescape(encodeURIComponent(response)).length;
|
||||
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
res.setHeader("Content-Length", len);
|
||||
res.end(response);
|
||||
}
|
||||
|
||||
function handleChannelData(params, req, res) {
|
||||
var clist = params.channel || "";
|
||||
clist = clist.split(",");
|
||||
|
@ -91,18 +102,15 @@ function handleChannelData(params, req, res) {
|
|||
data.push(d);
|
||||
}
|
||||
|
||||
var response = JSON.stringify(data, null, 4);
|
||||
var len = unescape(encodeURIComponent(response)).length;
|
||||
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
res.setHeader("Content-Length", len);
|
||||
res.end(response);
|
||||
sendJSON(res, data);
|
||||
}
|
||||
|
||||
function handleChannelList(params, req, res) {
|
||||
var session = params.session || "";
|
||||
var name = params.name || "";
|
||||
var pw = params.pw || "";
|
||||
if(!Auth.login(name, pw)) {
|
||||
var row = Auth.login(name, pw, session);
|
||||
if(!row || row.global_rank < 255) {
|
||||
res.send(403);
|
||||
return;
|
||||
}
|
||||
|
@ -112,3 +120,64 @@ function handleChannelList(params, req, res) {
|
|||
}
|
||||
handleChannelData({channel: clist.join(",")}, req, res);
|
||||
}
|
||||
|
||||
function handleLogin(params, req, res) {
|
||||
var session = params.session || "";
|
||||
var name = params.name || "";
|
||||
var pw = params.pw || "";
|
||||
|
||||
var row = Auth.login(name, pw, session);
|
||||
if(row) {
|
||||
sendJSON(res, {
|
||||
success: true,
|
||||
session: row.session_hash
|
||||
});
|
||||
}
|
||||
else {
|
||||
sendJSON(res, {
|
||||
success: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleRegister(params, req, res) {
|
||||
var name = params.name || "";
|
||||
var pw = params.pw || "";
|
||||
|
||||
if(pw == "") {
|
||||
sendJSON(res, {
|
||||
success: false,
|
||||
error: "You must provide a password"
|
||||
});
|
||||
return;
|
||||
}
|
||||
else if(Auth.isRegistered(name)) {
|
||||
sendJSON(res, {
|
||||
success: false,
|
||||
error: "That username is already taken"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
else if(!Auth.validateName(name)) {
|
||||
sendJSON(res, {
|
||||
success: false,
|
||||
error: "Invalid username. Usernames must be 1-20 characters long and consist only of alphanumeric characters and underscores"
|
||||
});
|
||||
}
|
||||
else {
|
||||
var session = Auth.register(name, pw);
|
||||
if(session) {
|
||||
Logger.syslog.log(this.ip + " registered " + name);
|
||||
sendJSON(res, {
|
||||
success: true,
|
||||
session: session
|
||||
});
|
||||
}
|
||||
else {
|
||||
sendJSON(res, {
|
||||
success: false,
|
||||
error: "I dunno what went wrong"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
82
auth.js
82
auth.js
|
@ -55,16 +55,36 @@ exports.register = function(name, pw) {
|
|||
return false;
|
||||
}
|
||||
var hash = bcrypt.hashSync(pw, 10);
|
||||
var query = "INSERT INTO registrations VALUES (NULL, '{1}', '{2}', 1)"
|
||||
var query = "INSERT INTO registrations VALUES (NULL, '{1}', '{2}', 1, '', 0)"
|
||||
.replace(/\{1\}/, name)
|
||||
.replace(/\{2\}/, hash);
|
||||
var results = db.querySync(query);
|
||||
db.closeSync();
|
||||
return results;
|
||||
if(results) {
|
||||
return exports.createSession(name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
exports.login = function(name, pw, session) {
|
||||
if(session) {
|
||||
var res = exports.loginSession(name, session);
|
||||
if(res) {
|
||||
return res;
|
||||
}
|
||||
else if(!pw) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
var row = exports.loginPassword(name, pw);
|
||||
if(row) {
|
||||
var hash = exports.createSession(name);
|
||||
row.session_hash = hash;
|
||||
return row;
|
||||
}
|
||||
}
|
||||
// Try to login
|
||||
exports.login = function(name, pw) {
|
||||
exports.loginPassword = function(name, pw) {
|
||||
var db = mysql.createConnectionSync();
|
||||
db.connectSync(Config.MYSQL_SERVER, Config.MYSQL_USER,
|
||||
Config.MYSQL_PASSWORD, Config.MYSQL_DB);
|
||||
|
@ -110,6 +130,62 @@ exports.login = function(name, pw) {
|
|||
return false;
|
||||
}
|
||||
|
||||
exports.createSession = function(name) {
|
||||
var salt = sessionSalt();
|
||||
var hash = hashlib.sha256(salt + name);
|
||||
var db = mysql.createConnectionSync();
|
||||
db.connectSync(Config.MYSQL_SERVER, Config.MYSQL_USER,
|
||||
Config.MYSQL_PASSWORD, Config.MYSQL_DB);
|
||||
if(!db.connectedSync()) {
|
||||
return false;
|
||||
}
|
||||
var query = ["UPDATE registrations SET ",
|
||||
"session_hash='{1}',",
|
||||
"expire={2} ",
|
||||
"WHERE uname='{3}'"].join("")
|
||||
.replace(/\{1\}/, hash)
|
||||
.replace(/\{2\}/, new Date().getTime() + 604800000)
|
||||
.replace(/\{3\}/, name)
|
||||
var results = db.querySync(query);
|
||||
return results ? hash : false;
|
||||
}
|
||||
|
||||
exports.loginSession = function(name, hash) {
|
||||
var db = mysql.createConnectionSync();
|
||||
db.connectSync(Config.MYSQL_SERVER, Config.MYSQL_USER,
|
||||
Config.MYSQL_PASSWORD, Config.MYSQL_DB);
|
||||
if(!db.connectedSync()) {
|
||||
return false;
|
||||
}
|
||||
var query = "SELECT * FROM registrations WHERE uname='{1}'"
|
||||
.replace(/\{1\}/, name)
|
||||
var results = db.querySync(query);
|
||||
var rows = results.fetchAllSync();
|
||||
if(rows.length != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var dbhash = rows[0].session_hash;
|
||||
if(hash != dbhash) {
|
||||
return false;
|
||||
}
|
||||
var timeout = rows[0].expire;
|
||||
if(timeout < new Date().getTime()) {
|
||||
return false;
|
||||
}
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
function sessionSalt() {
|
||||
var chars = "abcdefgihjklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
+ "0123456789!@#$%^&*_+=~";
|
||||
var salt = [];
|
||||
for(var i = 0; i < 32; i++) {
|
||||
salt.push(chars[parseInt(Math.random()*chars.length)]);
|
||||
}
|
||||
return salt.join('');
|
||||
}
|
||||
|
||||
exports.getGlobalRank = function(name) {
|
||||
var db = mysql.createConnectionSync();
|
||||
db.connectSync(Config.MYSQL_SERVER, Config.MYSQL_USER,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"author": "Calvin Montgomery",
|
||||
"name": "CyTube",
|
||||
"description": "Online media synchronizer and chat",
|
||||
"version": "1.2.7",
|
||||
"version": "1.3.0",
|
||||
"repository": {
|
||||
"url": "http://github.com/calzoneman/sync"
|
||||
},
|
||||
|
|
|
@ -9,7 +9,7 @@ The above copyright notice and this permission notice shall be included in all c
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
const VERSION = "1.2.7";
|
||||
const VERSION = "1.3.0";
|
||||
|
||||
var fs = require("fs");
|
||||
var Logger = require("./logger.js");
|
||||
|
|
20
user.js
20
user.js
|
@ -98,12 +98,13 @@ User.prototype.initCallbacks = function() {
|
|||
}.bind(this));
|
||||
|
||||
this.socket.on("login", function(data) {
|
||||
if(data.name == undefined || data.pw == undefined)
|
||||
return;
|
||||
if(data.pw.length > 100)
|
||||
data.pw = data.pw.substring(0, 100);
|
||||
var name = data.name || "";
|
||||
var pw = data.pw || "";
|
||||
var session = data.session || "";
|
||||
if(pw.length > 100)
|
||||
pw = pw.substring(0, 100);
|
||||
if(this.name == "")
|
||||
this.login(data.name, data.pw);
|
||||
this.login(name, pw, session);
|
||||
}.bind(this));
|
||||
|
||||
this.socket.on("register", function(data) {
|
||||
|
@ -325,7 +326,7 @@ User.prototype.handleAdm = function(data) {
|
|||
};
|
||||
|
||||
// Attempt to login
|
||||
User.prototype.login = function(name, pw) {
|
||||
User.prototype.login = function(name, pw, session) {
|
||||
if(this.channel != null && name != "") {
|
||||
for(var i = 0; i < this.channel.users.length; i++) {
|
||||
if(this.channel.users[i].name == name) {
|
||||
|
@ -338,7 +339,7 @@ User.prototype.login = function(name, pw) {
|
|||
}
|
||||
}
|
||||
// No password => try guest login
|
||||
if(pw == "") {
|
||||
if(pw == "" && session == "") {
|
||||
// Sorry bud, can't take that name
|
||||
if(Auth.isRegistered(name)) {
|
||||
this.socket.emit("login", {
|
||||
|
@ -372,10 +373,11 @@ User.prototype.login = function(name, pw) {
|
|||
}
|
||||
else {
|
||||
var row;
|
||||
if((row = Auth.login(name, pw))) {
|
||||
if((row = Auth.login(name, pw, session))) {
|
||||
this.loggedIn = true;
|
||||
this.socket.emit("login", {
|
||||
success: true
|
||||
success: true,
|
||||
session: row.session_hash
|
||||
});
|
||||
Logger.syslog.log(this.ip + " logged in as " + name);
|
||||
var chanrank = (this.channel != null) ? this.channel.getRank(name)
|
||||
|
|
|
@ -143,10 +143,9 @@ function initCallbacks() {
|
|||
$("#loginform").css("display", "none");
|
||||
$("#logoutform").css("display", "");
|
||||
$("#loggedin").css("display", "");
|
||||
if(pw != "") {
|
||||
createCookie("sync_uname", uname, 1);
|
||||
createCookie("sync_pw", pw, 1);
|
||||
}
|
||||
session = data.session;
|
||||
createCookie("sync_uname", uname, 7);
|
||||
createCookie("sync_session", session, 7);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ var VHEIGHT = "377";
|
|||
var IGNORED = [];
|
||||
var KICKED = false;
|
||||
var uname = readCookie("sync_uname");
|
||||
var pw = readCookie("sync_pw");
|
||||
var session = readCookie("sync_session");
|
||||
|
||||
var Rank = {
|
||||
Guest: 0,
|
||||
|
@ -116,10 +116,10 @@ socket.on("connect", function() {
|
|||
socket.emit("joinChannel", {
|
||||
name: params["channel"]
|
||||
});
|
||||
if(uname != null && pw != null && pw != "false") {
|
||||
if(uname && session) {
|
||||
socket.emit("login", {
|
||||
name: uname,
|
||||
pw: pw
|
||||
session: session
|
||||
});
|
||||
}
|
||||
$("<div/>").addClass("server-msg-reconnect")
|
||||
|
@ -194,40 +194,14 @@ $("#qlockbtn").click(function() {
|
|||
});
|
||||
});
|
||||
|
||||
function loginClick() {
|
||||
uname = $("#username").val();
|
||||
pw = $("#password").val();
|
||||
socket.emit("login", {
|
||||
name: uname,
|
||||
pw: pw
|
||||
});
|
||||
};
|
||||
|
||||
$("#login").click(loginClick);
|
||||
$("#username").keydown(function(ev) {
|
||||
if(ev.keyCode == 13)
|
||||
loginClick();
|
||||
});
|
||||
$("#password").keydown(function(ev) {
|
||||
if(ev.keyCode == 13)
|
||||
loginClick();
|
||||
});
|
||||
$("#login").click(showLoginFrame);
|
||||
|
||||
$("#logout").click(function() {
|
||||
eraseCookie("sync_uname");
|
||||
eraseCookie("sync_pw");
|
||||
eraseCookie("sync_session");
|
||||
document.location.reload(true);
|
||||
});
|
||||
|
||||
$("#register").click(function() {
|
||||
uname = $("#username").val();
|
||||
pw = $("#password").val();
|
||||
socket.emit("register", {
|
||||
name: uname,
|
||||
pw: pw
|
||||
});
|
||||
});
|
||||
|
||||
$("#chatline").keydown(function(ev) {
|
||||
if(ev.keyCode == 13 && $("#chatline").val() != "") {
|
||||
if($("#chatline").val().trim() == "/poll") {
|
||||
|
|
|
@ -843,3 +843,69 @@ function newPollMenu() {
|
|||
});
|
||||
modal.modal();
|
||||
}
|
||||
|
||||
function showLoginFrame() {
|
||||
var modal = $("<div/>").addClass("modal hide fade")
|
||||
.appendTo($("body"));
|
||||
var head = $("<div/>").addClass("modal-header")
|
||||
.appendTo(modal);
|
||||
$("<button/>").addClass("close")
|
||||
.attr("data-dismiss", "modal")
|
||||
.attr("aria-hidden", "true")
|
||||
.appendTo(head)[0].innerHTML = "×";
|
||||
$("<h3/>").text("Login").appendTo(head);
|
||||
var body = $("<div/>").addClass("modal-body").appendTo(modal);
|
||||
var frame = $("<iframe/>")
|
||||
.attr("id", "loginframe")
|
||||
.attr("src", "login.html")
|
||||
.css("border", "none")
|
||||
.css("width", "100%")
|
||||
.css("height", "300px")
|
||||
.css("margin", "0")
|
||||
.appendTo(body);
|
||||
var respond = function(e) {
|
||||
if(e.data.indexOf(":") == -1) {
|
||||
return;
|
||||
}
|
||||
if(e.data.substring(0, e.data.indexOf(":")) == "cytube-login") {
|
||||
var data = e.data.substring(e.data.indexOf(":")+1);
|
||||
data = JSON.parse(data);
|
||||
if(data.error) {
|
||||
alert(data.error);
|
||||
}
|
||||
else if(data.success) {
|
||||
session = data.session;
|
||||
uname = data.uname;
|
||||
socket.emit("login", {
|
||||
name: uname,
|
||||
session: session
|
||||
});
|
||||
if(window.removeEventListener) {
|
||||
window.removeEventListener("message", respond, false);
|
||||
}
|
||||
else if(window.detachEvent) {
|
||||
// If an IE dev ever reads this, please tell your company
|
||||
// to get their shit together
|
||||
window.detachEvent("onmessage", respond);
|
||||
}
|
||||
modal.modal("hide");
|
||||
}
|
||||
}
|
||||
}
|
||||
if(window.addEventListener) {
|
||||
window.addEventListener("message", respond, false);
|
||||
}
|
||||
else if(window.attachEvent) {
|
||||
// If an IE dev ever reads this, please tell your company to get
|
||||
// their shit together
|
||||
window.attachEvent("onmessage", respond);
|
||||
}
|
||||
setTimeout(function() {
|
||||
frame[0].contentWindow.postMessage("cytube-syn", document.location);
|
||||
}, 1000);
|
||||
var footer = $("<div/>").addClass("modal-footer").appendTo(modal);
|
||||
modal.on("hidden", function() {
|
||||
modal.remove();
|
||||
});
|
||||
modal.modal();
|
||||
}
|
||||
|
|
|
@ -29,10 +29,7 @@
|
|||
<li><a href="help.html">Help</a></li>
|
||||
</ul>
|
||||
<div class="navbar-form pull-right" id="loginform">
|
||||
<input class="span2" id="username" type="text" placeholder="Username">
|
||||
<input class="span2" id="password" type="password" placeholder="Password">
|
||||
<button class="btn" id="login">Login</button>
|
||||
<button class="btn" id="register">Register</button>
|
||||
<button class="btn" id="login">Login/Register</button>
|
||||
</div>
|
||||
<div class="navbar-form pull-right" id="logoutform" style="display: none;">
|
||||
<button class="btn" id="logout">Logout</button>
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>CyTube - Login</title>
|
||||
<link rel="stylesheet" href="assets/css/bootstrap.css">
|
||||
</head>
|
||||
<body>
|
||||
<form class="form-horizontal" action="javascript:void(0)">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="username">Username</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="username">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="pw">Password</label>
|
||||
<div class="controls">
|
||||
<input type="password" id="pw">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" style="display: none" id="pw2div">
|
||||
<label class="control-label" for="pw2" id="confirm">Confirm Password</label>
|
||||
<div class="controls">
|
||||
<input type="password" id="pw2">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<button class="btn" id="login">Login</button>
|
||||
<button class="btn" id="register">Register</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<script src="assets/js/jquery.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
var source;
|
||||
var respond = function(e) {
|
||||
if(e.data == "cytube-syn") {
|
||||
source = e.source;
|
||||
}
|
||||
}
|
||||
window.addEventListener("message", respond, false);
|
||||
|
||||
$("#login").click(function() {
|
||||
$.getJSON("api/json/login?name="+$("#username").val()+"&pw="+$("#pw").val(), function(data) {
|
||||
data.uname = $("#username").val();
|
||||
source.postMessage("cytube-login:"+JSON.stringify(data), document.location);
|
||||
});
|
||||
});
|
||||
$("#register").click(function() {
|
||||
if($("#pw2div").css("display") == "none") {
|
||||
$("#pw2div").css("display", "");
|
||||
return false;
|
||||
}
|
||||
else if($("#pw2").val() != $("#pw").val()) {
|
||||
$("#confirm").addClass("text-error");
|
||||
return;
|
||||
}
|
||||
$.getJSON("api/json/register?name="+$("#username").val()+"&pw="+$("#pw").val(), function(data) {
|
||||
console.log(data);
|
||||
data.uname = $("#username").val();
|
||||
source.postMessage("cytube-login:"+JSON.stringify(data), document.location);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue