users.js 16 KB

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