1 var Server = require("./server");
2 var util = require("./utilities");
3 var db = require("./database");
4 var Config = require("./config");
5 var ACP = require("./acp");
6 var Account = require("./account");
7 var Flags = require("./flags");
8 import { EventEmitter } from 'events';
9 import Logger from './logger';
10 import net from 'net';
12 const LOGGER = require('@calzoneman/jsli')('user');
14 function User(socket, ip, loginInfo) {
17 // Expanding IPv6 addresses shouldn't really be necessary
18 // At some point, the IPv6 related stuff should be revisited
19 this.realip = net.isIPv6(ip) ? util.expandIPv6(ip) : ip;
20 this.displayip = util.cloakIP(this.realip);
22 this.queueLimiter = util.newRateLimiter();
23 this.chatLimiter = util.newRateLimiter();
24 this.reqPlaylistLimiter = util.newRateLimiter();
25 this.awaytimer = false;
28 this.account = new Account.Account(this.realip, loginInfo, socket.context.aliases);
29 this.registrationTime = new Date(this.account.user.time);
30 this.setFlag(Flags.U_REGISTERED | Flags.U_LOGGED_IN | Flags.U_READY);
31 socket.emit("login", {
36 socket.emit("rank", this.account.effectiveRank);
37 if (this.account.globalRank >= 255) {
38 this.initAdminCallbacks();
40 this.emit("login", this.account);
41 LOGGER.info(ip + " logged in as " + this.getName());
43 this.account = new Account.Account(this.realip, null, socket.context.aliases);
44 socket.emit("rank", -1);
45 this.setFlag(Flags.U_READY);
46 this.once("login", account => {
47 if (account.globalRank >= 255) {
48 this.initAdminCallbacks();
53 socket.once("joinChannel", data => this.handleJoinChannel(data));
54 socket.once("initACP", () => this.handleInitACP());
55 socket.on("login", data => this.handleLogin(data));
58 User.prototype = Object.create(EventEmitter.prototype);
60 User.prototype.handleJoinChannel = function handleJoinChannel(data) {
61 if (typeof data !== "object" || typeof data.name !== "string") {
65 if (this.inChannel()) {
69 if (!util.isValidChannelName(data.name)) {
70 this.socket.emit("errorMsg", {
71 msg: "Invalid channel name. Channel names may consist of 1-30 " +
72 "characters in the set a-z, A-Z, 0-9, -, and _"
74 this.kick("Invalid channel name");
78 data.name = data.name.toLowerCase();
79 if (data.name in Config.get("channel-blacklist")) {
80 this.kick("This channel is blacklisted.");
84 this.waitFlag(Flags.U_READY, () => {
87 chan = Server.getServer().getChannel(data.name);
89 if (error.code === 'EWRONGPART') {
90 this.socket.emit("errorMsg", {
91 msg: "Channel '" + data.name + "' is hosted on another server. " +
92 "Try refreshing the page to update the connection URL."
95 LOGGER.error("Unexpected error from getChannel(): %s", error.stack);
96 this.socket.emit("errorMsg", {
97 msg: "Unable to join channel due to an internal error"
103 if (!chan.is(Flags.C_READY)) {
104 chan.once("loadFail", reason => {
105 this.socket.emit("errorMsg", {
109 this.kick(`Channel could not be loaded: ${reason}`);
112 chan.joinUser(this, data);
116 User.prototype.handleInitACP = function handleInitACP() {
117 this.waitFlag(Flags.U_LOGGED_IN, () => {
118 if (this.account.globalRank >= 255) {
121 this.kick("Attempted initACP from non privileged user. This incident " +
122 "will be reported.");
123 Logger.eventlog.log("[acp] Attempted initACP from socket client " +
124 this.getName() + "@" + this.realip);
129 User.prototype.handleLogin = function handleLogin(data) {
130 if (typeof data !== "object") {
131 this.socket.emit("errorMsg", {
132 msg: "Invalid login frame"
137 var name = data.name;
138 if (typeof name !== "string") {
142 var pw = data.pw || "";
143 if (typeof pw !== "string") {
147 if (this.is(Flags.U_LOGGING_IN) || this.is(Flags.U_LOGGED_IN)) {
152 this.guestLogin(name);
154 this.login(name, pw);
158 User.prototype.die = function () {
159 for (const key in this.socket._events) {
160 delete this.socket._events[key];
163 delete this.socket.typecheckedOn;
164 delete this.socket.typecheckedOnce;
166 for (const key in this.__evHandlers) {
167 delete this.__evHandlers[key];
170 if (this.awaytimer) {
171 clearTimeout(this.awaytimer);
177 User.prototype.is = function (flag) {
178 return Boolean(this.flags & flag);
181 User.prototype.setFlag = function (flag) {
183 this.emit("setFlag", flag);
186 User.prototype.clearFlag = function (flag) {
188 this.emit("clearFlag", flag);
191 User.prototype.waitFlag = function (flag, cb) {
196 var wait = function (f) {
198 self.removeListener("setFlag", wait);
202 self.on("setFlag", wait);
206 User.prototype.getName = function () {
207 return this.account.name;
210 User.prototype.getLowerName = function () {
211 return this.account.lowername;
214 User.prototype.inChannel = function () {
215 return this.channel != null && !this.channel.dead;
218 User.prototype.inRegisteredChannel = function () {
219 return this.inChannel() && this.channel.is(Flags.C_REGISTERED);
222 /* Called when a user's AFK status changes */
223 User.prototype.setAFK = function (afk) {
224 if (!this.inChannel()) {
228 /* No change in AFK status, don't need to change anything */
229 if (this.is(Flags.U_AFK) === afk) {
235 this.setFlag(Flags.U_AFK);
236 if (this.channel.modules.voteskip) {
237 this.channel.modules.voteskip.unvote(this.realip);
238 this.socket.emit("clearVoteskipVote");
241 this.clearFlag(Flags.U_AFK);
245 if (!this.inChannel()) {
247 * In unusual circumstances, the above emit("clearVoteskipVote")
248 * can cause the "disconnect" event to be fired synchronously,
249 * which results in this user no longer being in the channel.
254 /* Number of AFK users changed, voteskip state changes */
255 if (this.channel.modules.voteskip) {
256 this.channel.modules.voteskip.update();
259 this.emit('afk', afk);
262 /* Automatically tag a user as AFK after a period of inactivity */
263 User.prototype.autoAFK = function () {
265 if (self.awaytimer) {
266 clearTimeout(self.awaytimer);
269 if (!self.inChannel() || !self.channel.modules.options) {
273 /* Don't set a timer if the duration is invalid */
274 var timeout = parseFloat(self.channel.modules.options.get("afk_timeout"));
275 if (isNaN(timeout) || timeout <= 0) {
279 self.awaytimer = setTimeout(function () {
284 User.prototype.kick = function (reason) {
286 '%s (%s) was kicked: "%s"',
291 this.socket.emit("kick", { reason: reason });
292 this.socket.disconnect();
295 User.prototype.isAnonymous = function(){
297 return !self.is(Flags.U_LOGGED_IN);
300 User.prototype.initAdminCallbacks = function () {
302 self.socket.on("borrow-rank", function (rank) {
303 if (self.inChannel()) {
304 if (typeof rank !== "number") {
308 if (rank > self.account.globalRank) {
312 if (rank === 255 && self.account.globalRank > 255) {
313 rank = self.account.globalRank;
316 self.account.channelRank = rank;
317 self.account.effectiveRank = rank;
318 self.socket.emit("rank", rank);
319 self.channel.broadcastAll("setUserRank", {
320 name: self.getName(),
327 User.prototype.login = function (name, pw) {
329 self.setFlag(Flags.U_LOGGING_IN);
331 db.users.verifyLogin(name, pw, function (err, user) {
333 if (err === "Invalid username/password combination") {
334 Logger.eventlog.log("[loginfail] Login failed (bad password): " + name
335 + "@" + self.realip);
338 self.socket.emit("login", {
342 self.clearFlag(Flags.U_LOGGING_IN);
346 const oldRank = self.account.effectiveRank;
347 self.account.user = user;
348 self.account.update();
349 self.socket.emit("rank", self.account.effectiveRank);
350 self.emit("effectiveRankChange", self.account.effectiveRank, oldRank);
351 self.registrationTime = new Date(user.time);
352 self.setFlag(Flags.U_REGISTERED);
353 self.socket.emit("login", {
357 db.recordVisit(self.realip, self.getName());
358 LOGGER.info(self.realip + " logged in as " + user.name);
359 self.setFlag(Flags.U_LOGGED_IN);
360 self.clearFlag(Flags.U_LOGGING_IN);
361 self.emit("login", self.account);
365 var lastguestlogin = {};
366 User.prototype.guestLogin = function (name) {
369 if (!self.channel.modules.options.get("allow_anon_chat") && self.realip in lastguestlogin) {
370 var diff = (Date.now() - lastguestlogin[self.realip]) / 1000;
371 if (diff < Config.get("guest-login-delay")) {
372 self.socket.emit("login", {
374 error: "Guest logins are restricted to one per IP address per " +
375 Config.get("guest-login-delay") + " seconds."
381 if (!util.isValidUserName(name)) {
382 self.socket.emit("login", {
384 error: "Invalid username. Usernames must be 1-20 characters long and " +
385 "consist only of characters a-z, A-Z, 0-9, -, or _."
390 if (!self.channel.modules.options.get("allow_anon_chat") && name.match(Config.get("reserved-names.usernames"))) {
392 'Rejecting attempt by %s to use reserved username "%s"',
396 self.socket.emit("login", {
398 error: "That username is reserved."
403 // Prevent duplicate logins
404 self.setFlag(Flags.U_LOGGING_IN);
405 db.users.isUsernameTaken(name, function (err, taken) {
406 self.clearFlag(Flags.U_LOGGING_IN);
408 self.socket.emit("login", {
416 self.socket.emit("login", {
418 error: "That username is registered."
423 if (!self.channel.modules.options.get("allow_anon_chat") && self.inChannel()) {
424 var nameLower = name.toLowerCase();
425 for (var i = 0; i < self.channel.users.length; i++) {
426 if (self.channel.users[i].getLowerName() === nameLower) {
427 self.socket.emit("login", {
429 error: "That name is already in use on this channel."
436 lastguestlogin[self.realip] = Date.now();
438 const oldRank = self.account.effectiveRank;
439 self.account.guestName = name;
440 self.account.update();
441 self.socket.emit("rank", self.account.effectiveRank);
442 self.emit("effectiveRankChange", self.account.effectiveRank, oldRank);
443 self.socket.emit("login", {
448 db.recordVisit(self.realip, self.getName());
449 LOGGER.info(self.realip + " signed in as " + name);
450 self.setFlag(Flags.U_LOGGED_IN);
451 self.emit("login", self.account);
455 /* Clean out old login throttlers to save memory */
456 setInterval(function () {
457 var delay = Config.get("guest-login-delay");
458 for (var ip in lastguestlogin) {
459 var diff = (Date.now() - lastguestlogin[ip]) / 1000;
461 delete lastguestlogin[ip];
465 if (Config.get("aggressive-gc") && global && global.gc) {
470 User.prototype.getFirstSeenTime = function getFirstSeenTime() {
471 if (this.registrationTime && this.socket.context.ipSessionFirstSeen) {
473 this.registrationTime.getTime(),
474 this.socket.context.ipSessionFirstSeen.getTime()
476 } else if (this.registrationTime) {
477 return this.registrationTime.getTime();
478 } else if (this.socket.context.ipSessionFirstSeen) {
479 return this.socket.context.ipSessionFirstSeen.getTime();
481 LOGGER.error(`User "${this.getName()}" (IP: ${this.realip}) has neither ` +
482 "an IP session first seen time nor a registered account.");
487 User.prototype.setChannelRank = function setRank(rank) {
488 const oldRank = this.account.effectiveRank;
489 const changed = oldRank !== rank;
490 this.account.channelRank = rank;
491 this.account.update();
492 this.socket.emit("rank", this.account.effectiveRank);
494 this.emit("effectiveRankChange", this.account.effectiveRank, oldRank);
498 module.exports = User;