Move action log to DB, throttle registrations
This commit is contained in:
parent
7bc86037b7
commit
5df30cb8a9
10
acp.js
10
acp.js
|
|
@ -18,7 +18,7 @@ module.exports = {
|
||||||
init: function(user) {
|
init: function(user) {
|
||||||
ActionLog.record(user.ip, user.name, "acp-init");
|
ActionLog.record(user.ip, user.name, "acp-init");
|
||||||
user.socket.on("acp-announce", function(data) {
|
user.socket.on("acp-announce", function(data) {
|
||||||
ActionLog.record(user.ip, user.name, "acp-announce", [data]);
|
ActionLog.record(user.ip, user.name, "acp-announce", data);
|
||||||
Server.announcement = data;
|
Server.announcement = data;
|
||||||
Server.io.sockets.emit("announcement", data);
|
Server.io.sockets.emit("announcement", data);
|
||||||
});
|
});
|
||||||
|
|
@ -29,13 +29,13 @@ module.exports = {
|
||||||
});
|
});
|
||||||
|
|
||||||
user.socket.on("acp-global-ban", function(data) {
|
user.socket.on("acp-global-ban", function(data) {
|
||||||
ActionLog.record(user.ip, user.name, "acp-global-ban", [data.ip]);
|
ActionLog.record(user.ip, user.name, "acp-global-ban", data.ip);
|
||||||
Database.globalBanIP(data.ip, data.note);
|
Database.globalBanIP(data.ip, data.note);
|
||||||
user.socket.emit("acp-global-banlist", Database.refreshGlobalBans());
|
user.socket.emit("acp-global-banlist", Database.refreshGlobalBans());
|
||||||
});
|
});
|
||||||
|
|
||||||
user.socket.on("acp-global-unban", function(ip) {
|
user.socket.on("acp-global-unban", function(ip) {
|
||||||
ActionLog.record(user.ip, user.name, "acp-global-unban", [ip]);
|
ActionLog.record(user.ip, user.name, "acp-global-unban", ip);
|
||||||
Database.globalUnbanIP(ip);
|
Database.globalUnbanIP(ip);
|
||||||
user.socket.emit("acp-global-banlist", Database.refreshGlobalBans());
|
user.socket.emit("acp-global-banlist", Database.refreshGlobalBans());
|
||||||
});
|
});
|
||||||
|
|
@ -66,7 +66,7 @@ module.exports = {
|
||||||
return;
|
return;
|
||||||
try {
|
try {
|
||||||
var hash = Database.generatePasswordReset(user.ip, data.name, data.email);
|
var hash = Database.generatePasswordReset(user.ip, data.name, data.email);
|
||||||
ActionLog.record(user.ip, user.name, "acp-reset-password", [data.name]);
|
ActionLog.record(user.ip, user.name, "acp-reset-password", data.name);
|
||||||
}
|
}
|
||||||
catch(e) {
|
catch(e) {
|
||||||
user.socket.emit("acp-reset-password", {
|
user.socket.emit("acp-reset-password", {
|
||||||
|
|
@ -101,7 +101,7 @@ module.exports = {
|
||||||
if(!db)
|
if(!db)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ActionLog.record(user.ip, user.name, "acp-set-rank", [data]);
|
ActionLog.record(user.ip, user.name, "acp-set-rank", data);
|
||||||
var query = Database.createQuery(
|
var query = Database.createQuery(
|
||||||
"UPDATE registrations SET global_rank=? WHERE uname=?",
|
"UPDATE registrations SET global_rank=? WHERE uname=?",
|
||||||
[data.rank, data.name]
|
[data.rank, data.name]
|
||||||
|
|
|
||||||
135
actionlog.js
135
actionlog.js
|
|
@ -9,68 +9,95 @@ The above copyright notice and this permission notice shall be included in all c
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var fs = require("fs");
|
var Database = require("./database");
|
||||||
var Logger = require("./logger");
|
var Logger = require("./logger");
|
||||||
|
|
||||||
var buffer = [];
|
|
||||||
|
|
||||||
exports.record = function(ip, name, action, args) {
|
exports.record = function(ip, name, action, args) {
|
||||||
buffer.push(JSON.stringify({
|
if(typeof args === "undefined" || args === null) {
|
||||||
ip: ip,
|
args = "";
|
||||||
name: name,
|
} else {
|
||||||
action: action,
|
try {
|
||||||
args: args ? args : [],
|
args = JSON.stringify(args);
|
||||||
time: Date.now()
|
} catch(e) {
|
||||||
}));
|
args = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.flush = function() {
|
var db = Database.getConnection();
|
||||||
if(buffer.length == 0)
|
if(!db)
|
||||||
return;
|
return false;
|
||||||
var text = buffer.join("\n") + "\n";
|
|
||||||
buffer = [];
|
var query = Database.createQuery(
|
||||||
fs.appendFile("action.log", text, function(err) {
|
"INSERT INTO actionlog (ip, name, action, args, time) "+
|
||||||
if(err) {
|
"VALUES (?, ?, ?, ?, ?)",
|
||||||
errlog.log("Append to actionlog failed: ");
|
[ip, name, action, args, Date.now()]
|
||||||
errlog.log(err);
|
);
|
||||||
|
|
||||||
|
var result = db.querySync(query);
|
||||||
|
if(!result) {
|
||||||
|
Logger.errlog.log("! Failed to record action");
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.clear = function(actions) {
|
exports.clear = function(actions) {
|
||||||
clearInterval(FLUSH_TMR);
|
var db = Database.getConnection();
|
||||||
var rs = fs.createReadStream("action.log");
|
if(!db)
|
||||||
var ws = fs.createWriteStream("action.log.tmp");
|
return false;
|
||||||
function handleLine(ln) {
|
|
||||||
try {
|
var list = new Array(actions.length);
|
||||||
js = JSON.parse(ln);
|
for(var i = 0; i < actions.length; i++)
|
||||||
if(actions.indexOf(js.action) == -1)
|
list[i] = "?";
|
||||||
ws.write(ln + "\n");
|
|
||||||
}
|
var query = Database.createQuery(
|
||||||
catch(e) { }
|
"DELETE FROM actionlog WHERE action IN ("+
|
||||||
}
|
list.join(",")+
|
||||||
var buffer = "";
|
")",
|
||||||
rs.on("data", function(chunk) {
|
actions
|
||||||
buffer += chunk;
|
);
|
||||||
if(buffer.indexOf("\n") != -1) {
|
|
||||||
var lines = buffer.split("\n");
|
var result = db.querySync(query);
|
||||||
buffer = lines[lines.length - 1];
|
if(!result) {
|
||||||
lines.length = lines.length - 1;
|
Logger.errlog.log("! Failed to clear action log");
|
||||||
lines.forEach(handleLine);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
rs.on("end", function() {
|
|
||||||
handleLine(buffer);
|
|
||||||
ws.end();
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
fs.renameSync("action.log.tmp", "action.log");
|
|
||||||
}
|
|
||||||
catch(e) {
|
|
||||||
Logger.errlog.log("Failed to move action.log.tmp => action.log");
|
|
||||||
Logger.errlog.log(e);
|
|
||||||
}
|
|
||||||
FLUSH_TMR = setInterval(exports.flush, 15000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var FLUSH_TMR = setInterval(exports.flush, 15000);
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.tooManyRegistrations = function (ip) {
|
||||||
|
var db = Database.getConnection();
|
||||||
|
if(!db)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var query = Database.createQuery(
|
||||||
|
"SELECT * FROM actionlog WHERE ip=? AND action='register-success'"+
|
||||||
|
"AND time > ?",
|
||||||
|
[ip, Date.now() - 48 * 3600 * 1000]
|
||||||
|
);
|
||||||
|
|
||||||
|
var results = db.querySync(query);
|
||||||
|
if(!results) {
|
||||||
|
Logger.errlog.log("! Failed to check tooManyRegistrations");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows = results.fetchAllSync();
|
||||||
|
// TODO Config value for this
|
||||||
|
return rows.length > 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.readLog = function () {
|
||||||
|
var db = Database.getConnection();
|
||||||
|
if(!db)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var query = "SELECT * FROM actionlog";
|
||||||
|
var result = db.querySync(query);
|
||||||
|
if(!result) {
|
||||||
|
Logger.errlog.log("! Failed to read action log");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.fetchAllSync();
|
||||||
|
}
|
||||||
|
|
|
||||||
42
api.js
42
api.js
|
|
@ -34,12 +34,13 @@ var jsonHandlers = {
|
||||||
"getprofile" : handleProfileGet,
|
"getprofile" : handleProfileGet,
|
||||||
"setemail" : handleEmailChange,
|
"setemail" : handleEmailChange,
|
||||||
"admreports" : handleAdmReports,
|
"admreports" : handleAdmReports,
|
||||||
|
"readactionlog" : handleReadActionLog
|
||||||
};
|
};
|
||||||
|
|
||||||
function getClientIP(req) {
|
function getClientIP(req) {
|
||||||
var ip;
|
var ip;
|
||||||
var forward = req.header("x-forwarded-for");
|
var forward = req.header("x-forwarded-for");
|
||||||
if(forward) {
|
if(Config.REVERSE_PROXY && forward) {
|
||||||
ip = forward.split(",")[0];
|
ip = forward.split(",")[0];
|
||||||
}
|
}
|
||||||
if(!ip) {
|
if(!ip) {
|
||||||
|
|
@ -256,7 +257,7 @@ function handlePasswordReset(params, req, res) {
|
||||||
var hash = false;
|
var hash = false;
|
||||||
try {
|
try {
|
||||||
hash = Database.generatePasswordReset(ip, name, email);
|
hash = Database.generatePasswordReset(ip, name, email);
|
||||||
ActionLog.record(ip, name, "password-reset-generate");
|
ActionLog.record(ip, name, "password-reset-generate", email);
|
||||||
}
|
}
|
||||||
catch(e) {
|
catch(e) {
|
||||||
sendJSON(res, {
|
sendJSON(res, {
|
||||||
|
|
@ -429,7 +430,7 @@ function handleEmailChange(params, req, res) {
|
||||||
var row = Auth.login(name, pw);
|
var row = Auth.login(name, pw);
|
||||||
if(row) {
|
if(row) {
|
||||||
var success = Database.setUserEmail(name, email);
|
var success = Database.setUserEmail(name, email);
|
||||||
ActionLog.record(getClientIP(req), name, "email-update", [email]);
|
ActionLog.record(getClientIP(req), name, "email-update", email);
|
||||||
sendJSON(res, {
|
sendJSON(res, {
|
||||||
success: success,
|
success: success,
|
||||||
error: success ? "" : "Email update failed",
|
error: success ? "" : "Email update failed",
|
||||||
|
|
@ -447,6 +448,17 @@ function handleEmailChange(params, req, res) {
|
||||||
function handleRegister(params, req, res) {
|
function handleRegister(params, req, res) {
|
||||||
var name = params.name || "";
|
var name = params.name || "";
|
||||||
var pw = params.pw || "";
|
var pw = params.pw || "";
|
||||||
|
if(ActionLog.tooManyRegistrations(getClientIP(req))) {
|
||||||
|
ActionLog.record(getClientIP(req), name, "register-failure",
|
||||||
|
"Too many recent registrations from this IP");
|
||||||
|
sendJSON(res, {
|
||||||
|
success: false,
|
||||||
|
error: "Your IP address has registered several accounts in "+
|
||||||
|
"the past 48 hours. Please wait a while or ask an "+
|
||||||
|
"administrator for assistance."
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(pw == "") {
|
if(pw == "") {
|
||||||
sendJSON(res, {
|
sendJSON(res, {
|
||||||
|
|
@ -456,7 +468,8 @@ function handleRegister(params, req, res) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if(Auth.isRegistered(name)) {
|
else if(Auth.isRegistered(name)) {
|
||||||
ActionLog.record(getClientIP(req), name, "register-failure");
|
ActionLog.record(getClientIP(req), name, "register-failure",
|
||||||
|
"Name taken");
|
||||||
sendJSON(res, {
|
sendJSON(res, {
|
||||||
success: false,
|
success: false,
|
||||||
error: "That username is already taken"
|
error: "That username is already taken"
|
||||||
|
|
@ -464,7 +477,8 @@ function handleRegister(params, req, res) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if(!Auth.validateName(name)) {
|
else if(!Auth.validateName(name)) {
|
||||||
ActionLog.record(getClientIP(req), name, "register-failure");
|
ActionLog.record(getClientIP(req), name, "register-failure",
|
||||||
|
"Invalid name");
|
||||||
sendJSON(res, {
|
sendJSON(res, {
|
||||||
success: false,
|
success: false,
|
||||||
error: "Invalid username. Usernames must be 1-20 characters long and consist only of alphanumeric characters and underscores"
|
error: "Invalid username. Usernames must be 1-20 characters long and consist only of alphanumeric characters and underscores"
|
||||||
|
|
@ -495,6 +509,20 @@ function handleAdmReports(params, req, res) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleReadActionLog(params, req, res) {
|
||||||
|
var name = params.name || "";
|
||||||
|
var pw = params.pw || "";
|
||||||
|
var session = params.session || "";
|
||||||
|
var row = Auth.login(name, pw, session);
|
||||||
|
if(!row || row.global_rank < 255) {
|
||||||
|
res.send(403);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var actions = ActionLog.readLog();
|
||||||
|
sendJSON(res, actions);
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function
|
// Helper function
|
||||||
function pipeLast(res, file, len) {
|
function pipeLast(res, file, len) {
|
||||||
fs.stat(file, function(err, data) {
|
fs.stat(file, function(err, data) {
|
||||||
|
|
@ -529,10 +557,6 @@ function handleReadLog(params, req, res) {
|
||||||
else if(type == "err") {
|
else if(type == "err") {
|
||||||
pipeLast(res, "error.log", 1024*1024);
|
pipeLast(res, "error.log", 1024*1024);
|
||||||
}
|
}
|
||||||
else if(type == "action") {
|
|
||||||
ActionLog.flush();
|
|
||||||
pipeLast(res, "action.log", 1024*1024*100);
|
|
||||||
}
|
|
||||||
else if(type == "channel") {
|
else if(type == "channel") {
|
||||||
var chan = params.channel || "";
|
var chan = params.channel || "";
|
||||||
fs.exists("chanlogs/" + chan + ".log", function(exists) {
|
fs.exists("chanlogs/" + chan + ".log", function(exists) {
|
||||||
|
|
|
||||||
11
channel.js
11
channel.js
|
|
@ -278,14 +278,16 @@ function incrementalDump(chan) {
|
||||||
|
|
||||||
Channel.prototype.tryRegister = function(user) {
|
Channel.prototype.tryRegister = function(user) {
|
||||||
if(this.registered) {
|
if(this.registered) {
|
||||||
ActionLog.record(user.ip, user.name, "channel-register-failure", [this.name]);
|
ActionLog.record(user.ip, user.name, "channel-register-failure", [
|
||||||
|
this.name, "Channel already registered"]);
|
||||||
user.socket.emit("registerChannel", {
|
user.socket.emit("registerChannel", {
|
||||||
success: false,
|
success: false,
|
||||||
error: "This channel is already registered"
|
error: "This channel is already registered"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if(!user.loggedIn) {
|
else if(!user.loggedIn) {
|
||||||
ActionLog.record(user.ip, user.name, "channel-register-failure", [this.name]);
|
ActionLog.record(user.ip, user.name, "channel-register-failure", [
|
||||||
|
this.name, "Not logged in"]);
|
||||||
user.socket.emit("registerChannel", {
|
user.socket.emit("registerChannel", {
|
||||||
success: false,
|
success: false,
|
||||||
error: "You must log in to register a channel"
|
error: "You must log in to register a channel"
|
||||||
|
|
@ -293,7 +295,8 @@ Channel.prototype.tryRegister = function(user) {
|
||||||
|
|
||||||
}
|
}
|
||||||
else if(!Rank.hasPermission(user, "registerChannel")) {
|
else if(!Rank.hasPermission(user, "registerChannel")) {
|
||||||
ActionLog.record(user.ip, user.name, "channel-register-failure", [this.name]);
|
ActionLog.record(user.ip, user.name, "channel-register-failure", [
|
||||||
|
this.name, "Insufficient permissions"]);
|
||||||
user.socket.emit("registerChannel", {
|
user.socket.emit("registerChannel", {
|
||||||
success: false,
|
success: false,
|
||||||
error: "You don't have permission to register this channel"
|
error: "You don't have permission to register this channel"
|
||||||
|
|
@ -301,7 +304,7 @@ Channel.prototype.tryRegister = function(user) {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if(Database.registerChannel(this.name, user.name)) {
|
if(Database.registerChannel(this.name, user.name)) {
|
||||||
ActionLog.record(user.ip, user.name, "channel-register-success", [this.name]);
|
ActionLog.record(user.ip, user.name, "channel-register-success", this.name);
|
||||||
this.registered = true;
|
this.registered = true;
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
this.saveDump();
|
this.saveDump();
|
||||||
|
|
|
||||||
14
database.js
14
database.js
|
|
@ -187,6 +187,20 @@ function init() {
|
||||||
if(!results) {
|
if(!results) {
|
||||||
Logger.errlog.log("! Failed to create aliases table");
|
Logger.errlog.log("! Failed to create aliases table");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create action log table
|
||||||
|
query = ["CREATE TABLE IF NOT EXISTS `actionlog` (",
|
||||||
|
"`ip` VARCHAR(15) NOT NULL,",
|
||||||
|
"`name` VARCHAR(20) NOT NULL,",
|
||||||
|
"`action` VARCHAR(255) NOT NULL,",
|
||||||
|
"`args` TEXT NOT NULL,",
|
||||||
|
"`time` BIGINT NOT NULL,",
|
||||||
|
"PRIMARY KEY (`ip`, `time`), INDEX (`action`))",
|
||||||
|
"ENGINE = MyISAM;"].join("");
|
||||||
|
results = db.querySync(query);
|
||||||
|
if(!results) {
|
||||||
|
Logger.errlog.log("! Failed to create actionlog table");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* REGION Global Bans */
|
/* REGION Global Bans */
|
||||||
|
|
|
||||||
|
|
@ -142,18 +142,13 @@ function getErrlog() {
|
||||||
}
|
}
|
||||||
$("#errlog").click(getErrlog);
|
$("#errlog").click(getErrlog);
|
||||||
function getActionLog() {
|
function getActionLog() {
|
||||||
$.ajax(WEB_URL+"/api/plain/readlog?type=action&"+AUTH).done(function(data) {
|
$.getJSON(WEB_URL+"/api/json/readactionlog?"+AUTH+"&callback=?").done(function(data) {
|
||||||
var entries = [];
|
var entries = data;
|
||||||
var actions = [];
|
var actions = [];
|
||||||
data.split("\n").forEach(function(ln) {
|
entries.forEach(function (e) {
|
||||||
var entry;
|
if(actions.indexOf(e.action) == -1)
|
||||||
try {
|
actions.push(e.action);
|
||||||
entry = JSON.parse(ln);
|
e.time = parseInt(e.time);
|
||||||
if(actions.indexOf(entry.action) == -1)
|
|
||||||
actions.push(entry.action);
|
|
||||||
entries.push(entry);
|
|
||||||
}
|
|
||||||
catch(e) { }
|
|
||||||
});
|
});
|
||||||
var tbl = $("#actionlog table");
|
var tbl = $("#actionlog table");
|
||||||
tbl.data("sortby", "time");
|
tbl.data("sortby", "time");
|
||||||
|
|
@ -165,8 +160,8 @@ function getActionLog() {
|
||||||
$("<td/>").text(e.ip).appendTo(tr);
|
$("<td/>").text(e.ip).appendTo(tr);
|
||||||
$("<td/>").text(e.name).appendTo(tr);
|
$("<td/>").text(e.name).appendTo(tr);
|
||||||
$("<td/>").text(e.action).appendTo(tr);
|
$("<td/>").text(e.action).appendTo(tr);
|
||||||
$("<td/>").text(e.args.join(", ")).appendTo(tr);
|
$("<td/>").text(e.args).appendTo(tr);
|
||||||
$("<td/>").text(new Date(e.time).toTimeString()).appendTo(tr);
|
$("<td/>").text(new Date(e.time).toString()).appendTo(tr);
|
||||||
});
|
});
|
||||||
$("#actionlog table").data("entries", entries);
|
$("#actionlog table").data("entries", entries);
|
||||||
$("#actionlog_filter").html("");
|
$("#actionlog_filter").html("");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue