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.
 
 
 

629 lines
14 KiB

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