Change login sessions

This commit is contained in:
calzoneman 2015-02-15 21:56:00 -06:00
parent 10aa7519da
commit b579db5310
13 changed files with 343 additions and 490 deletions

View File

@ -64,6 +64,8 @@ http:
gzip: true gzip: true
# Customize the threshold byte size for applying gzip # Customize the threshold byte size for applying gzip
gzip-threshold: 1024 gzip-threshold: 1024
# Secret used for signed cookies. Can be anything, but make it unique and hard to guess
cookie-secret: 'change-me'
# HTTPS server details # HTTPS server details
https: https:

View File

@ -33,7 +33,8 @@ var defaults = {
minify: false, minify: false,
"max-age": "7d", "max-age": "7d",
gzip: true, gzip: true,
"gzip-threshold": 1024 "gzip-threshold": 1024,
"cookie-secret": "change-me"
}, },
https: { https: {
enabled: false, enabled: false,

View File

@ -1,4 +1,3 @@
//var db = require("../database");
var $util = require("../utilities"); var $util = require("../utilities");
var bcrypt = require("bcrypt"); var bcrypt = require("bcrypt");
var db = require("../database"); var db = require("../database");
@ -59,6 +58,25 @@ module.exports = {
}); });
}, },
getUser: function (name, callback) {
if (typeof callback !== "function") {
return;
}
db.query("SELECT * FROM `users` WHERE name = ?", [name], function (err, rows) {
if (err) {
callback(err, true);
return;
}
if (rows.length !== 1) {
return callback("User does not exist");
}
callback(null, rows[0]);
});
},
/** /**
* Registers a new user account * Registers a new user account
*/ */
@ -198,11 +216,7 @@ module.exports = {
} else if (!match) { } else if (!match) {
callback("Invalid username/password combination", null); callback("Invalid username/password combination", null);
} else { } else {
callback(null, { callback(null, rows[0]);
name: rows[0].name,
hash: rows[0].password,
global_rank: rows[0].global_rank
});
} }
}); });
}); });

View File

@ -6,7 +6,8 @@ const TBL_USERS = "" +
"`global_rank` INT NOT NULL," + "`global_rank` INT NOT NULL," +
"`email` VARCHAR(255) NOT NULL," + "`email` VARCHAR(255) NOT NULL," +
"`profile` TEXT CHARACTER SET utf8mb4 NOT NULL," + "`profile` TEXT CHARACTER SET utf8mb4 NOT NULL," +
"`ip` VARCHAR(39) NOT NULL," + "`time` BIGINT NOT NULL," + "`ip` VARCHAR(39) NOT NULL," +
"`time` BIGINT NOT NULL," +
"PRIMARY KEY(`id`)," + "PRIMARY KEY(`id`)," +
"UNIQUE(`name`)) " + "UNIQUE(`name`)) " +
"CHARACTER SET utf8"; "CHARACTER SET utf8";

50
lib/session.js Normal file
View File

@ -0,0 +1,50 @@
var dbAccounts = require("./database/accounts");
var util = require("./utilities");
var crypto = require("crypto");
function sha256(input) {
var hash = crypto.createHash("sha256");
hash.update(input);
return hash.digest("base64");
}
exports.genSession = function (account, expiration, cb) {
if (expiration instanceof Date) {
expiration = Date.parse(expiration);
}
var salt = crypto.pseudoRandomBytes(24).toString("base64");
var hashInput = [account.name, account.password, expiration, salt].join(":");
var hash = sha256(hashInput);
cb(null, [account.name, expiration, salt, hash].join(":"));
};
exports.verifySession = function (input, cb) {
var parts = input.split(":");
if (parts.length !== 4) {
return cb("Invalid auth string");
}
var name = parts[0];
var expiration = parts[1];
var salt = parts[2];
var hash = parts[3];
if (Date.now() > parseInt(expiration)) {
return cb("Session expired");
}
dbAccounts.getUser(name, function (err, account) {
if (err) {
return cb(err);
}
var hashInput = [account.name, account.password, expiration, salt].join(":");
if (sha256(hashInput) !== hash) {
return cb("Invalid auth string");
}
cb(null, account);
});
};

View File

@ -11,6 +11,7 @@ var db = require("../database");
var $util = require("../utilities"); var $util = require("../utilities");
var Config = require("../config"); var Config = require("../config");
var Server = require("../server"); var Server = require("../server");
var session = require("../session");
/** /**
* Handles a GET request for /account/edit * Handles a GET request for /account/edit
@ -20,23 +21,7 @@ function handleAccountEditPage(req, res) {
return; return;
} }
var loginName = false; sendJade(res, "account-edit", {});
if (req.cookies.auth) {
loginName = req.cookies.auth.split(":")[0];
}
db.users.verifyAuth(req.cookies.auth, function (err, user) {
if (err) {
return sendJade(res, "account-edit", {
loggedIn: false
});
}
sendJade(res, "account-edit", {
loggedIn: loginName !== false,
loginName: loginName
});
});
} }
/** /**
@ -64,10 +49,6 @@ function handleChangePassword(req, res) {
var name = req.body.name; var name = req.body.name;
var oldpassword = req.body.oldpassword; var oldpassword = req.body.oldpassword;
var newpassword = req.body.newpassword; var newpassword = req.body.newpassword;
var loginName = false;
if (req.cookies.auth) {
loginName = req.cookies.auth.split(":")[0];
}
if (typeof name !== "string" || if (typeof name !== "string" ||
typeof oldpassword !== "string" || typeof oldpassword !== "string" ||
@ -78,20 +59,23 @@ function handleChangePassword(req, res) {
if (newpassword.length === 0) { if (newpassword.length === 0) {
sendJade(res, "account-edit", { sendJade(res, "account-edit", {
loggedIn: loginName !== false,
loginName: loginName,
errorMessage: "New password must not be empty" errorMessage: "New password must not be empty"
}); });
return; return;
} }
if (!req.user) {
sendJade(res, "account-edit", {
errorMessage: "You must be logged in to change your password"
});
return;
}
newpassword = newpassword.substring(0, 100); newpassword = newpassword.substring(0, 100);
db.users.verifyLogin(name, oldpassword, function (err, user) { db.users.verifyLogin(name, oldpassword, function (err, user) {
if (err) { if (err) {
sendJade(res, "account-edit", { sendJade(res, "account-edit", {
loggedIn: loginName !== false,
loginName: loginName,
errorMessage: err errorMessage: err
}); });
return; return;
@ -100,18 +84,49 @@ function handleChangePassword(req, res) {
db.users.setPassword(name, newpassword, function (err, dbres) { db.users.setPassword(name, newpassword, function (err, dbres) {
if (err) { if (err) {
sendJade(res, "account-edit", { sendJade(res, "account-edit", {
loggedIn: loginName !== false,
loginName: loginName,
errorMessage: err errorMessage: err
}); });
return; return;
} }
Logger.eventlog.log("[account] " + webserver.ipForRequest(req) + Logger.eventlog.log("[account] " + webserver.ipForRequest(req) +
" changed password for " + name); " changed password for " + name);
sendJade(res, "account-edit", {
loggedIn: loginName !== false, db.users.getUser(name, function (err, user) {
loginName: loginName, if (err) {
successMessage: "Password changed." return sendJade(res, "account-edit", {
errorMessage: err
});
}
res.user = user;
var expiration = new Date(parseInt(req.signedCookies.auth.split(":")[1]));
session.genSession(user, expiration, function (err, auth) {
if (err) {
return sendJade(res, "account-edit", {
errorMessage: err
});
}
if (req.hostname.indexOf(Config.get("http.root-domain")) >= 0) {
res.cookie("auth", auth, {
domain: Config.get("http.root-domain-dotted"),
expires: expiration,
httpOnly: true,
signed: true
});
} else {
res.cookie("auth", auth, {
expires: expiration,
httpOnly: true,
signed: true
});
}
sendJade(res, "account-edit", {
successMessage: "Password changed."
});
});
}); });
}); });
}); });
@ -124,10 +139,6 @@ function handleChangeEmail(req, res) {
var name = req.body.name; var name = req.body.name;
var password = req.body.password; var password = req.body.password;
var email = req.body.email; var email = req.body.email;
var loginName = false;
if (req.cookies.auth) {
loginName = req.cookies.auth.split(":")[0];
}
if (typeof name !== "string" || if (typeof name !== "string" ||
typeof password !== "string" || typeof password !== "string" ||
@ -138,8 +149,6 @@ function handleChangeEmail(req, res) {
if (!$util.isValidEmail(email) && email !== "") { if (!$util.isValidEmail(email) && email !== "") {
sendJade(res, "account-edit", { sendJade(res, "account-edit", {
loggedIn: loginName !== false,
loginName: loginName,
errorMessage: "Invalid email address" errorMessage: "Invalid email address"
}); });
return; return;
@ -148,8 +157,6 @@ function handleChangeEmail(req, res) {
db.users.verifyLogin(name, password, function (err, user) { db.users.verifyLogin(name, password, function (err, user) {
if (err) { if (err) {
sendJade(res, "account-edit", { sendJade(res, "account-edit", {
loggedIn: loginName !== false,
loginName: loginName,
errorMessage: err errorMessage: err
}); });
return; return;
@ -158,8 +165,6 @@ function handleChangeEmail(req, res) {
db.users.setEmail(name, email, function (err, dbres) { db.users.setEmail(name, email, function (err, dbres) {
if (err) { if (err) {
sendJade(res, "account-edit", { sendJade(res, "account-edit", {
loggedIn: loginName !== false,
loginName: loginName,
errorMessage: err errorMessage: err
}); });
return; return;
@ -168,8 +173,6 @@ function handleChangeEmail(req, res) {
" changed email for " + name + " changed email for " + name +
" to " + email); " to " + email);
sendJade(res, "account-edit", { sendJade(res, "account-edit", {
loggedIn: loginName !== false,
loginName: loginName,
successMessage: "Email address changed." successMessage: "Email address changed."
}); });
}); });
@ -184,33 +187,17 @@ function handleAccountChannelPage(req, res) {
return; return;
} }
var loginName = false; if (!req.user) {
if (req.cookies.auth) { return sendJade(res, "account-channels", {
loginName = req.cookies.auth.split(":")[0]; channels: []
});
} }
if (loginName) { db.channels.listUserChannels(req.user.name, function (err, channels) {
db.users.verifyAuth(req.cookies.auth, function (err, user) {
if (err) {
return sendJade(res, "account-channels", {
loggedIn: false
});
}
db.channels.listUserChannels(loginName, function (err, channels) {
sendJade(res, "account-channels", {
loggedIn: true,
loginName: loginName,
channels: channels
});
});
});
} else {
sendJade(res, "account-channels", { sendJade(res, "account-channels", {
loggedIn: false, channels: channels
channels: [],
}); });
} });
} }
/** /**
@ -242,87 +229,64 @@ function handleNewChannel(req, res) {
return; return;
} }
var loginName = false; if (!req.user) {
if (req.cookies.auth) { return sendJade(res, "account-channels", {
loginName = req.cookies.auth.split(":")[0];
} else {
sendJade(res, "account-channels", {
loggedIn: false,
channels: [] channels: []
}); });
return;
} }
db.users.verifyAuth(req.cookies.auth, function (err, user) {
db.channels.listUserChannels(req.user.name, function (err, channels) {
if (err) { if (err) {
sendJade(res, "account-channels", { sendJade(res, "account-channels", {
loggedIn: false,
channels: [], channels: [],
newChannelError: err newChannelError: err
}); });
return; return;
} }
db.channels.listUserChannels(loginName, function (err, channels) { if (name.match(Config.get("reserved-names.channels"))) {
if (err) { sendJade(res, "account-channels", {
sendJade(res, "account-channels", { channels: channels,
loggedIn: true, newChannelError: "That channel name is reserved"
loginName: loginName, });
channels: [], return;
newChannelError: err }
});
return;
}
if (name.match(Config.get("reserved-names.channels"))) { if (channels.length >= Config.get("max-channels-per-user")) {
sendJade(res, "account-channels", { sendJade(res, "account-channels", {
loggedIn: true, channels: channels,
loginName: loginName, newChannelError: "You are not allowed to register more than " +
channels: channels, Config.get("max-channels-per-user") + " channels."
newChannelError: "That channel name is reserved" });
}); return;
return; }
}
if (channels.length >= Config.get("max-channels-per-user")) { db.channels.register(name, req.user.name, function (err, channel) {
sendJade(res, "account-channels", { if (!err) {
loggedIn: true, Logger.eventlog.log("[channel] " + req.user.name + "@" +
loginName: loginName, webserver.ipForRequest(req) +
channels: channels, " registered channel " + name);
newChannelError: "You are not allowed to register more than " + var sv = Server.getServer();
Config.get("max-channels-per-user") + " channels." if (sv.isChannelLoaded(name)) {
}); var chan = sv.getChannel(name);
return; var users = Array.prototype.slice.call(chan.users);
} users.forEach(function (u) {
u.kick("Channel reloading");
db.channels.register(name, user.name, function (err, channel) {
if (!err) {
Logger.eventlog.log("[channel] " + user.name + "@" +
webserver.ipForRequest(req) +
" registered channel " + name);
var sv = Server.getServer();
if (sv.isChannelLoaded(name)) {
var chan = sv.getChannel(name);
var users = Array.prototype.slice.call(chan.users);
users.forEach(function (u) {
u.kick("Channel reloading");
});
if (!chan.dead) {
chan.emit("empty");
}
}
channels.push({
name: name
}); });
if (!chan.dead) {
chan.emit("empty");
}
} }
channels.push({
name: name
sendJade(res, "account-channels", {
loggedIn: true,
loginName: loginName,
channels: channels,
newChannelError: err ? err : undefined
}); });
}
sendJade(res, "account-channels", {
channels: channels,
newChannelError: err ? err : undefined
}); });
}); });
}); });
@ -338,75 +302,55 @@ function handleDeleteChannel(req, res) {
return; return;
} }
var loginName = false; if (!req.user) {
if (req.cookies.auth) { return sendJade(res, "account-channels", {
loginName = req.cookies.auth.split(":")[0];
} else {
sendJade(res, "account-channels", {
loggedIn: false,
channels: [], channels: [],
}); });
return;
} }
db.users.verifyAuth(req.cookies.auth, function (err, user) {
db.channels.lookup(name, function (err, channel) {
if (err) { if (err) {
sendJade(res, "account-channels", { sendJade(res, "account-channels", {
loggedIn: false,
channels: [], channels: [],
deleteChannelError: err deleteChannelError: err
}); });
return; return;
} }
db.channels.lookup(name, function (err, channel) { if (channel.owner !== req.user.name && req.user.global_rank < 255) {
if (err) { db.channels.listUserChannels(req.user.name, function (err2, channels) {
sendJade(res, "account-channels", { sendJade(res, "account-channels", {
loggedIn: true, channels: err2 ? [] : channels,
loginName: loginName, deleteChannelError: "You do not have permission to delete this channel"
channels: [],
deleteChannelError: err
}); });
return; });
} return;
}
if (channel.owner !== user.name && user.global_rank < 255) { db.channels.drop(name, function (err) {
db.channels.listUserChannels(loginName, function (err2, channels) { if (!err) {
sendJade(res, "account-channels", { Logger.eventlog.log("[channel] " + req.user.name + "@" +
loggedIn: true, webserver.ipForRequest(req) + " deleted channel " +
loginName: loginName, name);
channels: err2 ? [] : channels, }
deleteChannelError: "You do not have permission to delete this channel" var sv = Server.getServer();
}); if (sv.isChannelLoaded(name)) {
var chan = sv.getChannel(name);
chan.clearFlag(require("../flags").C_REGISTERED);
var users = Array.prototype.slice.call(chan.users);
users.forEach(function (u) {
u.kick("Channel reloading");
}); });
return;
if (!chan.dead) {
chan.emit("empty");
}
} }
db.channels.listUserChannels(req.user.name, function (err2, channels) {
db.channels.drop(name, function (err) { sendJade(res, "account-channels", {
if (!err) { channels: err2 ? [] : channels,
Logger.eventlog.log("[channel] " + loginName + "@" + deleteChannelError: err ? err : undefined
webserver.ipForRequest(req) + " deleted channel " +
name);
}
var sv = Server.getServer();
if (sv.isChannelLoaded(name)) {
var chan = sv.getChannel(name);
chan.clearFlag(require("../flags").C_REGISTERED);
var users = Array.prototype.slice.call(chan.users);
users.forEach(function (u) {
u.kick("Channel reloading");
});
if (!chan.dead) {
chan.emit("empty");
}
}
db.channels.listUserChannels(loginName, function (err2, channels) {
sendJade(res, "account-channels", {
loggedIn: true,
loginName: loginName,
channels: err2 ? [] : channels,
deleteChannelError: err ? err : undefined
});
}); });
}); });
}); });
@ -421,70 +365,49 @@ function handleAccountProfilePage(req, res) {
return; return;
} }
var loginName = false; if (!req.user) {
if (!req.cookies.auth) {
return sendJade(res, "account-profile", { return sendJade(res, "account-profile", {
loggedIn: false,
profileImage: "", profileImage: "",
profileText: "" profileText: ""
}); });
} else {
loginName = req.cookies.auth.split(":")[0];
db.users.verifyAuth(req.cookies.auth, function (err, user) {
if (err) {
return sendJade(res, "account-profile", {
loggedIn: false
});
}
db.users.getProfile(loginName, function (err, profile) {
if (err) {
sendJade(res, "account-profile", {
loggedIn: true,
loginName: loginName,
profileError: err,
profileImage: "",
profileText: ""
});
return;
}
sendJade(res, "account-profile", {
loggedIn: true,
loginName: loginName,
profileImage: profile.image,
profileText: profile.text,
profileError: false
});
});
});
} }
db.users.getProfile(req.user.name, function (err, profile) {
if (err) {
sendJade(res, "account-profile", {
profileError: err,
profileImage: "",
profileText: ""
});
return;
}
sendJade(res, "account-profile", {
profileImage: profile.image,
profileText: profile.text,
profileError: false
});
});
} }
/** /**
* Handles a POST request to edit a profile * Handles a POST request to edit a profile
*/ */
function handleAccountProfile(req, res) { function handleAccountProfile(req, res) {
var loginName = false; if (!req.user) {
if (req.cookies.auth) { return sendJade(res, "account-profile", {
loginName = req.cookies.auth.split(":")[0];
} else {
sendJade(res, "account-profile", {
loggedIn: false,
profileImage: "", profileImage: "",
profileText: "", profileText: "",
profileError: "You must be logged in to edit your profile", profileError: "You must be logged in to edit your profile",
}); });
return;
} }
var image = req.body.image; var image = req.body.image;
var text = req.body.text; var text = req.body.text;
db.users.verifyAuth(req.cookies.auth, function (err, user) { db.users.setProfile(req.user.name, { image: image, text: text }, function (err) {
if (err) { if (err) {
sendJade(res, "account-profile", { sendJade(res, "account-profile", {
loggedIn: false,
profileImage: "", profileImage: "",
profileText: "", profileText: "",
profileError: err profileError: err
@ -492,25 +415,10 @@ function handleAccountProfile(req, res) {
return; return;
} }
db.users.setProfile(user.name, { image: image, text: text }, function (err) { sendJade(res, "account-profile", {
if (err) { profileImage: image,
sendJade(res, "account-profile", { profileText: text,
loggedIn: true, profileError: false
loginName: user.name,
profileImage: "",
profileText: "",
profileError: err
});
return;
}
sendJade(res, "account-profile", {
loggedIn: true,
loginName: user.name,
profileImage: image,
profileText: text,
profileError: false
});
}); });
}); });
} }
@ -664,8 +572,7 @@ function handlePasswordRecover(req, res) {
if (err) { if (err) {
sendJade(res, "account-passwordrecover", { sendJade(res, "account-passwordrecover", {
recovered: false, recovered: false,
recoverErr: err, recoverErr: err
loginName: false
}); });
return; return;
} }
@ -675,8 +582,7 @@ function handlePasswordRecover(req, res) {
recovered: false, recovered: false,
recoverErr: "This password recovery link has expired. Password " + recoverErr: "This password recovery link has expired. Password " +
"recovery links are valid only for 24 hours after " + "recovery links are valid only for 24 hours after " +
"submission.", "submission."
loginName: false
}); });
return; return;
} }
@ -691,8 +597,8 @@ function handlePasswordRecover(req, res) {
sendJade(res, "account-passwordrecover", { sendJade(res, "account-passwordrecover", {
recovered: false, recovered: false,
recoverErr: "Database error. Please contact an administrator if " + recoverErr: "Database error. Please contact an administrator if " +
"this persists.", "this persists."
loginName: false
}); });
return; return;
} }
@ -702,8 +608,7 @@ function handlePasswordRecover(req, res) {
sendJade(res, "account-passwordrecover", { sendJade(res, "account-passwordrecover", {
recovered: true, recovered: true,
recoverPw: newpw, recoverPw: newpw
loginName: false
}); });
}); });
}); });

View File

@ -8,31 +8,18 @@ var Config = require("../config");
function checkAdmin(cb) { function checkAdmin(cb) {
return function (req, res) { return function (req, res) {
var auth = req.cookies.auth; if (!req.user) {
if (!auth) { return res.send(403);
}
if (req.user.global_rank < 255) {
res.send(403); res.send(403);
Logger.eventlog.log("[acp] Attempted GET "+req.path+" from non-admin " +
user.name + "@" + webserver.ipForRequest(req));
return; return;
} }
db.users.verifyAuth(auth, function (err, user) {
if (err) {
if (err === "Invalid auth string" ||
err === "Auth string does not match an existing user") {
res.send(403);
} else {
res.send(500);
}
return;
}
if (user.global_rank < 255) { cb(req, res, req.user);
res.send(403);
Logger.eventlog.log("[acp] Attempted GET "+req.path+" from non-admin " +
user.name + "@" + webserver.ipForRequest(req));
return;
}
cb(req, res, user);
});
}; };
} }
@ -49,8 +36,6 @@ function handleAcp(req, res, user) {
sio += "/socket.io/socket.io.js"; sio += "/socket.io/socket.io.js";
sendJade(res, "acp", { sendJade(res, "acp", {
loginName: user.name,
loggedIn: true,
sioSource: sio sioSource: sio
}); });
} }

View File

@ -14,6 +14,7 @@ var $util = require("../utilities");
var db = require("../database"); var db = require("../database");
var Config = require("../config"); var Config = require("../config");
var url = require("url"); var url = require("url");
var session = require("../session");
/** /**
* Processes a login request. Sets a cookie upon successful authentication * Processes a login request. Sets a cookie upon successful authentication
@ -22,12 +23,28 @@ function handleLogin(req, res) {
var name = req.body.name; var name = req.body.name;
var password = req.body.password; var password = req.body.password;
var rememberMe = req.body.remember; var rememberMe = req.body.remember;
var dest = req.body.dest || req.header("referer") || null;
dest = dest.match(/login|logout/) ? null : dest;
if (typeof name !== "string" || typeof password !== "string") { if (typeof name !== "string" || typeof password !== "string") {
res.send(400); res.send(400);
return; return;
} }
var host = req.hostname;
if (host.indexOf(Config.get("http.root-domain")) === -1 &&
Config.get("http.alt-domains").indexOf(host) === -1) {
Logger.syslog.log("WARNING: Attempted login from non-approved domain " + host);
return res.send(403);
}
var expiration;
if (rememberMe) {
expiration = new Date("Fri, 31 Dec 9999 23:59:59 GMT");
} else {
expiration = new Date(Date.now() + 7*24*60*60*1000);
}
password = password.substring(0, 100); password = password.substring(0, 100);
db.users.verifyLogin(name, password, function (err, user) { db.users.verifyLogin(name, password, function (err, user) {
@ -40,126 +57,41 @@ function handleLogin(req, res) {
loggedIn: false, loggedIn: false,
loginError: err loginError: err
}); });
} else { return;
var auth = user.name + ":" + user.hash; }
var expiration;
if (rememberMe) {
expiration = new Date("Fri, 31 Dec 9999 23:59:59 GMT");
} else {
expiration = new Date(Date.now() + 7*24*60*60*1000);
}
res.cookie("auth", auth, { session.genSession(user, expiration, function (err, auth) {
expires: expiration, if (err) {
httpOnly: true
});
res.cookie("auth", auth, {
domain: Config.get("http.root-domain-dotted"),
expires: expiration,
httpOnly: true
});
// Try to find an appropriate redirect
var ref = req.header("referrer");
if (!ref) {
ref = req.body.redirect;
}
if (typeof ref !== "string") {
ref = "";
}
// Redirect to shim cookie layer if the host doesn't match
try {
var data = url.parse(ref);
if (data.host.indexOf(Config.get("http.root-domain")) === -1) {
var host = data.host.replace(/:\d+$/, "");
if (Config.get("http.alt-domains").indexOf(host) === -1) {
Logger.syslog.log("WARNING: Attempted login from non-approved "+
"domain " + host);
} else {
var dest = "/shimcookie?auth=" + encodeURIComponent(auth) +
"&rank=" + encodeURIComponent(user.global_rank) +
"&redirect=" + encodeURIComponent(ref);
res.redirect(data.protocol + "//" + data.host + dest);
return;
}
}
} catch (e) {
}
if (ref.match(/login|logout/)) {
ref = "";
}
if (ref) {
res.redirect(ref);
} else {
sendJade(res, "login", { sendJade(res, "login", {
loggedIn: true, loggedIn: false,
loginName: user.name loginError: err
});
return;
}
if (req.hostname.indexOf(Config.get("http.root-domain")) >= 0) {
res.cookie("auth", auth, {
domain: Config.get("http.root-domain-dotted"),
expires: expiration,
httpOnly: true,
signed: true
});
} else {
res.cookie("auth", auth, {
expires: expiration,
httpOnly: true,
signed: true
}); });
} }
}
});
}
function handleShimCookie(req, res) { if (dest) {
var auth = req.query.auth; res.redirect(dest);
var rank = req.query.rank; } else {
var redirect = req.query.redirect; res.user = user;
if (typeof auth !== "string" || typeof redirect !== "string" || sendJade(res, "login", {});
typeof rank !== "string") { }
res.send(400);
return;
}
res.cookie("auth", auth, {
expires: new Date(Date.now() + 7*24*60*60*1000),
httpOnly: true
});
res.cookie("rank", rank, {
expires: new Date(Date.now() + 7*24*60*60*1000),
});
if (redirect.match(/login|logout/)) {
redirect = "";
}
if (redirect) {
res.redirect(redirect);
} else {
sendJade(res, "login", {
loggedIn: true,
loginName: auth.split(":")[0]
}); });
} });
}
function handleShimLogout(req, res) {
var redirect = req.query.redirect;
if (typeof redirect !== "string") {
res.send(400);
return;
}
res.clearCookie("auth");
res.clearCookie("rank");
res.clearCookie("auth", { domain: Config.get("http.root-domain-dotted") });
res.clearCookie("rank", { domain: Config.get("http.root-domain-dotted") });
if (redirect.match(/login|logout/)) {
redirect = "";
}
if (redirect) {
res.redirect(redirect);
} else {
sendJade(res, "logout", {});
}
} }
/** /**
@ -170,20 +102,14 @@ function handleLoginPage(req, res) {
return; return;
} }
if (req.cookies.auth) { if (req.user) {
var split = req.cookies.auth.split(":"); return sendJade(res, "login", {
if (split.length === 2) { wasAlreadyLoggedIn: true
sendJade(res, "login", { });
wasAlreadyLoggedIn: true,
loggedIn: true,
loginName: split[0]
});
return;
}
} }
sendJade(res, "login", { sendJade(res, "login", {
loggedIn: false, redirect: req.query.dest || req.header("referer")
redirect: req.header("Referrer")
}); });
} }
@ -192,34 +118,17 @@ function handleLoginPage(req, res) {
*/ */
function handleLogout(req, res) { function handleLogout(req, res) {
res.clearCookie("auth"); res.clearCookie("auth");
res.clearCookie("rank");
// Try to find an appropriate redirect // Try to find an appropriate redirect
var ref = req.header("referrer"); var dest = req.query.dest || req.header("referer");
if (!ref) { dest = dest.match(/login|logout|account/) ? null : dest;
ref = req.query.redirect;
}
if (typeof ref !== "string") {
ref = "";
}
var host = req.hostname; var host = req.hostname;
if (host.indexOf(Config.get("http.root-domain")) !== -1) { if (host.indexOf(Config.get("http.root-domain")) !== -1) {
res.clearCookie("auth", { domain: Config.get("http.root-domain-dotted") }); res.clearCookie("auth", { domain: Config.get("http.root-domain-dotted") });
res.clearCookie("rank", { domain: Config.get("http.root-domain-dotted") }); }
} else {
var dest = Config.get("http.full-address") + "/shimlogout?redirect=" + if (dest) {
encodeURIComponent(ref);
res.redirect(dest); res.redirect(dest);
return;
}
if (ref.match(/login|logout/)) {
ref = "";
}
if (ref) {
res.redirect(ref);
} else { } else {
sendJade(res, "logout", {}); sendJade(res, "logout", {});
} }
@ -233,15 +142,9 @@ function handleRegisterPage(req, res) {
return; return;
} }
if (req.cookies.auth) { if (req.user) {
var split = req.cookies.auth.split(":"); sendJade(res, "register", {});
if (split.length === 2) { return;
sendJade(res, "register", {
loggedIn: true,
loginName: split[0]
});
return;
}
} }
sendJade(res, "register", { sendJade(res, "register", {
@ -324,7 +227,5 @@ module.exports = {
app.get("/logout", handleLogout); app.get("/logout", handleLogout);
app.get("/register", handleRegisterPage); app.get("/register", handleRegisterPage);
app.post("/register", handleRegister); app.post("/register", handleRegister);
app.get("/shimcookie", handleShimCookie);
app.get("/shimlogout", handleShimLogout);
} }
}; };

View File

@ -3,13 +3,12 @@ var fs = require("fs");
var path = require("path"); var path = require("path");
var Config = require("../config"); var Config = require("../config");
var templates = path.join(__dirname, "..", "..", "templates"); var templates = path.join(__dirname, "..", "..", "templates");
var cache = {}; var cache = {};
/** /**
* Merges locals with globals for jade rendering * Merges locals with globals for jade rendering
*/ */
function merge(locals) { function merge(locals, res) {
var _locals = { var _locals = {
siteTitle: Config.get("html-template.title"), siteTitle: Config.get("html-template.title"),
siteDescription: Config.get("html-template.description"), siteDescription: Config.get("html-template.description"),
@ -30,6 +29,8 @@ function merge(locals) {
* Renders and serves a jade template * Renders and serves a jade template
*/ */
function sendJade(res, view, locals) { function sendJade(res, view, locals) {
locals.loggedIn = locals.loggedIn || !!res.user;
locals.loginName = locals.loginName || res.user ? res.user.name : false;
if (!(view in cache) || Config.get("debug")) { if (!(view in cache) || Config.get("debug")) {
var file = path.join(templates, view + ".jade"); var file = path.join(templates, view + ".jade");
var fn = jade.compile(fs.readFileSync(file), { var fn = jade.compile(fs.readFileSync(file), {
@ -38,7 +39,7 @@ function sendJade(res, view, locals) {
}); });
cache[view] = fn; cache[view] = fn;
} }
var html = cache[view](merge(locals)); var html = cache[view](merge(locals, res));
res.send(html); res.send(html);
} }

View File

@ -13,6 +13,7 @@ var bodyParser = require("body-parser");
var cookieParser = require("cookie-parser"); var cookieParser = require("cookie-parser");
var static = require("serve-static"); var static = require("serve-static");
var morgan = require("morgan"); var morgan = require("morgan");
var session = require("../session");
const LOG_FORMAT = ':real-address - :remote-user [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"'; const LOG_FORMAT = ':real-address - :remote-user [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"';
morgan.token('real-address', function (req) { return req._ip; }); morgan.token('real-address', function (req) { return req._ip; });
@ -46,6 +47,10 @@ function ipForRequest(req) {
function redirectHttps(req, res) { function redirectHttps(req, res) {
if (!req.secure && Config.get("https.enabled")) { if (!req.secure && Config.get("https.enabled")) {
var ssldomain = Config.get("https.full-address"); var ssldomain = Config.get("https.full-address");
if (ssldomain.indexOf(req.hostname) < 0) {
return false;
}
res.redirect(ssldomain + req.path); res.redirect(ssldomain + req.path);
return true; return true;
} }
@ -74,11 +79,6 @@ function handleChannel(req, res) {
return; return;
} }
var loginName = false;
if (req.cookies.auth) {
loginName = req.cookies.auth.split(":")[0];
}
var sio; var sio;
if (net.isIPv6(ipForRequest(req))) { if (net.isIPv6(ipForRequest(req))) {
sio = Config.get("io.ipv6-default"); sio = Config.get("io.ipv6-default");
@ -92,8 +92,6 @@ function handleChannel(req, res) {
sendJade(res, "channel", { sendJade(res, "channel", {
channelName: req.params.channel, channelName: req.params.channel,
loggedIn: loginName !== false,
loginName: loginName,
sioSource: sio sioSource: sio
}); });
} }
@ -102,11 +100,6 @@ function handleChannel(req, res) {
* Handles a request for the index page * Handles a request for the index page
*/ */
function handleIndex(req, res) { function handleIndex(req, res) {
var loginName = false;
if (req.cookies.auth) {
loginName = req.cookies.auth.split(":")[0];
}
var channels = Server.getServer().packChannelList(true); var channels = Server.getServer().packChannelList(true);
channels.sort(function (a, b) { channels.sort(function (a, b) {
if (a.usercount === b.usercount) { if (a.usercount === b.usercount) {
@ -117,8 +110,6 @@ function handleIndex(req, res) {
}); });
sendJade(res, "index", { sendJade(res, "index", {
loggedIn: loginName !== false,
loginName: loginName,
channels: channels channels: channels
}); });
} }
@ -142,30 +133,19 @@ function handleSocketConfig(req, res) {
if (!iourl) { if (!iourl) {
iourl = Config.get("io.ipv4-default"); iourl = Config.get("io.ipv4-default");
} }
sioconfig += "var IO_URL='" + iourl + "';"; sioconfig += "var IO_URL='" + iourl + "';";
sioconfig += "var IO_V6=" + ipv6 + ";"; sioconfig += "var IO_V6=" + ipv6 + ";";
res.send(sioconfig); res.send(sioconfig);
} }
function handleUserAgreement(req, res) { function handleUserAgreement(req, res) {
var loginName = false;
if (req.cookies.auth) {
loginName = req.cookies.auth.split(":")[0];
}
sendJade(res, "tos", { sendJade(res, "tos", {
loggedIn: loginName !== false,
loginName: loginName,
domain: Config.get("http.domain") domain: Config.get("http.domain")
}); });
} }
function handleContactPage(req, res) { function handleContactPage(req, res) {
var loginName = false;
if (req.cookies.auth) {
loginName = req.cookies.auth.split(":")[0];
}
// Make a copy to prevent messing with the original // Make a copy to prevent messing with the original
var contacts = Config.get("contacts").map(function (c) { var contacts = Config.get("contacts").map(function (c) {
return { return {
@ -189,8 +169,6 @@ function handleContactPage(req, res) {
}); });
sendJade(res, "contact", { sendJade(res, "contact", {
loggedIn: loginName !== false,
loginName: loginName,
contacts: contacts contacts: contacts
}); });
} }
@ -208,7 +186,10 @@ module.exports = {
extended: false, extended: false,
limit: '1kb' // No POST data should ever exceed this size under normal usage limit: '1kb' // No POST data should ever exceed this size under normal usage
})); }));
app.use(cookieParser()); if (Config.get("http.cookie-secret") === "change-me") {
Logger.errlog.log("YOU SHOULD CHANGE THE VALUE OF cookie-secret IN config.yaml");
}
app.use(cookieParser(Config.get("http.cookie-secret")));
app.use(morgan(LOG_FORMAT, { app.use(morgan(LOG_FORMAT, {
stream: require("fs").createWriteStream(path.join(__dirname, "..", "..", stream: require("fs").createWriteStream(path.join(__dirname, "..", "..",
"http.log"), { "http.log"), {
@ -217,6 +198,24 @@ module.exports = {
}) })
})); }));
app.use(function (req, res, next) {
if (req.path.match(/^\/(css|js|img|boop).*$/)) {
return next();
}
if (!req.signedCookies || !req.signedCookies.auth) {
return next();
}
session.verifySession(req.signedCookies.auth, function (err, account) {
if (!err) {
req.user = res.user = account;
}
next();
});
});
if (Config.get("http.gzip")) { if (Config.get("http.gzip")) {
app.use(require("compression")({ threshold: Config.get("http.gzip-threshold") })); app.use(require("compression")({ threshold: Config.get("http.gzip-threshold") }));
Logger.syslog.log("Enabled gzip compression"); Logger.syslog.log("Enabled gzip compression");
@ -267,5 +266,5 @@ module.exports = {
redirectHttps: redirectHttps, redirectHttps: redirectHttps,
redirectHttp: redirectHttp, redirectHttp: redirectHttp
}; };

View File

@ -12,7 +12,7 @@ html(lang="en")
ul.nav.navbar-nav ul.nav.navbar-nav
mixin navdefaultlinks("/login") mixin navdefaultlinks("/login")
if loggedIn if loggedIn
mixin navlogoutform() mixin navlogoutform("/")
section#mainpage.container section#mainpage.container
if wasAlreadyLoggedIn if wasAlreadyLoggedIn
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3 .col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
@ -27,7 +27,7 @@ html(lang="en")
h2 Login h2 Login
form(role="form", action="/login", method="post") form(role="form", action="/login", method="post")
if redirect if redirect
input(type="hidden", name="redirect", value=redirect) input(type="hidden", name="dest", value=redirect)
.form-group .form-group
label(for="username") Username label(for="username") Username
input#username.form-control(type="text", name="name") input#username.form-control(type="text", name="name")

View File

@ -11,7 +11,7 @@ html(lang="en")
#nav-collapsible.collapse.navbar-collapse #nav-collapsible.collapse.navbar-collapse
ul.nav.navbar-nav ul.nav.navbar-nav
mixin navdefaultlinks("/logout") mixin navdefaultlinks("/logout")
mixin navloginform() mixin navloginform("/")
section#mainpage.container section#mainpage.container
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3 .col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
.alert.alert-info.center.messagebox .alert.alert-info.center.messagebox

View File

@ -27,13 +27,13 @@ mixin navdefaultlinks(page)
b.caret b.caret
ul.dropdown-menu ul.dropdown-menu
if loggedIn if loggedIn
li: a(href="/logout?redirect=#{page}") Logout li: a(href="/logout?dest=#{page}") Logout
li.divider li.divider
li: a(href="/account/channels") Channels li: a(href="/account/channels") Channels
li: a(href="/account/profile") Profile li: a(href="/account/profile") Profile
li: a(href="/account/edit") Change Password/Email li: a(href="/account/edit") Change Password/Email
else else
li: a(href="/login") Login li: a(href="/login?dest=#{page}") Login
li: a(href="/register") Register li: a(href="/register") Register
mixin navloginlogout(redirect) mixin navloginlogout(redirect)
@ -47,7 +47,7 @@ mixin navloginform(redirect)
- loginDomain = "" - loginDomain = ""
.visible-lg .visible-lg
form#loginform.navbar-form.navbar-right(action="#{loginDomain}/login", method="post") form#loginform.navbar-form.navbar-right(action="#{loginDomain}/login", method="post")
input(type="hidden", name="redirect", value=redirect) input(type="hidden", name="dest", value=redirect)
.form-group .form-group
input#username.form-control(type="text", name="name", placeholder="Username") input#username.form-control(type="text", name="name", placeholder="Username")
.form-group .form-group
@ -60,19 +60,13 @@ mixin navloginform(redirect)
button#login.btn.btn-default(type="submit") Login button#login.btn.btn-default(type="submit") Login
.visible-md .visible-md
p#loginform.navbar-text.pull-right p#loginform.navbar-text.pull-right
a#login.navbar-link(href="#{loginDomain}/login?redirect=#{redirect}") Log in a#login.navbar-link(href="#{loginDomain}/login?dest=#{redirect}") Log in
span &nbsp;&middot;&nbsp; span &nbsp;&middot;&nbsp;
a#register.navbar-link(href="/register") Register a#register.navbar-link(href="/register") Register
mixin navlogoutform(redirect) mixin navlogoutform(redirect)
if loginDomain == null
- loginDomain = ""
if redirect
- url = "logout?redirect=" + redirect
else
- url = "logout"
p#logoutform.navbar-text.pull-right p#logoutform.navbar-text.pull-right
span#welcome Welcome, #{loginName} span#welcome Welcome, #{loginName}
span &nbsp;&middot;&nbsp; span &nbsp;&middot;&nbsp;
a#logout.navbar-link(href="#{loginDomain}/#{url}") Logout a#logout.navbar-link(href="/logout?dest=#{redirect}") Logout