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.
 
 
 

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