users.js 19 KB

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