users.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  1. const loggerFactory = require('@alias/logger');
  2. const logger = loggerFactory('growi:routes:apiv3:user-group');
  3. const express = require('express');
  4. const router = express.Router();
  5. const path = require('path');
  6. const { body, query } = require('express-validator');
  7. const { isEmail } = require('validator');
  8. const { serializeUserSecurely } = require('../../models/serializers/user-serializer');
  9. const ErrorV3 = require('../../models/vo/error-apiv3');
  10. const PAGE_ITEMS = 50;
  11. const validator = {};
  12. /**
  13. * @swagger
  14. * tags:
  15. * name: Users
  16. */
  17. /**
  18. * @swagger
  19. *
  20. * components:
  21. * schemas:
  22. * User:
  23. * description: User
  24. * type: object
  25. * properties:
  26. * _id:
  27. * type: string
  28. * description: user ID
  29. * example: 5ae5fccfc5577b0004dbd8ab
  30. * lang:
  31. * type: string
  32. * description: language
  33. * example: 'en_US'
  34. * status:
  35. * type: integer
  36. * description: status
  37. * example: 0
  38. * admin:
  39. * type: boolean
  40. * description: whether the admin
  41. * example: false
  42. * email:
  43. * type: string
  44. * description: E-Mail address
  45. * example: alice@aaa.aaa
  46. * username:
  47. * type: string
  48. * description: username
  49. * example: alice
  50. * name:
  51. * type: string
  52. * description: full name
  53. * example: Alice
  54. * createdAt:
  55. * type: string
  56. * description: date created at
  57. * example: 2010-01-01T00:00:00.000Z
  58. */
  59. module.exports = (crowi) => {
  60. const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
  61. const loginRequired = require('../../middlewares/login-required')(crowi, true);
  62. const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
  63. const adminRequired = require('../../middlewares/admin-required')(crowi);
  64. const csrf = require('../../middlewares/csrf')(crowi);
  65. const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
  66. const {
  67. User,
  68. Page,
  69. ExternalAccount,
  70. UserGroupRelation,
  71. } = crowi.models;
  72. const statusNo = {
  73. registered: User.STATUS_REGISTERED,
  74. active: User.STATUS_ACTIVE,
  75. suspended: User.STATUS_SUSPENDED,
  76. invited: User.STATUS_INVITED,
  77. };
  78. validator.statusList = [
  79. query('selectedStatusList').if(value => value != null).custom((value, { req }) => {
  80. const { user } = req;
  81. if (user != null && user.admin) {
  82. return value;
  83. }
  84. throw new Error('the param \'selectedStatusList\' is not allowed to use by the users except administrators');
  85. }),
  86. // validate sortOrder : asc or desc
  87. query('sortOrder').isIn(['asc', 'desc']),
  88. // validate sort : what column you will sort
  89. query('sort').isIn(['id', 'status', 'username', 'name', 'email', 'createdAt', 'lastLoginAt']),
  90. query('page').isInt({ min: 1 }),
  91. query('forceIncludeAttributes').toArray().custom((value, { req }) => {
  92. // only the admin user can specify forceIncludeAttributes
  93. if (value.length === 0) {
  94. return true;
  95. }
  96. return req.user.admin;
  97. }),
  98. ];
  99. validator.recentCreatedByUser = [
  100. query('limit').if(value => value != null).isInt({ max: 300 }).withMessage('You should set less than 300 or not to set limit.'),
  101. ];
  102. const sendEmailByUserList = async(userList) => {
  103. const { appService, mailService } = crowi;
  104. const appTitle = appService.getAppTitle();
  105. const failedToSendEmailList = [];
  106. for (const user of userList) {
  107. try {
  108. // eslint-disable-next-line no-await-in-loop
  109. await mailService.send({
  110. to: user.email,
  111. subject: `Invitation to ${appTitle}`,
  112. template: path.join(crowi.localeDir, 'en_US/admin/userInvitation.txt'),
  113. vars: {
  114. email: user.email,
  115. password: user.password,
  116. url: crowi.appService.getSiteUrl(),
  117. appTitle,
  118. },
  119. });
  120. // eslint-disable-next-line no-await-in-loop
  121. await User.updateInvitationEmailSended(user.user.id);
  122. }
  123. catch (err) {
  124. logger.error(err);
  125. failedToSendEmailList.push({
  126. email: user.email,
  127. reason: err.message,
  128. });
  129. }
  130. }
  131. return { failedToSendEmailList };
  132. };
  133. /**
  134. * @swagger
  135. *
  136. * paths:
  137. * /users:
  138. * get:
  139. * tags: [Users]
  140. * operationId: listUsers
  141. * summary: /users
  142. * description: Select selected columns from users order by asc or desc
  143. * parameters:
  144. * - name: page
  145. * in: query
  146. * description: page number
  147. * schema:
  148. * type: number
  149. * - name: selectedStatusList
  150. * in: query
  151. * description: status list
  152. * schema:
  153. * type: string
  154. * - name: searchText
  155. * in: query
  156. * description: For incremental search value from input box
  157. * schema:
  158. * type: string
  159. * - name: sortOrder
  160. * in: query
  161. * description: asc or desc
  162. * schema:
  163. * type: string
  164. * - name: sort
  165. * in: query
  166. * description: sorting column
  167. * schema:
  168. * type: string
  169. * responses:
  170. * 200:
  171. * description: users are fetched
  172. * content:
  173. * application/json:
  174. * schema:
  175. * properties:
  176. * paginateResult:
  177. * $ref: '#/components/schemas/PaginateResult'
  178. */
  179. router.get('/', accessTokenParser, loginRequired, validator.statusList, apiV3FormValidator, async(req, res) => {
  180. const page = parseInt(req.query.page) || 1;
  181. // status
  182. const { forceIncludeAttributes } = req.query;
  183. const selectedStatusList = req.query.selectedStatusList || ['active'];
  184. const statusNoList = (selectedStatusList.includes('all')) ? Object.values(statusNo) : selectedStatusList.map(element => statusNo[element]);
  185. // Search from input
  186. const searchText = req.query.searchText || '';
  187. const searchWord = new RegExp(`${searchText}`);
  188. // Sort
  189. const { sort, sortOrder } = req.query;
  190. const sortOutput = {
  191. [sort]: (sortOrder === 'desc') ? -1 : 1,
  192. };
  193. // For more information about the external specification of the User API, see here (https://dev.growi.org/5fd7466a31d89500488248e3)
  194. const orConditions = [
  195. { name: { $in: searchWord } },
  196. { username: { $in: searchWord } },
  197. ];
  198. const query = {
  199. $and: [
  200. { status: { $in: statusNoList } },
  201. {
  202. $or: orConditions,
  203. },
  204. ],
  205. };
  206. try {
  207. if (req.user != null) {
  208. orConditions.push(
  209. {
  210. $and: [
  211. { isEmailPublished: true },
  212. { email: { $in: searchWord } },
  213. ],
  214. },
  215. );
  216. }
  217. if (forceIncludeAttributes.includes('email')) {
  218. orConditions.push({ email: { $in: searchWord } });
  219. }
  220. const paginateResult = await User.paginate(
  221. query,
  222. {
  223. sort: sortOutput,
  224. page,
  225. limit: PAGE_ITEMS,
  226. },
  227. );
  228. paginateResult.docs = paginateResult.docs.map((doc) => {
  229. // return email only when specified by query
  230. const { email } = doc;
  231. const user = serializeUserSecurely(doc);
  232. if (forceIncludeAttributes.includes('email')) {
  233. user.email = email;
  234. }
  235. return user;
  236. });
  237. return res.apiv3({ paginateResult });
  238. }
  239. catch (err) {
  240. const msg = 'Error occurred in fetching user group list';
  241. logger.error('Error', err);
  242. return res.apiv3Err(new ErrorV3(msg, 'user-group-list-fetch-failed'), 500);
  243. }
  244. });
  245. /**
  246. * @swagger
  247. *
  248. * paths:
  249. * /{id}/recent:
  250. * get:
  251. * tags: [Users]
  252. * operationId: recent created page of user id
  253. * summary: /usersIdReacent
  254. * parameters:
  255. * - name: id
  256. * in: path
  257. * required: true
  258. * description: id of user
  259. * schema:
  260. * type: string
  261. * responses:
  262. * 200:
  263. * description: users recent created pages are fetched
  264. * content:
  265. * application/json:
  266. * schema:
  267. * properties:
  268. * paginateResult:
  269. * $ref: '#/components/schemas/PaginateResult'
  270. */
  271. router.get('/:id/recent', accessTokenParser, loginRequired, validator.recentCreatedByUser, apiV3FormValidator, async(req, res) => {
  272. const { id } = req.params;
  273. let user;
  274. try {
  275. user = await User.findById(id);
  276. }
  277. catch (err) {
  278. const msg = 'Error occurred in find user';
  279. logger.error('Error', err);
  280. return res.apiv3Err(new ErrorV3(msg, 'retrieve-recent-created-pages-failed'), 500);
  281. }
  282. if (user == null) {
  283. return res.apiv3Err(new ErrorV3('find-user-is-not-found'));
  284. }
  285. const limit = parseInt(req.query.limit) || await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationM') || 30;
  286. const page = req.query.page;
  287. const offset = (page - 1) * limit;
  288. const queryOptions = { offset, limit };
  289. try {
  290. const result = await Page.findListByCreator(user, req.user, queryOptions);
  291. // Delete unnecessary data about users
  292. result.pages = result.pages.map((page) => {
  293. const user = page.lastUpdateUser.toObject();
  294. page.lastUpdateUser = user;
  295. return page;
  296. });
  297. return res.apiv3(result);
  298. }
  299. catch (err) {
  300. const msg = 'Error occurred in retrieve recent created pages for user';
  301. logger.error('Error', err);
  302. return res.apiv3Err(new ErrorV3(msg, 'retrieve-recent-created-pages-failed'), 500);
  303. }
  304. });
  305. validator.inviteEmail = [
  306. // isEmail prevents line breaks, so use isString
  307. body('shapedEmailList').custom((value) => {
  308. const array = value.filter((value) => { return isEmail(value) });
  309. if (array.length === 0) {
  310. throw new Error('At least one valid email address is required');
  311. }
  312. return array;
  313. }),
  314. ];
  315. /**
  316. * @swagger
  317. *
  318. * paths:
  319. * /users/invite:
  320. * post:
  321. * tags: [Users]
  322. * operationId: inviteUser
  323. * summary: /users/invite
  324. * description: Create new users and send Emails
  325. * parameters:
  326. * - name: shapedEmailList
  327. * in: query
  328. * description: Invitation emailList
  329. * schema:
  330. * type: object
  331. * - name: sendEmail
  332. * in: query
  333. * description: Whether to send mail
  334. * schema:
  335. * type: boolean
  336. * responses:
  337. * 200:
  338. * description: Inviting user success
  339. * content:
  340. * application/json:
  341. * schema:
  342. * properties:
  343. * createdUserList:
  344. * type: object
  345. * description: Users successfully created
  346. * existingEmailList:
  347. * type: object
  348. * description: Users email that already exists
  349. * failedEmailList:
  350. * type: object
  351. * description: Users email that failed to create or send email
  352. */
  353. router.post('/invite', loginRequiredStrictly, adminRequired, csrf, validator.inviteEmail, apiV3FormValidator, async(req, res) => {
  354. // Delete duplicate email addresses
  355. const emailList = Array.from(new Set(req.body.shapedEmailList));
  356. const failedEmailList = [];
  357. // Create users
  358. const createUser = await User.createUsersByEmailList(emailList);
  359. if (createUser.failedToCreateUserEmailList.length > 0) {
  360. failedEmailList.push(createUser.failedToCreateUserEmailList);
  361. }
  362. // Send email
  363. if (req.body.sendEmail) {
  364. const sendEmail = await sendEmailByUserList(createUser.createdUserList);
  365. if (sendEmail.failedToSendEmailList.length > 0) {
  366. failedEmailList.push(sendEmail.failedToSendEmailList);
  367. }
  368. }
  369. return res.apiv3({
  370. createdUserList: createUser.createdUserList,
  371. existingEmailList: createUser.existingEmailList,
  372. failedEmailList,
  373. }, 201);
  374. });
  375. /**
  376. * @swagger
  377. *
  378. * paths:
  379. * /users/{id}/giveAdmin:
  380. * put:
  381. * tags: [Users]
  382. * operationId: giveAdminUser
  383. * summary: /users/{id}/giveAdmin
  384. * description: Give user admin
  385. * parameters:
  386. * - name: id
  387. * in: path
  388. * required: true
  389. * description: id of user for admin
  390. * schema:
  391. * type: string
  392. * responses:
  393. * 200:
  394. * description: Give user admin success
  395. * content:
  396. * application/json:
  397. * schema:
  398. * properties:
  399. * userData:
  400. * type: object
  401. * description: data of admin user
  402. */
  403. router.put('/:id/giveAdmin', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  404. const { id } = req.params;
  405. try {
  406. const userData = await User.findById(id);
  407. await userData.makeAdmin();
  408. return res.apiv3({ userData });
  409. }
  410. catch (err) {
  411. logger.error('Error', err);
  412. return res.apiv3Err(new ErrorV3(err));
  413. }
  414. });
  415. /**
  416. * @swagger
  417. *
  418. * paths:
  419. * /users/{id}/removeAdmin:
  420. * put:
  421. * tags: [Users]
  422. * operationId: removeAdminUser
  423. * summary: /users/{id}/removeAdmin
  424. * description: Remove user admin
  425. * parameters:
  426. * - name: id
  427. * in: path
  428. * required: true
  429. * description: id of user for removing admin
  430. * schema:
  431. * type: string
  432. * responses:
  433. * 200:
  434. * description: Remove user admin success
  435. * content:
  436. * application/json:
  437. * schema:
  438. * properties:
  439. * userData:
  440. * type: object
  441. * description: data of removed admin user
  442. */
  443. router.put('/:id/removeAdmin', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  444. const { id } = req.params;
  445. try {
  446. const userData = await User.findById(id);
  447. await userData.removeFromAdmin();
  448. return res.apiv3({ userData });
  449. }
  450. catch (err) {
  451. logger.error('Error', err);
  452. return res.apiv3Err(new ErrorV3(err));
  453. }
  454. });
  455. /**
  456. * @swagger
  457. *
  458. * paths:
  459. * /users/{id}/activate:
  460. * put:
  461. * tags: [Users]
  462. * operationId: activateUser
  463. * summary: /users/{id}/activate
  464. * description: Activate user
  465. * parameters:
  466. * - name: id
  467. * in: path
  468. * required: true
  469. * description: id of activate user
  470. * schema:
  471. * type: string
  472. * responses:
  473. * 200:
  474. * description: Activationg user success
  475. * content:
  476. * application/json:
  477. * schema:
  478. * properties:
  479. * userData:
  480. * type: object
  481. * description: data of activate user
  482. */
  483. router.put('/:id/activate', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  484. // check user upper limit
  485. const isUserCountExceedsUpperLimit = await User.isUserCountExceedsUpperLimit();
  486. if (isUserCountExceedsUpperLimit) {
  487. const msg = 'Unable to activate because user has reached limit';
  488. logger.error('Error', msg);
  489. return res.apiv3Err(new ErrorV3(msg));
  490. }
  491. const { id } = req.params;
  492. try {
  493. const userData = await User.findById(id);
  494. await userData.statusActivate();
  495. return res.apiv3({ userData });
  496. }
  497. catch (err) {
  498. logger.error('Error', err);
  499. return res.apiv3Err(new ErrorV3(err));
  500. }
  501. });
  502. /**
  503. * @swagger
  504. *
  505. * paths:
  506. * /users/{id}/deactivate:
  507. * put:
  508. * tags: [Users]
  509. * operationId: deactivateUser
  510. * summary: /users/{id}/deactivate
  511. * description: Deactivate user
  512. * parameters:
  513. * - name: id
  514. * in: path
  515. * required: true
  516. * description: id of deactivate user
  517. * schema:
  518. * type: string
  519. * responses:
  520. * 200:
  521. * description: Deactivationg user success
  522. * content:
  523. * application/json:
  524. * schema:
  525. * properties:
  526. * userData:
  527. * type: object
  528. * description: data of deactivate user
  529. */
  530. router.put('/:id/deactivate', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  531. const { id } = req.params;
  532. try {
  533. const userData = await User.findById(id);
  534. await userData.statusSuspend();
  535. return res.apiv3({ userData });
  536. }
  537. catch (err) {
  538. logger.error('Error', err);
  539. return res.apiv3Err(new ErrorV3(err));
  540. }
  541. });
  542. /**
  543. * @swagger
  544. *
  545. * paths:
  546. * /users/{id}/remove:
  547. * delete:
  548. * tags: [Users]
  549. * operationId: removeUser
  550. * summary: /users/{id}/remove
  551. * description: Delete user
  552. * parameters:
  553. * - name: id
  554. * in: path
  555. * required: true
  556. * description: id of delete user
  557. * schema:
  558. * type: string
  559. * responses:
  560. * 200:
  561. * description: Deleting user success
  562. * content:
  563. * application/json:
  564. * schema:
  565. * properties:
  566. * userData:
  567. * type: object
  568. * description: data of delete user
  569. */
  570. router.delete('/:id/remove', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  571. const { id } = req.params;
  572. try {
  573. const userData = await User.findById(id);
  574. await UserGroupRelation.remove({ relatedUser: userData });
  575. await userData.statusDelete();
  576. await ExternalAccount.remove({ user: userData });
  577. await Page.removeByPath(`/user/${userData.username}`);
  578. return res.apiv3({ userData });
  579. }
  580. catch (err) {
  581. logger.error('Error', err);
  582. return res.apiv3Err(new ErrorV3(err));
  583. }
  584. });
  585. /**
  586. * @swagger
  587. *
  588. * paths:
  589. * /users/external-accounts:
  590. * get:
  591. * tags: [Users]
  592. * operationId: listExternalAccountsUsers
  593. * summary: /users/external-accounts
  594. * description: Get external-account
  595. * responses:
  596. * 200:
  597. * description: external-account are fetched
  598. * content:
  599. * application/json:
  600. * schema:
  601. * properties:
  602. * paginateResult:
  603. * $ref: '#/components/schemas/PaginateResult'
  604. */
  605. router.get('/external-accounts/', loginRequiredStrictly, adminRequired, async(req, res) => {
  606. const page = parseInt(req.query.page) || 1;
  607. try {
  608. const paginateResult = await ExternalAccount.findAllWithPagination({ page });
  609. return res.apiv3({ paginateResult });
  610. }
  611. catch (err) {
  612. const msg = 'Error occurred in fetching external-account list ';
  613. logger.error(msg, err);
  614. return res.apiv3Err(new ErrorV3(msg + err.message, 'external-account-list-fetch-failed'), 500);
  615. }
  616. });
  617. /**
  618. * @swagger
  619. *
  620. * paths:
  621. * /users/external-accounts/{id}/remove:
  622. * delete:
  623. * tags: [Users]
  624. * operationId: removeExternalAccountUser
  625. * summary: /users/external-accounts/{id}/remove
  626. * description: Delete ExternalAccount
  627. * parameters:
  628. * - name: id
  629. * in: path
  630. * required: true
  631. * description: id of ExternalAccount
  632. * schema:
  633. * type: string
  634. * responses:
  635. * 200:
  636. * description: External Account is removed
  637. * content:
  638. * application/json:
  639. * schema:
  640. * properties:
  641. * externalAccount:
  642. * type: object
  643. * description: A result of `ExtenralAccount.findByIdAndRemove`
  644. */
  645. router.delete('/external-accounts/:id/remove', loginRequiredStrictly, adminRequired, apiV3FormValidator, async(req, res) => {
  646. const { id } = req.params;
  647. try {
  648. const externalAccount = await ExternalAccount.findByIdAndRemove(id);
  649. return res.apiv3({ externalAccount });
  650. }
  651. catch (err) {
  652. const msg = 'Error occurred in deleting a external account ';
  653. logger.error(msg, err);
  654. return res.apiv3Err(new ErrorV3(msg + err.message, 'extenral-account-delete-failed'));
  655. }
  656. });
  657. /**
  658. * @swagger
  659. *
  660. * paths:
  661. * /users/update.imageUrlCache:
  662. * put:
  663. * tags: [Users]
  664. * operationId: update.imageUrlCache
  665. * summary: /users/update.imageUrlCache
  666. * description: update imageUrlCache
  667. * parameters:
  668. * - name: userIds
  669. * in: query
  670. * description: user id list
  671. * schema:
  672. * type: string
  673. * responses:
  674. * 200:
  675. * description: success creating imageUrlCached
  676. * content:
  677. * application/json:
  678. * schema:
  679. * properties:
  680. * userData:
  681. * type: object
  682. * description: users updated with imageUrlCached
  683. */
  684. router.put('/update.imageUrlCache', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  685. try {
  686. const userIds = req.body.userIds;
  687. const users = await User.find({ _id: { $in: userIds } });
  688. const requests = await Promise.all(users.map(async(user) => {
  689. return {
  690. updateOne: {
  691. filter: { _id: user._id },
  692. update: { $set: { imageUrlCached: await user.generateImageUrlCached() } },
  693. },
  694. };
  695. }));
  696. if (requests.length > 0) {
  697. await User.bulkWrite(requests);
  698. }
  699. return res.apiv3({});
  700. }
  701. catch (err) {
  702. logger.error('Error', err);
  703. return res.apiv3Err(new ErrorV3(err));
  704. }
  705. });
  706. /**
  707. * @swagger
  708. *
  709. * paths:
  710. * /users/reset-password:
  711. * put:
  712. * tags: [Users]
  713. * operationId: resetPassword
  714. * summary: /users/reset-password
  715. * description: update imageUrlCache
  716. * requestBody:
  717. * content:
  718. * application/json:
  719. * schema:
  720. * properties:
  721. * id:
  722. * type: string
  723. * description: user id for reset password
  724. * responses:
  725. * 200:
  726. * description: success resrt password
  727. * content:
  728. * application/json:
  729. * schema:
  730. * properties:
  731. * newPassword:
  732. * type: string
  733. * user:
  734. * type: object
  735. * description: Target user
  736. */
  737. router.put('/reset-password', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  738. const { id } = req.body;
  739. try {
  740. const [newPassword, user] = await Promise.all([
  741. await User.resetPasswordByRandomString(id),
  742. await User.findById(id)]);
  743. return res.apiv3({ newPassword, user });
  744. }
  745. catch (err) {
  746. logger.error('Error', err);
  747. return res.apiv3Err(new ErrorV3(err));
  748. }
  749. });
  750. return router;
  751. };