users.js 21 KB

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