From b579db531041ab74d770f287c6bb1a035a9f3e85 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 15 Feb 2015 21:56:00 -0600 Subject: [PATCH] Change login sessions --- config.template.yaml | 2 + lib/config.js | 3 +- lib/database/accounts.js | 26 ++- lib/database/tables.js | 3 +- lib/session.js | 50 +++++ lib/web/account.js | 411 +++++++++++++++------------------------ lib/web/acp.js | 31 +-- lib/web/auth.js | 219 ++++++--------------- lib/web/jade.js | 7 +- lib/web/webserver.js | 59 +++--- templates/login.jade | 4 +- templates/logout.jade | 2 +- templates/nav.jade | 16 +- 13 files changed, 343 insertions(+), 490 deletions(-) create mode 100644 lib/session.js diff --git a/config.template.yaml b/config.template.yaml index acc16472..7b8d3829 100644 --- a/config.template.yaml +++ b/config.template.yaml @@ -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: diff --git a/lib/config.js b/lib/config.js index 818e0c57..f6b17453 100644 --- a/lib/config.js +++ b/lib/config.js @@ -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, diff --git a/lib/database/accounts.js b/lib/database/accounts.js index ea9da3a2..51f0a101 100644 --- a/lib/database/accounts.js +++ b/lib/database/accounts.js @@ -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]); } }); }); diff --git a/lib/database/tables.js b/lib/database/tables.js index 0b4cb841..ab72cfd2 100644 --- a/lib/database/tables.js +++ b/lib/database/tables.js @@ -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"; diff --git a/lib/session.js b/lib/session.js new file mode 100644 index 00000000..59b27452 --- /dev/null +++ b/lib/session.js @@ -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); + }); +}; diff --git a/lib/web/account.js b/lib/web/account.js index dfea1060..20a9dc27 100644 --- a/lib/web/account.js +++ b/lib/web/account.js @@ -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 }); }); }); diff --git a/lib/web/acp.js b/lib/web/acp.js index 9a6ba158..17122f3b 100644 --- a/lib/web/acp.js +++ b/lib/web/acp.js @@ -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 }); } diff --git a/lib/web/auth.js b/lib/web/auth.js index 5c4e9756..e1116cf2 100644 --- a/lib/web/auth.js +++ b/lib/web/auth.js @@ -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 @@ -22,12 +23,28 @@ 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.match(/login|logout/) ? null : dest; if (typeof name !== "string" || typeof password !== "string") { res.send(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.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); db.users.verifyLogin(name, password, function (err, user) { @@ -40,126 +57,41 @@ function handleLogin(req, res) { loggedIn: false, loginError: err }); - } else { - 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); - } + return; + } - res.cookie("auth", auth, { - expires: expiration, - 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 { + 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) { + 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", {}); - } + }); } /** @@ -170,20 +102,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") }); } @@ -192,34 +118,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.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", {}); } @@ -233,15 +142,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", { @@ -324,7 +227,5 @@ module.exports = { app.get("/logout", handleLogout); app.get("/register", handleRegisterPage); app.post("/register", handleRegister); - app.get("/shimcookie", handleShimCookie); - app.get("/shimlogout", handleShimLogout); } }; diff --git a/lib/web/jade.js b/lib/web/jade.js index c5b4b220..7b34ac8b 100644 --- a/lib/web/jade.js +++ b/lib/web/jade.js @@ -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); } diff --git a/lib/web/webserver.js b/lib/web/webserver.js index bc733f23..2b3c861a 100644 --- a/lib/web/webserver.js +++ b/lib/web/webserver.js @@ -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 }; diff --git a/templates/login.jade b/templates/login.jade index 94326039..3ebf3ca5 100644 --- a/templates/login.jade +++ b/templates/login.jade @@ -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") diff --git a/templates/logout.jade b/templates/logout.jade index 919b3cc6..56240aeb 100644 --- a/templates/logout.jade +++ b/templates/logout.jade @@ -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 diff --git a/templates/nav.jade b/templates/nav.jade index a6176b6c..2fa0281a 100644 --- a/templates/nav.jade +++ b/templates/nav.jade @@ -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) @@ -47,7 +47,7 @@ mixin navloginform(redirect) - 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 @@ -60,19 +60,13 @@ mixin navloginform(redirect) button#login.btn.btn-default(type="submit") Login .visible-md 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  ·  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  ·  - a#logout.navbar-link(href="#{loginDomain}/#{url}") Logout + a#logout.navbar-link(href="/logout?dest=#{redirect}") Logout