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.
 
 
 

626 lines
13 KiB

  1. //Imports
  2. const bcrypt = require('bcrypt-nodejs');
  3. const messages = require('../misc/messages');
  4. const skins = require('../config/skins');
  5. const profanities = require('../misc/profanities');
  6. const fixes = require('../fixes/fixes');
  7. const spirits = require('../config/spirits');
  8. const ga = require('../security/ga');
  9. const eventEmitter = require('../misc/events');
  10. const checkLoginRewards = require('./auth/checkLoginRewards');
  11. //This section of code is in charge of ensuring that we only ever create one account at a time,
  12. // since we don't have a read/write lock on the characters table, we have to address it in code
  13. const createLockBuffer = [];
  14. const getCreateLock = async () => {
  15. const releaseLock = lockEntry => {
  16. createLockBuffer.spliceWhere(c => c === lockEntry);
  17. const nextEntry = createLockBuffer[0];
  18. if (!nextEntry)
  19. return;
  20. nextEntry.takeLock();
  21. };
  22. const promise = new Promise(async res => {
  23. let lockEntry = {};
  24. lockEntry.takeLock = res.bind(null, releaseLock.bind(null, lockEntry));
  25. if (!createLockBuffer.length) {
  26. createLockBuffer.push(lockEntry);
  27. lockEntry.takeLock();
  28. return;
  29. }
  30. createLockBuffer.push(lockEntry);
  31. });
  32. return promise;
  33. };
  34. //Component Definition
  35. module.exports = {
  36. type: 'auth',
  37. username: null,
  38. charname: null,
  39. characters: {},
  40. characterList: [],
  41. stash: null,
  42. accountInfo: null,
  43. customChannels: [],
  44. play: async function (data) {
  45. if (!this.username || this.charname)
  46. return;
  47. let character = this.characters[data.data.name];
  48. if (!character)
  49. return;
  50. else if (character.permadead)
  51. return;
  52. character.stash = this.stash;
  53. character.account = this.username;
  54. this.charname = character.name;
  55. checkLoginRewards(this, data, character, this.onSendRewards.bind(this, data, character));
  56. cons.modifyPlayerCount(1);
  57. },
  58. onSendRewards: async function (data, character) {
  59. await io.setAsync({
  60. key: this.username,
  61. table: 'accountInfo',
  62. value: this.accountInfo,
  63. serialize: true
  64. });
  65. this.obj.player.sessionStart = +new Date();
  66. this.obj.player.spawn(character, data.callback);
  67. let prophecies = this.obj.prophecies ? this.obj.prophecies.simplify().list : [];
  68. await leaderboard.setLevel(character.name, this.obj.stats.values.level, prophecies);
  69. },
  70. doSave: async function (callback) {
  71. const simple = this.obj.getSimple(true, true);
  72. delete simple.destroyed;
  73. delete simple.forceDestroy;
  74. simple.components.spliceWhere(f => (f.type === 'stash'));
  75. await io.setAsync({
  76. key: this.charname,
  77. table: 'character',
  78. value: simple,
  79. clean: true,
  80. serialize: true
  81. });
  82. await this.doSaveStash();
  83. if (callback)
  84. callback();
  85. },
  86. //This function is called from the 'forceSave' command. Because of this, the first argument is the action data
  87. // instead of (callback, saveStash)
  88. doSaveManual: async function (msg) {
  89. await this.doSave();
  90. process.send({
  91. module: 'atlas',
  92. method: 'resolveCallback',
  93. msg: {
  94. id: msg.callbackId
  95. }
  96. });
  97. },
  98. doSaveStash: async function () {
  99. const { username, obj: { stash } } = this;
  100. if (!stash.changed)
  101. return;
  102. await io.setAsync({
  103. key: username,
  104. table: 'stash',
  105. value: stash.serialize(),
  106. clean: true,
  107. serialize: true
  108. });
  109. },
  110. simplify: function (self) {
  111. if (!self)
  112. return;
  113. return {
  114. type: 'auth',
  115. username: this.username,
  116. charname: this.charname,
  117. accountInfo: this.accountInfo
  118. };
  119. },
  120. getCharacterList: async function (data) {
  121. if (!this.username)
  122. return;
  123. this.characterList = await io.getAsync({
  124. key: this.username,
  125. table: 'characterList',
  126. isArray: true
  127. });
  128. let res = this.characterList.map(c => ({
  129. name: c.name ? c.name : c,
  130. level: leaderboard.getLevel(c.name ? c.name : c)
  131. }));
  132. data.callback(res);
  133. },
  134. getCharacter: async function (data) {
  135. let charName = data.data.name;
  136. if (!this.characterList.some(c => (c.name === charName || c === charName)))
  137. return;
  138. let character = await io.getAsync({
  139. key: charName,
  140. table: 'character',
  141. clean: true
  142. });
  143. await eventEmitter.emit('onAfterGetCharacter', {
  144. obj: this.obj,
  145. character
  146. });
  147. fixes.fixCharacter(character);
  148. character.cell = skins.getCell(character.skinId);
  149. character.sheetName = skins.getSpritesheet(character.skinId);
  150. this.characters[charName] = character;
  151. await this.getCustomChannels(character);
  152. await this.verifySkin(character);
  153. data.callback(character);
  154. },
  155. getCustomChannels: async function (character) {
  156. this.customChannels = await io.getAsync({
  157. key: character.name,
  158. table: 'customChannels',
  159. isArray: true
  160. });
  161. let social = character.components.find(c => (c.type === 'social'));
  162. this.customChannels = fixes.fixCustomChannels(this.customChannels);
  163. if (social)
  164. social.customChannels = this.customChannels;
  165. },
  166. verifySkin: async function (character) {
  167. const doesOwn = await this.doesOwnSkin(character.skinId);
  168. if (doesOwn)
  169. return;
  170. const defaultTo = 'wizard';
  171. character.skinId = defaultTo;
  172. character.cell = skins.getCell(defaultTo);
  173. character.sheetName = skins.getSpritesheet(defaultTo);
  174. },
  175. doesOwnSkin: async function (skinId) {
  176. const allSkins = skins.getList();
  177. const filteredSkins = allSkins.filter(({ default: isDefaultSkin }) => isDefaultSkin);
  178. const msgSkinList = {
  179. obj: this,
  180. allSkins,
  181. filteredSkins
  182. };
  183. await eventEmitter.emit('onBeforeGetAccountSkins', msgSkinList);
  184. const result = filteredSkins.some(f => f.id === skinId);
  185. return result;
  186. },
  187. getSkinList: async function ({ callback }) {
  188. const allSkins = skins.getList();
  189. const filteredSkins = allSkins.filter(({ default: isDefaultSkin }) => isDefaultSkin);
  190. const msgSkinList = {
  191. obj: this,
  192. allSkins,
  193. filteredSkins
  194. };
  195. await eventEmitter.emit('onBeforeGetAccountSkins', msgSkinList);
  196. callback(filteredSkins);
  197. },
  198. login: async function (msg) {
  199. let credentials = msg.data;
  200. if (credentials.username === '' || credentials.password === '') {
  201. msg.callback(messages.login.allFields);
  202. return;
  203. } else if (credentials.username.length > 32) {
  204. msg.callback(messages.login.maxUsernameLength);
  205. return;
  206. }
  207. let storedPassword = await io.getAsync({
  208. key: credentials.username,
  209. table: 'login',
  210. noParse: true
  211. });
  212. bcrypt.compare(credentials.password, storedPassword, this.onLogin.bind(this, msg, storedPassword));
  213. },
  214. onLogin: async function (msg, storedPassword, err, compareResult) {
  215. const { data: { username } } = msg;
  216. if (!compareResult) {
  217. msg.callback(messages.login.incorrect);
  218. return;
  219. }
  220. const emBeforeLogin = {
  221. obj: this.obj,
  222. success: true,
  223. msg: null,
  224. username
  225. };
  226. await eventEmitter.emit('onBeforeLogin', emBeforeLogin);
  227. if (!emBeforeLogin.success) {
  228. msg.callback(emBeforeLogin.msg);
  229. return;
  230. }
  231. this.username = username;
  232. await cons.logOut(this.obj);
  233. this.initTracker();
  234. const accountInfo = await io.getAsync({
  235. key: username,
  236. table: 'accountInfo',
  237. noDefault: true
  238. }) || {
  239. loginStreak: 0,
  240. level: 0
  241. };
  242. const msgAccountInfo = {
  243. username,
  244. accountInfo
  245. };
  246. await eventEmitter.emit('onBeforeGetAccountInfo', msgAccountInfo);
  247. await eventEmitter.emit('onAfterLogin', { username });
  248. this.accountInfo = msgAccountInfo.accountInfo;
  249. msg.callback();
  250. },
  251. initTracker: function () {
  252. this.gaTracker = ga.connect(this.username);
  253. },
  254. track: function (category, action, label, value = 1) {
  255. process.send({
  256. method: 'track',
  257. serverId: this.obj.serverId,
  258. obj: {
  259. category,
  260. action,
  261. label,
  262. value
  263. }
  264. });
  265. },
  266. register: async function (msg) {
  267. let credentials = msg.data;
  268. if (credentials.username === '' || credentials.password === '') {
  269. msg.callback(messages.login.allFields);
  270. return;
  271. } else if (credentials.username.length > 32) {
  272. msg.callback(messages.login.maxUsernameLength);
  273. return;
  274. }
  275. let illegal = ["'", '"', '/', '\\', '(', ')', '[', ']', '{', '}', ':', ';', '<', '>', '+', '?', '*'];
  276. for (let i = 0; i < illegal.length; i++) {
  277. if (credentials.username.indexOf(illegal[i]) > -1) {
  278. msg.callback(messages.login.illegal);
  279. return;
  280. }
  281. }
  282. const emBeforeRegisterAccount = {
  283. obj: this.obj,
  284. success: true,
  285. msg: null,
  286. username: msg.data.username
  287. };
  288. await eventEmitter.emit('onBeforeRegisterAccount', emBeforeRegisterAccount);
  289. if (!emBeforeRegisterAccount.success) {
  290. msg.callback(emBeforeRegisterAccount.msg);
  291. return;
  292. }
  293. let exists = await io.getAsync({
  294. key: credentials.username,
  295. ignoreCase: true,
  296. table: 'login',
  297. noDefault: true,
  298. noParse: true
  299. });
  300. if (exists) {
  301. msg.callback(messages.login.exists);
  302. return;
  303. }
  304. bcrypt.hash(credentials.password, null, null, this.onHashGenerated.bind(this, msg));
  305. },
  306. onHashGenerated: async function (msg, err, hashedPassword) {
  307. await io.setAsync({
  308. key: msg.data.username,
  309. table: 'login',
  310. value: hashedPassword
  311. });
  312. this.accountInfo = {
  313. loginStreak: 0,
  314. level: 0
  315. };
  316. await io.setAsync({
  317. key: msg.data.username,
  318. table: 'characterList',
  319. value: [],
  320. serialize: true
  321. });
  322. this.username = msg.data.username;
  323. cons.logOut(this.obj);
  324. msg.callback();
  325. },
  326. /* eslint-disable-next-line max-lines-per-function */
  327. createCharacter: async function (msg) {
  328. let data = msg.data;
  329. let name = data.name;
  330. let error = null;
  331. if (name.length < 3 || name.length > 12)
  332. error = messages.createCharacter.nameLength;
  333. else if (!profanities.isClean(name))
  334. error = messages.login.invalid;
  335. else if (name.indexOf(' ') > -1)
  336. msg.callback(messages.login.invalid);
  337. else if (!spirits.list.includes(data.class))
  338. return;
  339. let nLen = name.length;
  340. for (let i = 0; i < nLen; i++) {
  341. let char = name[i].toLowerCase();
  342. let valid = [
  343. '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'
  344. ];
  345. if (!valid.includes(char)) {
  346. error = messages.login.invalid;
  347. break;
  348. }
  349. }
  350. if (error) {
  351. msg.callback(error);
  352. return;
  353. }
  354. console.log('Starting new character create for', name);
  355. const t1 = +new Date();
  356. const releaseCreateLock = await getCreateLock();
  357. const t2 = +new Date();
  358. console.log('Took', t2 - t1, 'ms to get a create lock');
  359. let exists = await io.getAsync({
  360. key: name,
  361. ignoreCase: true,
  362. table: 'character',
  363. noDefault: true
  364. });
  365. const t3 = +new Date();
  366. console.log('Took', t3 - t2, 'ms to check if the character exists');
  367. if (exists) {
  368. releaseCreateLock();
  369. msg.callback(messages.login.charExists);
  370. return;
  371. }
  372. let obj = this.obj;
  373. extend(obj, {
  374. name: name,
  375. skinId: data.skinId,
  376. class: data.class,
  377. cell: skins.getCell(data.skinId),
  378. sheetName: skins.getSpritesheet(data.skinId),
  379. x: null,
  380. y: null
  381. });
  382. let simple = this.obj.getSimple(true);
  383. const t4 = +new Date();
  384. console.log('Took', t4 - t3, 'ms to build the simpleObj');
  385. await this.verifySkin(simple);
  386. const t5 = +new Date();
  387. console.log('Took', t5 - t4, 'ms to verify the skin');
  388. let prophecies = (data.prophecies || []).filter(p => p);
  389. simple.components.push({
  390. type: 'prophecies',
  391. list: prophecies
  392. }, {
  393. type: 'social',
  394. customChannels: this.customChannels
  395. });
  396. const eBeforeSaveCharacter = {
  397. obj: simple,
  398. config: data
  399. };
  400. eventEmitter.emit('beforeSaveCharacter', eBeforeSaveCharacter);
  401. const t6 = +new Date();
  402. console.log('Took', t6 - t5, 'ms to run beforeSaveCharacter');
  403. await io.setAsync({
  404. key: name,
  405. table: 'character',
  406. value: eBeforeSaveCharacter.obj,
  407. serialize: true
  408. });
  409. this.characters[name] = simple;
  410. this.characterList.push(name);
  411. const t7 = +new Date();
  412. console.log('Took', t7 - t6, 'ms to save the character');
  413. await io.setAsync({
  414. key: this.username,
  415. table: 'characterList',
  416. value: this.characterList,
  417. serialize: true
  418. });
  419. const t8 = +new Date();
  420. console.log('Took', t8 - t7, 'ms to save the character list');
  421. releaseCreateLock();
  422. this.initTracker();
  423. this.play({
  424. data: {
  425. name: name
  426. },
  427. callback: msg.callback
  428. });
  429. },
  430. deleteCharacter: async function (msg) {
  431. let data = msg.data;
  432. if ((!data.name) || (!this.username))
  433. return;
  434. if (!this.characterList.some(c => ((c.name === data.name) || (c === data.name)))) {
  435. msg.callback([]);
  436. return;
  437. }
  438. await io.deleteAsync({
  439. key: data.name,
  440. table: 'character'
  441. });
  442. let name = data.name;
  443. this.characterList.spliceWhere(c => (c.name === name || c === name));
  444. let characterList = this.characterList
  445. .map(c => ({
  446. name: c.name ? c.name : c,
  447. level: leaderboard.getLevel(c.name ? c.name : c)
  448. }));
  449. await io.setAsync({
  450. key: this.username,
  451. table: 'characterList',
  452. value: characterList,
  453. serialize: true
  454. });
  455. await leaderboard.deleteCharacter(name);
  456. let result = this.characterList
  457. .map(c => ({
  458. name: c.name ? c.name : c,
  459. level: leaderboard.getLevel(c.name ? c.name : c)
  460. }));
  461. msg.callback(result);
  462. },
  463. permadie: function () {
  464. this.obj.permadead = true;
  465. this.doSave(this.onPermadie.bind(this));
  466. },
  467. onPermadie: function () {
  468. process.send({
  469. method: 'object',
  470. serverId: this.obj.serverId,
  471. obj: {
  472. dead: true
  473. }
  474. });
  475. },
  476. getAccountLevel: function () {
  477. return this.accountInfo.level;
  478. }
  479. };