Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 

637 рядки
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)
  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, saveStash = true) {
  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. if (saveStash)
  83. await this.doSaveStash();
  84. if (callback)
  85. callback();
  86. },
  87. doSaveStash: async function () {
  88. const { username, obj: { stash } } = this;
  89. if (!stash.changed)
  90. return;
  91. await io.setAsync({
  92. key: username,
  93. table: 'stash',
  94. value: stash.serialize(),
  95. clean: true,
  96. serialize: true
  97. });
  98. },
  99. simplify: function (self) {
  100. if (!self)
  101. return;
  102. return {
  103. type: 'auth',
  104. username: this.username,
  105. charname: this.charname,
  106. accountInfo: this.accountInfo
  107. };
  108. },
  109. getCharacterList: async function (data) {
  110. if (!this.username)
  111. return;
  112. this.characterList = await io.getAsync({
  113. key: this.username,
  114. table: 'characterList',
  115. isArray: true
  116. });
  117. let res = this.characterList.map(c => ({
  118. name: c.name ? c.name : c,
  119. level: leaderboard.getLevel(c.name ? c.name : c)
  120. }));
  121. data.callback(res);
  122. },
  123. getCharacter: async function (data) {
  124. let charName = data.data.name;
  125. if (!this.characterList.some(c => (c.name === charName || c === charName)))
  126. return;
  127. let character = await io.getAsync({
  128. key: charName,
  129. table: 'character',
  130. clean: true
  131. });
  132. eventEmitter.emit('onAfterGetCharacter', {
  133. obj: this.obj,
  134. character
  135. });
  136. fixes.fixCharacter(character);
  137. character.cell = skins.getCell(character.skinId);
  138. character.sheetName = skins.getSpritesheet(character.skinId);
  139. this.characters[charName] = character;
  140. await this.getCustomChannels(character);
  141. await this.verifySkin(character);
  142. data.callback(character);
  143. },
  144. getCustomChannels: async function (character) {
  145. this.customChannels = await io.getAsync({
  146. key: character.name,
  147. table: 'customChannels',
  148. isArray: true
  149. });
  150. let social = character.components.find(c => (c.type === 'social'));
  151. this.customChannels = fixes.fixCustomChannels(this.customChannels);
  152. if (social)
  153. social.customChannels = this.customChannels;
  154. },
  155. getStash: async function (data, character) {
  156. this.stash = await io.getAsync({
  157. key: this.username,
  158. table: 'stash',
  159. isArray: true,
  160. clean: true
  161. });
  162. fixes.fixStash(this.stash);
  163. await eventEmitter.emit('onAfterGetStash', {
  164. obj: this.obj,
  165. stash: this.stash
  166. });
  167. },
  168. verifySkin: async function (character) {
  169. const doesOwn = await this.doesOwnSkin(character.skinId);
  170. if (doesOwn)
  171. return;
  172. const defaultTo = 'wizard';
  173. character.skinId = defaultTo;
  174. character.cell = skins.getCell(defaultTo);
  175. character.sheetName = skins.getSpritesheet(defaultTo);
  176. },
  177. doesOwnSkin: async function (skinId) {
  178. const allSkins = skins.getList();
  179. const filteredSkins = allSkins.filter(({ default: isDefaultSkin }) => isDefaultSkin);
  180. const msgSkinList = {
  181. obj: this,
  182. allSkins,
  183. filteredSkins
  184. };
  185. await eventEmitter.emit('onBeforeGetAccountSkins', msgSkinList);
  186. const result = filteredSkins.some(f => f.id === skinId);
  187. return result;
  188. },
  189. getSkinList: async function ({ callback }) {
  190. const allSkins = skins.getList();
  191. const filteredSkins = allSkins.filter(({ default: isDefaultSkin }) => isDefaultSkin);
  192. const msgSkinList = {
  193. obj: this,
  194. allSkins,
  195. filteredSkins
  196. };
  197. await eventEmitter.emit('onBeforeGetAccountSkins', msgSkinList);
  198. callback(filteredSkins);
  199. },
  200. login: async function (msg) {
  201. let credentials = msg.data;
  202. if (credentials.username === '' || credentials.password === '') {
  203. msg.callback(messages.login.allFields);
  204. return;
  205. }
  206. let storedPassword = await io.getAsync({
  207. key: credentials.username,
  208. table: 'login',
  209. noParse: true
  210. });
  211. bcrypt.compare(credentials.password, storedPassword, this.onLogin.bind(this, msg, storedPassword));
  212. },
  213. onLogin: async function (msg, storedPassword, err, compareResult) {
  214. const { data: { username } } = msg;
  215. if (!compareResult) {
  216. msg.callback(messages.login.incorrect);
  217. return;
  218. }
  219. if (username === 'bbb' || username === 'aaa') {
  220. await io.setAsync({
  221. key: new Date(),
  222. table: 'error',
  223. value: username + '1'
  224. });
  225. }
  226. const emBeforeLogin = {
  227. obj: this.obj,
  228. success: true,
  229. msg: null
  230. };
  231. await eventEmitter.emit('onBeforeLogin', emBeforeLogin);
  232. if (username === 'bbb' || username === 'aaa') {
  233. await io.setAsync({
  234. key: new Date(),
  235. table: 'error',
  236. value: username + '2'
  237. });
  238. }
  239. if (!emBeforeLogin.success) {
  240. msg.callback(emBeforeLogin.msg);
  241. return;
  242. }
  243. this.username = username;
  244. await cons.logOut(this.obj);
  245. if (username === 'bbb' || username === 'aaa') {
  246. await io.setAsync({
  247. key: new Date(),
  248. table: 'error',
  249. value: username + '3'
  250. });
  251. }
  252. this.initTracker();
  253. const accountInfo = await io.getAsync({
  254. key: username,
  255. table: 'accountInfo',
  256. noDefault: true
  257. }) || {
  258. loginStreak: 0,
  259. level: 0
  260. };
  261. if (username === 'bbb' || username === 'aaa') {
  262. await io.setAsync({
  263. key: new Date(),
  264. table: 'error',
  265. value: username + '4'
  266. });
  267. }
  268. const msgAccountInfo = {
  269. username,
  270. accountInfo
  271. };
  272. await eventEmitter.emit('onBeforeGetAccountInfo', msgAccountInfo);
  273. if (username === 'bbb' || username === 'aaa') {
  274. await io.setAsync({
  275. key: new Date(),
  276. table: 'error',
  277. value: username + '5'
  278. });
  279. }
  280. await eventEmitter.emit('onAfterLogin', { username });
  281. if (username === 'bbb' || username === 'aaa') {
  282. await io.setAsync({
  283. key: new Date(),
  284. table: 'error',
  285. value: username + '6'
  286. });
  287. }
  288. this.accountInfo = msgAccountInfo.accountInfo;
  289. msg.callback();
  290. },
  291. initTracker: function () {
  292. this.gaTracker = ga.connect(this.username);
  293. },
  294. track: function (category, action, label, value = 1) {
  295. process.send({
  296. method: 'track',
  297. serverId: this.obj.serverId,
  298. obj: {
  299. category,
  300. action,
  301. label,
  302. value
  303. }
  304. });
  305. },
  306. register: async function (msg) {
  307. let credentials = msg.data;
  308. if (credentials.username === '' || credentials.password === '') {
  309. msg.callback(messages.login.allFields);
  310. return;
  311. } else if (credentials.username.length > 32) {
  312. msg.callback(messages.login.maxUsernameLength);
  313. return;
  314. }
  315. let illegal = ["'", '"', '/', '\\', '(', ')', '[', ']', '{', '}', ':', ';', '<', '>', '+', '?', '*'];
  316. for (let i = 0; i < illegal.length; i++) {
  317. if (credentials.username.indexOf(illegal[i]) > -1) {
  318. msg.callback(messages.login.illegal);
  319. return;
  320. }
  321. }
  322. const emBeforeRegisterAccount = {
  323. obj: this.obj,
  324. success: true,
  325. msg: null
  326. };
  327. await eventEmitter.emit('onBeforeRegisterAccount', emBeforeRegisterAccount);
  328. if (!emBeforeRegisterAccount.success) {
  329. msg.callback(emBeforeRegisterAccount.msg);
  330. return;
  331. }
  332. let exists = await io.getAsync({
  333. key: credentials.username,
  334. ignoreCase: true,
  335. table: 'login',
  336. noDefault: true,
  337. noParse: true
  338. });
  339. if (exists) {
  340. msg.callback(messages.login.exists);
  341. return;
  342. }
  343. bcrypt.hash(credentials.password, null, null, this.onHashGenerated.bind(this, msg));
  344. },
  345. onHashGenerated: async function (msg, err, hashedPassword) {
  346. await io.setAsync({
  347. key: msg.data.username,
  348. table: 'login',
  349. value: hashedPassword
  350. });
  351. this.accountInfo = {
  352. loginStreak: 0,
  353. level: 0
  354. };
  355. await io.setAsync({
  356. key: msg.data.username,
  357. table: 'characterList',
  358. value: [],
  359. serialize: true
  360. });
  361. this.username = msg.data.username;
  362. cons.logOut(this.obj);
  363. msg.callback();
  364. },
  365. createCharacter: async function (msg) {
  366. let data = msg.data;
  367. let name = data.name;
  368. let error = null;
  369. if (name.length < 3 || name.length > 12)
  370. error = messages.createCharacter.nameLength;
  371. else if (!profanities.isClean(name))
  372. error = messages.login.invalid;
  373. else if (name.indexOf(' ') > -1)
  374. msg.callback(messages.login.invalid);
  375. else if (!spirits.list.includes(data.class))
  376. return;
  377. let nLen = name.length;
  378. for (let i = 0; i < nLen; i++) {
  379. let char = name[i].toLowerCase();
  380. let valid = [
  381. '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'
  382. ];
  383. if (!valid.includes(char)) {
  384. error = messages.login.invalid;
  385. break;
  386. }
  387. }
  388. if (error) {
  389. msg.callback(error);
  390. return;
  391. }
  392. const releaseCreateLock = await getCreateLock();
  393. let exists = await io.getAsync({
  394. key: name,
  395. ignoreCase: true,
  396. table: 'character',
  397. noDefault: true
  398. });
  399. if (exists) {
  400. releaseCreateLock();
  401. msg.callback(messages.login.charExists);
  402. return;
  403. }
  404. let obj = this.obj;
  405. extend(obj, {
  406. name: name,
  407. skinId: data.skinId,
  408. class: data.class,
  409. cell: skins.getCell(data.skinId),
  410. sheetName: skins.getSpritesheet(data.skinId),
  411. x: null,
  412. y: null
  413. });
  414. let simple = this.obj.getSimple(true);
  415. await this.verifySkin(simple);
  416. let prophecies = (data.prophecies || []).filter(p => p);
  417. simple.components.push({
  418. type: 'prophecies',
  419. list: prophecies
  420. }, {
  421. type: 'social',
  422. customChannels: this.customChannels
  423. });
  424. await io.setAsync({
  425. key: name,
  426. table: 'character',
  427. value: simple,
  428. serialize: true
  429. });
  430. this.characters[name] = simple;
  431. this.characterList.push(name);
  432. await io.setAsync({
  433. key: this.username,
  434. table: 'characterList',
  435. value: this.characterList,
  436. serialize: true
  437. });
  438. releaseCreateLock();
  439. this.initTracker();
  440. this.play({
  441. data: {
  442. name: name
  443. },
  444. callback: msg.callback
  445. });
  446. },
  447. deleteCharacter: async function (msg) {
  448. let data = msg.data;
  449. if ((!data.name) || (!this.username))
  450. return;
  451. if (!this.characterList.some(c => ((c.name === data.name) || (c === data.name)))) {
  452. msg.callback([]);
  453. return;
  454. }
  455. await io.deleteAsync({
  456. key: data.name,
  457. table: 'character'
  458. });
  459. let name = data.name;
  460. this.characterList.spliceWhere(c => (c.name === name || c === name));
  461. let characterList = this.characterList
  462. .map(c => ({
  463. name: c.name ? c.name : c,
  464. level: leaderboard.getLevel(c.name ? c.name : c)
  465. }));
  466. await io.setAsync({
  467. key: this.username,
  468. table: 'characterList',
  469. value: characterList,
  470. serialize: true
  471. });
  472. await leaderboard.deleteCharacter(name);
  473. let result = this.characterList
  474. .map(c => ({
  475. name: c.name ? c.name : c,
  476. level: leaderboard.getLevel(c.name ? c.name : c)
  477. }));
  478. msg.callback(result);
  479. },
  480. permadie: function () {
  481. this.obj.permadead = true;
  482. this.doSave(this.onPermadie.bind(this));
  483. },
  484. onPermadie: function () {
  485. process.send({
  486. method: 'object',
  487. serverId: this.obj.serverId,
  488. obj: {
  489. dead: true
  490. }
  491. });
  492. },
  493. getAccountLevel: function () {
  494. return this.accountInfo.level;
  495. }
  496. };