Merge remote-tracking branch 'upstream/3.0' into 3.0

This commit is contained in:
bush6 2015-02-24 08:28:08 +10:00
commit c2a00420f2
20 changed files with 419 additions and 499 deletions

View File

@ -64,6 +64,8 @@ http:
gzip: true
# Customize the threshold byte size for applying gzip
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:

View File

@ -33,7 +33,8 @@ var defaults = {
minify: false,
"max-age": "7d",
gzip: true,
"gzip-threshold": 1024
"gzip-threshold": 1024,
"cookie-secret": "change-me"
},
https: {
enabled: false,
@ -207,7 +208,7 @@ function preprocessConfig(cfg) {
var root = cfg.http["root-domain"];
root = root.replace(/^\.*/, "");
cfg.http["root-domain"] = root;
if (root.indexOf(".") !== -1) {
if (root.indexOf(".") !== -1 && !net.isIP(root)) {
root = "." + root;
}
cfg.http["root-domain-dotted"] = root;

View File

@ -1,4 +1,3 @@
//var db = require("../database");
var $util = require("../utilities");
var bcrypt = require("bcrypt");
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
*/
@ -198,11 +216,7 @@ module.exports = {
} else if (!match) {
callback("Invalid username/password combination", null);
} else {
callback(null, {
name: rows[0].name,
hash: rows[0].password,
global_rank: rows[0].global_rank
});
callback(null, rows[0]);
}
});
});

View File

@ -6,7 +6,8 @@ const TBL_USERS = "" +
"`global_rank` INT NOT NULL," +
"`email` VARCHAR(255) 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`)," +
"UNIQUE(`name`)) " +
"CHARACTER SET utf8";

View File

@ -1,10 +1,10 @@
var sio = require("socket.io");
var cookieParser = require("cookie-parser")();
var Logger = require("../logger");
var db = require("../database");
var User = require("../user");
var Server = require("../server");
var Config = require("../config");
var cookieParser = require("cookie-parser")(Config.get("http.cookie-secret"));
var $util = require("../utilities");
var Flags = require("../flags");
var Account = require("../account");
@ -13,6 +13,7 @@ var net = require("net");
var util = require("../utilities");
var crypto = require("crypto");
var isTorExit = require("../tor").isTorExit;
var session = require("../session");
var CONNECT_RATE = {
burst: 5,
@ -32,8 +33,12 @@ function handleAuth(socket, accept) {
socket.user = false;
if (data.headers.cookie) {
cookieParser(data, null, function () {
var auth = data.cookies.auth;
db.users.verifyAuth(auth, function (err, user) {
var auth = data.signedCookies.auth;
if (!auth) {
return accept(null, true);
}
session.verifySession(auth, function (err, user) {
if (!err) {
socket.user = {
name: user.name,

54
lib/session.js Normal file
View File

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

View File

@ -8,31 +8,18 @@ var Config = require("../config");
function checkAdmin(cb) {
return function (req, res) {
var auth = req.cookies.auth;
if (!auth) {
if (!req.user) {
return res.send(403);
}
if (req.user.global_rank < 255) {
res.send(403);
Logger.eventlog.log("[acp] Attempted GET "+req.path+" from non-admin " +
user.name + "@" + webserver.ipForRequest(req));
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) {
res.send(403);
Logger.eventlog.log("[acp] Attempted GET "+req.path+" from non-admin " +
user.name + "@" + webserver.ipForRequest(req));
return;
}
cb(req, res, user);
});
cb(req, res, req.user);
};
}
@ -49,8 +36,6 @@ function handleAcp(req, res, user) {
sio += "/socket.io/socket.io.js";
sendJade(res, "acp", {
loginName: user.name,
loggedIn: true,
sioSource: sio
});
}

View File

@ -14,6 +14,7 @@ var $util = require("../utilities");
var db = require("../database");
var Config = require("../config");
var url = require("url");
var session = require("../session");
/**
* Processes a login request. Sets a cookie upon successful authentication
@ -21,12 +22,29 @@ var url = require("url");
function handleLogin(req, res) {
var name = req.body.name;
var password = req.body.password;
var rememberMe = req.body.remember;
var dest = req.body.dest || req.header("referer") || null;
dest = dest && dest.match(/login|logout/) ? null : dest;
if (typeof name !== "string" || typeof password !== "string") {
res.send(400);
res.sendStatus(400);
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.sendStatus(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);
db.users.verifyLogin(name, password, function (err, user) {
@ -39,124 +57,43 @@ function handleLogin(req, res) {
loggedIn: false,
loginError: err
});
} else {
var auth = user.name + ":" + user.hash;
res.cookie("auth", auth, {
expires: new Date(Date.now() + 7*24*60*60*1000),
httpOnly: true
});
return;
}
res.cookie("auth", auth, {
domain: Config.get("http.root-domain-dotted"),
expires: new Date(Date.now() + 7*24*60*60*1000),
httpOnly: true
});
res.cookie("rank", user.global_rank, {
domain: Config.get("http.root-domain-dotted"),
expires: new Date(Date.now() + 7*24*60*60*1000),
});
// 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 {
session.genSession(user, expiration, function (err, auth) {
if (err) {
sendJade(res, "login", {
loggedIn: true,
loginName: user.name
loggedIn: false,
loginError: err
});
return;
}
if (req.hostname.indexOf(Config.get("http.root-domain")) >= 0) {
// Prevent non-root cookie from screwing things up
res.clearCookie("auth");
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) {
var auth = req.query.auth;
var rank = req.query.rank;
var redirect = req.query.redirect;
if (typeof auth !== "string" || typeof redirect !== "string" ||
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]
if (dest) {
res.redirect(dest);
} else {
res.user = user;
sendJade(res, "login", {});
}
});
}
}
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", {});
}
});
}
/**
@ -167,20 +104,14 @@ function handleLoginPage(req, res) {
return;
}
if (req.cookies.auth) {
var split = req.cookies.auth.split(":");
if (split.length === 2) {
sendJade(res, "login", {
wasAlreadyLoggedIn: true,
loggedIn: true,
loginName: split[0]
});
return;
}
if (req.user) {
return sendJade(res, "login", {
wasAlreadyLoggedIn: true
});
}
sendJade(res, "login", {
loggedIn: false,
redirect: req.header("Referrer")
redirect: req.query.dest || req.header("referer")
});
}
@ -189,34 +120,17 @@ function handleLoginPage(req, res) {
*/
function handleLogout(req, res) {
res.clearCookie("auth");
res.clearCookie("rank");
// Try to find an appropriate redirect
var ref = req.header("referrer");
if (!ref) {
ref = req.query.redirect;
}
if (typeof ref !== "string") {
ref = "";
}
var dest = req.query.dest || req.header("referer");
dest = dest && dest.match(/login|logout|account/) ? null : dest;
var host = req.hostname;
if (host.indexOf(Config.get("http.root-domain")) !== -1) {
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=" +
encodeURIComponent(ref);
}
if (dest) {
res.redirect(dest);
return;
}
if (ref.match(/login|logout/)) {
ref = "";
}
if (ref) {
res.redirect(ref);
} else {
sendJade(res, "logout", {});
}
@ -230,15 +144,9 @@ function handleRegisterPage(req, res) {
return;
}
if (req.cookies.auth) {
var split = req.cookies.auth.split(":");
if (split.length === 2) {
sendJade(res, "register", {
loggedIn: true,
loginName: split[0]
});
return;
}
if (req.user) {
sendJade(res, "register", {});
return;
}
sendJade(res, "register", {
@ -260,7 +168,7 @@ function handleRegister(req, res) {
var ip = webserver.ipForRequest(req);
if (typeof name !== "string" || typeof password !== "string") {
res.send(400);
res.sendStatus(400);
return;
}
@ -321,7 +229,5 @@ module.exports = {
app.get("/logout", handleLogout);
app.get("/register", handleRegisterPage);
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 Config = require("../config");
var templates = path.join(__dirname, "..", "..", "templates");
var cache = {};
/**
* Merges locals with globals for jade rendering
*/
function merge(locals) {
function merge(locals, res) {
var _locals = {
siteTitle: Config.get("html-template.title"),
siteDescription: Config.get("html-template.description"),
@ -30,6 +29,8 @@ function merge(locals) {
* Renders and serves a jade template
*/
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")) {
var file = path.join(templates, view + ".jade");
var fn = jade.compile(fs.readFileSync(file), {
@ -38,7 +39,7 @@ function sendJade(res, view, locals) {
});
cache[view] = fn;
}
var html = cache[view](merge(locals));
var html = cache[view](merge(locals, res));
res.send(html);
}

View File

@ -13,6 +13,7 @@ var bodyParser = require("body-parser");
var cookieParser = require("cookie-parser");
var static = require("serve-static");
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"';
morgan.token('real-address', function (req) { return req._ip; });
@ -46,6 +47,10 @@ function ipForRequest(req) {
function redirectHttps(req, res) {
if (!req.secure && Config.get("https.enabled")) {
var ssldomain = Config.get("https.full-address");
if (ssldomain.indexOf(req.hostname) < 0) {
return false;
}
res.redirect(ssldomain + req.path);
return true;
}
@ -74,11 +79,6 @@ function handleChannel(req, res) {
return;
}
var loginName = false;
if (req.cookies.auth) {
loginName = req.cookies.auth.split(":")[0];
}
var sio;
if (net.isIPv6(ipForRequest(req))) {
sio = Config.get("io.ipv6-default");
@ -92,8 +92,6 @@ function handleChannel(req, res) {
sendJade(res, "channel", {
channelName: req.params.channel,
loggedIn: loginName !== false,
loginName: loginName,
sioSource: sio
});
}
@ -102,11 +100,6 @@ function handleChannel(req, res) {
* Handles a request for the index page
*/
function handleIndex(req, res) {
var loginName = false;
if (req.cookies.auth) {
loginName = req.cookies.auth.split(":")[0];
}
var channels = Server.getServer().packChannelList(true);
channels.sort(function (a, b) {
if (a.usercount === b.usercount) {
@ -117,8 +110,6 @@ function handleIndex(req, res) {
});
sendJade(res, "index", {
loggedIn: loginName !== false,
loginName: loginName,
channels: channels
});
}
@ -142,30 +133,19 @@ function handleSocketConfig(req, res) {
if (!iourl) {
iourl = Config.get("io.ipv4-default");
}
sioconfig += "var IO_URL='" + iourl + "';";
sioconfig += "var IO_V6=" + ipv6 + ";";
res.send(sioconfig);
}
function handleUserAgreement(req, res) {
var loginName = false;
if (req.cookies.auth) {
loginName = req.cookies.auth.split(":")[0];
}
sendJade(res, "tos", {
loggedIn: loginName !== false,
loginName: loginName,
domain: Config.get("http.domain")
});
}
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
var contacts = Config.get("contacts").map(function (c) {
return {
@ -189,8 +169,6 @@ function handleContactPage(req, res) {
});
sendJade(res, "contact", {
loggedIn: loginName !== false,
loginName: loginName,
contacts: contacts
});
}
@ -208,7 +186,10 @@ module.exports = {
extended: false,
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, {
stream: require("fs").createWriteStream(path.join(__dirname, "..", "..",
"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")) {
app.use(require("compression")({ threshold: Config.get("http.gzip-threshold") }));
Logger.syslog.log("Enabled gzip compression");
@ -267,5 +266,5 @@ module.exports = {
redirectHttps: redirectHttps,
redirectHttp: redirectHttp,
redirectHttp: redirectHttp
};

View File

@ -12,7 +12,7 @@ html(lang="en")
ul.nav.navbar-nav
mixin navdefaultlinks("/login")
if loggedIn
mixin navlogoutform()
mixin navlogoutform("/")
section#mainpage.container
if wasAlreadyLoggedIn
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
@ -27,7 +27,7 @@ html(lang="en")
h2 Login
form(role="form", action="/login", method="post")
if redirect
input(type="hidden", name="redirect", value=redirect)
input(type="hidden", name="dest", value=redirect)
.form-group
label(for="username") Username
input#username.form-control(type="text", name="name")
@ -35,6 +35,11 @@ html(lang="en")
label(for="password") Password
input#password.form-control(type="password", name="password")
a(href="/account/passwordreset") Forgot password?
.form-group
.checkbox
label
input(type="checkbox", name="remember")
| Remember me
button.btn.btn-success.btn-block(type="submit") Login
else
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3

View File

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

View File

@ -27,13 +27,13 @@ mixin navdefaultlinks(page)
b.caret
ul.dropdown-menu
if loggedIn
li: a(href="/logout?redirect=#{page}") Logout
li: a(href="/logout?dest=#{page}") Logout
li.divider
li: a(href="/account/channels") Channels
li: a(href="/account/profile") Profile
li: a(href="/account/edit") Change Password/Email
else
li: a(href="/login") Login
li: a(href="/login?dest=#{page}") Login
li: a(href="/register") Register
mixin navloginlogout(redirect)
@ -43,25 +43,30 @@ mixin navloginlogout(redirect)
mixin navloginform(redirect)
mixin navloginform(redirect)
.visible-md.visible-lg
if loginDomain == null
- loginDomain = ""
if loginDomain == null
- loginDomain = ""
.visible-lg
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
input#username.form-control(type="text", name="name", placeholder="Username")
.form-group
input#password.form-control(type="password", name="password", placeholder="Password")
.form-group
.checkbox
label
input(type="checkbox", name="remember")
span.navbar-text-nofloat Remember me
button#login.btn.btn-default(type="submit") Login
.visible-md
p#loginform.navbar-text.pull-right
a#login.navbar-link(href="#{loginDomain}/login?dest=#{redirect}") Log in
span &nbsp;&middot;&nbsp;
a#register.navbar-link(href="/register") Register
mixin navlogoutform(redirect)
if loginDomain == null
- loginDomain = ""
if redirect
- url = "logout?redirect=" + redirect
else
- url = "logout"
p#logoutform.navbar-text.pull-right
span#welcome Welcome, #{loginName}
span &nbsp;&middot;&nbsp;
a#logout.navbar-link(href="#{loginDomain}/#{url}") Logout
a#logout.navbar-link(href="/logout?dest=#{redirect}") Logout

View File

@ -69,3 +69,11 @@ footer {
.queue_entry.queue_active {
background-color: #d9edf7;
}
.navbar-inverse .navbar-text-nofloat {
color: #9d9d9d;
}
.queue_entry.queue_active {
background-color: #d9edf7;
}

View File

@ -78,6 +78,10 @@ input.form-control[type="email"], textarea.form-control {
background-color: #060606;
}
.navbar-inverse .navbar-text-nofloat {
color: #888;
}
.queue_entry.queue_active {
background-color: #333333;
}

View File

@ -63,3 +63,11 @@ footer {
.queue_entry.queue_active {
background-color: #d9edf7;
}
.navbar-inverse .navbar-text-nofloat {
color: #9d9d9d;
}
.queue_entry.queue_active {
background-color: #d9edf7;
}

View File

@ -169,3 +169,7 @@ input.form-control[type="email"], textarea.form-control {
#newpollbtn {
margin-top: 10px;
}
.navbar-inverse .navbar-text-nofloat {
color: #c8c8c8;
}

View File

@ -93,3 +93,7 @@ input.form-control[type="email"], textarea.form-control {
border-color: #aaaaaa;
background-color: #272b30;
}
.navbar-inverse .navbar-text-nofloat {
color: #ccc;
}

View File

@ -251,6 +251,9 @@ function addUserDropdown(entry) {
.text("Kick")
.click(function () {
var reason = prompt("Enter kick reason (optional)");
if (reason === null) {
return;
}
socket.emit("chatMsg", {
msg: "/kick " + name + " " + reason,
meta: {}
@ -302,6 +305,9 @@ function addUserDropdown(entry) {
.text("Name Ban")
.click(function () {
var reason = prompt("Enter ban reason (optional)");
if (reason === null) {
return;
}
socket.emit("chatMsg", {
msg: "/ban " + name + " " + reason,
meta: {}
@ -312,6 +318,9 @@ function addUserDropdown(entry) {
.text("IP Ban")
.click(function () {
var reason = prompt("Enter ban reason (optional)");
if (reason === null) {
return;
}
socket.emit("chatMsg", {
msg: "/ipban " + name + " " + reason,
meta: {}
@ -1317,21 +1326,21 @@ function parseMediaLink(url) {
/* Shorthand URIs */
// To catch Google Plus by ID alone
if ((m = url.match(/(?:gp:)?(\d{21}_\d{19}_\d{19})/))) {
if ((m = url.match(/^(?:gp:)?(\d{21}_\d{19}_\d{19})/))) {
return {
id: m[1],
type: "gp"
};
}
// So we still trim DailyMotion URLs
if((m = url.match(/dm:([^\?&#_]+)/))) {
if((m = url.match(/^dm:([^\?&#_]+)/))) {
return {
id: m[1],
type: "dm"
};
}
// Generic for the rest.
if ((m = url.match(/([a-z]{2}):([^\?&#]+)/))) {
if ((m = url.match(/^([a-z]{2}):([^\?&#]+)/))) {
return {
id: m[2],
type: m[1]