You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

632 lines
15 KiB

  1. let bcrypt = require('bcrypt-nodejs');
  2. let io = require('../security/io');
  3. let messages = require('../misc/messages');
  4. let connections = require('../security/connections');
  5. let leaderboard = require('../leaderboard/leaderboard');
  6. let skins = require('../config/skins');
  7. let roles = require('../config/roles');
  8. let profanities = require('../misc/profanities');
  9. let fixes = require('../fixes/fixes');
  10. let loginRewards = require('../config/loginRewards');
  11. let mail = require('../misc/mail');
  12. module.exports = {
  13. type: 'auth',
  14. username: null,
  15. charname: null,
  16. characters: {},
  17. characterList: [],
  18. stash: null,
  19. accountInfo: null,
  20. customChannels: [],
  21. play: function (data) {
  22. if (this.username === null)
  23. return;
  24. let character = this.characters[data.data.name];
  25. if (!character)
  26. return;
  27. if (character.permadead)
  28. return;
  29. character.stash = this.stash;
  30. character.account = this.username;
  31. this.charname = character.name;
  32. this.checkLoginReward(data, character);
  33. },
  34. checkLoginReward: function (data, character) {
  35. let accountInfo = this.accountInfo;
  36. let scheduler = require('../misc/scheduler');
  37. let time = scheduler.getTime();
  38. let lastLogin = accountInfo.lastLogin;
  39. if ((!lastLogin) || (lastLogin.day !== time.day)) {
  40. let daysSkipped = 1;
  41. if (lastLogin) {
  42. if (time.day > lastLogin.day)
  43. daysSkipped = time.day - lastLogin.day;
  44. else {
  45. let daysInMonth = scheduler.daysInMonth(lastLogin.month);
  46. daysSkipped = (daysInMonth - lastLogin.day) + time.day;
  47. for (let i = lastLogin.month + 1; i < time.month - 1; i++)
  48. daysSkipped += scheduler.daysInMonth(i);
  49. }
  50. }
  51. if (daysSkipped === 1) {
  52. accountInfo.loginStreak++;
  53. if (accountInfo.loginStreak > 21)
  54. accountInfo.loginStreak = 21;
  55. } else {
  56. accountInfo.loginStreak -= (daysSkipped - 1);
  57. if (accountInfo.loginStreak < 1)
  58. accountInfo.loginStreak = 1;
  59. }
  60. let rewards = loginRewards.generate(accountInfo.loginStreak);
  61. mail.sendMail(character.name, rewards, this.onSendRewards.bind(this, data, character));
  62. } else
  63. this.onSendRewards(data, character);
  64. accountInfo.lastLogin = time;
  65. },
  66. onSendRewards: function (data, character) {
  67. delete mail.busy[character.name];
  68. io.set({
  69. ent: this.username,
  70. field: 'accountInfo',
  71. value: JSON.stringify(this.accountInfo),
  72. callback: this.onUpdateAccountInfo.bind(this, data, character)
  73. });
  74. },
  75. onUpdateAccountInfo: function (data, character) {
  76. this.obj.player.sessionStart = +new Date();
  77. this.obj.player.spawn(character, data.callback);
  78. let prophecies = this.obj.prophecies ? this.obj.prophecies.simplify().list : [];
  79. leaderboard.setLevel(character.name, this.obj.stats.values.level, prophecies);
  80. },
  81. doSave: function (extensionObj) {
  82. let simple = this.obj.getSimple(true, true);
  83. simple.components.spliceWhere(c => c.type === 'stash');
  84. let stats = simple.components.find(c => c.type === 'stats');
  85. stats.values = extend(true, {}, stats.values);
  86. let social = simple.components.find(c => c.type === 'social');
  87. delete social.party;
  88. delete social.customChannels;
  89. let statKeys = Object.keys(stats.values);
  90. let sLen = statKeys.length;
  91. for (let i = 0; i < sLen; i++) {
  92. let s = statKeys[i];
  93. if (
  94. (
  95. (s.indexOf('xp') > -1) &&
  96. (s !== 'xpIncrease')
  97. ) ||
  98. (s === 'level') ||
  99. (s === 'hp') ||
  100. (s === 'mana')
  101. )
  102. continue;
  103. delete stats.values[s];
  104. }
  105. //Calculate and store the ttl for effects
  106. let time = +new Date();
  107. simple.components.find(e => e.type === 'effects').effects.forEach(function (e) {
  108. if (e.expire)
  109. return;
  110. e.expire = time + (e.ttl * 350);
  111. });
  112. let callback = null;
  113. if (extensionObj) {
  114. callback = extensionObj.callback;
  115. delete extensionObj.callback;
  116. }
  117. extend(true, simple, extensionObj);
  118. io.set({
  119. ent: this.charname,
  120. field: 'character',
  121. value: JSON.stringify(simple).split('\'').join('`'),
  122. callback: callback
  123. });
  124. //Save stash
  125. io.set({
  126. ent: this.username,
  127. field: 'stash',
  128. value: JSON.stringify(this.obj.stash.items).split('\'').join('`')
  129. });
  130. },
  131. simplify: function () {
  132. return {
  133. type: 'auth',
  134. username: this.username,
  135. charname: this.charname,
  136. skins: this.skins
  137. };
  138. },
  139. getCharacterList: function (data) {
  140. if (this.username === null)
  141. return;
  142. io.get({
  143. ent: this.username,
  144. field: 'characterList',
  145. callback: this.onGetCharacterList.bind(this, data)
  146. });
  147. },
  148. onGetCharacterList: function (data, result) {
  149. let characters = JSON.parse(result || '[]');
  150. this.characterList = characters;
  151. result = characters
  152. .map(c => ({
  153. name: c.name ? c.name : c,
  154. level: leaderboard.getLevel(c.name ? c.name : c)
  155. }));
  156. data.callback(result);
  157. },
  158. getCharacter: function (data) {
  159. let name = data.data.name;
  160. if (!this.characterList.some(c => ((c.name === name) || (c === name))))
  161. return;
  162. io.get({
  163. ent: name,
  164. field: 'character',
  165. callback: this.onGetCharacter.bind(this, data)
  166. });
  167. },
  168. onGetCharacter: function (data, result) {
  169. if (result) {
  170. result = result.split('`').join('\'');
  171. result = result.replace(/''+/g, '\'');
  172. } else {
  173. console.log('char not found');
  174. console.log(data);
  175. }
  176. let character = JSON.parse(result || '{}');
  177. fixes.fixCharacter(character);
  178. //Hack for old characters
  179. if (!character.skinId)
  180. character.skinId = character.class + ' 1';
  181. character.cell = skins.getCell(character.skinId);
  182. character.sheetName = skins.getSpritesheet(character.skinId);
  183. this.characters[data.data.name] = character;
  184. this.getCustomChannels(data, character);
  185. },
  186. getCustomChannels: function (data, character) {
  187. io.get({
  188. ent: character.name,
  189. field: 'customChannels',
  190. callback: this.onGetCustomChannels.bind(this, data, character)
  191. });
  192. },
  193. onGetCustomChannels: function (data, character, result) {
  194. this.customChannels = JSON
  195. .parse(result || '[]')
  196. .filter(c => (typeof (c) === 'string'))
  197. .map(c => c.split(' ').join(''))
  198. .filter(c => (c.length > 0));
  199. this.customChannels = this.customChannels
  200. .filter((c, i) => (this.customChannels.indexOf(c) === i));
  201. let social = character.components.find(c => (c.type === 'social'));
  202. if (social)
  203. social.customChannels = this.customChannels;
  204. this.getStash(data, character);
  205. },
  206. getStash: function (data, character) {
  207. io.get({
  208. ent: this.username,
  209. field: 'stash',
  210. callback: this.onGetStash.bind(this, data, character)
  211. });
  212. },
  213. onGetStash: function (data, character, result) {
  214. if (result) {
  215. result = result.split('`').join('\'');
  216. result = result.replace(/''+/g, '\'');
  217. }
  218. this.stash = JSON.parse(result || '[]');
  219. fixes.fixStash(this.stash);
  220. if (this.skins !== null) {
  221. this.verifySkin(character);
  222. data.callback(character);
  223. } else {
  224. data.callback = data.callback.bind(null, character);
  225. this.getSkins(data, character);
  226. }
  227. },
  228. getSkins: function (msg, character) {
  229. io.get({
  230. ent: this.username,
  231. field: 'skins',
  232. callback: this.onGetSkins.bind(this, msg, character)
  233. });
  234. },
  235. onGetSkins: function (msg, character, result) {
  236. this.skins = JSON.parse(result || '[]');
  237. fixes.fixSkins(this.username, this.skins);
  238. let list = [...this.skins, ...roles.getSkins(this.username)];
  239. let skinList = skins.getSkinList(list);
  240. this.verifySkin(character);
  241. msg.callback(skinList);
  242. },
  243. saveSkin: function (skinId) {
  244. if (!this.skins) {
  245. this.getSkins({
  246. callback: this.saveSkin.bind(this, skinId)
  247. });
  248. return;
  249. }
  250. this.skins.push(skinId);
  251. io.set({
  252. ent: this.username,
  253. field: 'skins',
  254. value: JSON.stringify(this.skins),
  255. callback: this.onSaveSkin.bind(this)
  256. });
  257. },
  258. onSaveSkin: function () {
  259. },
  260. verifySkin: function (character) {
  261. if (!character)
  262. return;
  263. let list = [...this.skins, ...roles.getSkins(this.username)];
  264. let skinList = skins.getSkinList(list);
  265. if (!skinList.some(s => (s.id === character.skinId))) {
  266. character.skinId = '1.0';
  267. character.cell = skins.getCell(character.skinId);
  268. character.sheetName = skins.getSpritesheet(character.skinId);
  269. }
  270. },
  271. doesOwnSkin: function (skinId) {
  272. return this.skins.some(s => s === skinId);
  273. },
  274. login: function (msg) {
  275. let credentials = msg.data;
  276. if ((credentials.username === '') | (credentials.password === '')) {
  277. msg.callback(messages.login.allFields);
  278. return;
  279. }
  280. this.username = credentials.username;
  281. io.get({
  282. ent: credentials.username,
  283. field: 'login',
  284. callback: this.onHashCompare.bind(this, msg)
  285. });
  286. },
  287. onHashCompare: function (msg, storedPassword) {
  288. let credentials = msg.data;
  289. bcrypt.compare(credentials.password, storedPassword, this.onLogin.bind(this, msg, storedPassword));
  290. },
  291. onLogin: function (msg, storedPassword, err, compareResult) {
  292. if (!storedPassword)
  293. msg.callback(messages.login.incorrect);
  294. else if (compareResult) { //If stored password matches the hashed password entered by the user, log them in directly
  295. this.onLoginVerified(msg);
  296. } 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
  297. this.onUnhashedLogin(msg);
  298. } else
  299. msg.callback(messages.login.incorrect);
  300. },
  301. onUnhashedLogin: function (msg) {
  302. bcrypt.hash(msg.data.password, null, null, this.onPasswordHashed.bind(this, msg));
  303. },
  304. onPasswordHashed: function (msg, err, hashedPassword) {
  305. io.set({
  306. ent: msg.data.username,
  307. field: 'login',
  308. value: hashedPassword,
  309. callback: this.onLoginVerified.bind(this, msg)
  310. });
  311. },
  312. onLoginVerified: function (msg) {
  313. this.username = msg.data.username;
  314. connections.logOut(this.obj);
  315. io.get({
  316. ent: msg.data.username,
  317. field: 'accountInfo',
  318. callback: this.onGetAccountInfo.bind(this, msg)
  319. });
  320. },
  321. onGetAccountInfo: function (msg, info) {
  322. if (!info) {
  323. info = {
  324. loginStreak: 0
  325. };
  326. } else
  327. info = JSON.parse(info);
  328. this.accountInfo = info;
  329. msg.callback();
  330. },
  331. register: function (msg) {
  332. let credentials = msg.data;
  333. if ((credentials.username === '') || (credentials.password === '')) {
  334. msg.callback(messages.login.allFields);
  335. return;
  336. }
  337. let illegal = ["'", '"', '/', '(', ')', '[', ']', '{', '}', ':', ';', '<', '>'];
  338. for (let i = 0; i < illegal.length; i++) {
  339. if ((credentials.username.indexOf(illegal[i]) > -1) || (credentials.password.indexOf(illegal[i]) > -1)) {
  340. msg.callback(messages.login.illegal);
  341. return;
  342. }
  343. }
  344. io.get({
  345. ent: credentials.username,
  346. field: 'login',
  347. callback: this.onCheckExists.bind(this, msg)
  348. });
  349. },
  350. onCheckExists: function (msg, result) {
  351. if (result) {
  352. msg.callback(messages.login.exists);
  353. return;
  354. }
  355. let credentials = msg.data;
  356. bcrypt.hash(credentials.password, null, null, this.onHashGenerated.bind(this, msg));
  357. },
  358. onHashGenerated: function (msg, err, hashedPassword) {
  359. io.set({
  360. ent: msg.data.username,
  361. field: 'login',
  362. value: hashedPassword,
  363. callback: this.onRegister.bind(this, msg)
  364. });
  365. },
  366. onRegister: function (msg, result) {
  367. this.accountInfo = {
  368. loginStreak: 0
  369. };
  370. io.set({
  371. ent: msg.data.username,
  372. field: 'characterList',
  373. value: '[]',
  374. callback: this.onCreateCharacterList.bind(this, msg)
  375. });
  376. },
  377. onCreateCharacterList: function (msg, result) {
  378. this.username = msg.data.username;
  379. connections.logOut(this.obj);
  380. msg.callback();
  381. },
  382. createCharacter: function (msg) {
  383. let data = msg.data;
  384. if ((data.name.length < 3) || (data.name.length > 12)) {
  385. msg.callback(messages.createCharacter.nameLength);
  386. return;
  387. }
  388. if (!profanities.isClean(data.name)) {
  389. msg.callback(messages.login.invalid);
  390. return;
  391. }
  392. let name = data.name;
  393. if (name.indexOf(' ') > -1) {
  394. msg.callback(messages.login.invalid);
  395. return;
  396. }
  397. let nLen = name.length;
  398. for (let i = 0; i < nLen; i++) {
  399. let char = name[i].toLowerCase();
  400. let valid = [
  401. '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'
  402. ];
  403. if (valid.indexOf(char) === -1) {
  404. msg.callback(messages.login.invalid);
  405. return;
  406. }
  407. }
  408. io.get({
  409. ent: data.name,
  410. field: 'character',
  411. callback: this.onCheckCharacterExists.bind(this, msg)
  412. });
  413. },
  414. onCheckCharacterExists: function (msg, result) {
  415. if (result) {
  416. msg.callback(messages.login.charExists);
  417. return;
  418. }
  419. let data = msg.data;
  420. this.obj.name = data.name;
  421. this.obj.skinId = data.skinId;
  422. this.obj.class = data.class;
  423. this.obj.cell = skins.getCell(this.obj.skinId);
  424. this.obj.sheetName = skins.getSpritesheet(this.obj.skinId);
  425. this.verifySkin(this.obj);
  426. let simple = this.obj.getSimple(true);
  427. let prophecies = data.prophecies || [];
  428. prophecies = prophecies.filter((p, i) => (prophecies.indexOf(p) === i));
  429. simple.components.push({
  430. type: 'prophecies',
  431. list: prophecies
  432. });
  433. io.set({
  434. ent: data.name,
  435. field: 'character',
  436. value: JSON.stringify(simple),
  437. callback: this.onCreateCharacter.bind(this, msg)
  438. });
  439. },
  440. onCreateCharacter: function (msg, result) {
  441. let name = msg.data.name;
  442. let simple = this.obj.getSimple(true);
  443. simple.components.push({
  444. type: 'prophecies',
  445. list: msg.data.prophecies || []
  446. });
  447. simple.components.push({
  448. type: 'social',
  449. customChannels: this.customChannels
  450. });
  451. this.characters[name] = simple;
  452. this.characterList.push(name);
  453. io.set({
  454. ent: this.username,
  455. field: 'characterList',
  456. value: JSON.stringify(this.characterList),
  457. callback: this.onAppendList.bind(this, msg)
  458. });
  459. },
  460. deleteCharacter: function (msg) {
  461. let data = msg.data;
  462. if ((!data.name) || (!this.username))
  463. return;
  464. if (!this.characterList.some(c => ((c.name === data.name) || (c === data.name)))) {
  465. msg.callback([]);
  466. return;
  467. }
  468. io.delete({
  469. ent: data.name,
  470. field: 'character',
  471. callback: this.onDeleteCharacter.bind(this, msg)
  472. });
  473. },
  474. onDeleteCharacter: function (msg, result) {
  475. this.characterList.spliceWhere(c => ((c.name === msg.data.name) || (c === msg.data.name)));
  476. let characterList = this.characterList
  477. .map(c => ({
  478. name: c.name ? c.name : c,
  479. level: leaderboard.getLevel(c.name ? c.name : c)
  480. }));
  481. io.set({
  482. ent: this.username,
  483. field: 'characterList',
  484. value: JSON.stringify(characterList),
  485. callback: this.onRemoveFromList.bind(this, msg)
  486. });
  487. leaderboard.deleteCharacter(msg.data.name);
  488. },
  489. onRemoveFromList: function (msg, result) {
  490. result = this.characterList
  491. .map(c => ({
  492. name: c.name ? c.name : c,
  493. level: leaderboard.getLevel(c.name ? c.name : c)
  494. }));
  495. msg.callback(result);
  496. },
  497. onAppendList: function (msg, result) {
  498. this.play({
  499. data: {
  500. name: msg.data.name
  501. },
  502. callback: msg.callback
  503. });
  504. },
  505. permadie: function () {
  506. this.obj.permadead = true;
  507. this.doSave({
  508. permadead: true,
  509. callback: this.onPermadie.bind(this)
  510. });
  511. },
  512. onPermadie: function () {
  513. process.send({
  514. method: 'object',
  515. serverId: this.obj.serverId,
  516. obj: {
  517. dead: true
  518. }
  519. });
  520. }
  521. };