me.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. /**
  2. * @swagger
  3. *
  4. * components:
  5. * schemas:
  6. * UserGroup:
  7. * description: UserGroup
  8. * type: object
  9. * properties:
  10. * __v:
  11. * type: number
  12. * description: record version
  13. * example: 0
  14. * _id:
  15. * type: string
  16. * description: user group ID
  17. * example: 5e2d56c1e35da4004ef7e0b0
  18. * createdAt:
  19. * type: string
  20. * description: date created at
  21. * example: 2010-01-01T00:00:00.000Z
  22. */
  23. /**
  24. * @swagger
  25. *
  26. * components:
  27. * schemas:
  28. * UserGroupRelation:
  29. * description: UserGroupRelation
  30. * type: object
  31. * properties:
  32. * __v:
  33. * type: number
  34. * description: record version
  35. * example: 0
  36. * _id:
  37. * type: string
  38. * description: user group relation ID
  39. * example: 5e2d56cbe35da4004ef7e0b1
  40. * relatedGroup:
  41. * $ref: '#/components/schemas/UserGroup'
  42. * relatedUser:
  43. * $ref: '#/components/schemas/User/properties/_id'
  44. * createdAt:
  45. * type: string
  46. * description: date created at
  47. * example: 2010-01-01T00:00:00.000Z
  48. */
  49. module.exports = function(crowi, app) {
  50. const debug = require('debug')('growi:routes:me');
  51. const logger = require('@alias/logger')('growi:routes:me');
  52. const models = crowi.models;
  53. const User = models.User;
  54. const UserGroupRelation = models.UserGroupRelation;
  55. const ExternalAccount = models.ExternalAccount;
  56. const ApiResponse = require('../util/apiResponse');
  57. // , pluginService = require('../service/plugin')
  58. const actions = {};
  59. const api = {};
  60. actions.api = api;
  61. /**
  62. * @swagger
  63. *
  64. * /me/user-group-relations:
  65. * get:
  66. * tags: [Me, CrowiCompatibles]
  67. * operationId: getUserGroupRelations
  68. * summary: /me/user-group-relations
  69. * description: Get user group relations
  70. * responses:
  71. * 200:
  72. * description: Succeeded to get user group relations.
  73. * content:
  74. * application/json:
  75. * schema:
  76. * properties:
  77. * ok:
  78. * $ref: '#/components/schemas/V1Response/properties/ok'
  79. * userGroupRelations:
  80. * type: array
  81. * items:
  82. * $ref: '#/components/schemas/UserGroupRelation'
  83. * 403:
  84. * $ref: '#/components/responses/403'
  85. * 500:
  86. * $ref: '#/components/responses/500'
  87. */
  88. /**
  89. * retrieve user-group-relation documents
  90. * @param {object} req
  91. * @param {object} res
  92. */
  93. api.userGroupRelations = function(req, res) {
  94. UserGroupRelation.findAllRelationForUser(req.user)
  95. .then((userGroupRelations) => {
  96. return res.json(ApiResponse.success({ userGroupRelations }));
  97. });
  98. };
  99. actions.index = function(req, res) {
  100. const userForm = req.body.userForm;
  101. const userData = req.user;
  102. if (req.method === 'POST' && req.form.isValid) {
  103. const name = userForm.name;
  104. const email = userForm.email;
  105. const lang = userForm.lang;
  106. const isEmailPublished = userForm.isEmailPublished;
  107. /*
  108. * disabled because the system no longer allows undefined email -- 2017.10.06 Yuki Takei
  109. *
  110. if (!User.isEmailValid(email)) {
  111. req.form.errors.push('You can\'t update to that email address');
  112. return res.render('me/index', {});
  113. }
  114. */
  115. User.findOneAndUpdate(
  116. /* eslint-disable object-curly-newline */
  117. { email: userData.email }, // query
  118. { name, email, lang, isEmailPublished }, // updating data
  119. { runValidators: true, context: 'query' }, // for validation
  120. // see https://www.npmjs.com/package/mongoose-unique-validator#find--updates -- 2017.09.24 Yuki Takei
  121. /* eslint-enable object-curly-newline */
  122. (err) => {
  123. if (err) {
  124. Object.keys(err.errors).forEach((e) => {
  125. req.form.errors.push(err.errors[e].message);
  126. });
  127. return res.render('me/index', {});
  128. }
  129. req.i18n.changeLanguage(lang);
  130. req.flash('successMessage', req.t('Updated'));
  131. return res.redirect('/me');
  132. },
  133. );
  134. }
  135. else { // method GET
  136. /*
  137. * disabled because the system no longer allows undefined email -- 2017.10.06 Yuki Takei
  138. *
  139. /// そのうちこのコードはいらなくなるはず
  140. if (!userData.isEmailSet()) {
  141. req.flash('warningMessage', 'メールアドレスが設定されている必要があります');
  142. }
  143. */
  144. return res.render('me/index', {
  145. });
  146. }
  147. };
  148. actions.imagetype = function(req, res) {
  149. if (req.method !== 'POST') {
  150. // do nothing
  151. return;
  152. }
  153. if (!req.form.isValid) {
  154. req.flash('errorMessage', req.form.errors.join('\n'));
  155. return;
  156. }
  157. const imagetypeForm = req.body.imagetypeForm;
  158. const userData = req.user;
  159. const isGravatarEnabled = imagetypeForm.isGravatarEnabled;
  160. userData.updateIsGravatarEnabled(isGravatarEnabled, (err, userData) => {
  161. if (err) {
  162. /* eslint-disable no-restricted-syntax, no-prototype-builtins */
  163. for (const e in err.errors) {
  164. if (err.errors.hasOwnProperty(e)) {
  165. req.form.errors.push(err.errors[e].message);
  166. }
  167. }
  168. /* eslint-enable no-restricted-syntax, no-prototype-builtins */
  169. return res.render('me/index', {});
  170. }
  171. req.flash('successMessage', req.t('Updated'));
  172. return res.redirect('/me');
  173. });
  174. };
  175. actions.externalAccounts = {};
  176. actions.externalAccounts.list = function(req, res) {
  177. const userData = req.user;
  178. const renderVars = {};
  179. ExternalAccount.find({ user: userData })
  180. .then((externalAccounts) => {
  181. renderVars.externalAccounts = externalAccounts;
  182. return;
  183. })
  184. .then(() => {
  185. if (req.method === 'POST' && req.form.isValid) {
  186. // TODO impl
  187. return res.render('me/external-accounts', renderVars);
  188. }
  189. // method GET
  190. return res.render('me/external-accounts', renderVars);
  191. });
  192. };
  193. actions.externalAccounts.disassociate = function(req, res) {
  194. const userData = req.user;
  195. const redirectWithFlash = (type, msg) => {
  196. req.flash(type, msg);
  197. return res.redirect('/me/external-accounts');
  198. };
  199. if (req.body == null) {
  200. redirectWithFlash('errorMessage', 'Invalid form.');
  201. }
  202. // make sure password set or this user has two or more ExternalAccounts
  203. new Promise((resolve, reject) => {
  204. if (userData.password != null) {
  205. resolve(true);
  206. }
  207. else {
  208. ExternalAccount.count({ user: userData })
  209. .then((count) => {
  210. resolve(count > 1);
  211. });
  212. }
  213. })
  214. .then((isDisassociatable) => {
  215. if (!isDisassociatable) {
  216. const e = new Error();
  217. e.name = 'couldntDisassociateError';
  218. throw e;
  219. }
  220. const providerType = req.body.providerType;
  221. const accountId = req.body.accountId;
  222. return ExternalAccount.findOneAndRemove({ providerType, accountId, user: userData });
  223. })
  224. .then((account) => {
  225. if (account == null) {
  226. return redirectWithFlash('errorMessage', 'ExternalAccount not found.');
  227. }
  228. return redirectWithFlash('successMessage', 'Successfully disassociated.');
  229. })
  230. .catch((err) => {
  231. if (err) {
  232. if (err.name === 'couldntDisassociateError') {
  233. return redirectWithFlash('couldntDisassociateError', true);
  234. }
  235. return redirectWithFlash('errorMessage', err.message);
  236. }
  237. });
  238. };
  239. actions.externalAccounts.associateLdap = function(req, res) {
  240. const passport = require('passport');
  241. const passportService = crowi.passportService;
  242. const redirectWithFlash = (type, msg) => {
  243. req.flash(type, msg);
  244. return res.redirect('/me/external-accounts');
  245. };
  246. if (!passportService.isLdapStrategySetup) {
  247. debug('LdapStrategy has not been set up');
  248. return redirectWithFlash('warning', 'LdapStrategy has not been set up');
  249. }
  250. passport.authenticate('ldapauth', (err, user, info) => {
  251. if (res.headersSent) { // dirty hack -- 2017.09.25
  252. return; // cz: somehow passport.authenticate called twice when ECONNREFUSED error occurred
  253. }
  254. if (err) { // DB Error
  255. logger.error('LDAP Server Error: ', err);
  256. return redirectWithFlash('warningMessage', 'LDAP Server Error occured.');
  257. }
  258. if (info && info.message) {
  259. return redirectWithFlash('warningMessage', info.message);
  260. }
  261. if (user) {
  262. // create ExternalAccount
  263. const ldapAccountId = passportService.getLdapAccountIdFromReq(req);
  264. const user = req.user;
  265. ExternalAccount.associate('ldap', ldapAccountId, user)
  266. .then(() => {
  267. return redirectWithFlash('successMessage', 'Successfully added.');
  268. })
  269. .catch((err) => {
  270. return redirectWithFlash('errorMessage', err.message);
  271. });
  272. }
  273. })(req, res, () => {});
  274. };
  275. actions.password = function(req, res) {
  276. const passwordForm = req.body.mePassword;
  277. const userData = req.user;
  278. /*
  279. * disabled because the system no longer allows undefined email -- 2017.10.06 Yuki Takei
  280. *
  281. // パスワードを設定する前に、emailが設定されている必要がある (schemaを途中で変更したため、最初の方の人は登録されていないかもしれないため)
  282. // そのうちこのコードはいらなくなるはず
  283. if (!userData.isEmailSet()) {
  284. return res.redirect('/me');
  285. }
  286. */
  287. if (req.method === 'POST' && req.form.isValid) {
  288. const newPassword = passwordForm.newPassword;
  289. const newPasswordConfirm = passwordForm.newPasswordConfirm;
  290. const oldPassword = passwordForm.oldPassword;
  291. if (userData.isPasswordSet() && !userData.isPasswordValid(oldPassword)) {
  292. req.form.errors.push('Wrong current password');
  293. return res.render('me/password', {
  294. });
  295. }
  296. // check password confirm
  297. if (newPassword !== newPasswordConfirm) {
  298. req.form.errors.push('Failed to verify passwords');
  299. }
  300. else {
  301. userData.updatePassword(newPassword, (err, userData) => {
  302. if (err) {
  303. /* eslint-disable no-restricted-syntax, no-prototype-builtins */
  304. for (const e in err.errors) {
  305. if (err.errors.hasOwnProperty(e)) {
  306. req.form.errors.push(err.errors[e].message);
  307. }
  308. }
  309. return res.render('me/password', {});
  310. }
  311. /* eslint-enable no-restricted-syntax, no-prototype-builtins */
  312. req.flash('successMessage', 'Password updated');
  313. return res.redirect('/me/password');
  314. });
  315. }
  316. }
  317. else { // method GET
  318. return res.render('me/password', {
  319. });
  320. }
  321. };
  322. actions.apiToken = function(req, res) {
  323. const userData = req.user;
  324. if (req.method === 'POST' && req.form.isValid) {
  325. userData.updateApiToken()
  326. .then((userData) => {
  327. req.flash('successMessage', 'API Token updated');
  328. return res.redirect('/me/apiToken');
  329. })
  330. .catch((err) => {
  331. // req.flash('successMessage',);
  332. req.form.errors.push('Failed to update API Token');
  333. return res.render('me/api_token', {
  334. });
  335. });
  336. }
  337. else {
  338. return res.render('me/api_token', {
  339. });
  340. }
  341. };
  342. actions.updates = function(req, res) {
  343. res.render('me/update', {
  344. });
  345. };
  346. return actions;
  347. };