mirror of https://github.com/calzoneman/sync.git
730 lines
18 KiB
JavaScript
730 lines
18 KiB
JavaScript
var mysql = require("mysql-libmysqlclient");
|
|
var Logger = require("./logger");
|
|
var Media = require("./media").Media;
|
|
var bcrypt = require("bcrypt");
|
|
var hashlib = require("node_hash");
|
|
|
|
var db = false;
|
|
var SERVER = "";
|
|
var USER = "";
|
|
var DATABASE = "";
|
|
var PASSWORD = "";
|
|
var CONFIG = {};
|
|
|
|
function setup(cfg) {
|
|
SERVER = cfg.MYSQL_SERVER;
|
|
USER = cfg.MYSQL_USER;
|
|
DATABASE = cfg.MYSQL_DB;
|
|
PASSWORD = cfg.MYSQL_PASSWORD;
|
|
CONFIG = cfg;
|
|
}
|
|
|
|
function getConnection() {
|
|
if(db && db.connectedSync()) {
|
|
return db;
|
|
}
|
|
db = mysql.createConnectionSync();
|
|
db.connectSync(SERVER, USER, PASSWORD, DATABASE);
|
|
if(!db.connectedSync()) {
|
|
//Logger.errlog.log("DB connection failed");
|
|
return false;
|
|
}
|
|
if(CONFIG.DEBUG) {
|
|
db._querySync = db.querySync;
|
|
db.querySync = function(q) {
|
|
Logger.syslog.log("DEBUG: " + q);
|
|
return this._querySync(q);
|
|
}
|
|
}
|
|
return db;
|
|
}
|
|
|
|
function createQuery(template, args) {
|
|
var last = -1;
|
|
while(template.indexOf("?", last) >= 0) {
|
|
var idx = template.indexOf("?", last);
|
|
var arg = args.shift();
|
|
if(typeof arg == "string") {
|
|
arg = arg.replace(/([\'])/g, "\\$1");
|
|
if(idx == 0 || template[idx-1] != "`") {
|
|
arg = "'" + arg + "'";
|
|
}
|
|
}
|
|
if(arg === null || arg === undefined) {
|
|
arg = "NULL";
|
|
}
|
|
var first = template.substring(0, idx);
|
|
template = first + template.substring(idx).replace("?", arg);
|
|
last = idx + (arg+"").length;
|
|
}
|
|
return template;
|
|
}
|
|
|
|
function init() {
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return false;
|
|
}
|
|
|
|
// Create channel table
|
|
var query = ["CREATE TABLE IF NOT EXISTS `channels` (",
|
|
"`id` INT NOT NULL AUTO_INCREMENT,",
|
|
"`name` VARCHAR(255) NOT NULL,",
|
|
"PRIMARY KEY(`id`))",
|
|
"ENGINE = MyISAM;"].join("");
|
|
var results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to create channels table");
|
|
}
|
|
|
|
// Create registration table
|
|
query = ["CREATE TABLE IF NOT EXISTS `registrations` (",
|
|
"`id` INT NOT NULL AUTO_INCREMENT,",
|
|
"`uname` VARCHAR(20) NOT NULL,",
|
|
"`pw` VARCHAR(64) NOT NULL,",
|
|
"`global_rank` INT NOT NULL,",
|
|
"`session_hash` VARCHAR(64) NOT NULL,",
|
|
"`expire` BIGINT NOT NULL,",
|
|
"`profile_image` VARCHAR(255) NOT NULL,",
|
|
"`profile_text` TEXT NOT NULL,",
|
|
"PRIMARY KEY (`id`))",
|
|
"ENGINE = MyISAM;"].join("");
|
|
|
|
results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to create registrations table");
|
|
}
|
|
|
|
// Create global bans table
|
|
query = ["CREATE TABLE IF NOT EXISTS `global_bans` (",
|
|
"`ip` VARCHAR(15) NOT NULL,",
|
|
"`note` VARCHAR(255) NOT NULL,",
|
|
"PRIMARY KEY (`ip`))",
|
|
"ENGINE = MyISAM;"].join("");
|
|
|
|
results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to create global ban table");
|
|
}
|
|
|
|
refreshGlobalBans();
|
|
|
|
// Create password reset table
|
|
query = ["CREATE TABLE IF NOT EXISTS `password_reset` (",
|
|
"`ip` VARCHAR(15) NOT NULL,",
|
|
"`name` VARCHAR(20) NOT NULL,",
|
|
"`hash` VARCHAR(64) NOT NULL,",
|
|
"`email` VARCHAR(255) NOT NULL,",
|
|
"`expire` BIGINT NOT NULL,",
|
|
"PRIMARY KEY (`name`))",
|
|
"ENGINE = MyISAM;"].join("");
|
|
|
|
results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to create password reset table");
|
|
}
|
|
}
|
|
|
|
/* REGION Global Bans */
|
|
|
|
function checkGlobalBan(ip) {
|
|
const re = /(\d+)\.(\d+)\.(\d+)\.(\d+)/;
|
|
var s16 = ip.replace(re, "$1.$2");
|
|
var s24 = ip.replace(re, "$1.$2.$3");
|
|
return (ip in global_bans ||
|
|
s16 in global_bans ||
|
|
s24 in global_bans);
|
|
}
|
|
|
|
function refreshGlobalBans() {
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return;
|
|
}
|
|
|
|
var query = "SELECT * FROM `global_bans` WHERE 1";
|
|
var results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to load global bans");
|
|
}
|
|
else {
|
|
var rows = results.fetchAllSync();
|
|
global_bans = {};
|
|
for(var i = 0; i < rows.length; i++) {
|
|
global_bans[rows[i].ip] = rows[i].note;
|
|
}
|
|
}
|
|
return global_bans;
|
|
}
|
|
|
|
function globalBanIP(ip, reason) {
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return;
|
|
}
|
|
|
|
var query = createQuery(
|
|
"INSERT INTO `global_bans` VALUES (?, ?)",
|
|
[ip, reason]
|
|
);
|
|
return db.querySync(query);
|
|
}
|
|
|
|
function globalUnbanIP(ip) {
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return;
|
|
}
|
|
|
|
var query = createQuery(
|
|
"DELETE FROM `global_bans` WHERE ip=?",
|
|
[ip]
|
|
);
|
|
|
|
return db.querySync(query);
|
|
}
|
|
|
|
/* REGION Channel Registration/Loading */
|
|
|
|
function registerChannel(name) {
|
|
if(!name.match(/^[a-zA-Z0-9-_]+$/)) {
|
|
return false;
|
|
}
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return false;
|
|
}
|
|
|
|
// Library table
|
|
var query = ["CREATE TABLE `?` (",
|
|
"`id` VARCHAR(255) NOT NULL,",
|
|
"`title` VARCHAR(255) NOT NULL,",
|
|
"`seconds` INT NOT NULL,",
|
|
"`type` VARCHAR(2) NOT NULL,",
|
|
"PRIMARY KEY (`id`))",
|
|
"ENGINE = MyISAM;"].join("");
|
|
query = createQuery(query, ["chan_" + name + "_library"]);
|
|
|
|
var results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to create table: chan_"+name+"_library");
|
|
return false;
|
|
}
|
|
|
|
// Rank table
|
|
query = ["CREATE TABLE `?` (",
|
|
"`name` VARCHAR(32) NOT NULL,",
|
|
"`rank` INT NOT NULL,",
|
|
"UNIQUE (`name`))",
|
|
"ENGINE = MyISAM;"].join("");
|
|
query = createQuery(query, ["chan_" + name + "_ranks"]);
|
|
|
|
results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to create table: chan_"+name+"_ranks");
|
|
return false;
|
|
}
|
|
|
|
// Ban table
|
|
query = ["CREATE TABLE `?` (",
|
|
"`ip` VARCHAR(15) NOT NULL,",
|
|
"`name` VARCHAR(32) NOT NULL,",
|
|
"`banner` VARCHAR(32) NOT NULL,",
|
|
"PRIMARY KEY (`ip`))",
|
|
"ENGINE = MyISAM;"].join("");
|
|
query = createQuery(query, ["chan_" + name + "_bans"]);
|
|
|
|
results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to create table: chan_"+name+"_bans");
|
|
return false;
|
|
}
|
|
|
|
// Insert into channel table
|
|
query = createQuery(
|
|
"INSERT INTO `channels` VALUES (NULL, ?)",
|
|
[name]
|
|
);
|
|
|
|
results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to insert into channel table: " + name);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function loadChannel(chan) {
|
|
if(!chan.name.match(/^[a-zA-Z0-9-_]+$/)) {
|
|
return false;
|
|
}
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return false;
|
|
}
|
|
|
|
var query = createQuery(
|
|
"SELECT * FROM `channels` WHERE name=?",
|
|
[chan.name]
|
|
);
|
|
|
|
var results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to query channel table");
|
|
}
|
|
else {
|
|
var rows = results.fetchAllSync();
|
|
if(rows.length == 0) {
|
|
// Unregistered
|
|
Logger.syslog.log("Channel " + chan.name + " is unregistered");
|
|
return;
|
|
}
|
|
// Database is case insensitive
|
|
else if(rows[0].name != chan.name) {
|
|
chan.name = rows[0].name;
|
|
}
|
|
chan.registered = true;
|
|
}
|
|
|
|
// Load channel library
|
|
query = createQuery(
|
|
"SELECT * FROM `?`",
|
|
["chan_" + chan.name + "_library"]
|
|
);
|
|
|
|
results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to load channel library: " + chan.name);
|
|
}
|
|
else {
|
|
var rows = results.fetchAllSync();
|
|
for(var i = 0; i < rows.length; i++) {
|
|
var r = rows[i];
|
|
var m = new Media(r.id, r.title, r.seconds, r.type);
|
|
chan.library[r.id] = m;
|
|
}
|
|
}
|
|
|
|
// Load channel bans
|
|
query = createQuery(
|
|
"SELECT * FROM `?`",
|
|
["chan_" + chan.name + "_bans"]
|
|
);
|
|
|
|
results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to load channel bans: " + chan.name);
|
|
}
|
|
else {
|
|
var rows = results.fetchAllSync();
|
|
for(var i = 0; i < rows.length; i++) {
|
|
var r = rows[i];
|
|
if(r.ip == "*") {
|
|
chan.namebans[r.name] = r.banner;
|
|
}
|
|
else {
|
|
chan.ipbans[r.ip] = [r.name, r.banner];
|
|
}
|
|
}
|
|
}
|
|
|
|
chan.logger.log("*** Loaded channel from database");
|
|
Logger.syslog.log("Loaded channel " + chan.name + " from database");
|
|
}
|
|
|
|
function deleteChannel(name) {
|
|
if(!name.match(/^[a-zA-Z0-9-_]+$/)) {
|
|
return false;
|
|
}
|
|
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return false;
|
|
}
|
|
|
|
var query = "DROP TABLE `chan_?_bans`, `chan_?_ranks`, `chan_?_library`"
|
|
.replace(/\?/g, name);
|
|
|
|
var results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to delete channel tables for " + name);
|
|
return false;
|
|
}
|
|
|
|
query = createQuery(
|
|
"DELETE FROM `channels` WHERE name=?",
|
|
[name]
|
|
);
|
|
|
|
results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to delete row from channel table: " + name);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* REGION Channel data */
|
|
|
|
function getChannelRank(chan, name) {
|
|
if(!chan.match(/^[a-zA-Z0-9-_]+$/)) {
|
|
return false;
|
|
}
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return 0;
|
|
}
|
|
|
|
var query;
|
|
if(typeof name == "object") {
|
|
var n = "(?";
|
|
for(var i = 1; i < name.length; i++) {
|
|
n += ",?";
|
|
}
|
|
n += ")"
|
|
name.unshift("chan_" + chan + "_ranks");
|
|
query = createQuery(
|
|
"SELECT * FROM `?` WHERE name IN " + n,
|
|
name
|
|
);
|
|
}
|
|
else {
|
|
query = createQuery(
|
|
"SELECT * FROM `?` WHERE name=?",
|
|
["chan_"+chan+"_ranks", name]
|
|
);
|
|
}
|
|
|
|
var results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to lookup chan_"+chan+"_ranks");
|
|
return 0;
|
|
}
|
|
|
|
var rows = results.fetchAllSync();
|
|
if(typeof name == "object") {
|
|
var ranks = [];
|
|
for(var i = 0; i < rows.length; i++) {
|
|
ranks.push(rows[i].rank);
|
|
}
|
|
while(ranks.length < rows.length) {
|
|
ranks.push(0);
|
|
}
|
|
return ranks;
|
|
}
|
|
if(rows.length == 0) {
|
|
return 0;
|
|
}
|
|
|
|
return rows[0].rank;
|
|
}
|
|
|
|
function setChannelRank(chan, name, rank) {
|
|
if(!chan.match(/^[a-zA-Z0-9-_]+$/)) {
|
|
return false;
|
|
}
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return false;
|
|
}
|
|
|
|
var query = createQuery(
|
|
["INSERT INTO `?` ",
|
|
"(`name`, `rank`) ",
|
|
"VALUES ",
|
|
"(?, ?) ",
|
|
"ON DUPLICATE KEY UPDATE ",
|
|
"`rank`=?"].join(""),
|
|
["chan_"+chan+"_ranks", name, rank, rank]
|
|
);
|
|
|
|
return db.querySync(query);
|
|
}
|
|
|
|
function listChannelRanks(chan) {
|
|
if(!chan.match(/^[a-zA-Z0-9-_]+$/)) {
|
|
return [];
|
|
}
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return [];
|
|
}
|
|
|
|
var query = createQuery(
|
|
"SELECT * FROM `?` WHERE 1",
|
|
["chan_"+chan+"_ranks"]
|
|
);
|
|
|
|
var results = db.querySync(query);
|
|
if(!results) {
|
|
return [];
|
|
}
|
|
|
|
return results.fetchAllSync();
|
|
}
|
|
|
|
function addToLibrary(chan, media) {
|
|
if(!chan.match(/^[a-zA-Z0-9-_]+$/)) {
|
|
return false;
|
|
}
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return false;
|
|
}
|
|
|
|
var query = createQuery(
|
|
["INSERT INTO `?` ",
|
|
"(`id`, `title`, `seconds`, `type`) ",
|
|
"VALUES ",
|
|
"(?, ?, ?, ?)"].join(""),
|
|
["chan_"+chan+"_library", media.id, media.title, media.seconds, media.type]
|
|
);
|
|
|
|
return db.querySync(query);
|
|
}
|
|
|
|
function removeFromLibrary(chan, id) {
|
|
if(!chan.match(/^[a-zA-Z0-9-_]+$/)) {
|
|
return false;
|
|
}
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return false;
|
|
}
|
|
|
|
var query = createQuery(
|
|
"DELETE FROM `?` WHERE id=?",
|
|
["chan_"+chan+"_library", id]
|
|
);
|
|
|
|
return db.querySync(query);
|
|
}
|
|
|
|
function channelBan(chan, ip, name, banby) {
|
|
if(!chan.match(/^[a-zA-Z0-9-_]+$/)) {
|
|
return false;
|
|
}
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return false;
|
|
}
|
|
|
|
var query = createQuery(
|
|
["INSERT INTO `?` ",
|
|
"(`ip`, `name`, `banner`) ",
|
|
"VALUES ",
|
|
"(?, ?, ?)"].join(""),
|
|
["chan_"+chan+"_bans", ip, name, banby]
|
|
);
|
|
|
|
return db.querySync(query);
|
|
}
|
|
|
|
function channelUnbanIP(chan, ip) {
|
|
if(!chan.match(/^[a-zA-Z0-9-_]+$/)) {
|
|
return false;
|
|
}
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return false;
|
|
}
|
|
|
|
var query = createQuery(
|
|
"DELETE FROM `?` WHERE `ip`=?",
|
|
["chan_"+chan+"_bans", ip]
|
|
);
|
|
|
|
return db.querySync(query);
|
|
}
|
|
|
|
function channelUnbanName(chan, name) {
|
|
if(!chan.match(/^[a-zA-Z0-9-_]+$/)) {
|
|
return false;
|
|
}
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return false;
|
|
}
|
|
|
|
var query = createQuery(
|
|
"DELETE FROM `?` WHERE `ip`='*' AND `name`=?",
|
|
["chan_"+chan+"_bans", name]
|
|
);
|
|
|
|
return db.querySync(query);
|
|
}
|
|
|
|
/* REGION Users */
|
|
|
|
function setProfile(name, data) {
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return false;
|
|
}
|
|
|
|
var query = createQuery(
|
|
["UPDATE `registrations` SET ",
|
|
"`profile_image`=?,",
|
|
"`profile_text`=? ",
|
|
"WHERE uname=?"].join(""),
|
|
[data.image, data.text, name]
|
|
);
|
|
|
|
return db.querySync(query);
|
|
}
|
|
|
|
function setUserEmail(name, email) {
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return false;
|
|
}
|
|
|
|
var query = createQuery(
|
|
"UPDATE `registrations` SET `email`=? WHERE `uname`=?",
|
|
[email, name]
|
|
);
|
|
|
|
var results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to set user email");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function generatePasswordReset(ip, name, email) {
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return false;
|
|
}
|
|
|
|
var query = createQuery(
|
|
"SELECT `email` FROM `registrations` WHERE `uname`=?",
|
|
[name]
|
|
);
|
|
|
|
var results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to retrieve user email");
|
|
return false;
|
|
}
|
|
|
|
var rows = results.fetchAllSync();
|
|
if(rows.length == 0) {
|
|
throw "Provided username does not exist";
|
|
}
|
|
if(rows[0].email != email) {
|
|
throw "Provided email does not match user's email";
|
|
}
|
|
|
|
// Validation complete, now time to reset it
|
|
var hash = hashlib.sha256(Date.now() + name);
|
|
var exp = Date.now() + 24*60*60*1000;
|
|
query = createQuery(
|
|
["INSERT INTO `password_reset` (",
|
|
"`ip`, `name`, `hash`, `email`, `expire`",
|
|
") VALUES (",
|
|
"?, ?, ?, ?, ?",
|
|
") ON DUPLICATE KEY UPDATE `expire`=?"].join(""),
|
|
[ip, name, hash, email, exp, exp]
|
|
);
|
|
|
|
results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to insert password reset");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function recoverPassword(hash) {
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return false;
|
|
}
|
|
|
|
var query = createQuery(
|
|
"SELECT * FROM password_reset WHERE hash=?",
|
|
[hash]
|
|
);
|
|
|
|
var results = db.querySync(query);
|
|
if(!results) {
|
|
Logger.errlog.log("! Failed to retrieve from password_reset");
|
|
throw "Database error. Contact an administrator";
|
|
}
|
|
|
|
var rows = results.fetchAllSync();
|
|
if(rows.length == 0) {
|
|
throw "Invalid password reset link";
|
|
}
|
|
|
|
db.querySync(createQuery(
|
|
"DELETE FROM password_reset WHERE hash=?",
|
|
[hash]
|
|
));
|
|
|
|
if(Date.now() > rows[0].expire) {
|
|
throw "Link expired. Password resets are valid for 24 hours";
|
|
}
|
|
|
|
var pw;
|
|
if(!(pw = resetPassword(rows[0].name))) {
|
|
throw "Operation failed. Contact an administrator.";
|
|
}
|
|
|
|
return [rows[0].name, pw];
|
|
}
|
|
|
|
function resetPassword(name) {
|
|
var db = getConnection();
|
|
if(!db) {
|
|
return false;
|
|
}
|
|
|
|
var pw = "";
|
|
for(var i = 0; i < 10; i++) {
|
|
pw += "abcdefghijklmnopqrstuvwxyz"[parseInt(Math.random() * 25)];
|
|
}
|
|
var hash = bcrypt.hashSync(pw, 10);
|
|
var query = createQuery(
|
|
"UPDATE `registrations` SET `pw`=? WHERE `uname`=?",
|
|
[hash, name]
|
|
);
|
|
|
|
var results = db.querySync(query);
|
|
if(!results) {
|
|
return false;
|
|
}
|
|
|
|
return pw;
|
|
}
|
|
|
|
exports.setup = setup;
|
|
exports.getConnection = getConnection;
|
|
exports.createQuery = createQuery;
|
|
exports.init = init;
|
|
exports.checkGlobalBan = checkGlobalBan;
|
|
exports.refreshGlobalBans = refreshGlobalBans;
|
|
exports.globalBanIP = globalBanIP;
|
|
exports.globalUnbanIP = globalUnbanIP;
|
|
exports.registerChannel = registerChannel;
|
|
exports.loadChannel = loadChannel;
|
|
exports.deleteChannel = deleteChannel;
|
|
exports.getChannelRank = getChannelRank;
|
|
exports.setChannelRank = setChannelRank;
|
|
exports.listChannelRanks = listChannelRanks;
|
|
exports.addToLibrary = addToLibrary;
|
|
exports.removeFromLibrary = removeFromLibrary;
|
|
exports.channelBan = channelBan;
|
|
exports.channelUnbanIP = channelUnbanIP;
|
|
exports.channelUnbanName = channelUnbanName;
|
|
exports.setProfile = setProfile;
|
|
exports.setUserEmail = setUserEmail;
|
|
exports.generatePasswordReset = generatePasswordReset;
|
|
exports.recoverPassword = recoverPassword;
|
|
exports.resetPassword = resetPassword;
|