users.js 58 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229
  1. import config from "config";
  2. import async from "async";
  3. import axios from "axios";
  4. import bcrypt from "bcrypt";
  5. import sha256 from "sha256";
  6. import { isAdminRequired, isLoginRequired } from "./hooks";
  7. import moduleManager from "../../index";
  8. const DBModule = moduleManager.modules.db;
  9. const UtilsModule = moduleManager.modules.utils;
  10. const IOModule = moduleManager.modules.io;
  11. const CacheModule = moduleManager.modules.cache;
  12. const MailModule = moduleManager.modules.mail;
  13. const PunishmentsModule = moduleManager.modules.punishments;
  14. const ActivitiesModule = moduleManager.modules.activities;
  15. const PlaylistsModule = moduleManager.modules.playlists;
  16. CacheModule.runJob("SUB", {
  17. channel: "user.updatePreferences",
  18. cb: res => {
  19. IOModule.runJob("SOCKETS_FROM_USER", { userId: res.userId }, this).then(response => {
  20. response.sockets.forEach(socket => {
  21. socket.emit("keep.event:user.preferences.changed", res.preferences);
  22. });
  23. });
  24. }
  25. });
  26. CacheModule.runJob("SUB", {
  27. channel: "user.updateOrderOfPlaylists",
  28. cb: res => {
  29. IOModule.runJob("SOCKETS_FROM_USER", { userId: res.userId }, this).then(response => {
  30. response.sockets.forEach(socket => {
  31. socket.emit("event:user.orderOfPlaylists.changed", res.orderOfPlaylists);
  32. });
  33. });
  34. IOModule.runJob("EMIT_TO_ROOM", {
  35. room: `profile-${res.userId}-playlists`,
  36. args: ["event:user.orderOfPlaylists.changed", res.orderOfPlaylists]
  37. });
  38. }
  39. });
  40. CacheModule.runJob("SUB", {
  41. channel: "user.updateUsername",
  42. cb: user => {
  43. IOModule.runJob("SOCKETS_FROM_USER", { userId: user._id }).then(response => {
  44. response.sockets.forEach(socket => {
  45. socket.emit("event:user.username.changed", user.username);
  46. });
  47. });
  48. }
  49. });
  50. CacheModule.runJob("SUB", {
  51. channel: "user.removeSessions",
  52. cb: userId => {
  53. IOModule.runJob("SOCKETS_FROM_USER_WITHOUT_CACHE", { userId }).then(response => {
  54. response.sockets.forEach(socket => {
  55. socket.emit("keep.event:user.session.removed");
  56. });
  57. });
  58. }
  59. });
  60. CacheModule.runJob("SUB", {
  61. channel: "user.linkPassword",
  62. cb: userId => {
  63. IOModule.runJob("SOCKETS_FROM_USER", { userId }).then(response => {
  64. response.sockets.forEach(socket => {
  65. socket.emit("event:user.linkPassword");
  66. });
  67. });
  68. }
  69. });
  70. CacheModule.runJob("SUB", {
  71. channel: "user.unlinkPassword",
  72. cb: userId => {
  73. IOModule.runJob("SOCKETS_FROM_USER", { userId }).then(response => {
  74. response.sockets.forEach(socket => {
  75. socket.emit("event:user.unlinkPassword");
  76. });
  77. });
  78. }
  79. });
  80. CacheModule.runJob("SUB", {
  81. channel: "user.linkGithub",
  82. cb: userId => {
  83. IOModule.runJob("SOCKETS_FROM_USER", { userId }).then(response => {
  84. response.sockets.forEach(socket => {
  85. socket.emit("event:user.linkGithub");
  86. });
  87. });
  88. }
  89. });
  90. CacheModule.runJob("SUB", {
  91. channel: "user.unlinkGithub",
  92. cb: userId => {
  93. IOModule.runJob("SOCKETS_FROM_USER", { userId }).then(response => {
  94. response.sockets.forEach(socket => {
  95. socket.emit("event:user.unlinkGithub");
  96. });
  97. });
  98. }
  99. });
  100. CacheModule.runJob("SUB", {
  101. channel: "user.ban",
  102. cb: data => {
  103. IOModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(response => {
  104. response.sockets.forEach(socket => {
  105. socket.emit("keep.event:banned", data.punishment);
  106. socket.disconnect(true);
  107. });
  108. });
  109. }
  110. });
  111. CacheModule.runJob("SUB", {
  112. channel: "user.favoritedStation",
  113. cb: data => {
  114. IOModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(response => {
  115. response.sockets.forEach(socket => {
  116. socket.emit("event:user.favoritedStation", data.stationId);
  117. });
  118. });
  119. }
  120. });
  121. CacheModule.runJob("SUB", {
  122. channel: "user.unfavoritedStation",
  123. cb: data => {
  124. IOModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(response => {
  125. response.sockets.forEach(socket => {
  126. socket.emit("event:user.unfavoritedStation", data.stationId);
  127. });
  128. });
  129. }
  130. });
  131. export default {
  132. /**
  133. * Lists all Users
  134. *
  135. * @param {object} session - the session object automatically added by socket.io
  136. * @param {Function} cb - gets called with the result
  137. */
  138. index: isAdminRequired(async function index(session, cb) {
  139. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  140. async.waterfall(
  141. [
  142. next => {
  143. userModel.find({}).exec(next);
  144. }
  145. ],
  146. async (err, users) => {
  147. if (err) {
  148. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  149. this.log("ERROR", "USER_INDEX", `Indexing users failed. "${err}"`);
  150. return cb({ status: "failure", message: err });
  151. }
  152. this.log("SUCCESS", "USER_INDEX", `Indexing users successful.`);
  153. const filteredUsers = [];
  154. users.forEach(user => {
  155. filteredUsers.push({
  156. _id: user._id,
  157. name: user.name,
  158. username: user.username,
  159. role: user.role,
  160. liked: user.liked,
  161. disliked: user.disliked,
  162. songsRequested: user.statistics.songsRequested,
  163. email: {
  164. address: user.email.address,
  165. verified: user.email.verified
  166. },
  167. avatar: {
  168. type: user.avatar.type,
  169. url: user.avatar.url,
  170. color: user.avatar.color
  171. },
  172. hasPassword: !!user.services.password,
  173. services: { github: user.services.github }
  174. });
  175. });
  176. return cb({ status: "success", data: filteredUsers });
  177. }
  178. );
  179. }),
  180. /**
  181. * Logs user in
  182. *
  183. * @param {object} session - the session object automatically added by socket.io
  184. * @param {string} identifier - the email of the user
  185. * @param {string} password - the plaintext of the user
  186. * @param {Function} cb - gets called with the result
  187. */
  188. async login(session, identifier, password, cb) {
  189. identifier = identifier.toLowerCase();
  190. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  191. const sessionSchema = await CacheModule.runJob(
  192. "GET_SCHEMA",
  193. {
  194. schemaName: "session"
  195. },
  196. this
  197. );
  198. async.waterfall(
  199. [
  200. // check if a user with the requested identifier exists
  201. next => {
  202. userModel.findOne(
  203. {
  204. $or: [{ "email.address": identifier }]
  205. },
  206. next
  207. );
  208. },
  209. // if the user doesn't exist, respond with a failure
  210. // otherwise compare the requested password and the actual users password
  211. (user, next) => {
  212. if (!user) return next("User not found");
  213. if (!user.services.password || !user.services.password.password)
  214. return next("The account you are trying to access uses GitHub to log in.");
  215. return bcrypt.compare(sha256(password), user.services.password.password, (err, match) => {
  216. if (err) return next(err);
  217. if (!match) return next("Incorrect password");
  218. return next(null, user);
  219. });
  220. },
  221. (user, next) => {
  222. UtilsModule.runJob("GUID", {}, this).then(sessionId => {
  223. next(null, user, sessionId);
  224. });
  225. },
  226. (user, sessionId, next) => {
  227. CacheModule.runJob(
  228. "HSET",
  229. {
  230. table: "sessions",
  231. key: sessionId,
  232. value: sessionSchema(sessionId, user._id)
  233. },
  234. this
  235. )
  236. .then(() => {
  237. next(null, sessionId);
  238. })
  239. .catch(next);
  240. }
  241. ],
  242. async (err, sessionId) => {
  243. if (err && err !== true) {
  244. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  245. this.log(
  246. "ERROR",
  247. "USER_PASSWORD_LOGIN",
  248. `Login failed with password for user "${identifier}". "${err}"`
  249. );
  250. return cb({ status: "failure", message: err });
  251. }
  252. this.log("SUCCESS", "USER_PASSWORD_LOGIN", `Login successful with password for user "${identifier}"`);
  253. return cb({
  254. status: "success",
  255. message: "Login successful",
  256. user: {},
  257. SID: sessionId
  258. });
  259. }
  260. );
  261. },
  262. /**
  263. * Registers a new user
  264. *
  265. * @param {object} session - the session object automatically added by socket.io
  266. * @param {string} username - the username for the new user
  267. * @param {string} email - the email for the new user
  268. * @param {string} password - the plaintext password for the new user
  269. * @param {object} recaptcha - the recaptcha data
  270. * @param {Function} cb - gets called with the result
  271. */
  272. async register(session, username, email, password, recaptcha, cb) {
  273. email = email.toLowerCase();
  274. const verificationToken = await UtilsModule.runJob(
  275. "GENERATE_RANDOM_STRING",
  276. {
  277. length: 64
  278. },
  279. this
  280. );
  281. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  282. const verifyEmailSchema = await MailModule.runJob(
  283. "GET_SCHEMA",
  284. {
  285. schemaName: "verifyEmail"
  286. },
  287. this
  288. );
  289. async.waterfall(
  290. [
  291. next => {
  292. if (config.get("registrationDisabled") === true)
  293. return next("Registration is not allowed at this time.");
  294. return next();
  295. },
  296. next => {
  297. if (!DBModule.passwordValid(password))
  298. return next("Invalid password. Check if it meets all the requirements.");
  299. return next();
  300. },
  301. // verify the request with google recaptcha
  302. next => {
  303. if (config.get("apis.recaptcha.enabled") === true)
  304. axios
  305. .post("https://www.google.com/recaptcha/api/siteverify", {
  306. data: {
  307. secret: config.get("apis").recaptcha.secret,
  308. response: recaptcha
  309. }
  310. })
  311. .then(res => next(null, res.data))
  312. .catch(err => next(err));
  313. else next(null, null, null);
  314. },
  315. // check if the response from Google recaptcha is successful
  316. // if it is, we check if a user with the requested username already exists
  317. (body, next) => {
  318. if (config.get("apis.recaptcha.enabled") === true)
  319. if (body.success !== true) return next("Response from recaptcha was not successful.");
  320. return userModel.findOne({ username: new RegExp(`^${username}$`, "i") }, next);
  321. },
  322. // if the user already exists, respond with that
  323. // otherwise check if a user with the requested email already exists
  324. (user, next) => {
  325. if (user) return next("A user with that username already exists.");
  326. return userModel.findOne({ "email.address": email }, next);
  327. },
  328. // if the user already exists, respond with that
  329. // otherwise, generate a salt to use with hashing the new users password
  330. (user, next) => {
  331. if (user) return next("A user with that email already exists.");
  332. return bcrypt.genSalt(10, next);
  333. },
  334. // hash the password
  335. (salt, next) => {
  336. bcrypt.hash(sha256(password), salt, next);
  337. },
  338. (hash, next) => {
  339. UtilsModule.runJob("GENERATE_RANDOM_STRING", { length: 12 }, this).then(_id => {
  340. next(null, hash, _id);
  341. });
  342. },
  343. // create the user object
  344. (hash, _id, next) => {
  345. next(null, {
  346. _id,
  347. username,
  348. email: {
  349. address: email,
  350. verificationToken
  351. },
  352. services: {
  353. password: {
  354. password: hash
  355. }
  356. }
  357. });
  358. },
  359. // generate the url for gravatar avatar
  360. (user, next) => {
  361. UtilsModule.runJob(
  362. "CREATE_GRAVATAR",
  363. {
  364. email: user.email.address
  365. },
  366. this
  367. ).then(url => {
  368. user.avatar = {
  369. type: "gravatar",
  370. url
  371. };
  372. next(null, user);
  373. });
  374. },
  375. // save the new user to the database
  376. (user, next) => {
  377. userModel.create(user, next);
  378. },
  379. // respond with the new user
  380. (user, next) => {
  381. verifyEmailSchema(email, username, verificationToken, err => {
  382. next(err, user._id);
  383. });
  384. },
  385. // create a liked songs playlist for the new user
  386. (userId, next) => {
  387. PlaylistsModule.runJob("CREATE_READ_ONLY_PLAYLIST", {
  388. userId,
  389. displayName: "Liked Songs",
  390. type: "user"
  391. })
  392. .then(likedSongsPlaylist => {
  393. next(null, likedSongsPlaylist, userId);
  394. })
  395. .catch(err => next(err));
  396. },
  397. // create a disliked songs playlist for the new user
  398. (likedSongsPlaylist, userId, next) => {
  399. PlaylistsModule.runJob("CREATE_READ_ONLY_PLAYLIST", {
  400. userId,
  401. displayName: "Disliked Songs",
  402. type: "user"
  403. })
  404. .then(dislikedSongsPlaylist => {
  405. next(null, { likedSongsPlaylist, dislikedSongsPlaylist }, userId);
  406. })
  407. .catch(err => next(err));
  408. },
  409. // associate liked + disliked songs playlist to the user object
  410. ({ likedSongsPlaylist, dislikedSongsPlaylist }, userId, next) => {
  411. userModel.updateOne(
  412. { _id: userId },
  413. { $set: { likedSongsPlaylist, dislikedSongsPlaylist } },
  414. { runValidators: true },
  415. err => {
  416. if (err) return next(err);
  417. return next(null, userId);
  418. }
  419. );
  420. }
  421. ],
  422. async (err, user) => {
  423. if (err && err !== true) {
  424. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  425. this.log(
  426. "ERROR",
  427. "USER_PASSWORD_REGISTER",
  428. `Register failed with password for user "${username}"."${err}"`
  429. );
  430. return cb({ status: "failure", message: err });
  431. }
  432. ActivitiesModule.runJob("ADD_ACTIVITY", {
  433. userId: user._id,
  434. activityType: "created_account"
  435. });
  436. this.log(
  437. "SUCCESS",
  438. "USER_PASSWORD_REGISTER",
  439. `Register successful with password for user "${username}".`
  440. );
  441. const result = await this.module.runJob(
  442. "RUN_ACTION2",
  443. {
  444. session,
  445. namespace: "users",
  446. action: "login",
  447. args: [email, password]
  448. },
  449. this
  450. );
  451. const obj = {
  452. status: "success",
  453. message: "Successfully registered."
  454. };
  455. if (result.status === "success") {
  456. obj.SID = result.SID;
  457. }
  458. return cb(obj);
  459. }
  460. );
  461. },
  462. /**
  463. * Logs out a user
  464. *
  465. * @param {object} session - the session object automatically added by socket.io
  466. * @param {Function} cb - gets called with the result
  467. */
  468. logout(session, cb) {
  469. async.waterfall(
  470. [
  471. next => {
  472. CacheModule.runJob(
  473. "HGET",
  474. {
  475. table: "sessions",
  476. key: session.sessionId
  477. },
  478. this
  479. )
  480. .then(session => {
  481. next(null, session);
  482. })
  483. .catch(next);
  484. },
  485. (session, next) => {
  486. if (!session) return next("Session not found");
  487. return next(null, session);
  488. },
  489. (session, next) => {
  490. CacheModule.runJob(
  491. "HDEL",
  492. {
  493. table: "sessions",
  494. key: session.sessionId
  495. },
  496. this
  497. )
  498. .then(() => {
  499. next();
  500. })
  501. .catch(next);
  502. }
  503. ],
  504. async err => {
  505. if (err && err !== true) {
  506. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  507. this.log("ERROR", "USER_LOGOUT", `Logout failed. "${err}" `);
  508. cb({ status: "failure", message: err });
  509. } else {
  510. this.log("SUCCESS", "USER_LOGOUT", `Logout successful.`);
  511. cb({
  512. status: "success",
  513. message: "Successfully logged out."
  514. });
  515. }
  516. }
  517. );
  518. },
  519. /**
  520. * Removes all sessions for a user
  521. *
  522. * @param {object} session - the session object automatically added by socket.io
  523. * @param {string} userId - the id of the user we are trying to delete the sessions of
  524. * @param {Function} cb - gets called with the result
  525. */
  526. removeSessions: isLoginRequired(async function removeSessions(session, userId, cb) {
  527. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  528. async.waterfall(
  529. [
  530. next => {
  531. userModel.findOne({ _id: session.userId }, (err, user) => {
  532. if (err) return next(err);
  533. if (user.role !== "admin" && session.userId !== userId)
  534. return next("Only admins and the owner of the account can remove their sessions.");
  535. return next();
  536. });
  537. },
  538. next => {
  539. CacheModule.runJob("HGETALL", { table: "sessions" }, this)
  540. .then(sessions => {
  541. next(null, sessions);
  542. })
  543. .catch(next);
  544. },
  545. (sessions, next) => {
  546. if (!sessions) return next("There are no sessions for this user to remove.");
  547. const keys = Object.keys(sessions);
  548. return next(null, keys, sessions);
  549. },
  550. (keys, sessions, next) => {
  551. CacheModule.runJob("PUB", {
  552. channel: "user.removeSessions",
  553. value: userId
  554. });
  555. async.each(
  556. keys,
  557. (sessionId, callback) => {
  558. const session = sessions[sessionId];
  559. if (session.userId === userId) {
  560. // TODO Also maybe add this to this runJob
  561. CacheModule.runJob("HDEL", {
  562. channel: "sessions",
  563. key: sessionId
  564. })
  565. .then(() => {
  566. callback(null);
  567. })
  568. .catch(next);
  569. }
  570. },
  571. err => {
  572. next(err);
  573. }
  574. );
  575. }
  576. ],
  577. async err => {
  578. if (err) {
  579. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  580. this.log(
  581. "ERROR",
  582. "REMOVE_SESSIONS_FOR_USER",
  583. `Couldn't remove all sessions for user "${userId}". "${err}"`
  584. );
  585. return cb({ status: "failure", message: err });
  586. }
  587. this.log("SUCCESS", "REMOVE_SESSIONS_FOR_USER", `Removed all sessions for user "${userId}".`);
  588. return cb({
  589. status: "success",
  590. message: "Successfully removed all sessions."
  591. });
  592. }
  593. );
  594. }),
  595. /**
  596. * Updates the order of a user's playlists
  597. *
  598. * @param {object} session - the session object automatically added by socket.io
  599. * @param {Array} orderOfPlaylists - array of playlist ids (with a specific order)
  600. * @param {Function} cb - gets called with the result
  601. */
  602. updateOrderOfPlaylists: isLoginRequired(async function updateOrderOfPlaylists(session, orderOfPlaylists, cb) {
  603. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  604. async.waterfall(
  605. [
  606. next => {
  607. userModel.updateOne(
  608. { _id: session.userId },
  609. { $set: { "preferences.orderOfPlaylists": orderOfPlaylists } },
  610. { runValidators: true },
  611. next
  612. );
  613. }
  614. ],
  615. async err => {
  616. if (err) {
  617. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  618. this.log(
  619. "ERROR",
  620. "UPDATE_ORDER_OF_USER_PLAYLISTS",
  621. `Couldn't update order of playlists for user "${session.userId}" to "${orderOfPlaylists}". "${err}"`
  622. );
  623. return cb({ status: "failure", message: err });
  624. }
  625. CacheModule.runJob("PUB", {
  626. channel: "user.updateOrderOfPlaylists",
  627. value: {
  628. orderOfPlaylists,
  629. userId: session.userId
  630. }
  631. });
  632. this.log(
  633. "SUCCESS",
  634. "UPDATE_ORDER_OF_USER_PLAYLISTS",
  635. `Updated order of playlists for user "${session.userId}" to "${orderOfPlaylists}".`
  636. );
  637. return cb({
  638. status: "success",
  639. message: "Order of playlists successfully updated"
  640. });
  641. }
  642. );
  643. }),
  644. /**
  645. * Updates a user's preferences
  646. *
  647. * @param {object} session - the session object automatically added by socket.io
  648. * @param {object} preferences - object containing preferences
  649. * @param {boolean} preferences.nightmode - whether or not the user is using the night mode theme
  650. * @param {boolean} preferences.autoSkipDisliked - whether to automatically skip disliked songs
  651. * @param {boolean} preferences.activityLogPublic - whether or not a user's activity log can be publicly viewed
  652. * @param {Function} cb - gets called with the result
  653. */
  654. updatePreferences: isLoginRequired(async function updatePreferences(session, preferences, cb) {
  655. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  656. async.waterfall(
  657. [
  658. next => {
  659. userModel.updateOne(
  660. { _id: session.userId },
  661. {
  662. $set: {
  663. preferences: {
  664. nightmode: preferences.nightmode,
  665. autoSkipDisliked: preferences.autoSkipDisliked,
  666. activityLogPublic: preferences.activityLogPublic
  667. }
  668. }
  669. },
  670. { runValidators: true },
  671. next
  672. );
  673. }
  674. ],
  675. async err => {
  676. if (err) {
  677. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  678. this.log(
  679. "ERROR",
  680. "UPDATE_USER_PREFERENCES",
  681. `Couldn't update preferences for user "${session.userId}" to "${JSON.stringify(
  682. preferences
  683. )}". "${err}"`
  684. );
  685. return cb({ status: "failure", message: err });
  686. }
  687. CacheModule.runJob("PUB", {
  688. channel: "user.updatePreferences",
  689. value: {
  690. preferences,
  691. userId: session.userId
  692. }
  693. });
  694. this.log(
  695. "SUCCESS",
  696. "UPDATE_USER_PREFERENCES",
  697. `Updated preferences for user "${session.userId}" to "${JSON.stringify(preferences)}".`
  698. );
  699. return cb({
  700. status: "success",
  701. message: "Preferences successfully updated"
  702. });
  703. }
  704. );
  705. }),
  706. /**
  707. * Retrieves a user's preferences
  708. *
  709. * @param {object} session - the session object automatically added by socket.io
  710. * @param {Function} cb - gets called with the result
  711. */
  712. getPreferences: isLoginRequired(async function updatePreferences(session, cb) {
  713. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  714. async.waterfall(
  715. [
  716. next => {
  717. userModel.findById(session.userId).select({ preferences: -1 }).exec(next);
  718. }
  719. ],
  720. async (err, { preferences }) => {
  721. if (err) {
  722. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  723. this.log(
  724. "ERROR",
  725. "GET_USER_PREFERENCES",
  726. `Couldn't retrieve preferences for user "${session.userId}". "${err}"`
  727. );
  728. return cb({ status: "failure", message: err });
  729. }
  730. this.log(
  731. "SUCCESS",
  732. "GET_USER_PREFERENCES",
  733. `Successfully obtained preferences for user "${session.userId}".`
  734. );
  735. return cb({
  736. status: "success",
  737. message: "Preferences successfully retrieved",
  738. data: preferences
  739. });
  740. }
  741. );
  742. }),
  743. /**
  744. * Gets user object from username (only a few properties)
  745. *
  746. * @param {object} session - the session object automatically added by socket.io
  747. * @param {string} username - the username of the user we are trying to find
  748. * @param {Function} cb - gets called with the result
  749. */
  750. findByUsername: async function findByUsername(session, username, cb) {
  751. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  752. async.waterfall(
  753. [
  754. next => {
  755. userModel.findOne({ username: new RegExp(`^${username}$`, "i") }, next);
  756. },
  757. (account, next) => {
  758. if (!account) return next("User not found.");
  759. return next(null, account);
  760. }
  761. ],
  762. async (err, account) => {
  763. if (err && err !== true) {
  764. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  765. this.log("ERROR", "FIND_BY_USERNAME", `User not found for username "${username}". "${err}"`);
  766. return cb({ status: "failure", message: err });
  767. }
  768. this.log("SUCCESS", "FIND_BY_USERNAME", `User found for username "${username}".`);
  769. return cb({
  770. status: "success",
  771. data: {
  772. _id: account._id,
  773. name: account.name,
  774. username: account.username,
  775. location: account.location,
  776. bio: account.bio,
  777. role: account.role,
  778. avatar: account.avatar,
  779. createdAt: account.createdAt
  780. }
  781. });
  782. }
  783. );
  784. },
  785. /**
  786. * Gets a username from an userId
  787. *
  788. * @param {object} session - the session object automatically added by socket.io
  789. * @param {string} userId - the userId of the person we are trying to get the username from
  790. * @param {Function} cb - gets called with the result
  791. */
  792. async getUsernameFromId(session, userId, cb) {
  793. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  794. userModel
  795. .findById(userId)
  796. .then(user => {
  797. if (user) {
  798. this.log("SUCCESS", "GET_USERNAME_FROM_ID", `Found username for userId "${userId}".`);
  799. return cb({
  800. status: "success",
  801. data: user.username
  802. });
  803. }
  804. this.log(
  805. "ERROR",
  806. "GET_USERNAME_FROM_ID",
  807. `Getting the username from userId "${userId}" failed. User not found.`
  808. );
  809. return cb({
  810. status: "failure",
  811. message: "Couldn't find the user."
  812. });
  813. })
  814. .catch(async err => {
  815. if (err && err !== true) {
  816. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  817. this.log(
  818. "ERROR",
  819. "GET_USERNAME_FROM_ID",
  820. `Getting the username from userId "${userId}" failed. "${err}"`
  821. );
  822. cb({ status: "failure", message: err });
  823. }
  824. });
  825. },
  826. /**
  827. * Gets a user from a userId
  828. *
  829. * @param {object} session - the session object automatically added by socket.io
  830. * @param {string} userId - the userId of the person we are trying to get the username from
  831. * @param {Function} cb - gets called with the result
  832. */
  833. getUserFromId: isAdminRequired(async function getUserFromId(session, userId, cb) {
  834. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  835. userModel
  836. .findById(userId)
  837. .then(user => {
  838. if (user) {
  839. this.log("SUCCESS", "GET_USER_FROM_ID", `Found user for userId "${userId}".`);
  840. return cb({
  841. status: "success",
  842. data: {
  843. _id: user._id,
  844. username: user.username,
  845. role: user.role,
  846. liked: user.liked,
  847. disliked: user.disliked,
  848. songsRequested: user.statistics.songsRequested,
  849. email: {
  850. address: user.email.address,
  851. verified: user.email.verified
  852. },
  853. hasPassword: !!user.services.password,
  854. services: { github: user.services.github }
  855. }
  856. });
  857. }
  858. this.log(
  859. "ERROR",
  860. "GET_USER_FROM_ID",
  861. `Getting the user from userId "${userId}" failed. User not found.`
  862. );
  863. return cb({
  864. status: "failure",
  865. message: "Couldn't find the user."
  866. });
  867. })
  868. .catch(async err => {
  869. if (err && err !== true) {
  870. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  871. this.log("ERROR", "GET_USER_FROM_ID", `Getting the user from userId "${userId}" failed. "${err}"`);
  872. cb({ status: "failure", message: err });
  873. }
  874. });
  875. }),
  876. // TODO Fix security issues
  877. /**
  878. * Gets user info from session
  879. *
  880. * @param {object} session - the session object automatically added by socket.io
  881. * @param {Function} cb - gets called with the result
  882. */
  883. async findBySession(session, cb) {
  884. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  885. async.waterfall(
  886. [
  887. next => {
  888. CacheModule.runJob(
  889. "HGET",
  890. {
  891. table: "sessions",
  892. key: session.sessionId
  893. },
  894. this
  895. )
  896. .then(session => {
  897. next(null, session);
  898. })
  899. .catch(next);
  900. },
  901. (session, next) => {
  902. if (!session) return next("Session not found.");
  903. return next(null, session);
  904. },
  905. (session, next) => {
  906. userModel.findOne({ _id: session.userId }, next);
  907. },
  908. (user, next) => {
  909. if (!user) return next("User not found.");
  910. return next(null, user);
  911. }
  912. ],
  913. async (err, user) => {
  914. if (err && err !== true) {
  915. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  916. this.log("ERROR", "FIND_BY_SESSION", `User not found. "${err}"`);
  917. return cb({ status: "failure", message: err });
  918. }
  919. const data = {
  920. email: {
  921. address: user.email.address
  922. },
  923. avatar: user.avatar,
  924. username: user.username,
  925. name: user.name,
  926. location: user.location,
  927. bio: user.bio
  928. };
  929. if (user.services.password && user.services.password.password) data.password = true;
  930. if (user.services.github && user.services.github.id) data.github = true;
  931. this.log("SUCCESS", "FIND_BY_SESSION", `User found. "${user.username}".`);
  932. return cb({
  933. status: "success",
  934. data
  935. });
  936. }
  937. );
  938. },
  939. /**
  940. * Updates a user's username
  941. *
  942. * @param {object} session - the session object automatically added by socket.io
  943. * @param {string} updatingUserId - the updating user's id
  944. * @param {string} newUsername - the new username
  945. * @param {Function} cb - gets called with the result
  946. */
  947. updateUsername: isLoginRequired(async function updateUsername(session, updatingUserId, newUsername, cb) {
  948. const userModel = await DBModule.runJob(
  949. "GET_MODEL",
  950. {
  951. modelName: "user"
  952. },
  953. this
  954. );
  955. async.waterfall(
  956. [
  957. next => {
  958. if (updatingUserId === session.userId) return next(null, true);
  959. return userModel.findOne({ _id: session.userId }, next);
  960. },
  961. (user, next) => {
  962. if (user !== true && (!user || user.role !== "admin")) return next("Invalid permissions.");
  963. return userModel.findOne({ _id: updatingUserId }, next);
  964. },
  965. (user, next) => {
  966. if (!user) return next("User not found.");
  967. if (user.username === newUsername)
  968. return next("New username can't be the same as the old username.");
  969. return next(null);
  970. },
  971. next => {
  972. userModel.findOne({ username: new RegExp(`^${newUsername}$`, "i") }, next);
  973. },
  974. (user, next) => {
  975. if (!user) return next();
  976. if (user._id === updatingUserId) return next();
  977. return next("That username is already in use.");
  978. },
  979. next => {
  980. userModel.updateOne(
  981. { _id: updatingUserId },
  982. { $set: { username: newUsername } },
  983. { runValidators: true },
  984. next
  985. );
  986. }
  987. ],
  988. async err => {
  989. if (err && err !== true) {
  990. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  991. this.log(
  992. "ERROR",
  993. "UPDATE_USERNAME",
  994. `Couldn't update username for user "${updatingUserId}" to username "${newUsername}". "${err}"`
  995. );
  996. return cb({ status: "failure", message: err });
  997. }
  998. CacheModule.runJob("PUB", {
  999. channel: "user.updateUsername",
  1000. value: {
  1001. username: newUsername,
  1002. _id: updatingUserId
  1003. }
  1004. });
  1005. this.log(
  1006. "SUCCESS",
  1007. "UPDATE_USERNAME",
  1008. `Updated username for user "${updatingUserId}" to username "${newUsername}".`
  1009. );
  1010. return cb({
  1011. status: "success",
  1012. message: "Username updated successfully"
  1013. });
  1014. }
  1015. );
  1016. }),
  1017. /**
  1018. * Updates a user's email
  1019. *
  1020. * @param {object} session - the session object automatically added by socket.io
  1021. * @param {string} updatingUserId - the updating user's id
  1022. * @param {string} newEmail - the new email
  1023. * @param {Function} cb - gets called with the result
  1024. */
  1025. updateEmail: isLoginRequired(async function updateEmail(session, updatingUserId, newEmail, cb) {
  1026. newEmail = newEmail.toLowerCase();
  1027. const verificationToken = await UtilsModule.runJob("GENERATE_RANDOM_STRING", { length: 64 }, this);
  1028. const userModel = await DBModule.runJob(
  1029. "GET_MODEL",
  1030. {
  1031. modelName: "user"
  1032. },
  1033. this
  1034. );
  1035. const verifyEmailSchema = await MailModule.runJob(
  1036. "GET_SCHEMA",
  1037. {
  1038. schemaName: "verifyEmail"
  1039. },
  1040. this
  1041. );
  1042. async.waterfall(
  1043. [
  1044. next => {
  1045. if (updatingUserId === session.userId) return next(null, true);
  1046. return userModel.findOne({ _id: session.userId }, next);
  1047. },
  1048. (user, next) => {
  1049. if (user !== true && (!user || user.role !== "admin")) return next("Invalid permissions.");
  1050. return userModel.findOne({ _id: updatingUserId }, next);
  1051. },
  1052. (user, next) => {
  1053. if (!user) return next("User not found.");
  1054. if (user.email.address === newEmail)
  1055. return next("New email can't be the same as your the old email.");
  1056. return next();
  1057. },
  1058. next => {
  1059. userModel.findOne({ "email.address": newEmail }, next);
  1060. },
  1061. (user, next) => {
  1062. if (!user) return next();
  1063. if (user._id === updatingUserId) return next();
  1064. return next("That email is already in use.");
  1065. },
  1066. // regenerate the url for gravatar avatar
  1067. next => {
  1068. UtilsModule.runJob("CREATE_GRAVATAR", { email: newEmail }, this).then(url => {
  1069. next(null, url);
  1070. });
  1071. },
  1072. (newAvatarUrl, next) => {
  1073. userModel.updateOne(
  1074. { _id: updatingUserId },
  1075. {
  1076. $set: {
  1077. "avatar.url": newAvatarUrl,
  1078. "email.address": newEmail,
  1079. "email.verified": false,
  1080. "email.verificationToken": verificationToken
  1081. }
  1082. },
  1083. { runValidators: true },
  1084. next
  1085. );
  1086. },
  1087. (res, next) => {
  1088. userModel.findOne({ _id: updatingUserId }, next);
  1089. },
  1090. (user, next) => {
  1091. verifyEmailSchema(newEmail, user.username, verificationToken, err => {
  1092. next(err);
  1093. });
  1094. }
  1095. ],
  1096. async err => {
  1097. if (err && err !== true) {
  1098. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1099. this.log(
  1100. "ERROR",
  1101. "UPDATE_EMAIL",
  1102. `Couldn't update email for user "${updatingUserId}" to email "${newEmail}". '${err}'`
  1103. );
  1104. return cb({ status: "failure", message: err });
  1105. }
  1106. this.log(
  1107. "SUCCESS",
  1108. "UPDATE_EMAIL",
  1109. `Updated email for user "${updatingUserId}" to email "${newEmail}".`
  1110. );
  1111. return cb({
  1112. status: "success",
  1113. message: "Email updated successfully."
  1114. });
  1115. }
  1116. );
  1117. }),
  1118. /**
  1119. * Updates a user's name
  1120. *
  1121. * @param {object} session - the session object automatically added by socket.io
  1122. * @param {string} updatingUserId - the updating user's id
  1123. * @param {string} newBio - the new name
  1124. * @param {Function} cb - gets called with the result
  1125. */
  1126. updateName: isLoginRequired(async function updateName(session, updatingUserId, newName, cb) {
  1127. const userModel = await DBModule.runJob(
  1128. "GET_MODEL",
  1129. {
  1130. modelName: "user"
  1131. },
  1132. this
  1133. );
  1134. async.waterfall(
  1135. [
  1136. next => {
  1137. if (updatingUserId === session.userId) return next(null, true);
  1138. return userModel.findOne({ _id: session.userId }, next);
  1139. },
  1140. (user, next) => {
  1141. if (user !== true && (!user || user.role !== "admin")) return next("Invalid permissions.");
  1142. return userModel.findOne({ _id: updatingUserId }, next);
  1143. },
  1144. (user, next) => {
  1145. if (!user) return next("User not found.");
  1146. return userModel.updateOne(
  1147. { _id: updatingUserId },
  1148. { $set: { name: newName } },
  1149. { runValidators: true },
  1150. next
  1151. );
  1152. }
  1153. ],
  1154. async err => {
  1155. if (err && err !== true) {
  1156. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1157. this.log(
  1158. "ERROR",
  1159. "UPDATE_NAME",
  1160. `Couldn't update name for user "${updatingUserId}" to name "${newName}". "${err}"`
  1161. );
  1162. cb({ status: "failure", message: err });
  1163. } else {
  1164. this.log(
  1165. "SUCCESS",
  1166. "UPDATE_NAME",
  1167. `Updated name for user "${updatingUserId}" to name "${newName}".`
  1168. );
  1169. cb({
  1170. status: "success",
  1171. message: "Name updated successfully"
  1172. });
  1173. }
  1174. }
  1175. );
  1176. }),
  1177. /**
  1178. * Updates a user's location
  1179. *
  1180. * @param {object} session - the session object automatically added by socket.io
  1181. * @param {string} updatingUserId - the updating user's id
  1182. * @param {string} newLocation - the new location
  1183. * @param {Function} cb - gets called with the result
  1184. */
  1185. updateLocation: isLoginRequired(async function updateLocation(session, updatingUserId, newLocation, cb) {
  1186. const userModel = await DBModule.runJob(
  1187. "GET_MODEL",
  1188. {
  1189. modelName: "user"
  1190. },
  1191. this
  1192. );
  1193. async.waterfall(
  1194. [
  1195. next => {
  1196. if (updatingUserId === session.userId) return next(null, true);
  1197. return userModel.findOne({ _id: session.userId }, next);
  1198. },
  1199. (user, next) => {
  1200. if (user !== true && (!user || user.role !== "admin")) return next("Invalid permissions.");
  1201. return userModel.findOne({ _id: updatingUserId }, next);
  1202. },
  1203. (user, next) => {
  1204. if (!user) return next("User not found.");
  1205. return userModel.updateOne(
  1206. { _id: updatingUserId },
  1207. { $set: { location: newLocation } },
  1208. { runValidators: true },
  1209. next
  1210. );
  1211. }
  1212. ],
  1213. async err => {
  1214. if (err && err !== true) {
  1215. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1216. this.log(
  1217. "ERROR",
  1218. "UPDATE_LOCATION",
  1219. `Couldn't update location for user "${updatingUserId}" to location "${newLocation}". "${err}"`
  1220. );
  1221. return cb({ status: "failure", message: err });
  1222. }
  1223. this.log(
  1224. "SUCCESS",
  1225. "UPDATE_LOCATION",
  1226. `Updated location for user "${updatingUserId}" to location "${newLocation}".`
  1227. );
  1228. return cb({
  1229. status: "success",
  1230. message: "Location updated successfully"
  1231. });
  1232. }
  1233. );
  1234. }),
  1235. /**
  1236. * Updates a user's bio
  1237. *
  1238. * @param {object} session - the session object automatically added by socket.io
  1239. * @param {string} updatingUserId - the updating user's id
  1240. * @param {string} newBio - the new bio
  1241. * @param {Function} cb - gets called with the result
  1242. */
  1243. updateBio: isLoginRequired(async function updateBio(session, updatingUserId, newBio, cb) {
  1244. const userModel = await DBModule.runJob(
  1245. "GET_MODEL",
  1246. {
  1247. modelName: "user"
  1248. },
  1249. this
  1250. );
  1251. async.waterfall(
  1252. [
  1253. next => {
  1254. if (updatingUserId === session.userId) return next(null, true);
  1255. return userModel.findOne({ _id: session.userId }, next);
  1256. },
  1257. (user, next) => {
  1258. if (user !== true && (!user || user.role !== "admin")) return next("Invalid permissions.");
  1259. return userModel.findOne({ _id: updatingUserId }, next);
  1260. },
  1261. (user, next) => {
  1262. if (!user) return next("User not found.");
  1263. return userModel.updateOne(
  1264. { _id: updatingUserId },
  1265. { $set: { bio: newBio } },
  1266. { runValidators: true },
  1267. next
  1268. );
  1269. }
  1270. ],
  1271. async err => {
  1272. if (err && err !== true) {
  1273. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1274. this.log(
  1275. "ERROR",
  1276. "UPDATE_BIO",
  1277. `Couldn't update bio for user "${updatingUserId}" to bio "${newBio}". "${err}"`
  1278. );
  1279. cb({ status: "failure", message: err });
  1280. } else {
  1281. this.log("SUCCESS", "UPDATE_BIO", `Updated bio for user "${updatingUserId}" to bio "${newBio}".`);
  1282. cb({
  1283. status: "success",
  1284. message: "Bio updated successfully"
  1285. });
  1286. }
  1287. }
  1288. );
  1289. }),
  1290. /**
  1291. * Updates the type of a user's avatar
  1292. *
  1293. * @param {object} session - the session object automatically added by socket.io
  1294. * @param {string} updatingUserId - the updating user's id
  1295. * @param {string} newType - the new type
  1296. * @param {Function} cb - gets called with the result
  1297. */
  1298. updateAvatarType: isLoginRequired(async function updateAvatarType(session, updatingUserId, newAvatar, cb) {
  1299. const userModel = await DBModule.runJob(
  1300. "GET_MODEL",
  1301. {
  1302. modelName: "user"
  1303. },
  1304. this
  1305. );
  1306. async.waterfall(
  1307. [
  1308. next => {
  1309. if (updatingUserId === session.userId) return next(null, true);
  1310. return userModel.findOne({ _id: session.userId }, next);
  1311. },
  1312. (user, next) => {
  1313. if (user !== true && (!user || user.role !== "admin")) return next("Invalid permissions.");
  1314. return userModel.findOne({ _id: updatingUserId }, next);
  1315. },
  1316. (user, next) => {
  1317. if (!user) return next("User not found.");
  1318. return userModel.findOneAndUpdate(
  1319. { _id: updatingUserId },
  1320. { $set: { "avatar.type": newAvatar.type, "avatar.color": newAvatar.color } },
  1321. { new: true, runValidators: true },
  1322. next
  1323. );
  1324. }
  1325. ],
  1326. async err => {
  1327. if (err && err !== true) {
  1328. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1329. this.log(
  1330. "ERROR",
  1331. "UPDATE_AVATAR_TYPE",
  1332. `Couldn't update avatar type for user "${updatingUserId}" to type "${newAvatar.type}". "${err}"`
  1333. );
  1334. return cb({ status: "failure", message: err });
  1335. }
  1336. this.log(
  1337. "SUCCESS",
  1338. "UPDATE_AVATAR_TYPE",
  1339. `Updated avatar type for user "${updatingUserId}" to type "${newAvatar.type}".`
  1340. );
  1341. return cb({
  1342. status: "success",
  1343. message: "Avatar type updated successfully"
  1344. });
  1345. }
  1346. );
  1347. }),
  1348. /**
  1349. * Updates a user's role
  1350. *
  1351. * @param {object} session - the session object automatically added by socket.io
  1352. * @param {string} updatingUserId - the updating user's id
  1353. * @param {string} newRole - the new role
  1354. * @param {Function} cb - gets called with the result
  1355. */
  1356. updateRole: isAdminRequired(async function updateRole(session, updatingUserId, newRole, cb) {
  1357. newRole = newRole.toLowerCase();
  1358. const userModel = await DBModule.runJob(
  1359. "GET_MODEL",
  1360. {
  1361. modelName: "user"
  1362. },
  1363. this
  1364. );
  1365. async.waterfall(
  1366. [
  1367. next => {
  1368. userModel.findOne({ _id: updatingUserId }, next);
  1369. },
  1370. (user, next) => {
  1371. if (!user) return next("User not found.");
  1372. if (user.role === newRole) return next("New role can't be the same as the old role.");
  1373. return next();
  1374. },
  1375. next => {
  1376. userModel.updateOne(
  1377. { _id: updatingUserId },
  1378. { $set: { role: newRole } },
  1379. { runValidators: true },
  1380. next
  1381. );
  1382. }
  1383. ],
  1384. async err => {
  1385. if (err && err !== true) {
  1386. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1387. this.log(
  1388. "ERROR",
  1389. "UPDATE_ROLE",
  1390. `User "${session.userId}" couldn't update role for user "${updatingUserId}" to role "${newRole}". "${err}"`
  1391. );
  1392. return cb({ status: "failure", message: err });
  1393. }
  1394. this.log(
  1395. "SUCCESS",
  1396. "UPDATE_ROLE",
  1397. `User "${session.userId}" updated the role of user "${updatingUserId}" to role "${newRole}".`
  1398. );
  1399. return cb({
  1400. status: "success",
  1401. message: "Role successfully updated."
  1402. });
  1403. }
  1404. );
  1405. }),
  1406. /**
  1407. * Updates a user's password
  1408. *
  1409. * @param {object} session - the session object automatically added by socket.io
  1410. * @param {string} previousPassword - the previous password
  1411. * @param {string} newPassword - the new password
  1412. * @param {Function} cb - gets called with the result
  1413. */
  1414. updatePassword: isLoginRequired(async function updatePassword(session, previousPassword, newPassword, cb) {
  1415. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  1416. async.waterfall(
  1417. [
  1418. next => {
  1419. userModel.findOne({ _id: session.userId }, next);
  1420. },
  1421. (user, next) => {
  1422. if (!user.services.password) return next("This account does not have a password set.");
  1423. return next(null, user.services.password.password);
  1424. },
  1425. (storedPassword, next) => {
  1426. bcrypt.compare(sha256(previousPassword), storedPassword).then(res => {
  1427. if (res) return next();
  1428. return next("Please enter the correct previous password.");
  1429. });
  1430. },
  1431. next => {
  1432. if (!DBModule.passwordValid(newPassword))
  1433. return next("Invalid new password. Check if it meets all the requirements.");
  1434. return next();
  1435. },
  1436. next => {
  1437. bcrypt.genSalt(10, next);
  1438. },
  1439. // hash the password
  1440. (salt, next) => {
  1441. bcrypt.hash(sha256(newPassword), salt, next);
  1442. },
  1443. (hashedPassword, next) => {
  1444. userModel.updateOne(
  1445. { _id: session.userId },
  1446. {
  1447. $set: {
  1448. "services.password.password": hashedPassword
  1449. }
  1450. },
  1451. next
  1452. );
  1453. }
  1454. ],
  1455. async err => {
  1456. if (err) {
  1457. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1458. this.log(
  1459. "ERROR",
  1460. "UPDATE_PASSWORD",
  1461. `Failed updating user password of user '${session.userId}'. '${err}'.`
  1462. );
  1463. return cb({ status: "failure", message: err });
  1464. }
  1465. this.log("SUCCESS", "UPDATE_PASSWORD", `User '${session.userId}' updated their password.`);
  1466. return cb({
  1467. status: "success",
  1468. message: "Password successfully updated."
  1469. });
  1470. }
  1471. );
  1472. }),
  1473. /**
  1474. * Requests a password for a session
  1475. *
  1476. * @param {object} session - the session object automatically added by socket.io
  1477. * @param {string} email - the email of the user that requests a password reset
  1478. * @param {Function} cb - gets called with the result
  1479. */
  1480. requestPassword: isLoginRequired(async function requestPassword(session, cb) {
  1481. const code = await UtilsModule.runJob("GENERATE_RANDOM_STRING", { length: 8 }, this);
  1482. const passwordRequestSchema = await MailModule.runJob(
  1483. "GET_SCHEMA",
  1484. {
  1485. schemaName: "passwordRequest"
  1486. },
  1487. this
  1488. );
  1489. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  1490. async.waterfall(
  1491. [
  1492. next => {
  1493. userModel.findOne({ _id: session.userId }, next);
  1494. },
  1495. (user, next) => {
  1496. if (!user) return next("User not found.");
  1497. if (user.services.password && user.services.password.password)
  1498. return next("You already have a password set.");
  1499. return next(null, user);
  1500. },
  1501. (user, next) => {
  1502. const expires = new Date();
  1503. expires.setDate(expires.getDate() + 1);
  1504. userModel.findOneAndUpdate(
  1505. { "email.address": user.email.address },
  1506. {
  1507. $set: {
  1508. "services.password": {
  1509. set: { code, expires }
  1510. }
  1511. }
  1512. },
  1513. { runValidators: true },
  1514. next
  1515. );
  1516. },
  1517. (user, next) => {
  1518. passwordRequestSchema(user.email.address, user.username, code, next);
  1519. }
  1520. ],
  1521. async err => {
  1522. if (err && err !== true) {
  1523. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1524. this.log(
  1525. "ERROR",
  1526. "REQUEST_PASSWORD",
  1527. `UserId '${session.userId}' failed to request password. '${err}'`
  1528. );
  1529. return cb({ status: "failure", message: err });
  1530. }
  1531. this.log(
  1532. "SUCCESS",
  1533. "REQUEST_PASSWORD",
  1534. `UserId '${session.userId}' successfully requested a password.`
  1535. );
  1536. return cb({
  1537. status: "success",
  1538. message: "Successfully requested password."
  1539. });
  1540. }
  1541. );
  1542. }),
  1543. /**
  1544. * Verifies a password code
  1545. *
  1546. * @param {object} session - the session object automatically added by socket.io
  1547. * @param {string} code - the password code
  1548. * @param {Function} cb - gets called with the result
  1549. */
  1550. verifyPasswordCode: isLoginRequired(async function verifyPasswordCode(session, code, cb) {
  1551. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  1552. async.waterfall(
  1553. [
  1554. next => {
  1555. if (!code || typeof code !== "string") return next("Invalid code.");
  1556. return userModel.findOne(
  1557. {
  1558. "services.password.set.code": code,
  1559. _id: session.userId
  1560. },
  1561. next
  1562. );
  1563. },
  1564. (user, next) => {
  1565. if (!user) return next("Invalid code.");
  1566. if (user.services.password.set.expires < new Date()) return next("That code has expired.");
  1567. return next(null);
  1568. }
  1569. ],
  1570. async err => {
  1571. if (err && err !== true) {
  1572. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1573. this.log("ERROR", "VERIFY_PASSWORD_CODE", `Code '${code}' failed to verify. '${err}'`);
  1574. cb({ status: "failure", message: err });
  1575. } else {
  1576. this.log("SUCCESS", "VERIFY_PASSWORD_CODE", `Code '${code}' successfully verified.`);
  1577. cb({
  1578. status: "success",
  1579. message: "Successfully verified password code."
  1580. });
  1581. }
  1582. }
  1583. );
  1584. }),
  1585. /**
  1586. * Adds a password to a user with a code
  1587. *
  1588. * @param {object} session - the session object automatically added by socket.io
  1589. * @param {string} code - the password code
  1590. * @param {string} newPassword - the new password code
  1591. * @param {Function} cb - gets called with the result
  1592. */
  1593. changePasswordWithCode: isLoginRequired(async function changePasswordWithCode(session, code, newPassword, cb) {
  1594. const userModel = await DBModule.runJob(
  1595. "GET_MODEL",
  1596. {
  1597. modelName: "user"
  1598. },
  1599. this
  1600. );
  1601. async.waterfall(
  1602. [
  1603. next => {
  1604. if (!code || typeof code !== "string") return next("Invalid code.");
  1605. return userModel.findOne({ "services.password.set.code": code }, next);
  1606. },
  1607. (user, next) => {
  1608. if (!user) return next("Invalid code.");
  1609. if (!user.services.password.set.expires > new Date()) return next("That code has expired.");
  1610. return next();
  1611. },
  1612. next => {
  1613. if (!DBModule.passwordValid(newPassword))
  1614. return next("Invalid password. Check if it meets all the requirements.");
  1615. return next();
  1616. },
  1617. next => {
  1618. bcrypt.genSalt(10, next);
  1619. },
  1620. // hash the password
  1621. (salt, next) => {
  1622. bcrypt.hash(sha256(newPassword), salt, next);
  1623. },
  1624. (hashedPassword, next) => {
  1625. userModel.updateOne(
  1626. { "services.password.set.code": code },
  1627. {
  1628. $set: {
  1629. "services.password.password": hashedPassword
  1630. },
  1631. $unset: { "services.password.set": "" }
  1632. },
  1633. { runValidators: true },
  1634. next
  1635. );
  1636. }
  1637. ],
  1638. async err => {
  1639. if (err && err !== true) {
  1640. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1641. this.log("ERROR", "ADD_PASSWORD_WITH_CODE", `Code '${code}' failed to add password. '${err}'`);
  1642. cb({ status: "failure", message: err });
  1643. } else {
  1644. this.log("SUCCESS", "ADD_PASSWORD_WITH_CODE", `Code '${code}' successfully added password.`);
  1645. CacheModule.runJob("PUB", {
  1646. channel: "user.linkPassword",
  1647. value: session.userId
  1648. });
  1649. cb({
  1650. status: "success",
  1651. message: "Successfully added password."
  1652. });
  1653. }
  1654. }
  1655. );
  1656. }),
  1657. /**
  1658. * Unlinks password from user
  1659. *
  1660. * @param {object} session - the session object automatically added by socket.io
  1661. * @param {Function} cb - gets called with the result
  1662. */
  1663. unlinkPassword: isLoginRequired(async function unlinkPassword(session, cb) {
  1664. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  1665. async.waterfall(
  1666. [
  1667. next => {
  1668. userModel.findOne({ _id: session.userId }, next);
  1669. },
  1670. (user, next) => {
  1671. if (!user) return next("Not logged in.");
  1672. if (!user.services.github || !user.services.github.id)
  1673. return next("You can't remove password login without having GitHub login.");
  1674. return userModel.updateOne({ _id: session.userId }, { $unset: { "services.password": "" } }, next);
  1675. }
  1676. ],
  1677. async err => {
  1678. if (err && err !== true) {
  1679. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1680. this.log(
  1681. "ERROR",
  1682. "UNLINK_PASSWORD",
  1683. `Unlinking password failed for userId '${session.userId}'. '${err}'`
  1684. );
  1685. cb({ status: "failure", message: err });
  1686. } else {
  1687. this.log(
  1688. "SUCCESS",
  1689. "UNLINK_PASSWORD",
  1690. `Unlinking password successful for userId '${session.userId}'.`
  1691. );
  1692. CacheModule.runJob("PUB", {
  1693. channel: "user.unlinkPassword",
  1694. value: session.userId
  1695. });
  1696. cb({
  1697. status: "success",
  1698. message: "Successfully unlinked password."
  1699. });
  1700. }
  1701. }
  1702. );
  1703. }),
  1704. /**
  1705. * Unlinks GitHub from user
  1706. *
  1707. * @param {object} session - the session object automatically added by socket.io
  1708. * @param {Function} cb - gets called with the result
  1709. */
  1710. unlinkGitHub: isLoginRequired(async function unlinkGitHub(session, cb) {
  1711. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  1712. async.waterfall(
  1713. [
  1714. next => {
  1715. userModel.findOne({ _id: session.userId }, next);
  1716. },
  1717. (user, next) => {
  1718. if (!user) return next("Not logged in.");
  1719. if (!user.services.password || !user.services.password.password)
  1720. return next("You can't remove GitHub login without having password login.");
  1721. return userModel.updateOne({ _id: session.userId }, { $unset: { "services.github": "" } }, next);
  1722. }
  1723. ],
  1724. async err => {
  1725. if (err && err !== true) {
  1726. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1727. this.log(
  1728. "ERROR",
  1729. "UNLINK_GITHUB",
  1730. `Unlinking GitHub failed for userId '${session.userId}'. '${err}'`
  1731. );
  1732. cb({ status: "failure", message: err });
  1733. } else {
  1734. this.log("SUCCESS", "UNLINK_GITHUB", `Unlinking GitHub successful for userId '${session.userId}'.`);
  1735. CacheModule.runJob("PUB", {
  1736. channel: "user.unlinkGithub",
  1737. value: session.userId
  1738. });
  1739. cb({
  1740. status: "success",
  1741. message: "Successfully unlinked GitHub."
  1742. });
  1743. }
  1744. }
  1745. );
  1746. }),
  1747. /**
  1748. * Requests a password reset for an email
  1749. *
  1750. * @param {object} session - the session object automatically added by socket.io
  1751. * @param {string} email - the email of the user that requests a password reset
  1752. * @param {Function} cb - gets called with the result
  1753. */
  1754. async requestPasswordReset(session, email, cb) {
  1755. const code = await UtilsModule.runJob("GENERATE_RANDOM_STRING", { length: 8 }, this);
  1756. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  1757. const resetPasswordRequestSchema = await MailModule.runJob(
  1758. "GET_SCHEMA",
  1759. {
  1760. schemaName: "resetPasswordRequest"
  1761. },
  1762. this
  1763. );
  1764. async.waterfall(
  1765. [
  1766. next => {
  1767. if (!email || typeof email !== "string") return next("Invalid email.");
  1768. email = email.toLowerCase();
  1769. return userModel.findOne({ "email.address": email }, next);
  1770. },
  1771. (user, next) => {
  1772. if (!user) return next("User not found.");
  1773. if (!user.services.password || !user.services.password.password)
  1774. return next("User does not have a password set, and probably uses GitHub to log in.");
  1775. return next(null, user);
  1776. },
  1777. (user, next) => {
  1778. const expires = new Date();
  1779. expires.setDate(expires.getDate() + 1);
  1780. userModel.findOneAndUpdate(
  1781. { "email.address": email },
  1782. {
  1783. $set: {
  1784. "services.password.reset": {
  1785. code,
  1786. expires
  1787. }
  1788. }
  1789. },
  1790. { runValidators: true },
  1791. next
  1792. );
  1793. },
  1794. (user, next) => {
  1795. resetPasswordRequestSchema(user.email.address, user.username, code, next);
  1796. }
  1797. ],
  1798. async err => {
  1799. if (err && err !== true) {
  1800. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1801. this.log(
  1802. "ERROR",
  1803. "REQUEST_PASSWORD_RESET",
  1804. `Email '${email}' failed to request password reset. '${err}'`
  1805. );
  1806. cb({ status: "failure", message: err });
  1807. } else {
  1808. this.log(
  1809. "SUCCESS",
  1810. "REQUEST_PASSWORD_RESET",
  1811. `Email '${email}' successfully requested a password reset.`
  1812. );
  1813. cb({
  1814. status: "success",
  1815. message: "Successfully requested password reset."
  1816. });
  1817. }
  1818. }
  1819. );
  1820. },
  1821. /**
  1822. * Verifies a reset code
  1823. *
  1824. * @param {object} session - the session object automatically added by socket.io
  1825. * @param {string} code - the password reset code
  1826. * @param {Function} cb - gets called with the result
  1827. */
  1828. async verifyPasswordResetCode(session, code, cb) {
  1829. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  1830. async.waterfall(
  1831. [
  1832. next => {
  1833. if (!code || typeof code !== "string") return next("Invalid code.");
  1834. return userModel.findOne({ "services.password.reset.code": code }, next);
  1835. },
  1836. (user, next) => {
  1837. if (!user) return next("Invalid code.");
  1838. if (!user.services.password.reset.expires > new Date()) return next("That code has expired.");
  1839. return next(null);
  1840. }
  1841. ],
  1842. async err => {
  1843. if (err && err !== true) {
  1844. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1845. this.log("ERROR", "VERIFY_PASSWORD_RESET_CODE", `Code '${code}' failed to verify. '${err}'`);
  1846. cb({ status: "failure", message: err });
  1847. } else {
  1848. this.log("SUCCESS", "VERIFY_PASSWORD_RESET_CODE", `Code '${code}' successfully verified.`);
  1849. cb({
  1850. status: "success",
  1851. message: "Successfully verified password reset code."
  1852. });
  1853. }
  1854. }
  1855. );
  1856. },
  1857. /**
  1858. * Changes a user's password with a reset code
  1859. *
  1860. * @param {object} session - the session object automatically added by socket.io
  1861. * @param {string} code - the password reset code
  1862. * @param {string} newPassword - the new password reset code
  1863. * @param {Function} cb - gets called with the result
  1864. */
  1865. async changePasswordWithResetCode(session, code, newPassword, cb) {
  1866. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  1867. async.waterfall(
  1868. [
  1869. next => {
  1870. if (!code || typeof code !== "string") return next("Invalid code.");
  1871. return userModel.findOne({ "services.password.reset.code": code }, next);
  1872. },
  1873. (user, next) => {
  1874. if (!user) return next("Invalid code.");
  1875. if (!user.services.password.reset.expires > new Date()) return next("That code has expired.");
  1876. return next();
  1877. },
  1878. next => {
  1879. if (!DBModule.passwordValid(newPassword))
  1880. return next("Invalid password. Check if it meets all the requirements.");
  1881. return next();
  1882. },
  1883. next => {
  1884. bcrypt.genSalt(10, next);
  1885. },
  1886. // hash the password
  1887. (salt, next) => {
  1888. bcrypt.hash(sha256(newPassword), salt, next);
  1889. },
  1890. (hashedPassword, next) => {
  1891. userModel.updateOne(
  1892. { "services.password.reset.code": code },
  1893. {
  1894. $set: {
  1895. "services.password.password": hashedPassword
  1896. },
  1897. $unset: { "services.password.reset": "" }
  1898. },
  1899. { runValidators: true },
  1900. next
  1901. );
  1902. }
  1903. ],
  1904. async err => {
  1905. if (err && err !== true) {
  1906. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1907. this.log(
  1908. "ERROR",
  1909. "CHANGE_PASSWORD_WITH_RESET_CODE",
  1910. `Code '${code}' failed to change password. '${err}'`
  1911. );
  1912. cb({ status: "failure", message: err });
  1913. } else {
  1914. this.log(
  1915. "SUCCESS",
  1916. "CHANGE_PASSWORD_WITH_RESET_CODE",
  1917. `Code '${code}' successfully changed password.`
  1918. );
  1919. cb({
  1920. status: "success",
  1921. message: "Successfully changed password."
  1922. });
  1923. }
  1924. }
  1925. );
  1926. },
  1927. /**
  1928. * Bans a user by userId
  1929. *
  1930. * @param {object} session - the session object automatically added by socket.io
  1931. * @param {string} value - the user id that is going to be banned
  1932. * @param {string} reason - the reason for the ban
  1933. * @param {string} expiresAt - the time the ban expires
  1934. * @param {Function} cb - gets called with the result
  1935. */
  1936. banUserById: isAdminRequired(function banUserById(session, userId, reason, expiresAt, cb) {
  1937. async.waterfall(
  1938. [
  1939. next => {
  1940. if (!userId) return next("You must provide a userId to ban.");
  1941. if (!reason) return next("You must provide a reason for the ban.");
  1942. return next();
  1943. },
  1944. next => {
  1945. if (!expiresAt || typeof expiresAt !== "string") return next("Invalid expire date.");
  1946. const date = new Date();
  1947. switch (expiresAt) {
  1948. case "1h":
  1949. expiresAt = date.setHours(date.getHours() + 1);
  1950. break;
  1951. case "12h":
  1952. expiresAt = date.setHours(date.getHours() + 12);
  1953. break;
  1954. case "1d":
  1955. expiresAt = date.setDate(date.getDate() + 1);
  1956. break;
  1957. case "1w":
  1958. expiresAt = date.setDate(date.getDate() + 7);
  1959. break;
  1960. case "1m":
  1961. expiresAt = date.setMonth(date.getMonth() + 1);
  1962. break;
  1963. case "3m":
  1964. expiresAt = date.setMonth(date.getMonth() + 3);
  1965. break;
  1966. case "6m":
  1967. expiresAt = date.setMonth(date.getMonth() + 6);
  1968. break;
  1969. case "1y":
  1970. expiresAt = date.setFullYear(date.getFullYear() + 1);
  1971. break;
  1972. case "never":
  1973. expiresAt = new Date(3093527980800000);
  1974. break;
  1975. default:
  1976. return next("Invalid expire date.");
  1977. }
  1978. return next();
  1979. },
  1980. next => {
  1981. PunishmentsModule.runJob(
  1982. "ADD_PUNISHMENT",
  1983. {
  1984. type: "banUserId",
  1985. value: userId,
  1986. reason,
  1987. expiresAt,
  1988. punishedBy: "" // needs changed
  1989. },
  1990. this
  1991. )
  1992. .then(punishment => next(null, punishment))
  1993. .catch(next);
  1994. },
  1995. (punishment, next) => {
  1996. CacheModule.runJob("PUB", {
  1997. channel: "user.ban",
  1998. value: { userId, punishment }
  1999. });
  2000. next();
  2001. }
  2002. ],
  2003. async err => {
  2004. if (err && err !== true) {
  2005. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  2006. this.log(
  2007. "ERROR",
  2008. "BAN_USER_BY_ID",
  2009. `User ${session.userId} failed to ban user ${userId} with the reason ${reason}. '${err}'`
  2010. );
  2011. cb({ status: "failure", message: err });
  2012. } else {
  2013. this.log(
  2014. "SUCCESS",
  2015. "BAN_USER_BY_ID",
  2016. `User ${session.userId} has successfully banned user ${userId} with the reason ${reason}.`
  2017. );
  2018. cb({
  2019. status: "success",
  2020. message: "Successfully banned user."
  2021. });
  2022. }
  2023. }
  2024. );
  2025. })
  2026. };