CyTube/lib/user.js

442 lines
12 KiB
JavaScript
Raw Normal View History

var Logger = require("./logger");
2013-10-11 21:31:40 +00:00
var Server = require("./server");
var util = require("./utilities");
var MakeEmitter = require("./emitter");
2014-01-06 15:55:12 +00:00
var db = require("./database");
var InfoGetter = require("./get-info");
2014-01-22 23:11:26 +00:00
var Config = require("./config");
2014-01-30 03:50:45 +00:00
var ACP = require("./acp");
2014-05-21 02:30:14 +00:00
var Account = require("./account");
var Flags = require("./flags");
function User(socket) {
var self = this;
MakeEmitter(self);
2014-05-21 02:30:14 +00:00
self.flags = 0;
self.socket = socket;
self.realip = socket._realip;
self.displayip = socket._displayip;
self.hostmask = socket._hostmask;
self.account = Account.default(self.realip);
self.channel = null;
self.queueLimiter = util.newRateLimiter();
self.chatLimiter = util.newRateLimiter();
self.awaytimer = false;
2013-02-16 05:02:42 +00:00
2014-05-21 02:30:14 +00:00
var announcement = Server.getServer().announcement;
if (announcement != null) {
self.socket.emit("announcement", announcement);
}
self.socket.once("joinChannel", function (data) {
if (typeof data !== "object" || typeof data.name !== "string") {
return;
}
2014-05-21 02:30:14 +00:00
if (self.inChannel()) {
return;
}
if (!util.isValidChannelName(data.name)) {
self.socket.emit("errorMsg", {
msg: "Invalid channel name. Channel names may consist of 1-30 " +
"characters in the set a-z, A-Z, 0-9, -, and _"
});
self.kick("Invalid channel name");
return;
}
data.name = data.name.toLowerCase();
if (data.name in Config.get("channel-blacklist")) {
self.kick("This channel is blacklisted.");
return;
}
2014-05-21 02:30:14 +00:00
self.waitFlag(Flags.U_READY, function () {
var chan = Server.getServer().getChannel(data.name);
chan.joinUser(self, data);
});
2014-02-02 18:41:41 +00:00
});
2014-01-30 03:50:45 +00:00
self.socket.once("initACP", function () {
2014-05-21 02:30:14 +00:00
self.waitFlag(Flags.U_LOGGED_IN, function () {
if (self.account.globalRank >= 255) {
2014-01-30 03:50:45 +00:00
ACP.init(self);
} else {
self.kick("Attempted initACP from non privileged user. This incident " +
"will be reported.");
Logger.eventlog.log("[acp] Attempted initACP from socket client " +
self.getName() + "@" + self.realip);
2014-01-30 03:50:45 +00:00
}
});
});
2014-01-06 15:55:12 +00:00
self.socket.on("login", function (data) {
data = (typeof data === "object") ? data : {};
var name = data.name;
if (typeof name !== "string") {
return;
}
var pw = data.pw || "";
if (typeof pw !== "string") {
pw = "";
}
2014-05-21 02:30:14 +00:00
if (self.is(Flags.U_LOGGING_IN) || self.is(Flags.U_LOGGED_IN)) {
return;
}
if (!pw) {
2014-01-06 15:55:12 +00:00
self.guestLogin(name);
2014-05-21 02:30:14 +00:00
} else {
2014-02-16 19:27:01 +00:00
self.login(name, pw);
2014-01-06 15:55:12 +00:00
}
});
2014-05-21 02:30:14 +00:00
self.on("login", function (account) {
if (account.globalRank >= 255) {
self.initAdminCallbacks();
}
2014-01-26 06:13:33 +00:00
});
}
2013-02-16 05:02:42 +00:00
2014-05-21 02:30:14 +00:00
User.prototype.die = function () {
for (var key in this.socket._events) {
delete this.socket._events[key];
}
delete this.socket.typecheckedOn;
delete this.socket.typecheckedOnce;
for (var key in this.__evHandlers) {
delete this.__evHandlers[key];
}
if (this.awaytimer) {
clearTimeout(this.awaytimer);
}
this.dead = true;
};
User.prototype.is = function (flag) {
return Boolean(this.flags & flag);
};
User.prototype.setFlag = function (flag) {
this.flags |= flag;
this.emit("setFlag", flag);
};
User.prototype.clearFlag = function (flag) {
this.flags &= ~flag;
this.emit("clearFlag", flag);
};
User.prototype.waitFlag = function (flag, cb) {
var self = this;
if (self.is(flag)) {
cb();
} else {
var wait = function (f) {
if (f === flag) {
2014-05-21 02:30:14 +00:00
self.unbind("setFlag", wait);
cb();
}
};
self.on("setFlag", wait);
}
};
User.prototype.getName = function () {
return this.account.name;
};
User.prototype.getLowerName = function () {
return this.account.lowername;
};
2013-09-18 23:27:42 +00:00
User.prototype.inChannel = function () {
return this.channel != null && !this.channel.dead;
2013-11-25 22:20:15 +00:00
};
2014-05-21 02:30:14 +00:00
/* Called when a user's AFK status changes */
User.prototype.setAFK = function (afk) {
if (!this.inChannel()) {
return;
}
2014-05-21 02:30:14 +00:00
/* No change in AFK status, don't need to change anything */
if (this.is(Flags.U_AFK) === afk) {
2014-05-24 02:07:54 +00:00
this.autoAFK();
2013-08-01 19:12:57 +00:00
return;
}
2013-09-26 18:29:36 +00:00
if (afk) {
2014-05-21 02:30:14 +00:00
this.setFlag(Flags.U_AFK);
if (this.channel.modules.voteskip) {
this.channel.modules.voteskip.unvote(this.realip);
}
2013-09-12 03:16:56 +00:00
} else {
2014-05-21 02:30:14 +00:00
this.clearFlag(Flags.U_AFK);
2013-08-01 13:39:10 +00:00
this.autoAFK();
}
2014-05-21 02:30:14 +00:00
/* Number of AFK users changed, voteskip state changes */
if (this.channel.modules.voteskip) {
this.channel.modules.voteskip.update();
}
this.channel.broadcastAll("setAFK", {
name: this.getName(),
2013-08-01 19:12:57 +00:00
afk: afk
});
2013-09-26 18:29:36 +00:00
};
2014-05-21 02:30:14 +00:00
/* Automatically tag a user as AFK after a period of inactivity */
User.prototype.autoAFK = function () {
2013-09-26 18:29:36 +00:00
var self = this;
if (self.awaytimer) {
2013-09-26 18:29:36 +00:00
clearTimeout(self.awaytimer);
}
2014-05-21 02:30:14 +00:00
if (!self.inChannel() || !self.channel.modules.options) {
return;
2013-11-30 03:09:19 +00:00
}
2014-05-21 02:30:14 +00:00
/* Don't set a timer if the duration is invalid */
var timeout = parseFloat(self.channel.modules.options.get("afk_timeout"));
if (isNaN(timeout) || timeout <= 0) {
2013-11-30 03:09:19 +00:00
return;
}
2013-09-26 18:29:36 +00:00
self.awaytimer = setTimeout(function () {
self.setAFK(true);
2013-11-30 03:09:19 +00:00
}, timeout * 1000);
2013-09-26 18:29:36 +00:00
};
2013-10-12 23:59:50 +00:00
User.prototype.kick = function (reason) {
this.socket.emit("kick", { reason: reason });
2014-10-26 03:49:22 +00:00
this.socket.disconnect();
2013-10-12 23:59:50 +00:00
};
User.prototype.initAdminCallbacks = function () {
var self = this;
self.socket.on("borrow-rank", function (rank) {
if (self.inChannel()) {
if (typeof rank !== "number") {
return;
}
2014-05-21 02:30:14 +00:00
if (rank > self.account.globalRank) {
return;
}
2014-05-21 02:30:14 +00:00
if (rank === 255 && self.account.globalRank > 255) {
rank = self.account.globalRank;
}
2014-05-21 02:30:14 +00:00
self.account.channelRank = rank;
2014-10-10 01:46:16 +00:00
self.account.effectiveRank = rank;
self.socket.emit("rank", rank);
2014-05-21 02:30:14 +00:00
self.channel.broadcastAll("setUserRank", {
name: self.getName(),
rank: rank
});
}
});
};
2014-02-16 19:27:01 +00:00
User.prototype.login = function (name, pw) {
var self = this;
2014-05-21 02:30:14 +00:00
self.setFlag(Flags.U_LOGGING_IN);
2014-02-16 19:27:01 +00:00
db.users.verifyLogin(name, pw, function (err, user) {
if (err) {
if (err === "Invalid username/password combination") {
Logger.eventlog.log("[loginfail] Login failed (bad password): " + name
+ "@" + self.realip);
2014-02-16 19:27:01 +00:00
}
self.socket.emit("login", {
success: false,
error: err
});
2014-07-02 04:09:20 +00:00
self.clearFlag(Flags.U_LOGGING_IN);
2014-02-16 19:27:01 +00:00
return;
}
2014-05-21 02:30:14 +00:00
var opts = { name: user.name };
2014-02-16 19:27:01 +00:00
if (self.inChannel()) {
2014-05-21 02:30:14 +00:00
opts.channel = self.channel.name;
2014-02-16 19:27:01 +00:00
}
2014-05-21 02:30:14 +00:00
self.setFlag(Flags.U_REGISTERED);
self.refreshAccount(opts, function (err, account) {
if (err) {
Logger.errlog.log("[SEVERE] getAccount failed for user " + user.name);
Logger.errlog.log(err);
2014-07-02 04:09:20 +00:00
self.clearFlag(Flags.U_REGISTERED);
self.clearFlag(Flags.U_LOGGING_IN);
2014-05-21 02:30:14 +00:00
return;
}
self.socket.emit("login", {
success: true,
name: user.name
});
db.recordVisit(self.realip, self.getName());
2014-05-21 02:30:14 +00:00
self.socket.emit("rank", self.account.effectiveRank);
Logger.syslog.log(self.realip + " logged in as " + user.name);
2014-05-21 02:30:14 +00:00
if (self.inChannel()) {
self.channel.logger.log(self.displayip + " logged in as " + user.name);
2014-05-21 02:30:14 +00:00
}
self.setFlag(Flags.U_LOGGED_IN);
2014-07-02 04:09:20 +00:00
self.clearFlag(Flags.U_LOGGING_IN);
2014-05-21 02:30:14 +00:00
self.emit("login", self.account);
});
2014-02-16 19:27:01 +00:00
});
};
2013-05-03 03:13:46 +00:00
var lastguestlogin = {};
2013-09-26 18:18:37 +00:00
User.prototype.guestLogin = function (name) {
2013-08-17 20:54:23 +00:00
var self = this;
2013-09-26 18:18:37 +00:00
if (self.realip in lastguestlogin) {
var diff = (Date.now() - lastguestlogin[self.realip]) / 1000;
2014-01-22 23:11:26 +00:00
if (diff < Config.get("guest-login-delay")) {
2013-08-17 20:54:23 +00:00
self.socket.emit("login", {
success: false,
error: "Guest logins are restricted to one per IP address per " +
2014-01-22 23:11:26 +00:00
Config.get("guest-login-delay") + " seconds."
2013-08-17 20:54:23 +00:00
});
return;
2013-08-17 20:54:23 +00:00
}
2013-09-26 18:18:37 +00:00
}
2013-08-17 20:54:23 +00:00
if (!util.isValidUserName(name)) {
2013-09-26 18:18:37 +00:00
self.socket.emit("login", {
success: false,
error: "Invalid username. Usernames must be 1-20 characters long and " +
2014-05-01 00:36:01 +00:00
"consist only of characters a-z, A-Z, 0-9, -, or _."
2013-09-26 18:18:37 +00:00
});
return;
}
2013-08-17 20:54:23 +00:00
// Prevent duplicate logins
2014-05-21 02:30:14 +00:00
self.setFlag(Flags.U_LOGGING_IN);
db.users.isUsernameTaken(name, function (err, taken) {
2014-05-21 02:30:14 +00:00
self.clearFlag(Flags.U_LOGGING_IN);
2013-09-26 18:29:36 +00:00
if (err) {
2013-09-26 18:18:37 +00:00
self.socket.emit("login", {
success: false,
error: err
2013-09-26 18:18:37 +00:00
});
return;
}
2013-08-17 20:54:23 +00:00
2013-09-26 18:29:36 +00:00
if (taken) {
2013-08-17 20:54:23 +00:00
self.socket.emit("login", {
2013-09-26 18:18:37 +00:00
success: false,
error: "That username is registered."
2013-02-16 05:02:42 +00:00
});
2013-09-26 18:18:37 +00:00
return;
}
2013-09-26 18:29:36 +00:00
if (self.inChannel()) {
var nameLower = name.toLowerCase();
for (var i = 0; i < self.channel.users.length; i++) {
2014-05-21 02:30:14 +00:00
if (self.channel.users[i].getLowerName() === nameLower) {
2013-09-26 18:18:37 +00:00
self.socket.emit("login", {
success: false,
error: "That name is already in use on this channel."
2013-09-26 18:18:37 +00:00
});
return;
}
2013-06-19 21:54:27 +00:00
}
2013-09-26 18:18:37 +00:00
}
// Login succeeded
lastguestlogin[self.realip] = Date.now();
2014-05-21 02:30:14 +00:00
var opts = { name: name };
2013-09-26 18:29:36 +00:00
if (self.inChannel()) {
2014-05-21 02:30:14 +00:00
opts.channel = self.channel.name;
2013-09-26 18:18:37 +00:00
}
2014-05-21 02:30:14 +00:00
self.refreshAccount(opts, function (err, account) {
if (err) {
Logger.errlog.log("[SEVERE] getAccount failed for guest login " + name);
Logger.errlog.log(err);
return;
}
2013-09-26 18:29:36 +00:00
2014-05-21 02:30:14 +00:00
self.socket.emit("login", {
success: true,
name: name,
guest: true
});
db.recordVisit(self.realip, self.getName());
2014-05-21 02:30:14 +00:00
self.socket.emit("rank", 0);
Logger.syslog.log(self.realip + " signed in as " + name);
2014-05-21 02:30:14 +00:00
if (self.inChannel()) {
self.channel.logger.log(self.displayip + " signed in as " + name);
2014-05-21 02:30:14 +00:00
}
self.setFlag(Flags.U_LOGGED_IN);
self.emit("login", self.account);
});
});
2013-09-26 18:29:36 +00:00
};
2013-02-16 05:02:42 +00:00
/* Clean out old login throttlers to save memory */
setInterval(function () {
var delay = Config.get("guest-login-delay");
for (var ip in lastguestlogin) {
var diff = (Date.now() - lastguestlogin[ip]) / 1000;
if (diff > delay) {
delete lastguestlogin[ip];
}
}
if (Config.get("aggressive-gc") && global && global.gc) {
global.gc();
}
}, 5 * 60 * 1000);
2014-05-21 02:30:14 +00:00
User.prototype.refreshAccount = function (opts, cb) {
if (!cb) {
cb = opts;
opts = {};
}
var different = false;
for (var key in opts) {
if (opts[key] !== this.account[key]) {
different = true;
break;
}
}
if (!different) {
return;
}
var name = ("name" in opts) ? opts.name : this.account.name;
opts.registered = this.is(Flags.U_REGISTERED);
var self = this;
var old = this.account;
Account.getAccount(name, this.realip, opts, function (err, account) {
2014-05-21 02:30:14 +00:00
if (!err) {
/* Update account if anything changed in the meantime */
for (var key in old) {
if (self.account[key] !== old[key]) {
account[key] = self.account[key];
}
}
self.account = account;
}
cb(err, account);
});
2014-02-02 18:41:41 +00:00
};
2013-07-16 03:01:12 +00:00
module.exports = User;