mirror of https://github.com/calzoneman/sync.git
Add action log
This commit is contained in:
parent
9d90f6c660
commit
6943845d7a
14
acp.js
14
acp.js
|
@ -1,24 +1,30 @@
|
|||
var Server = require("./server");
|
||||
var Auth = require("./auth");
|
||||
var Database = require("./database");
|
||||
var ActionLog = require("./actionlog");
|
||||
|
||||
module.exports = {
|
||||
init: function(user) {
|
||||
ActionLog.record(user.ip, user.name, "acp-init");
|
||||
user.socket.on("acp-announce", function(data) {
|
||||
ActionLog.record(user.ip, user.name, ["acp-announce", data]);
|
||||
Server.announcement = data;
|
||||
Server.io.sockets.emit("announcement", data);
|
||||
});
|
||||
|
||||
user.socket.on("acp-announce-clear", function() {
|
||||
ActionLog.record(user.ip, user.name, "acp-announce-clear");
|
||||
Server.announcement = null;
|
||||
});
|
||||
|
||||
user.socket.on("acp-global-ban", function(data) {
|
||||
ActionLog.record(user.ip, user.name, ["acp-global-ban", data.ip]);
|
||||
Database.globalBanIP(data.ip, data.note);
|
||||
user.socket.emit("acp-global-banlist", Database.refreshGlobalBans());
|
||||
});
|
||||
|
||||
user.socket.on("acp-global-unban", function(ip) {
|
||||
ActionLog.record(user.ip, user.name, ["acp-global-unban", data.ip]);
|
||||
Database.globalUnbanIP(ip);
|
||||
user.socket.emit("acp-global-banlist", Database.refreshGlobalBans());
|
||||
});
|
||||
|
@ -49,6 +55,7 @@ module.exports = {
|
|||
return;
|
||||
try {
|
||||
var hash = Database.generatePasswordReset(user.ip, data.name, data.email);
|
||||
ActionLog.record(user.ip, user.name, ["acp-reset-password", data.name]);
|
||||
}
|
||||
catch(e) {
|
||||
user.socket.emit("acp-reset-password", {
|
||||
|
@ -83,6 +90,7 @@ module.exports = {
|
|||
if(!db)
|
||||
return;
|
||||
|
||||
ActionLog.record(user.ip, user.name, ["acp-set-rank", data]);
|
||||
var query = Database.createQuery(
|
||||
"UPDATE registrations SET global_rank=? WHERE uname=?",
|
||||
[data.name, data.rank]
|
||||
|
@ -120,6 +128,7 @@ module.exports = {
|
|||
var c = Server.channels[data.name];
|
||||
if(!c)
|
||||
return;
|
||||
ActionLog.record(user.ip, user.name, "acp-channel-unload");
|
||||
c.initialized = data.save;
|
||||
c.users.forEach(function(u) {
|
||||
c.kick(u, "Channel shutting down");
|
||||
|
@ -127,5 +136,10 @@ module.exports = {
|
|||
Server.unload(c);
|
||||
}
|
||||
});
|
||||
|
||||
user.socket.on("acp-actionlog-clear", function() {
|
||||
ActionLog.clear();
|
||||
ActionLog.record(user.ip, user.name, "acp-actionlog-clear");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
var fs = require("fs");
|
||||
|
||||
var buffer = [];
|
||||
|
||||
exports.record = function(ip, name, action) {
|
||||
buffer.push(JSON.stringify({
|
||||
ip: ip,
|
||||
name: name,
|
||||
action: action,
|
||||
time: Date.now()
|
||||
}));
|
||||
}
|
||||
|
||||
exports.flush = function() {
|
||||
if(buffer.length == 0)
|
||||
return;
|
||||
var text = buffer.join("\n") + "\n";
|
||||
buffer = [];
|
||||
fs.appendFile("action.log", text, function(err) {
|
||||
if(err) {
|
||||
errlog.log("Append to actionlog failed: ");
|
||||
errlog.log(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
exports.clear = function() {
|
||||
try {
|
||||
fs.renameSync("action.log", "action-until-"+Date.now()+".log");
|
||||
}
|
||||
catch(e) { }
|
||||
}
|
||||
|
||||
setInterval(exports.flush, 15000);
|
126
api.js
126
api.js
|
@ -15,6 +15,7 @@ var Logger = require("./logger.js");
|
|||
var apilog = new Logger.Logger("api.log");
|
||||
var Database = require("./database.js");
|
||||
var Config = require("./config.js");
|
||||
var ActionLog = require("./actionlog.js");
|
||||
var fs = require("fs");
|
||||
|
||||
var plainHandlers = {
|
||||
|
@ -32,11 +33,21 @@ var jsonHandlers = {
|
|||
"setprofile" : handleProfileChange,
|
||||
"getprofile" : handleProfileGet,
|
||||
"setemail" : handleEmailChange,
|
||||
"globalbans" : handleGlobalBans,
|
||||
"admreports" : handleAdmReports,
|
||||
"acppwreset" : handleAcpPasswordReset
|
||||
};
|
||||
|
||||
function getClientIP(req) {
|
||||
var ip;
|
||||
var forward = req.header("x-forwarded-for");
|
||||
if(forward) {
|
||||
ip = forward.split(",")[0];
|
||||
}
|
||||
if(!ip) {
|
||||
ip = req.connection.remoteAddress;
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
function handle(path, req, res) {
|
||||
var parts = path.split("/");
|
||||
var last = parts[parts.length - 1];
|
||||
|
@ -193,12 +204,14 @@ function handleLogin(params, req, res) {
|
|||
|
||||
var row = Auth.login(name, pw, session);
|
||||
if(row) {
|
||||
ActionLog.record(getClientIP(req), name, "login-success");
|
||||
sendJSON(res, {
|
||||
success: true,
|
||||
session: row.session_hash
|
||||
});
|
||||
}
|
||||
else {
|
||||
ActionLog.record(getClientIP(req), name, "login-failure");
|
||||
sendJSON(res, {
|
||||
error: "Invalid username/password",
|
||||
success: false
|
||||
|
@ -219,6 +232,7 @@ function handlePasswordChange(params, req, res) {
|
|||
}
|
||||
var row = Auth.login(name, oldpw);
|
||||
if(row) {
|
||||
ActionLog.record(getClientIP(req), name, "password-change");
|
||||
var success = Auth.setUserPassword(name, newpw);
|
||||
sendJSON(res, {
|
||||
success: success,
|
||||
|
@ -237,11 +251,12 @@ function handlePasswordChange(params, req, res) {
|
|||
function handlePasswordReset(params, req, res) {
|
||||
var name = params.name || "";
|
||||
var email = unescape(params.email || "");
|
||||
var ip = req.socket.address().address;
|
||||
var ip = getClientIP(req);
|
||||
|
||||
var hash = false;
|
||||
try {
|
||||
hash = Database.generatePasswordReset(ip, name, email);
|
||||
ActionLog.record(ip, name, "password-reset-generate");
|
||||
}
|
||||
catch(e) {
|
||||
sendJSON(res, {
|
||||
|
@ -301,7 +316,7 @@ function handlePasswordReset(params, req, res) {
|
|||
|
||||
function handlePasswordRecover(params, req, res) {
|
||||
var hash = params.hash || "";
|
||||
var ip = req.socket.address().address;
|
||||
var ip = getClientIP(req);
|
||||
|
||||
try {
|
||||
var info = Database.recoverPassword(hash);
|
||||
|
@ -310,10 +325,12 @@ function handlePasswordRecover(params, req, res) {
|
|||
name: info[0],
|
||||
pw: info[1]
|
||||
});
|
||||
ActionLog.record(ip, name, "password-recover-success");
|
||||
Logger.syslog.log(ip + " recovered password for " + name);
|
||||
return;
|
||||
}
|
||||
catch(e) {
|
||||
ActionLog.record(ip, name, "password-recover-failure");
|
||||
sendJSON(res, {
|
||||
success: false,
|
||||
error: e
|
||||
|
@ -412,6 +429,7 @@ function handleEmailChange(params, req, res) {
|
|||
var row = Auth.login(name, pw);
|
||||
if(row) {
|
||||
var success = Database.setUserEmail(name, email);
|
||||
ActionLog.record(getClientIP(req), name, ["email-update", email]);
|
||||
sendJSON(res, {
|
||||
success: success,
|
||||
error: success ? "" : "Email update failed",
|
||||
|
@ -438,6 +456,7 @@ function handleRegister(params, req, res) {
|
|||
return;
|
||||
}
|
||||
else if(Auth.isRegistered(name)) {
|
||||
ActionLog.record(getClientIP(req), name, "register-failure");
|
||||
sendJSON(res, {
|
||||
success: false,
|
||||
error: "That username is already taken"
|
||||
|
@ -445,6 +464,7 @@ function handleRegister(params, req, res) {
|
|||
return false;
|
||||
}
|
||||
else if(!Auth.validateName(name)) {
|
||||
ActionLog.record(getClientIP(req), name, "register-failure");
|
||||
sendJSON(res, {
|
||||
success: false,
|
||||
error: "Invalid username. Usernames must be 1-20 characters long and consist only of alphanumeric characters and underscores"
|
||||
|
@ -453,7 +473,8 @@ function handleRegister(params, req, res) {
|
|||
else {
|
||||
var session = Auth.register(name, pw);
|
||||
if(session) {
|
||||
Logger.syslog.log(this.ip + " registered " + name);
|
||||
ActionLog.record(getClientIP(req), name, "register-success");
|
||||
Logger.syslog.log(getClientIP(req) + " registered " + name);
|
||||
sendJSON(res, {
|
||||
success: true,
|
||||
session: session
|
||||
|
@ -468,103 +489,12 @@ function handleRegister(params, req, res) {
|
|||
}
|
||||
}
|
||||
|
||||
function handleGlobalBans(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 action = params.action || "list";
|
||||
if(action == "list") {
|
||||
var gbans = Database.refreshGlobalBans();
|
||||
sendJSON(res, gbans);
|
||||
}
|
||||
else if(action == "add") {
|
||||
var ip = params.ip || "";
|
||||
var reason = params.reason || "";
|
||||
if(!ip.match(/\d+\.\d+\.(\d+\.(\d+)?)?/)) {
|
||||
sendJSON(res, {
|
||||
error: "Invalid IP address"
|
||||
});
|
||||
return;
|
||||
}
|
||||
var result = Database.globalBanIP(ip, reason);
|
||||
sendJSON(res, {
|
||||
success: result,
|
||||
ip: ip,
|
||||
reason: reason
|
||||
});
|
||||
}
|
||||
else if(action == "remove") {
|
||||
var ip = params.ip || "";
|
||||
if(!ip.match(/\d+\.\d+\.(\d+\.(\d+)?)?/)) {
|
||||
sendJSON(res, {
|
||||
error: "Invalid IP address"
|
||||
});
|
||||
return;
|
||||
}
|
||||
var result = Database.globalUnbanIP(ip);
|
||||
sendJSON(res, {
|
||||
success: result,
|
||||
ip: ip,
|
||||
});
|
||||
}
|
||||
else {
|
||||
sendJSON(res, {
|
||||
error: "Invalid action: " + action
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleAdmReports(params, req, res) {
|
||||
sendJSON(res, {
|
||||
error: "Not implemented"
|
||||
});
|
||||
}
|
||||
|
||||
function handleAcpPasswordReset(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 action = params.action || "";
|
||||
if(action == "reset") {
|
||||
var uname = params.reset_name;
|
||||
if(Auth.getGlobalRank(uname) > row.global_rank) {
|
||||
sendJSON(res, {
|
||||
success: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
var new_pw = Database.resetPassword(uname);
|
||||
if(new_pw) {
|
||||
sendJSON(res, {
|
||||
success: true,
|
||||
pw: new_pw
|
||||
});
|
||||
}
|
||||
else {
|
||||
sendJSON(res, {
|
||||
success: false
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
sendJSON(res, {
|
||||
success: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function
|
||||
function pipeLast(res, file, len) {
|
||||
fs.stat(file, function(err, data) {
|
||||
|
@ -599,6 +529,10 @@ function handleReadLog(params, req, res) {
|
|||
else if(type == "err") {
|
||||
pipeLast(res, "error.log", 1024*1024);
|
||||
}
|
||||
else if(type == "action") {
|
||||
ActionLog.flush();
|
||||
pipeLast(res, "action.log", 1024*1024*100);
|
||||
}
|
||||
else if(type == "channel") {
|
||||
var chan = params.channel || "";
|
||||
fs.exists("chanlogs/" + chan + ".log", function(exists) {
|
||||
|
|
|
@ -268,12 +268,14 @@ function incrementalDump(chan) {
|
|||
|
||||
Channel.prototype.tryRegister = function(user) {
|
||||
if(this.registered) {
|
||||
ActionLog.record(user.ip, user.name, ["channel-register-failure", this.name]);
|
||||
user.socket.emit("registerChannel", {
|
||||
success: false,
|
||||
error: "This channel is already registered"
|
||||
});
|
||||
}
|
||||
else if(!user.loggedIn) {
|
||||
ActionLog.record(user.ip, user.name, ["channel-register-failure", this.name]);
|
||||
user.socket.emit("registerChannel", {
|
||||
success: false,
|
||||
error: "You must log in to register a channel"
|
||||
|
@ -281,6 +283,7 @@ Channel.prototype.tryRegister = function(user) {
|
|||
|
||||
}
|
||||
else if(!Rank.hasPermission(user, "registerChannel")) {
|
||||
ActionLog.record(user.ip, user.name, ["channel-register-failure", this.name]);
|
||||
user.socket.emit("registerChannel", {
|
||||
success: false,
|
||||
error: "You don't have permission to register this channel"
|
||||
|
@ -288,6 +291,7 @@ Channel.prototype.tryRegister = function(user) {
|
|||
}
|
||||
else {
|
||||
if(Database.registerChannel(this.name)) {
|
||||
ActionLog.record(user.ip, user.name, ["channel-register-success", this.name]);
|
||||
this.registered = true;
|
||||
this.initialized = true;
|
||||
this.saveDump();
|
||||
|
|
17
user.js
17
user.js
|
@ -18,6 +18,7 @@ var Database = require("./database.js");
|
|||
var Logger = require("./logger.js");
|
||||
var Config = require("./config.js");
|
||||
var ACP = require("./acp");
|
||||
var ActionLog = require("./actionlog");
|
||||
|
||||
// Represents a client connected via socket.io
|
||||
var User = function(socket, ip) {
|
||||
|
@ -526,13 +527,6 @@ User.prototype.initCallbacks = function() {
|
|||
var lastguestlogin = {};
|
||||
// Attempt to login
|
||||
User.prototype.login = function(name, pw, session) {
|
||||
if(this.channel != null && name != "") {
|
||||
for(var i = 0; i < this.channel.users.length; i++) {
|
||||
if(this.channel.users[i].name == name) {
|
||||
this.channel.kick(this.channel.users[i], "Duplicate login");
|
||||
}
|
||||
}
|
||||
}
|
||||
// No password => try guest login
|
||||
if(pw == "" && session == "") {
|
||||
if(this.ip in lastguestlogin) {
|
||||
|
@ -593,6 +587,14 @@ User.prototype.login = function(name, pw, session) {
|
|||
try {
|
||||
var row;
|
||||
if((row = Auth.login(name, pw, session))) {
|
||||
if(this.channel != null) {
|
||||
for(var i = 0; i < this.channel.users.length; i++) {
|
||||
if(this.channel.users[i].name == name) {
|
||||
this.channel.kick(this.channel.users[i], "Duplicate login");
|
||||
}
|
||||
}
|
||||
}
|
||||
ActionLog.record(this.ip, name, "login-success");
|
||||
this.loggedIn = true;
|
||||
this.socket.emit("login", {
|
||||
success: true,
|
||||
|
@ -620,6 +622,7 @@ User.prototype.login = function(name, pw, session) {
|
|||
}
|
||||
// Wrong password
|
||||
else {
|
||||
ActionLog.record(this.ip, this.name, "login-failure");
|
||||
this.socket.emit("login", {
|
||||
success: false,
|
||||
error: "Invalid session"
|
||||
|
|
17
www/acp.html
17
www/acp.html
|
@ -59,6 +59,7 @@
|
|||
<li id="li_gbans"><a href="javascript:void(0)" id="show_gbans">Global Bans</a></li>
|
||||
<li id="li_userlookup"><a href="javascript:void(0)" id="show_userlookup">Users</a></li>
|
||||
<li id="li_chanloaded"><a href="javascript:void(0)" id="show_chanloaded">Loaded Channels</a></li>
|
||||
<li id="li_actionlog"><a href="javascript:void(0)" id="show_actionlog">Action Log</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -174,6 +175,22 @@
|
|||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<div class="span12" id="actionlog">
|
||||
<h3>Action Log</h3>
|
||||
<select multiple="multiple" id="actionlog_filter">
|
||||
</select>
|
||||
<button class="btn btn-danger" id="actionlog_clear">Clear</button>
|
||||
<table class="table table-bordered table-striped table-compact">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<th>Name</th>
|
||||
<th>Action</th>
|
||||
<th>Time</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /container -->
|
||||
<div class="push"></div>
|
||||
|
|
|
@ -48,6 +48,27 @@ $("#show_chanloaded").click(function() {
|
|||
$("#listloaded_refresh").click(function() {
|
||||
socket.emit("acp-list-loaded");
|
||||
});
|
||||
menuHandler("#show_actionlog", "#actionlog");
|
||||
$("#show_actionlog").click(getActionLog);
|
||||
$("#actionlog_filter").click(function() {
|
||||
var actions = $(this).val();
|
||||
$("#actionlog tbody").remove();
|
||||
$("#actionlog table").data("entries").forEach(function(e) {
|
||||
if(typeof e.action == "string" && actions.indexOf(e.action) == -1)
|
||||
return;
|
||||
if(typeof e.action == "object" && "0" in e.action && actions.indexOf(e.action[0]) == -1)
|
||||
return;
|
||||
|
||||
var tr = $("<tr/>").appendTo($("#actionlog table"));
|
||||
$("<td/>").text(e.ip).appendTo(tr);
|
||||
$("<td/>").text(e.name).appendTo(tr);
|
||||
$("<td/>").text(e.action).appendTo(tr);
|
||||
$("<td/>").text(new Date(e.time).toTimeString()).appendTo(tr);
|
||||
});
|
||||
});
|
||||
$("#actionlog_clear").click(function() {
|
||||
socket.emit("acp-actionlog-clear");
|
||||
});
|
||||
|
||||
function getSyslog() {
|
||||
$.ajax(WEB_URL+"/api/plain/readlog?type=sys&"+AUTH).done(function(data) {
|
||||
|
@ -61,6 +82,39 @@ function getErrlog() {
|
|||
});
|
||||
}
|
||||
$("#errlog").click(getErrlog);
|
||||
function getActionLog() {
|
||||
$.ajax(WEB_URL+"/api/plain/readlog?type=action&"+AUTH).done(function(data) {
|
||||
var entries = [];
|
||||
var actions = [];
|
||||
data.split("\n").forEach(function(ln) {
|
||||
var entry;
|
||||
try {
|
||||
entry = JSON.parse(ln);
|
||||
if(typeof entry.action == "string") {
|
||||
if(actions.indexOf(entry.action) == -1)
|
||||
actions.push(entry.action);
|
||||
}
|
||||
else if(typeof entry.action == "object" && "0" in entry.action) {
|
||||
if(actions.indexOf(entry.action[0]) == -1)
|
||||
actions.push(entry.action[0]);
|
||||
}
|
||||
entries.push(entry);
|
||||
}
|
||||
catch(e) { }
|
||||
});
|
||||
entries.sort(function(a, b) {
|
||||
return a.time == b.time ? 0 : (a.time < b.time ? 1 : -1);
|
||||
});
|
||||
$("#actionlog table").data("entries", entries);
|
||||
$("#actionlog_filter").html("");
|
||||
actions.sort(function(a, b) {
|
||||
return a == b ? 0 : (a < b ? -1 : 1);
|
||||
});
|
||||
actions.forEach(function(a) {
|
||||
$("<option/>").text(a).val(a).appendTo($("#actionlog_filter"));
|
||||
});
|
||||
});
|
||||
}
|
||||
function getChanlog() {
|
||||
var chan = $("#channame").val();
|
||||
$.ajax(WEB_URL+"/api/plain/readlog?type=channel&channel="+chan+"&"+AUTH).done(function(data) {
|
||||
|
|
Loading…
Reference in New Issue