personal-setting.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. /* eslint-disable no-unused-vars */
  2. const loggerFactory = require('@alias/logger');
  3. const logger = loggerFactory('growi:routes:apiv3:personal-setting');
  4. const express = require('express');
  5. const passport = require('passport');
  6. const router = express.Router();
  7. const { body } = require('express-validator/check');
  8. const ErrorV3 = require('../../models/vo/error-apiv3');
  9. /**
  10. * @swagger
  11. * tags:
  12. * name: PsersonalSetting
  13. */
  14. /**
  15. * @swagger
  16. *
  17. * components:
  18. * schemas:
  19. * PersonalSettings:
  20. * description: personal settings
  21. * type: object
  22. * properties:
  23. * name:
  24. * type: string
  25. * email:
  26. * type: string
  27. * lang:
  28. * type: string
  29. * isEmailPublished:
  30. * type: boolean
  31. * Passwords:
  32. * description: passwords for update
  33. * type: object
  34. * properties:
  35. * oldPassword:
  36. * type: string
  37. * newPassword:
  38. * type: string
  39. * newPasswordConfirm:
  40. * type: string
  41. * AssociateUser:
  42. * description: Ldap account for associate
  43. * type: object
  44. * properties:
  45. * username:
  46. * type: string
  47. * password:
  48. * type: string
  49. * DisassociateUser:
  50. * description: Ldap account for disassociate
  51. * type: object
  52. * properties:
  53. * providerType:
  54. * type: string
  55. * accountId:
  56. * type: string
  57. */
  58. module.exports = (crowi) => {
  59. const accessTokenParser = require('../../middleware/access-token-parser')(crowi);
  60. const loginRequiredStrictly = require('../../middleware/login-required')(crowi);
  61. const csrf = require('../../middleware/csrf')(crowi);
  62. const { User, ExternalAccount } = crowi.models;
  63. const { ApiV3FormValidator } = crowi.middlewares;
  64. const validator = {
  65. personal: [
  66. body('name').isString().not().isEmpty(),
  67. body('email').isEmail(),
  68. body('lang').isString().isIn(crowi.locales),
  69. body('isEmailPublished').isBoolean(),
  70. ],
  71. imageType: [
  72. body('isGravatarEnabled').isBoolean(),
  73. ],
  74. password: [
  75. body('oldPassword').isString(),
  76. body('newPassword').isString().not().isEmpty()
  77. .isLength({ min: 6 })
  78. .withMessage('password must be at least 6 characters long'),
  79. body('newPasswordConfirm').isString().not().isEmpty()
  80. .custom((value, { req }) => {
  81. return (value === req.body.newPassword);
  82. }),
  83. ],
  84. associateLdap: [
  85. body('username').isString().not().isEmpty(),
  86. body('password').isString().not().isEmpty(),
  87. ],
  88. disassociateLdap: [
  89. body('providerType').isString().not().isEmpty(),
  90. body('accountId').isString().not().isEmpty(),
  91. ],
  92. };
  93. /**
  94. * @swagger
  95. *
  96. * /personal-setting:
  97. * get:
  98. * tags: [PersonalSetting]
  99. * operationId: getPersonalSetting
  100. * summary: /personal-setting
  101. * description: Get personal parameters
  102. * responses:
  103. * 200:
  104. * description: params of personal
  105. * content:
  106. * application/json:
  107. * schema:
  108. * properties:
  109. * currentUser:
  110. * type: object
  111. * description: personal params
  112. */
  113. router.get('/', accessTokenParser, loginRequiredStrictly, async (req, res) => {
  114. const currentUser = await User.findUserByUsername(req.user.username);
  115. return res.apiv3({ currentUser });
  116. });
  117. /**
  118. * @swagger
  119. *
  120. * /personal-setting:
  121. * put:
  122. * tags: [PersonalSetting]
  123. * operationId: updatePersonalSetting
  124. * summary: /personal-setting
  125. * description: Update personal setting
  126. * requestBody:
  127. * required: true
  128. * content:
  129. * application/json:
  130. * schema:
  131. * $ref: '#/components/schemas/PersonalSettings'
  132. * responses:
  133. * 200:
  134. * description: params of personal
  135. * content:
  136. * application/json:
  137. * schema:
  138. * properties:
  139. * currentUser:
  140. * type: object
  141. * description: personal params
  142. */
  143. router.put('/', accessTokenParser, loginRequiredStrictly, csrf, validator.personal, ApiV3FormValidator, async (req, res) => {
  144. try {
  145. const user = await User.findOne({ _id: req.user.id });
  146. user.name = req.body.name;
  147. user.email = req.body.email;
  148. user.lang = req.body.lang;
  149. user.isEmailPublished = req.body.isEmailPublished;
  150. const updatedUser = await user.save();
  151. req.i18n.changeLanguage(req.body.lang);
  152. return res.apiv3({ updatedUser });
  153. } catch (err) {
  154. logger.error(err);
  155. return res.apiv3Err('update-personal-settings-failed');
  156. }
  157. });
  158. /**
  159. * @swagger
  160. *
  161. * /personal-setting/image-type:
  162. * put:
  163. * tags: [PersonalSetting]
  164. * operationId: putUserImageType
  165. * summary: /personal-setting/image-type
  166. * description: Update user image type
  167. * responses:
  168. * 200:
  169. * description: succeded to update user image type
  170. * content:
  171. * application/json:
  172. * schema:
  173. * properties:
  174. * userData:
  175. * type: object
  176. * description: user data
  177. */
  178. router.put('/image-type', accessTokenParser, loginRequiredStrictly, csrf, validator.imageType, ApiV3FormValidator, async (req, res) => {
  179. const { isGravatarEnabled } = req.body;
  180. try {
  181. const userData = await req.user.updateIsGravatarEnabled(isGravatarEnabled);
  182. return res.apiv3({ userData });
  183. } catch (err) {
  184. logger.error(err);
  185. return res.apiv3Err('update-personal-settings-failed');
  186. }
  187. });
  188. /**
  189. * @swagger
  190. *
  191. * /personal-setting/external-accounts:
  192. * get:
  193. * tags: [PersonalSetting]
  194. * operationId: getExternalAccounts
  195. * summary: /personal-setting/external-accounts
  196. * description: Get external accounts that linked current user
  197. * responses:
  198. * 200:
  199. * description: external accounts
  200. * content:
  201. * application/json:
  202. * schema:
  203. * properties:
  204. * externalAccounts:
  205. * type: object
  206. * description: array of external accounts
  207. */
  208. router.get('/external-accounts', accessTokenParser, loginRequiredStrictly, async (req, res) => {
  209. const userData = req.user;
  210. try {
  211. const externalAccounts = await ExternalAccount.find({ user: userData });
  212. return res.apiv3({ externalAccounts });
  213. } catch (err) {
  214. logger.error(err);
  215. return res.apiv3Err('get-external-accounts-failed');
  216. }
  217. });
  218. /**
  219. * @swagger
  220. *
  221. * /personal-setting/password:
  222. * put:
  223. * tags: [PersonalSetting]
  224. * operationId: putUserPassword
  225. * summary: /personal-setting/password
  226. * description: Update user password
  227. * requestBody:
  228. * required: true
  229. * content:
  230. * application/json:
  231. * schema:
  232. * $ref: '#/components/schemas/Passwords'
  233. * responses:
  234. * 200:
  235. * description: user password
  236. * content:
  237. * application/json:
  238. * schema:
  239. * properties:
  240. * userData:
  241. * type: object
  242. * description: user data updated
  243. */
  244. router.put('/password', accessTokenParser, loginRequiredStrictly, csrf, validator.password, ApiV3FormValidator, async (req, res) => {
  245. const { body, user } = req;
  246. const { oldPassword, newPassword } = body;
  247. if (user.isPasswordSet() && !user.isPasswordValid(oldPassword)) {
  248. return res.apiv3Err('wrong-current-password', 400);
  249. }
  250. try {
  251. const userData = await user.updatePassword(newPassword);
  252. return res.apiv3({ userData });
  253. } catch (err) {
  254. logger.error(err);
  255. return res.apiv3Err('update-password-failed');
  256. }
  257. });
  258. /**
  259. * @swagger
  260. *
  261. * /personal-setting/api-token:
  262. * put:
  263. * tags: [PersonalSetting]
  264. * operationId: putUserApiToken
  265. * summary: /personal-setting/api-token
  266. * description: Update user api token
  267. * responses:
  268. * 200:
  269. * description: succeded to update user api token
  270. * content:
  271. * application/json:
  272. * schema:
  273. * properties:
  274. * userData:
  275. * type: object
  276. * description: user data
  277. */
  278. router.put('/api-token', loginRequiredStrictly, csrf, async (req, res) => {
  279. const { user } = req;
  280. try {
  281. const userData = await user.updateApiToken();
  282. return res.apiv3({ userData });
  283. } catch (err) {
  284. logger.error(err);
  285. return res.apiv3Err('update-api-token-failed');
  286. }
  287. });
  288. /**
  289. * @swagger
  290. *
  291. * /personal-setting/associate-ldap:
  292. * put:
  293. * tags: [PersonalSetting]
  294. * operationId: associateLdapAccount
  295. * summary: /personal-setting/associate-ldap
  296. * description: associate Ldap account
  297. * requestBody:
  298. * required: true
  299. * content:
  300. * application/json:
  301. * schema:
  302. * $ref: '#/components/schemas/AssociateUser'
  303. * responses:
  304. * 200:
  305. * description: succeded to associate Ldap account
  306. * content:
  307. * application/json:
  308. * schema:
  309. * properties:
  310. * associateUser:
  311. * type: object
  312. * description: Ldap account associate to me
  313. */
  314. router.put('/associate-ldap', accessTokenParser, loginRequiredStrictly, csrf, validator.associateLdap, ApiV3FormValidator, async (req, res) => {
  315. const { passportService } = crowi;
  316. const { user, body } = req;
  317. const { username } = body;
  318. if (!passportService.isLdapStrategySetup) {
  319. logger.error('LdapStrategy has not been set up');
  320. return res.apiv3Err('associate-ldap-account-failed', 405);
  321. }
  322. try {
  323. await passport.authenticate('ldapauth');
  324. const associateUser = await ExternalAccount.associate('ldap', username, user);
  325. return res.apiv3({ associateUser });
  326. } catch (err) {
  327. logger.error(err);
  328. return res.apiv3Err('associate-ldap-account-failed');
  329. }
  330. });
  331. /**
  332. * @swagger
  333. *
  334. * /personal-setting/disassociate-ldap:
  335. * put:
  336. * tags: [PersonalSetting]
  337. * operationId: disassociateLdapAccount
  338. * summary: /personal-setting/disassociate-ldap
  339. * description: disassociate Ldap account
  340. * requestBody:
  341. * required: true
  342. * content:
  343. * application/json:
  344. * schema:
  345. * $ref: '#/components/schemas/DisassociateUser'
  346. * responses:
  347. * 200:
  348. * description: succeded to disassociate Ldap account
  349. * content:
  350. * application/json:
  351. * schema:
  352. * properties:
  353. * disassociateUser:
  354. * type: object
  355. * description: Ldap account disassociate to me
  356. */
  357. router.put('/disassociate-ldap', accessTokenParser, loginRequiredStrictly, csrf, validator.disassociateLdap, ApiV3FormValidator, async (req, res) => {
  358. const { user, body } = req;
  359. const { providerType, accountId } = body;
  360. try {
  361. const count = await ExternalAccount.count({ user });
  362. // make sure password set or this user has two or more ExternalAccounts
  363. if (user.password == null && count <= 1) {
  364. return res.apiv3Err('disassociate-ldap-account-failed');
  365. }
  366. const disassociateUser = await ExternalAccount.findOneAndRemove({ providerType, accountId, user });
  367. return res.apiv3({ disassociateUser });
  368. } catch (err) {
  369. logger.error(err);
  370. return res.apiv3Err('disassociate-ldap-account-failed');
  371. }
  372. });
  373. return router;
  374. };