Do a bit of intermediate database work

This commit is contained in:
calzoneman 2013-12-12 16:28:30 -06:00
parent b889f7b4c8
commit cfd1b0618d
4 changed files with 596 additions and 637 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,498 +1,497 @@
//var db = require("../database");
var $util = require("../utilities");
var bcrypt = require("bcrypt");
var db = require("../database");
var registrationLock = {};
var blackHole = function () { };
module.exports = function (db) {
return {
/**
* Initialize the accounts table
*/
init: function () {
db.query("CREATE TABLE IF NOT EXISTS `users` (" +
"`id` INT NOT NULL AUTO_INCREMENT," +
"`name` VARCHAR(20) NOT NULL," +
"`password` VARCHAR(64) NOT NULL," +
"`global_rank` INT NOT NULL," +
"`email` VARCHAR(255) NOT NULL," +
"`profile` TEXT NOT NULL," +
"`ip` VARCHAR(39) NOT NULL," +
"`time` BIGINT NOT NULL, " +
"PRIMARY KEY(`id`), INDEX(`name`)) " +
"CHARACTER SET utf8");
},
module.exports = {
/**
* Initialize the accounts table
*/
init: function () {
db.query("CREATE TABLE IF NOT EXISTS `users` (" +
"`id` INT NOT NULL AUTO_INCREMENT," +
"`name` VARCHAR(20) NOT NULL," +
"`password` VARCHAR(64) NOT NULL," +
"`global_rank` INT NOT NULL," +
"`email` VARCHAR(255) NOT NULL," +
"`profile` TEXT NOT NULL," +
"`ip` VARCHAR(39) NOT NULL," +
"`time` BIGINT NOT NULL, " +
"PRIMARY KEY(`id`), INDEX(`name`)) " +
"CHARACTER SET utf8");
},
/**
* Check if a username is taken
*/
isUsernameTaken: function (name, callback) {
db.query("SELECT name FROM `users` WHERE name=?", [name],
function (err, rows) {
if (err) {
callback(err, true);
return;
}
callback(null, rows.length > 0);
});
},
/**
* Search for a user by name
*/
search: function (name, fields, callback) {
/* This bit allows it to accept varargs
Function can be called as (name, callback) or
(name, fields, callback)
*/
if (typeof callback !== "function") {
if (typeof fields === "function") {
callback = fields;
fields = ["name"];
} else {
return;
}
}
// Don't allow search to return password hashes
if (fields.indexOf("password") !== -1) {
fields.splice(fields.indexOf("password"));
}
db.query("SELECT " + fields.join(",") + " FROM `users` WHERE name LIKE ?",
["%"+name+"%"],
function (err, rows) {
if (err) {
callback(err, true);
return;
}
callback(null, rows);
});
},
/**
* Registers a new user account
*/
register: function (name, pw, email, ip, callback) {
// Start off with a boatload of error checking
if (typeof callback !== "function") {
callback = blackHole;
}
if (typeof name !== "string" || typeof pw !== "string") {
callback(new Error("You must provide a nonempty username and password"), null);
/**
* Check if a username is taken
*/
isUsernameTaken: function (name, callback) {
db.query("SELECT name FROM `users` WHERE name=?", [name],
function (err, rows) {
if (err) {
callback(err, true);
return;
}
var lname = name.toLowerCase();
callback(null, rows.length > 0);
});
},
if (registrationLock[lname]) {
callback(new Error("There is already a registration in progress for "+name),
null);
/**
* Search for a user by name
*/
search: function (name, fields, callback) {
/* This bit allows it to accept varargs
Function can be called as (name, callback) or
(name, fields, callback)
*/
if (typeof callback !== "function") {
if (typeof fields === "function") {
callback = fields;
fields = ["name"];
} else {
return;
}
}
// Don't allow search to return password hashes
if (fields.indexOf("password") !== -1) {
fields.splice(fields.indexOf("password"));
}
db.query("SELECT " + fields.join(",") + " FROM `users` WHERE name LIKE ?",
["%"+name+"%"],
function (err, rows) {
if (err) {
callback(err, true);
return;
}
callback(null, rows);
});
},
/**
* Registers a new user account
*/
register: function (name, pw, email, ip, callback) {
// Start off with a boatload of error checking
if (typeof callback !== "function") {
callback = blackHole;
}
if (typeof name !== "string" || typeof pw !== "string") {
callback(new Error("You must provide a nonempty username and password"), null);
return;
}
var lname = name.toLowerCase();
if (registrationLock[lname]) {
callback(new Error("There is already a registration in progress for "+name),
null);
return;
}
if (!$util.isValidUserName(name)) {
callback(new Error("Invalid username. Usernames may consist of 1-20 " +
"characters a-z, A-Z, 0-9, -, _, and accented letters."),
null);
return;
}
if (typeof email !== "string") {
email = "";
}
if (typeof ip !== "string") {
ip = "";
}
// From this point forward, actual registration happens
// registrationLock prevents concurrent database activity
// on the same user account
registrationLock[lname] = true;
this.isUsernameTaken(name, function (err, taken) {
if (err) {
delete registrationLock[lname];
callback(err, null);
return;
}
if (!$util.isValidUserName(name)) {
callback(new Error("Invalid username. Usernames may consist of 1-20 " +
"characters a-z, A-Z, 0-9, -, _, and accented letters."),
null);
if (taken) {
delete registrationLock[lname];
callback(new Error("Username is already registered"), null);
return;
}
if (typeof email !== "string") {
email = "";
}
if (typeof ip !== "string") {
ip = "";
}
// From this point forward, actual registration happens
// registrationLock prevents concurrent database activity
// on the same user account
registrationLock[lname] = true;
this.isUsernameTaken(name, function (err, taken) {
bcrypt.hash(pw, 10, function (err, hash) {
if (err) {
delete registrationLock[lname];
callback(err, null);
return;
}
if (taken) {
db.query("INSERT INTO `users` " +
"(`name`, `password`, `global_rank`, `email`, `ip`, `time`)" +
" VALUES " +
"(?, ?, ?, ?, ?, ?)",
[name, hash, 1, email, ip, Date.now()],
function (err, res) {
delete registrationLock[lname];
callback(new Error("Username is already registered"), null);
return;
}
bcrypt.hash(pw, 10, function (err, hash) {
if (err) {
delete registrationLock[lname];
callback(err, null);
return;
}
db.query("INSERT INTO `users` " +
"(`name`, `password`, `global_rank`, `email`, `ip`, `time`)" +
" VALUES " +
"(?, ?, ?, ?, ?, ?)",
[name, hash, 1, email, ip, Date.now()],
function (err, res) {
delete registrationLock[lname];
if (err) {
callback(err, null);
} else {
callback(null, {
name: name,
hash: hash
});
}
});
});
});
},
/**
* Verify a username/password pair
*/
verifyLogin: function (name, pw, callback) {
if (typeof callback !== "function") {
return;
}
if (typeof name !== "string" || typeof pw !== "string") {
callback(new Error("Invalid username/password combination"), null);
return;
}
/* Passwords are capped at 100 characters to prevent a potential
denial of service vector through causing the server to hash
ridiculously long strings.
*/
pw = pw.substring(0, 100);
/* Note: rather than hash the password and then query based on name and
password, I query by name, then use bcrypt.compare() to check that
the hashes match.
*/
db.query("SELECT name,password,global_rank FROM `users` WHERE name=?",
[name],
function (err, rows) {
if (err) {
callback(err, null);
return;
}
if (rows.length === 0) {
callback(new Error("User does not exist"), null);
return;
}
bcrypt.compare(pw, rows[0].password, function (err, match) {
if (err) {
callback(err, null);
} else if (!match) {
callback(new Error("Invalid username/password combination"), null);
} else {
callback(null, {
name: rows[0].name,
hash: rows[0].password,
global_rank: rows[0].global_rank
name: name,
hash: hash
});
}
});
});
},
});
},
/**
* Verify an auth string of the form name:hash
*/
verifyAuth: function (auth, callback) {
if (typeof callback !== "function") {
return;
}
if (typeof auth !== "string") {
callback(new Error("Invalid auth string"), null);
/**
* Verify a username/password pair
*/
verifyLogin: function (name, pw, callback) {
if (typeof callback !== "function") {
return;
}
if (typeof name !== "string" || typeof pw !== "string") {
callback(new Error("Invalid username/password combination"), null);
return;
}
/* Passwords are capped at 100 characters to prevent a potential
denial of service vector through causing the server to hash
ridiculously long strings.
*/
pw = pw.substring(0, 100);
/* Note: rather than hash the password and then query based on name and
password, I query by name, then use bcrypt.compare() to check that
the hashes match.
*/
db.query("SELECT name,password,global_rank FROM `users` WHERE name=?",
[name],
function (err, rows) {
if (err) {
callback(err, null);
return;
}
var split = auth.split(":");
if (split.length !== 2) {
callback(new Error("Invalid auth string"), null);
if (rows.length === 0) {
callback(new Error("User does not exist"), null);
return;
}
var name = split[0];
var hash = split[1];
db.query("SELECT name,password,global_rank FROM `users` WHERE " +
"name=? and password=?", [name, hash],
function (err, rows) {
bcrypt.compare(pw, rows[0].password, function (err, match) {
if (err) {
callback(err, null);
return;
}
if (rows.length === 0) {
callback(new Error("Auth string does not match an existing user"), null);
return;
}
callback(null, {
name: rows[0].name,
hash: rows[0].password,
global_rank: rows[0].global_rank
});
});
},
/**
* Change a user's password
*/
setPassword: function (name, pw, callback) {
if (typeof callback !== "function") {
callback = blackHole;
}
if (typeof name !== "string" || typeof pw !== "string") {
callback(new Error("Invalid username/password combination"), null);
return;
}
/* Passwords are capped at 100 characters to prevent a potential
denial of service vector through causing the server to hash
ridiculously long strings.
*/
pw = pw.substring(0, 100);
bcrypt.hash(pw, 10, function (err, hash) {
if (err) {
callback(err, null);
return;
}
db.query("UPDATE `users` SET password=? WHERE name=?",
[hash, name],
function (err, result) {
callback(err, err ? null : true);
});
});
},
/**
* Lookup a user's global rank
*/
getGlobalRank: function (name, callback) {
if (typeof callback !== "function") {
return;
}
if (typeof name !== "string") {
callback(new Error("Invalid username"), null);
return;
}
db.query("SELECT global_rank FROM `users` WHERE name=?", [name],
function (err, rows) {
if (err) {
callback(err, null);
} else if (rows.length === 0) {
callback(new Error("User does not exist"), null);
} else if (!match) {
callback(new Error("Invalid username/password combination"), null);
} else {
callback(null, rows[0].global_rank);
callback(null, {
name: rows[0].name,
hash: rows[0].password,
global_rank: rows[0].global_rank
});
}
});
},
});
},
/**
* Updates a user's global rank
*/
setGlobalRank: function (name, rank, callback) {
if (typeof callback !== "function") {
callback = blackHole;
}
/**
* Verify an auth string of the form name:hash
*/
verifyAuth: function (auth, callback) {
if (typeof callback !== "function") {
return;
}
if (typeof auth !== "string") {
callback(new Error("Invalid auth string"), null);
return;
}
if (typeof name !== "string") {
callback(new Error("Invalid username"), null);
var split = auth.split(":");
if (split.length !== 2) {
callback(new Error("Invalid auth string"), null);
return;
}
var name = split[0];
var hash = split[1];
db.query("SELECT name,password,global_rank FROM `users` WHERE " +
"name=? and password=?", [name, hash],
function (err, rows) {
if (err) {
callback(err, null);
return;
}
if (typeof rank !== "number") {
callback(new Error("Invalid rank"), null);
if (rows.length === 0) {
callback(new Error("Auth string does not match an existing user"), null);
return;
}
db.query("UPDATE `users` SET global_rank=? WHERE name=?", [rank, name],
callback(null, {
name: rows[0].name,
hash: rows[0].password,
global_rank: rows[0].global_rank
});
});
},
/**
* Change a user's password
*/
setPassword: function (name, pw, callback) {
if (typeof callback !== "function") {
callback = blackHole;
}
if (typeof name !== "string" || typeof pw !== "string") {
callback(new Error("Invalid username/password combination"), null);
return;
}
/* Passwords are capped at 100 characters to prevent a potential
denial of service vector through causing the server to hash
ridiculously long strings.
*/
pw = pw.substring(0, 100);
bcrypt.hash(pw, 10, function (err, hash) {
if (err) {
callback(err, null);
return;
}
db.query("UPDATE `users` SET password=? WHERE name=?",
[hash, name],
function (err, result) {
callback(err, err ? null : true);
});
},
});
},
/**
* Lookup multiple users' global rank in one query
*/
getGlobalRanks: function (names, callback) {
if (typeof callback !== "function") {
return;
/**
* Lookup a user's global rank
*/
getGlobalRank: function (name, callback) {
if (typeof callback !== "function") {
return;
}
if (typeof name !== "string") {
callback(new Error("Invalid username"), null);
return;
}
db.query("SELECT global_rank FROM `users` WHERE name=?", [name],
function (err, rows) {
if (err) {
callback(err, null);
} else if (rows.length === 0) {
callback(new Error("User does not exist"), null);
} else {
callback(null, rows[0].global_rank);
}
});
},
if (!(names instanceof Array)) {
callback(new Error("Expected array of names, got " + typeof names), null);
return;
/**
* Updates a user's global rank
*/
setGlobalRank: function (name, rank, callback) {
if (typeof callback !== "function") {
callback = blackHole;
}
if (typeof name !== "string") {
callback(new Error("Invalid username"), null);
return;
}
if (typeof rank !== "number") {
callback(new Error("Invalid rank"), null);
return;
}
db.query("UPDATE `users` SET global_rank=? WHERE name=?", [rank, name],
function (err, result) {
callback(err, err ? null : true);
});
},
/**
* Lookup multiple users' global rank in one query
*/
getGlobalRanks: function (names, callback) {
if (typeof callback !== "function") {
return;
}
if (!(names instanceof Array)) {
callback(new Error("Expected array of names, got " + typeof names), null);
return;
}
var list = "(" + names.map(function () { return "?";}).join(",") + ")";
db.query("SELECT global_rank FROM `users` WHERE name IN " + list, names,
function (err, rows) {
if (err) {
callback(err, null);
} else if (rows.length === 0) {
callback(null, []);
} else {
callback(null, rows.map(function (x) { return x.global_rank; }));
}
});
},
var list = "(" + names.map(function () { return "?";}).join(",") + ")";
/**
* Lookup a user's email
*/
getEmail: function (name, callback) {
if (typeof callback !== "function") {
return;
}
db.query("SELECT global_rank FROM `users` WHERE name IN " + list, names,
function (err, rows) {
if (err) {
callback(err, null);
} else if (rows.length === 0) {
callback(null, []);
} else {
callback(null, rows.map(function (x) { return x.global_rank; }));
if (typeof name !== "string") {
callback(new Error("Invalid username"), null);
return;
}
db.query("SELECT email FROM `users` WHERE name=?", [name],
function (err, rows) {
if (err) {
callback(err, null);
} else if (rows.length === 0) {
callback(new Error("User does not exist"), null);
} else {
callback(null, rows[0].email);
}
});
},
/**
* Updates a user's email
*/
setEmail: function (name, email, callback) {
if (typeof callback !== "function") {
callback = blackHole;
}
if (typeof name !== "string") {
callback(new Error("Invalid username"), null);
return;
}
if (typeof email !== "string") {
callback(new Error("Invalid email"), null);
return;
}
db.query("UPDATE `users` SET email=? WHERE name=?", [email, name],
function (err, result) {
callback(err, err ? null : true);
});
},
/**
* Lookup a user's profile
*/
getProfile: function (name, callback) {
if (typeof callback !== "function") {
return;
}
if (typeof name !== "string") {
callback(new Error("Invalid username"), null);
return;
}
db.query("SELECT profile FROM `users` WHERE name=?", [name],
function (err, rows) {
if (err) {
callback(err, null);
} else if (rows.length === 0) {
callback(new Error("User does not exist"), null);
} else {
var userprof = {
image: "",
text: ""
};
try {
var profile = JSON.parse(rows[0].profile);
userprof.image = profile.image || "";
userprof.text = profile.text || "";
callback(null, userprof);
} catch (e) {
callback(e, null);
}
});
},
/**
* Lookup a user's email
*/
getEmail: function (name, callback) {
if (typeof callback !== "function") {
return;
}
});
},
if (typeof name !== "string") {
callback(new Error("Invalid username"), null);
return;
}
/**
* Updates a user's profile
*/
setProfile: function (name, profile, callback) {
if (typeof callback !== "function") {
callback = blackHole;
}
db.query("SELECT email FROM `users` WHERE name=?", [name],
function (err, rows) {
if (err) {
callback(err, null);
} else if (rows.length === 0) {
callback(new Error("User does not exist"), null);
} else {
callback(null, rows[0].email);
}
});
},
if (typeof name !== "string") {
callback(new Error("Invalid username"), null);
return;
}
/**
* Updates a user's email
*/
setEmail: function (name, email, callback) {
if (typeof callback !== "function") {
callback = blackHole;
}
if (typeof profile !== "object") {
callback(new Error("Invalid profile"), null);
return;
}
if (typeof name !== "string") {
callback(new Error("Invalid username"), null);
return;
}
// Cast to string to guarantee string type
profile.image += "";
profile.text += "";
if (typeof email !== "string") {
callback(new Error("Invalid email"), null);
return;
}
// Limit size
profile.image = profile.image.substring(0, 255);
profile.text = profile.text.substring(0, 255);
db.query("UPDATE `users` SET email=? WHERE name=?", [email, name],
function (err, result) {
callback(err, err ? null : true);
});
},
// Stringify the literal to guarantee I only get the keys I want
var profilejson = JSON.stringify({
image: profile.image,
text: profile.text
});
/**
* Lookup a user's profile
*/
getProfile: function (name, callback) {
if (typeof callback !== "function") {
return;
}
db.query("UPDATE `users` SET profile=? WHERE name=?", [profilejson, name],
function (err, result) {
callback(err, err ? null : true);
});
},
if (typeof name !== "string") {
callback(new Error("Invalid username"), null);
return;
}
generatePasswordReset: function (ip, name, email, callback) {
if (typeof callback !== "function") {
return;
}
db.query("SELECT profile FROM `users` WHERE name=?", [name],
function (err, rows) {
if (err) {
callback(err, null);
} else if (rows.length === 0) {
callback(new Error("User does not exist"), null);
} else {
var userprof = {
image: "",
text: ""
};
try {
var profile = JSON.parse(rows[0].profile);
userprof.image = profile.image || "";
userprof.text = profile.text || "";
callback(null, userprof);
} catch (e) {
callback(e, null);
}
}
});
},
callback(new Error("generatePasswordReset is not implemented"), null);
},
/**
* Updates a user's profile
*/
setProfile: function (name, profile, callback) {
if (typeof callback !== "function") {
callback = blackHole;
}
recoverPassword: function (hash, callback) {
if (typeof callback !== "function") {
return;
}
if (typeof name !== "string") {
callback(new Error("Invalid username"), null);
return;
}
if (typeof profile !== "object") {
callback(new Error("Invalid profile"), null);
return;
}
// Cast to string to guarantee string type
profile.image += "";
profile.text += "";
// Limit size
profile.image = profile.image.substring(0, 255);
profile.text = profile.text.substring(0, 255);
// Stringify the literal to guarantee I only get the keys I want
var profilejson = JSON.stringify({
image: profile.image,
text: profile.text
});
db.query("UPDATE `users` SET profile=? WHERE name=?", [profilejson, name],
function (err, result) {
callback(err, err ? null : true);
});
},
generatePasswordReset: function (ip, name, email, callback) {
if (typeof callback !== "function") {
return;
}
callback(new Error("generatePasswordReset is not implemented"), null);
},
recoverPassword: function (hash, callback) {
if (typeof callback !== "function") {
return;
}
callback(new Error("recoverPassword is not implemented"), null);
},
};
callback(new Error("recoverPassword is not implemented"), null);
},
};

View File

@ -67,13 +67,26 @@ var Server = function (cfg) {
// database init ------------------------------------------------------
var Database = require("./database");
self.db = new Database(self.cfg);
self.db = Database;
self.db.init(self.cfg);
// webserver init -----------------------------------------------------
self.httplog = new Logger.Logger(path.join(__dirname,
"../httpaccess.log"));
self.express = express();
require("./web/webserver").init(self.express);
self.express.get("/old/:channel(*)", function (req, res, next) {
var c = req.params.channel;
if (!$util.isValidChannelName(c)) {
res.redirect("/" + c);
return;
}
self.logHTTP(req);
res.sendfile("channel.html", {
root: path.join(__dirname, "../www")
});
});
/*
self.express.use(express.urlencoded());
self.express.use(express.json());

View File

@ -478,7 +478,7 @@ $(window).resize(function() {
/* load channel */
var loc = document.location+"";
var m = loc.match(/\/r\/([a-zA-Z0-9-_]+)$/);
var m = loc.match(/\/old\/([a-zA-Z0-9-_]+)$/);
if(m) {
CHANNEL.name = m[1];
}