users.js 21 KB

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