let bcrypt = require('bcrypt-nodejs'); let io = require('../security/io'); let messages = require('../misc/messages'); let connections = require('../security/connections'); let leaderboard = require('../leaderboard/leaderboard'); let skins = require('../config/skins'); let roles = require('../config/roles'); let profanities = require('../misc/profanities'); let fixes = require('../fixes/fixes'); let loginRewards = require('../config/loginRewards'); let mail = require('../misc/mail'); module.exports = { type: 'auth', username: null, charname: null, characters: {}, characterList: [], stash: null, accountInfo: null, customChannels: [], play: function (data) { if (this.username === null) return; let character = this.characters[data.data.name]; if (!character) return; if (character.permadead) return; character.stash = this.stash; character.account = this.username; this.charname = character.name; this.checkLoginReward(data, character); }, checkLoginReward: function (data, character) { let accountInfo = this.accountInfo; let scheduler = require('../misc/scheduler'); let time = scheduler.getTime(); let lastLogin = accountInfo.lastLogin; if ((!lastLogin) || (lastLogin.day !== time.day)) { let daysSkipped = 1; if (lastLogin) { if (time.day > lastLogin.day) daysSkipped = time.day - lastLogin.day; else { let daysInMonth = scheduler.daysInMonth(lastLogin.month); daysSkipped = (daysInMonth - lastLogin.day) + time.day; for (let i = lastLogin.month + 1; i < time.month - 1; i++) daysSkipped += scheduler.daysInMonth(i); } } if (daysSkipped === 1) { accountInfo.loginStreak++; if (accountInfo.loginStreak > 21) accountInfo.loginStreak = 21; } else { accountInfo.loginStreak -= (daysSkipped - 1); if (accountInfo.loginStreak < 1) accountInfo.loginStreak = 1; } let rewards = loginRewards.generate(accountInfo.loginStreak); mail.sendMail(character.name, rewards, this.onSendRewards.bind(this, data, character)); } else this.onSendRewards(data, character); accountInfo.lastLogin = time; }, onSendRewards: function (data, character) { delete mail.busy[character.name]; io.set({ ent: this.username, field: 'accountInfo', value: JSON.stringify(this.accountInfo), callback: this.onUpdateAccountInfo.bind(this, data, character) }); }, onUpdateAccountInfo: function (data, character) { this.obj.player.sessionStart = +new Date(); this.obj.player.spawn(character, data.callback); let prophecies = this.obj.prophecies ? this.obj.prophecies.simplify().list : []; leaderboard.setLevel(character.name, this.obj.stats.values.level, prophecies); }, doSave: function (extensionObj) { let simple = this.obj.getSimple(true, true); simple.components.spliceWhere(c => c.type === 'stash'); let stats = simple.components.find(c => c.type === 'stats'); stats.values = extend(true, {}, stats.values); let social = simple.components.find(c => c.type === 'social'); delete social.party; delete social.customChannels; let statKeys = Object.keys(stats.values); let sLen = statKeys.length; for (let i = 0; i < sLen; i++) { let s = statKeys[i]; if ( ( (s.indexOf('xp') > -1) && (s !== 'xpIncrease') ) || (s === 'level') || (s === 'hp') || (s === 'mana') ) continue; delete stats.values[s]; } //Calculate and store the ttl for effects let time = +new Date(); simple.components.find(e => e.type === 'effects').effects.forEach(function (e) { if (e.expire) return; e.expire = time + (e.ttl * 350); }); let callback = null; if (extensionObj) { callback = extensionObj.callback; delete extensionObj.callback; } extend(true, simple, extensionObj); io.set({ ent: this.charname, field: 'character', value: JSON.stringify(simple).split('\'').join('`'), callback: callback }); //Save stash io.set({ ent: this.username, field: 'stash', value: JSON.stringify(this.obj.stash.items).split('\'').join('`') }); }, simplify: function () { return { type: 'auth', username: this.username, charname: this.charname, skins: this.skins }; }, getCharacterList: function (data) { if (this.username === null) return; io.get({ ent: this.username, field: 'characterList', callback: this.onGetCharacterList.bind(this, data) }); }, onGetCharacterList: function (data, result) { let characters = JSON.parse(result || '[]'); this.characterList = characters; result = characters .map(c => ({ name: c.name ? c.name : c, level: leaderboard.getLevel(c.name ? c.name : c) })); data.callback(result); }, getCharacter: function (data) { let name = data.data.name; if (!this.characterList.some(c => ((c.name === name) || (c === name)))) return; io.get({ ent: name, field: 'character', callback: this.onGetCharacter.bind(this, data) }); }, onGetCharacter: function (data, result) { if (result) { result = result.split('`').join('\''); result = result.replace(/''+/g, '\''); } else { console.log('char not found'); console.log(data); } let character = JSON.parse(result || '{}'); fixes.fixCharacter(character); //Hack for old characters if (!character.skinId) character.skinId = character.class + ' 1'; character.cell = skins.getCell(character.skinId); character.sheetName = skins.getSpritesheet(character.skinId); this.characters[data.data.name] = character; this.getCustomChannels(data, character); }, getCustomChannels: function (data, character) { io.get({ ent: character.name, field: 'customChannels', callback: this.onGetCustomChannels.bind(this, data, character) }); }, onGetCustomChannels: function (data, character, result) { this.customChannels = JSON .parse(result || '[]') .filter(c => (typeof (c) === 'string')) .map(c => c.split(' ').join('')) .filter(c => (c.length > 0)); this.customChannels = this.customChannels .filter((c, i) => (this.customChannels.indexOf(c) === i)); let social = character.components.find(c => (c.type === 'social')); if (social) social.customChannels = this.customChannels; this.getStash(data, character); }, getStash: function (data, character) { io.get({ ent: this.username, field: 'stash', callback: this.onGetStash.bind(this, data, character) }); }, onGetStash: function (data, character, result) { if (result) { result = result.split('`').join('\''); result = result.replace(/''+/g, '\''); } this.stash = JSON.parse(result || '[]'); fixes.fixStash(this.stash); if (this.skins !== null) { this.verifySkin(character); data.callback(character); } else { data.callback = data.callback.bind(null, character); this.getSkins(data, character); } }, getSkins: function (msg, character) { io.get({ ent: this.username, field: 'skins', callback: this.onGetSkins.bind(this, msg, character) }); }, onGetSkins: function (msg, character, result) { this.skins = JSON.parse(result || '[]'); fixes.fixSkins(this.username, this.skins); let list = [...this.skins, ...roles.getSkins(this.username)]; let skinList = skins.getSkinList(list); this.verifySkin(character); msg.callback(skinList); }, saveSkin: function (skinId) { if (!this.skins) { this.getSkins({ callback: this.saveSkin.bind(this, skinId) }); return; } this.skins.push(skinId); io.set({ ent: this.username, field: 'skins', value: JSON.stringify(this.skins), callback: this.onSaveSkin.bind(this) }); }, onSaveSkin: function () { }, verifySkin: function (character) { if (!character) return; let list = [...this.skins, ...roles.getSkins(this.username)]; let skinList = skins.getSkinList(list); if (!skinList.some(s => (s.id === character.skinId))) { character.skinId = '1.0'; character.cell = skins.getCell(character.skinId); character.sheetName = skins.getSpritesheet(character.skinId); } }, doesOwnSkin: function (skinId) { return this.skins.some(s => s === skinId); }, login: function (msg) { let credentials = msg.data; if ((credentials.username === '') | (credentials.password === '')) { msg.callback(messages.login.allFields); return; } this.username = credentials.username; io.get({ ent: credentials.username, field: 'login', callback: this.onHashCompare.bind(this, msg) }); }, onHashCompare: function (msg, storedPassword) { let credentials = msg.data; bcrypt.compare(credentials.password, storedPassword, this.onLogin.bind(this, msg, storedPassword)); }, onLogin: function (msg, storedPassword, err, compareResult) { if (!storedPassword) msg.callback(messages.login.incorrect); else if (compareResult) { //If stored password matches the hashed password entered by the user, log them in directly this.onLoginVerified(msg); } else if (msg.data.password === storedPassword) { //If the stored password matches a plaintext password entered by the user; In that case the password gets hashed for the future this.onUnhashedLogin(msg); } else msg.callback(messages.login.incorrect); }, onUnhashedLogin: function (msg) { bcrypt.hash(msg.data.password, null, null, this.onPasswordHashed.bind(this, msg)); }, onPasswordHashed: function (msg, err, hashedPassword) { io.set({ ent: msg.data.username, field: 'login', value: hashedPassword, callback: this.onLoginVerified.bind(this, msg) }); }, onLoginVerified: function (msg) { this.username = msg.data.username; connections.logOut(this.obj); io.get({ ent: msg.data.username, field: 'accountInfo', callback: this.onGetAccountInfo.bind(this, msg) }); }, onGetAccountInfo: function (msg, info) { if (!info) { info = { loginStreak: 0 }; } else info = JSON.parse(info); this.accountInfo = info; msg.callback(); }, register: function (msg) { let credentials = msg.data; if ((credentials.username === '') || (credentials.password === '')) { msg.callback(messages.login.allFields); return; } let illegal = ["'", '"', '/', '(', ')', '[', ']', '{', '}', ':', ';', '<', '>']; for (let i = 0; i < illegal.length; i++) { if ((credentials.username.indexOf(illegal[i]) > -1) || (credentials.password.indexOf(illegal[i]) > -1)) { msg.callback(messages.login.illegal); return; } } io.get({ ent: credentials.username, field: 'login', callback: this.onCheckExists.bind(this, msg) }); }, onCheckExists: function (msg, result) { if (result) { msg.callback(messages.login.exists); return; } let credentials = msg.data; bcrypt.hash(credentials.password, null, null, this.onHashGenerated.bind(this, msg)); }, onHashGenerated: function (msg, err, hashedPassword) { io.set({ ent: msg.data.username, field: 'login', value: hashedPassword, callback: this.onRegister.bind(this, msg) }); }, onRegister: function (msg, result) { this.accountInfo = { loginStreak: 0 }; io.set({ ent: msg.data.username, field: 'characterList', value: '[]', callback: this.onCreateCharacterList.bind(this, msg) }); }, onCreateCharacterList: function (msg, result) { this.username = msg.data.username; connections.logOut(this.obj); msg.callback(); }, createCharacter: function (msg) { let data = msg.data; if ((data.name.length < 3) || (data.name.length > 12)) { msg.callback(messages.createCharacter.nameLength); return; } if (!profanities.isClean(data.name)) { msg.callback(messages.login.invalid); return; } let name = data.name; if (name.indexOf(' ') > -1) { msg.callback(messages.login.invalid); return; } let nLen = name.length; for (let i = 0; i < nLen; i++) { let char = name[i].toLowerCase(); let valid = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' ]; if (valid.indexOf(char) === -1) { msg.callback(messages.login.invalid); return; } } io.get({ ent: data.name, field: 'character', callback: this.onCheckCharacterExists.bind(this, msg) }); }, onCheckCharacterExists: function (msg, result) { if (result) { msg.callback(messages.login.charExists); return; } let data = msg.data; this.obj.name = data.name; this.obj.skinId = data.skinId; this.obj.class = data.class; this.obj.cell = skins.getCell(this.obj.skinId); this.obj.sheetName = skins.getSpritesheet(this.obj.skinId); this.verifySkin(this.obj); let simple = this.obj.getSimple(true); let prophecies = data.prophecies || []; prophecies = prophecies.filter((p, i) => (prophecies.indexOf(p) === i)); simple.components.push({ type: 'prophecies', list: prophecies }); io.set({ ent: data.name, field: 'character', value: JSON.stringify(simple), callback: this.onCreateCharacter.bind(this, msg) }); }, onCreateCharacter: function (msg, result) { let name = msg.data.name; let simple = this.obj.getSimple(true); simple.components.push({ type: 'prophecies', list: msg.data.prophecies || [] }); simple.components.push({ type: 'social', customChannels: this.customChannels }); this.characters[name] = simple; this.characterList.push(name); io.set({ ent: this.username, field: 'characterList', value: JSON.stringify(this.characterList), callback: this.onAppendList.bind(this, msg) }); }, deleteCharacter: function (msg) { let data = msg.data; if ((!data.name) || (!this.username)) return; if (!this.characterList.some(c => ((c.name === data.name) || (c === data.name)))) { msg.callback([]); return; } io.delete({ ent: data.name, field: 'character', callback: this.onDeleteCharacter.bind(this, msg) }); }, onDeleteCharacter: function (msg, result) { this.characterList.spliceWhere(c => ((c.name === msg.data.name) || (c === msg.data.name))); let characterList = this.characterList .map(c => ({ name: c.name ? c.name : c, level: leaderboard.getLevel(c.name ? c.name : c) })); io.set({ ent: this.username, field: 'characterList', value: JSON.stringify(characterList), callback: this.onRemoveFromList.bind(this, msg) }); leaderboard.deleteCharacter(msg.data.name); }, onRemoveFromList: function (msg, result) { result = this.characterList .map(c => ({ name: c.name ? c.name : c, level: leaderboard.getLevel(c.name ? c.name : c) })); msg.callback(result); }, onAppendList: function (msg, result) { this.play({ data: { name: msg.data.name }, callback: msg.callback }); }, permadie: function () { this.obj.permadead = true; this.doSave({ permadead: true, callback: this.onPermadie.bind(this) }); }, onPermadie: function () { process.send({ method: 'object', serverId: this.obj.serverId, obj: { dead: true } }); } };