admin.js 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471
  1. module.exports = function (crowi, app) {
  2. 'use strict';
  3. var debug = require('debug')('growi:routes:admin'),
  4. logger = require('@alias/logger')('growi:routes:admin'),
  5. fs = require('fs'),
  6. models = crowi.models,
  7. Page = models.Page,
  8. PageGroupRelation = models.PageGroupRelation,
  9. User = models.User,
  10. ExternalAccount = models.ExternalAccount,
  11. UserGroup = models.UserGroup,
  12. UserGroupRelation = models.UserGroupRelation,
  13. Config = models.Config,
  14. GlobalNotificationSetting = models.GlobalNotificationSetting,
  15. GlobalNotificationMailSetting = models.GlobalNotificationMailSetting,
  16. GlobalNotificationSlackSetting = models.GlobalNotificationSlackSetting,
  17. PluginUtils = require('../plugins/plugin-utils'),
  18. pluginUtils = new PluginUtils(),
  19. ApiResponse = require('../util/apiResponse'),
  20. recommendedXssWhiteList = require('../util/recommendedXssWhiteList'),
  21. importer = require('../util/importer')(crowi)
  22. ,
  23. MAX_PAGE_LIST = 50,
  24. actions = {};
  25. function createPager(total, limit, page, pagesCount, maxPageList) {
  26. const pager = {
  27. page: page,
  28. pagesCount: pagesCount,
  29. pages: [],
  30. total: total,
  31. previous: null,
  32. previousDots: false,
  33. next: null,
  34. nextDots: false,
  35. };
  36. if (page > 1) {
  37. pager.previous = page - 1;
  38. }
  39. if (page < pagesCount) {
  40. pager.next = page + 1;
  41. }
  42. let pagerMin = Math.max(1, Math.ceil(page - maxPageList / 2));
  43. let pagerMax = Math.min(pagesCount, Math.floor(page + maxPageList / 2));
  44. if (pagerMin === 1) {
  45. if (MAX_PAGE_LIST < pagesCount) {
  46. pagerMax = MAX_PAGE_LIST;
  47. } else {
  48. pagerMax = pagesCount;
  49. }
  50. }
  51. if (pagerMax === pagesCount) {
  52. if ((pagerMax - MAX_PAGE_LIST) < 1) {
  53. pagerMin = 1;
  54. } else {
  55. pagerMin = pagerMax - MAX_PAGE_LIST;
  56. }
  57. }
  58. pager.previousDots = null;
  59. if (pagerMin > 1) {
  60. pager.previousDots = true;
  61. }
  62. pager.nextDots = null;
  63. if (pagerMax < pagesCount) {
  64. pager.nextDots = true;
  65. }
  66. for (let i = pagerMin; i <= pagerMax; i++) {
  67. pager.pages.push(i);
  68. }
  69. return pager;
  70. }
  71. actions.index = function (req, res) {
  72. return res.render('admin/index', {
  73. plugins: pluginUtils.listPlugins(crowi.rootDir),
  74. });
  75. };
  76. // app.get('/admin/app' , admin.app.index);
  77. actions.app = {};
  78. actions.app.index = function (req, res) {
  79. var settingForm;
  80. settingForm = Config.setupCofigFormData('crowi', req.config);
  81. return res.render('admin/app', {
  82. settingForm: settingForm,
  83. });
  84. };
  85. actions.app.settingUpdate = function (req, res) {};
  86. // app.get('/admin/security' , admin.security.index);
  87. actions.security = {};
  88. actions.security.index = function (req, res) {
  89. const settingForm = Config.setupCofigFormData('crowi', req.config);
  90. return res.render('admin/security', {
  91. settingForm
  92. });
  93. };
  94. // app.get('/admin/markdown' , admin.markdown.index);
  95. actions.markdown = {};
  96. actions.markdown.index = function (req, res) {
  97. const config = crowi.getConfig();
  98. const markdownSetting = Config.setupCofigFormData('markdown', config);
  99. return res.render('admin/markdown', {
  100. markdownSetting: markdownSetting,
  101. recommendedXssWhiteList: recommendedXssWhiteList,
  102. });
  103. };
  104. // app.post('/admin/markdown/lineBreaksSetting' , admin.markdown.lineBreaksSetting);
  105. actions.markdown.lineBreaksSetting = function (req, res) {
  106. var markdownSetting = req.form.markdownSetting;
  107. req.session.markdownSetting = markdownSetting;
  108. if (req.form.isValid) {
  109. Config.updateNamespaceByArray('markdown', markdownSetting, function (err, config) {
  110. Config.updateConfigCache('markdown', config);
  111. req.session.markdownSetting = null;
  112. req.flash('successMessage', ['Successfully updated!']);
  113. return res.redirect('/admin/markdown');
  114. });
  115. } else {
  116. req.flash('errorMessage', req.form.errors);
  117. return res.redirect('/admin/markdown');
  118. }
  119. };
  120. // app.post('/admin/markdown/xss-setting' , admin.markdown.xssSetting);
  121. actions.markdown.xssSetting = function (req, res) {
  122. let xssSetting = req.form.markdownSetting;
  123. xssSetting['markdown:xss:tagWhiteList'] = stringToArray(xssSetting['markdown:xss:tagWhiteList']);
  124. xssSetting['markdown:xss:attrWhiteList'] = stringToArray(xssSetting['markdown:xss:attrWhiteList']);
  125. req.session.markdownSetting = xssSetting;
  126. if (req.form.isValid) {
  127. Config.updateNamespaceByArray('markdown', xssSetting, function (err, config) {
  128. Config.updateConfigCache('markdown', config);
  129. req.session.xssSetting = null;
  130. req.flash('successMessage', ['Successfully updated!']);
  131. return res.redirect('/admin/markdown');
  132. });
  133. } else {
  134. req.flash('errorMessage', req.form.errors);
  135. return res.redirect('/admin/markdown');
  136. }
  137. };
  138. const stringToArray = (string) => {
  139. const array = string.split(',');
  140. return array.map(item => item.trim());
  141. };
  142. // app.get('/admin/customize' , admin.customize.index);
  143. actions.customize = {};
  144. actions.customize.index = function (req, res) {
  145. var settingForm;
  146. settingForm = Config.setupCofigFormData('crowi', req.config);
  147. const highlightJsCssSelectorOptions = {
  148. 'github': {
  149. name: '[Light] GitHub',
  150. border: false
  151. },
  152. 'github-gist': {
  153. name: '[Light] GitHub Gist',
  154. border: true
  155. },
  156. 'atom-one-light': {
  157. name: '[Light] Atom One Light',
  158. border: true
  159. },
  160. 'xcode': {
  161. name: '[Light] Xcode',
  162. border: true
  163. },
  164. 'vs': {
  165. name: '[Light] Vs',
  166. border: true
  167. },
  168. 'atom-one-dark': {
  169. name: '[Dark] Atom One Dark',
  170. border: false
  171. },
  172. 'hybrid': {
  173. name: '[Dark] Hybrid',
  174. border: false
  175. },
  176. 'monokai': {
  177. name: '[Dark] Monokai',
  178. border: false
  179. },
  180. 'tomorrow-night': {
  181. name: '[Dark] Tomorrow Night',
  182. border: false
  183. },
  184. 'vs2015': {
  185. name: '[Dark] Vs 2015',
  186. border: false
  187. },
  188. };
  189. return res.render('admin/customize', {
  190. settingForm: settingForm,
  191. highlightJsCssSelectorOptions: highlightJsCssSelectorOptions
  192. });
  193. };
  194. // app.get('/admin/notification' , admin.notification.index);
  195. actions.notification = {};
  196. actions.notification.index = async (req, res) => {
  197. const config = crowi.getConfig();
  198. const UpdatePost = crowi.model('UpdatePost');
  199. let slackSetting = Config.setupCofigFormData('notification', config);
  200. const hasSlackIwhUrl = Config.hasSlackIwhUrl(config);
  201. const hasSlackToken = Config.hasSlackToken(config);
  202. if (!Config.hasSlackIwhUrl(req.config)) {
  203. slackSetting['slack:incomingWebhookUrl'] = '';
  204. }
  205. if (req.session.slackSetting) {
  206. slackSetting = req.session.slackSetting;
  207. req.session.slackSetting = null;
  208. }
  209. const globalNotifications = await GlobalNotificationSetting.findAll();
  210. const userNotifications = await UpdatePost.findAll();
  211. return res.render('admin/notification', {
  212. userNotifications,
  213. slackSetting,
  214. hasSlackIwhUrl,
  215. hasSlackToken,
  216. globalNotifications,
  217. });
  218. };
  219. // app.post('/admin/notification/slackSetting' , admin.notification.slackauth);
  220. actions.notification.slackSetting = function (req, res) {
  221. var slackSetting = req.form.slackSetting;
  222. req.session.slackSetting = slackSetting;
  223. if (req.form.isValid) {
  224. Config.updateNamespaceByArray('notification', slackSetting, function (err, config) {
  225. Config.updateConfigCache('notification', config);
  226. req.flash('successMessage', ['Successfully Updated!']);
  227. req.session.slackSetting = null;
  228. // Re-setup
  229. crowi.setupSlack().then(function () {
  230. return res.redirect('/admin/notification');
  231. });
  232. });
  233. } else {
  234. req.flash('errorMessage', req.form.errors);
  235. return res.redirect('/admin/notification');
  236. }
  237. };
  238. // app.get('/admin/notification/slackAuth' , admin.notification.slackauth);
  239. actions.notification.slackAuth = function (req, res) {
  240. const code = req.query.code;
  241. const config = crowi.getConfig();
  242. if (!code || !Config.hasSlackConfig(req.config)) {
  243. return res.redirect('/admin/notification');
  244. }
  245. const slack = crowi.slack;
  246. slack.getOauthAccessToken(code)
  247. .then(data => {
  248. debug('oauth response', data);
  249. Config.updateNamespaceByArray('notification', {
  250. 'slack:token': data.access_token
  251. }, function (err, config) {
  252. if (err) {
  253. req.flash('errorMessage', ['Failed to save access_token. Please try again.']);
  254. } else {
  255. Config.updateConfigCache('notification', config);
  256. req.flash('successMessage', ['Successfully Connected!']);
  257. }
  258. return res.redirect('/admin/notification');
  259. });
  260. }).catch(err => {
  261. debug('oauth response ERROR', err);
  262. req.flash('errorMessage', ['Failed to fetch access_token. Please do connect again.']);
  263. return res.redirect('/admin/notification');
  264. });
  265. };
  266. actions.search = {};
  267. actions.search.index = function (req, res) {
  268. return res.render('admin/search', {});
  269. };
  270. // app.post('/admin/notification/slackIwhSetting' , admin.notification.slackIwhSetting);
  271. actions.notification.slackIwhSetting = function (req, res) {
  272. var slackIwhSetting = req.form.slackIwhSetting;
  273. if (req.form.isValid) {
  274. Config.updateNamespaceByArray('notification', slackIwhSetting, function (err, config) {
  275. Config.updateConfigCache('notification', config);
  276. req.flash('successMessage', ['Successfully Updated!']);
  277. // Re-setup
  278. crowi.setupSlack().then(function () {
  279. return res.redirect('/admin/notification#slack-incoming-webhooks');
  280. });
  281. });
  282. } else {
  283. req.flash('errorMessage', req.form.errors);
  284. return res.redirect('/admin/notification#slack-incoming-webhooks');
  285. }
  286. };
  287. // app.post('/admin/notification/slackSetting/disconnect' , admin.notification.disconnectFromSlack);
  288. actions.notification.disconnectFromSlack = function (req, res) {
  289. const config = crowi.getConfig();
  290. const slack = crowi.slack;
  291. Config.updateNamespaceByArray('notification', {
  292. 'slack:token': ''
  293. }, function (err, config) {
  294. Config.updateConfigCache('notification', config);
  295. req.flash('successMessage', ['Successfully Disconnected!']);
  296. return res.redirect('/admin/notification');
  297. });
  298. };
  299. actions.globalNotification = {};
  300. actions.globalNotification.detail = async (req, res) => {
  301. const notificationSettingId = req.params.id;
  302. let renderVars = {};
  303. if (notificationSettingId) {
  304. try {
  305. renderVars.setting = await GlobalNotificationSetting.findOne({
  306. _id: notificationSettingId
  307. });
  308. } catch (err) {
  309. logger.error(`Error in finding a global notification setting with {_id: ${notificationSettingId}}`);
  310. }
  311. }
  312. return res.render('admin/global-notification-detail', renderVars);
  313. };
  314. actions.globalNotification.create = (req, res) => {
  315. const form = req.form.notificationGlobal;
  316. let setting;
  317. switch (form.notifyToType) {
  318. case 'mail':
  319. setting = new GlobalNotificationMailSetting(crowi);
  320. setting.toEmail = form.toEmail;
  321. break;
  322. // case 'slack':
  323. // setting = new GlobalNotificationSlackSetting(crowi);
  324. // setting.slackChannels = form.slackChannels;
  325. // break;
  326. default:
  327. logger.error('GlobalNotificationSetting Type Error: undefined type');
  328. req.flash('errorMessage', 'Error occurred in creating a new global notification setting: undefined notification type');
  329. return res.redirect('/admin/notification#global-notification');
  330. }
  331. setting.triggerPath = form.triggerPath;
  332. setting.triggerEvents = getNotificationEvents(form);
  333. setting.save();
  334. return res.redirect('/admin/notification#global-notification');
  335. };
  336. actions.globalNotification.update = async (req, res) => {
  337. const form = req.form.notificationGlobal;
  338. const setting = await GlobalNotificationSetting.findOne({
  339. _id: form.id
  340. });
  341. switch (form.notifyToType) {
  342. case 'mail':
  343. setting.toEmail = form.toEmail;
  344. break;
  345. // case 'slack':
  346. // setting.slackChannels = form.slackChannels;
  347. // break;
  348. default:
  349. logger.error('GlobalNotificationSetting Type Error: undefined type');
  350. req.flash('errorMessage', 'Error occurred in updating the global notification setting: undefined notification type');
  351. return res.redirect('/admin/notification#global-notification');
  352. }
  353. setting.triggerPath = form.triggerPath;
  354. setting.triggerEvents = getNotificationEvents(form);
  355. setting.save();
  356. return res.redirect('/admin/notification#global-notification');
  357. };
  358. actions.globalNotification.remove = async (req, res) => {
  359. const id = req.params.id;
  360. try {
  361. await GlobalNotificationSetting.findOneAndRemove({
  362. _id: id
  363. });
  364. return res.redirect('/admin/notification#global-notification');
  365. } catch (err) {
  366. req.flash('errorMessage', 'Error in deleting global notification setting');
  367. return res.redirect('/admin/notification#global-notification');
  368. }
  369. };
  370. const getNotificationEvents = (form) => {
  371. let triggerEvents = [];
  372. const triggerEventKeys = Object.keys(form).filter(key => key.match(/^triggerEvent/));
  373. triggerEventKeys.forEach(key => {
  374. if (form[key]) {
  375. triggerEvents.push(form[key]);
  376. }
  377. });
  378. return triggerEvents;
  379. };
  380. actions.search.buildIndex = function (req, res) {
  381. var search = crowi.getSearcher();
  382. if (!search) {
  383. return res.redirect('/admin');
  384. }
  385. return new Promise(function (resolve, reject) {
  386. search.deleteIndex()
  387. .then(function (data) {
  388. debug('Index deleted.');
  389. resolve();
  390. }).catch(function (err) {
  391. debug('Delete index Error, but if it is initialize, its ok.', err);
  392. resolve();
  393. });
  394. })
  395. .then(function () {
  396. return search.buildIndex();
  397. })
  398. .then(function (data) {
  399. if (!data.errors) {
  400. debug('Index created.');
  401. }
  402. return search.addAllPages();
  403. })
  404. .then(function (data) {
  405. if (!data.errors) {
  406. debug('Data is successfully indexed.');
  407. req.flash('successMessage', 'Data is successfully indexed.');
  408. } else {
  409. debug('Data index error.', data.errors);
  410. req.flash('errorMessage', `Data index error: ${data.errors}`);
  411. }
  412. return res.redirect('/admin/search');
  413. })
  414. .catch(function (err) {
  415. debug('Error', err);
  416. req.flash('errorMessage', `Error: ${err}`);
  417. return res.redirect('/admin/search');
  418. });
  419. };
  420. actions.user = {};
  421. actions.user.index = function (req, res) {
  422. var page = parseInt(req.query.page) || 1;
  423. User.findUsersWithPagination({
  424. page: page
  425. }, function (err, result) {
  426. const pager = createPager(result.total, result.limit, result.page, result.pages, MAX_PAGE_LIST);
  427. return res.render('admin/users', {
  428. users: result.docs,
  429. pager: pager
  430. });
  431. });
  432. };
  433. actions.user.invite = function (req, res) {
  434. var form = req.form.inviteForm;
  435. var toSendEmail = form.sendEmail || false;
  436. if (req.form.isValid) {
  437. User.createUsersByInvitation(form.emailList.split('\n'), toSendEmail, function (err, userList) {
  438. if (err) {
  439. req.flash('errorMessage', req.form.errors.join('\n'));
  440. } else {
  441. req.flash('createdUser', userList);
  442. }
  443. return res.redirect('/admin/users');
  444. });
  445. } else {
  446. req.flash('errorMessage', req.form.errors.join('\n'));
  447. return res.redirect('/admin/users');
  448. }
  449. };
  450. actions.user.makeAdmin = function (req, res) {
  451. var id = req.params.id;
  452. User.findById(id, function (err, userData) {
  453. userData.makeAdmin(function (err, userData) {
  454. if (err === null) {
  455. req.flash('successMessage', userData.name + 'さんのアカウントを管理者に設定しました。');
  456. } else {
  457. req.flash('errorMessage', '更新に失敗しました。');
  458. debug(err, userData);
  459. }
  460. return res.redirect('/admin/users');
  461. });
  462. });
  463. };
  464. actions.user.removeFromAdmin = function (req, res) {
  465. var id = req.params.id;
  466. User.findById(id, function (err, userData) {
  467. userData.removeFromAdmin(function (err, userData) {
  468. if (err === null) {
  469. req.flash('successMessage', userData.name + 'さんのアカウントを管理者から外しました。');
  470. } else {
  471. req.flash('errorMessage', '更新に失敗しました。');
  472. debug(err, userData);
  473. }
  474. return res.redirect('/admin/users');
  475. });
  476. });
  477. };
  478. actions.user.activate = function (req, res) {
  479. var id = req.params.id;
  480. User.findById(id, function (err, userData) {
  481. userData.statusActivate(function (err, userData) {
  482. if (err === null) {
  483. req.flash('successMessage', userData.name + 'さんのアカウントを有効化しました');
  484. } else {
  485. req.flash('errorMessage', '更新に失敗しました。');
  486. debug(err, userData);
  487. }
  488. return res.redirect('/admin/users');
  489. });
  490. });
  491. };
  492. actions.user.suspend = function (req, res) {
  493. var id = req.params.id;
  494. User.findById(id, function (err, userData) {
  495. userData.statusSuspend(function (err, userData) {
  496. if (err === null) {
  497. req.flash('successMessage', userData.name + 'さんのアカウントを利用停止にしました');
  498. } else {
  499. req.flash('errorMessage', '更新に失敗しました。');
  500. debug(err, userData);
  501. }
  502. return res.redirect('/admin/users');
  503. });
  504. });
  505. };
  506. actions.user.remove = function (req, res) {
  507. const id = req.params.id;
  508. let username = '';
  509. return new Promise((resolve, reject) => {
  510. User.findById(id, (err, userData) => {
  511. username = userData.username;
  512. return resolve(userData);
  513. });
  514. })
  515. .then((userData) => {
  516. return new Promise((resolve, reject) => {
  517. userData.statusDelete((err, userData) => {
  518. if (err) {
  519. reject(err);
  520. }
  521. resolve(userData);
  522. });
  523. });
  524. })
  525. .then((userData) => {
  526. // remove all External Accounts
  527. return ExternalAccount.remove({
  528. user: userData
  529. }).then(() => userData);
  530. })
  531. .then((userData) => {
  532. return Page.removePageByPath(`/user/${username}`).then(() => userData);
  533. })
  534. .then((userData) => {
  535. req.flash('successMessage', `${username} さんのアカウントを削除しました`);
  536. return res.redirect('/admin/users');
  537. })
  538. .catch((err) => {
  539. req.flash('errorMessage', '削除に失敗しました。');
  540. return res.redirect('/admin/users');
  541. });
  542. };
  543. // これやったときの relation の挙動未確認
  544. actions.user.removeCompletely = function (req, res) {
  545. // ユーザーの物理削除
  546. var id = req.params.id;
  547. User.removeCompletelyById(id, function (err, removed) {
  548. if (err) {
  549. debug('Error while removing user.', err, id);
  550. req.flash('errorMessage', '完全な削除に失敗しました。');
  551. } else {
  552. req.flash('successMessage', '削除しました');
  553. }
  554. return res.redirect('/admin/users');
  555. });
  556. };
  557. // app.post('/_api/admin/users.resetPassword' , admin.api.usersResetPassword);
  558. actions.user.resetPassword = function (req, res) {
  559. const id = req.body.user_id;
  560. const User = crowi.model('User');
  561. User.resetPasswordByRandomString(id)
  562. .then(function (data) {
  563. data.user = User.filterToPublicFields(data.user);
  564. return res.json(ApiResponse.success(data));
  565. }).catch(function (err) {
  566. debug('Error on reseting password', err);
  567. return res.json(ApiResponse.error('Error'));
  568. });
  569. };
  570. actions.externalAccount = {};
  571. actions.externalAccount.index = function (req, res) {
  572. const page = parseInt(req.query.page) || 1;
  573. ExternalAccount.findAllWithPagination({
  574. page
  575. })
  576. .then((result) => {
  577. const pager = createPager(result.total, result.limit, result.page, result.pages, MAX_PAGE_LIST);
  578. return res.render('admin/external-accounts', {
  579. accounts: result.docs,
  580. pager: pager
  581. });
  582. });
  583. };
  584. actions.externalAccount.remove = function (req, res) {
  585. const accountId = req.params.id;
  586. ExternalAccount.findOneAndRemove({
  587. accountId
  588. })
  589. .then((result) => {
  590. if (result == null) {
  591. req.flash('errorMessage', '削除に失敗しました。');
  592. return res.redirect('/admin/users/external-accounts');
  593. } else {
  594. req.flash('successMessage', `外部アカウント '${accountId}' を削除しました`);
  595. return res.redirect('/admin/users/external-accounts');
  596. }
  597. });
  598. };
  599. actions.userGroup = {};
  600. actions.userGroup.index = function (req, res) {
  601. var page = parseInt(req.query.page) || 1;
  602. var renderVar = {
  603. userGroups: [],
  604. userGroupRelations: new Map(),
  605. pager: null,
  606. };
  607. UserGroup.findUserGroupsWithPagination({
  608. page: page
  609. })
  610. .then((result) => {
  611. const pager = createPager(result.total, result.limit, result.page, result.pages, MAX_PAGE_LIST);
  612. var userGroups = result.docs;
  613. renderVar.userGroups = userGroups;
  614. renderVar.pager = pager;
  615. return userGroups.map((userGroup) => {
  616. return new Promise((resolve, reject) => {
  617. UserGroupRelation.findAllRelationForUserGroup(userGroup)
  618. .then((relations) => {
  619. return resolve([userGroup, relations]);
  620. });
  621. });
  622. });
  623. })
  624. .then((allRelationsPromise) => {
  625. return Promise.all(allRelationsPromise);
  626. })
  627. .then((relations) => {
  628. renderVar.userGroupRelations = new Map(relations);
  629. debug('in findUserGroupsWithPagination findAllRelationForUserGroupResult', renderVar.userGroupRelations);
  630. return res.render('admin/user-groups', renderVar);
  631. })
  632. .catch(function (err) {
  633. debug('Error on find all relations', err);
  634. return res.json(ApiResponse.error('Error'));
  635. });
  636. };
  637. // グループ詳細
  638. actions.userGroup.detail = function (req, res) {
  639. const userGroupId = req.params.id;
  640. const renderVar = {
  641. userGroup: null,
  642. userGroupRelations: [],
  643. pageGroupRelations: [],
  644. notRelatedusers: []
  645. };
  646. let targetUserGroup = null;
  647. UserGroup.findOne({
  648. _id: userGroupId
  649. })
  650. .then(function (userGroup) {
  651. targetUserGroup = userGroup;
  652. if (targetUserGroup == null) {
  653. req.flash('errorMessage', 'グループがありません');
  654. throw new Error('no userGroup is exists. ', name);
  655. } else {
  656. renderVar.userGroup = targetUserGroup;
  657. return Promise.all([
  658. // get all user and group relations
  659. UserGroupRelation.findAllRelationForUserGroup(targetUserGroup),
  660. // get all page and group relations
  661. PageGroupRelation.findAllRelationForUserGroup(targetUserGroup),
  662. // get all not related users for group
  663. UserGroupRelation.findUserByNotRelatedGroup(targetUserGroup),
  664. ]);
  665. }
  666. })
  667. .then((resolves) => {
  668. renderVar.userGroupRelations = resolves[0];
  669. renderVar.pageGroupRelations = resolves[1];
  670. renderVar.notRelatedusers = resolves[2];
  671. debug('notRelatedusers', renderVar.notRelatedusers);
  672. return res.render('admin/user-group-detail', renderVar);
  673. })
  674. .catch((err) => {
  675. req.flash('errorMessage', 'ユーザグループの検索に失敗しました');
  676. debug('Error on get userGroupDetail', err);
  677. return res.redirect('/admin/user-groups');
  678. });
  679. };
  680. //グループの生成
  681. actions.userGroup.create = function (req, res) {
  682. const form = req.form.createGroupForm;
  683. if (req.form.isValid) {
  684. const userGroupName = crowi.xss.process(form.userGroupName);
  685. UserGroup.createGroupByName(userGroupName)
  686. .then((newUserGroup) => {
  687. req.flash('successMessage', newUserGroup.name);
  688. req.flash('createdUserGroup', newUserGroup);
  689. return res.redirect('/admin/user-groups');
  690. })
  691. .catch((err) => {
  692. debug('create userGroup error:', err);
  693. req.flash('errorMessage', '同じグループ名が既に存在します。');
  694. });
  695. } else {
  696. req.flash('errorMessage', req.form.errors.join('\n'));
  697. return res.redirect('/admin/user-groups');
  698. }
  699. };
  700. //
  701. actions.userGroup.update = function (req, res) {
  702. const userGroupId = req.params.userGroupId;
  703. const name = crowi.xss.process(req.body.name);
  704. UserGroup.findById(userGroupId)
  705. .then((userGroupData) => {
  706. if (userGroupData == null) {
  707. req.flash('errorMessage', 'グループの検索に失敗しました。');
  708. return new Promise();
  709. } else {
  710. // 名前存在チェック
  711. return UserGroup.isRegisterableName(name)
  712. .then((isRegisterableName) => {
  713. // 既に存在するグループ名に更新しようとした場合はエラー
  714. if (!isRegisterableName) {
  715. req.flash('errorMessage', 'グループ名が既に存在します。');
  716. } else {
  717. return userGroupData.updateName(name)
  718. .then(() => {
  719. req.flash('successMessage', 'グループ名を更新しました。');
  720. })
  721. .catch((err) => {
  722. req.flash('errorMessage', 'グループ名の更新に失敗しました。');
  723. });
  724. }
  725. });
  726. }
  727. })
  728. .then(() => {
  729. return res.redirect('/admin/user-group-detail/' + userGroupId);
  730. });
  731. };
  732. actions.userGroup.uploadGroupPicture = function (req, res) {
  733. var fileUploader = require('../util/fileUploader')(crowi, app);
  734. //var storagePlugin = new pluginService('storage');
  735. //var storage = require('../service/storage').StorageService(config);
  736. var userGroupId = req.params.userGroupId;
  737. var tmpFile = req.file || null;
  738. if (!tmpFile) {
  739. return res.json({
  740. 'status': false,
  741. 'message': 'File type error.'
  742. });
  743. }
  744. UserGroup.findById(userGroupId, function (err, userGroupData) {
  745. if (!userGroupData) {
  746. return res.json({
  747. 'status': false,
  748. 'message': 'UserGroup error.'
  749. });
  750. }
  751. var tmpPath = tmpFile.path;
  752. var filePath = UserGroup.createUserGroupPictureFilePath(userGroupData, tmpFile.filename + tmpFile.originalname);
  753. var acceptableFileType = /image\/.+/;
  754. if (!tmpFile.mimetype.match(acceptableFileType)) {
  755. return res.json({
  756. 'status': false,
  757. 'message': 'File type error. Only image files is allowed to set as user picture.',
  758. });
  759. }
  760. var tmpFileStream = fs.createReadStream(tmpPath, {
  761. flags: 'r',
  762. encoding: null,
  763. fd: null,
  764. mode: '0666',
  765. autoClose: true
  766. });
  767. fileUploader.uploadFile(filePath, tmpFile.mimetype, tmpFileStream, {})
  768. .then(function (data) {
  769. var imageUrl = fileUploader.generateUrl(filePath);
  770. userGroupData.updateImage(imageUrl)
  771. .then(() => {
  772. fs.unlink(tmpPath, function (err) {
  773. if (err) {
  774. debug('Error while deleting tmp file.', err);
  775. }
  776. return res.json({
  777. 'status': true,
  778. 'url': imageUrl,
  779. 'message': '',
  780. });
  781. });
  782. });
  783. }).catch(function (err) {
  784. debug('Uploading error', err);
  785. return res.json({
  786. 'status': false,
  787. 'message': 'Error while uploading to ',
  788. });
  789. });
  790. });
  791. };
  792. actions.userGroup.deletePicture = function (req, res) {
  793. const userGroupId = req.params.userGroupId;
  794. let userGroupName = null;
  795. UserGroup.findById(userGroupId)
  796. .then((userGroupData) => {
  797. if (userGroupData == null) {
  798. return Promise.reject();
  799. } else {
  800. userGroupName = userGroupData.name;
  801. return userGroupData.deleteImage();
  802. }
  803. })
  804. .then((updated) => {
  805. req.flash('successMessage', 'Deleted group picture');
  806. return res.redirect('/admin/user-group-detail/' + userGroupId);
  807. })
  808. .catch((err) => {
  809. debug('An error occured.', err);
  810. req.flash('errorMessage', 'Error while deleting group picture');
  811. if (userGroupName == null) {
  812. return res.redirect('/admin/user-groups/');
  813. } else {
  814. return res.redirect('/admin/user-group-detail/' + userGroupId);
  815. }
  816. });
  817. };
  818. // app.post('/_api/admin/user-group/delete' , admin.userGroup.removeCompletely);
  819. actions.userGroup.removeCompletely = function (req, res) {
  820. const id = req.body.user_group_id;
  821. const fileUploader = require('../util/fileUploader')(crowi, app);
  822. UserGroup.removeCompletelyById(id)
  823. //// TODO remove attachments
  824. // couldn't remove because filePath includes '/uploads/uploads'
  825. // Error: ENOENT: no such file or directory, unlink 'C:\dev\growi\public\uploads\uploads\userGroup\5b1df18ab69611651cc71495.png
  826. //
  827. // .then(removed => {
  828. // if (removed.image != null) {
  829. // fileUploader.deleteFile(null, removed.image);
  830. // }
  831. // })
  832. .then(() => {
  833. req.flash('successMessage', '削除しました');
  834. return res.redirect('/admin/user-groups');
  835. })
  836. .catch((err) => {
  837. debug('Error while removing userGroup.', err, id);
  838. req.flash('errorMessage', '完全な削除に失敗しました。');
  839. return res.redirect('/admin/user-groups');
  840. });
  841. };
  842. actions.userGroupRelation = {};
  843. actions.userGroupRelation.index = function (req, res) {
  844. };
  845. actions.userGroupRelation.create = function (req, res) {
  846. const User = crowi.model('User');
  847. const UserGroup = crowi.model('UserGroup');
  848. const UserGroupRelation = crowi.model('UserGroupRelation');
  849. // req params
  850. const userName = req.body.user_name;
  851. const userGroupId = req.body.user_group_id;
  852. let user = null;
  853. let userGroup = null;
  854. Promise.all([
  855. // ユーザグループをIDで検索
  856. UserGroup.findById(userGroupId),
  857. // ユーザを名前で検索
  858. User.findUserByUsername(userName),
  859. ])
  860. .then((resolves) => {
  861. userGroup = resolves[0];
  862. user = resolves[1];
  863. // Relation を作成
  864. UserGroupRelation.createRelation(userGroup, user);
  865. })
  866. .then((result) => {
  867. return res.redirect('/admin/user-group-detail/' + userGroup.id);
  868. }).catch((err) => {
  869. debug('Error on create user-group relation', err);
  870. req.flash('errorMessage', 'Error on create user-group relation');
  871. return res.redirect('/admin/user-group-detail/' + userGroup.id);
  872. });
  873. };
  874. actions.userGroupRelation.remove = function (req, res) {
  875. const UserGroupRelation = crowi.model('UserGroupRelation');
  876. const userGroupId = req.params.id;
  877. const relationId = req.params.relationId;
  878. UserGroupRelation.removeById(relationId)
  879. .then(() => {
  880. return res.redirect('/admin/user-group-detail/' + userGroupId);
  881. })
  882. .catch((err) => {
  883. debug('Error on remove user-group-relation', err);
  884. req.flash('errorMessage', 'グループのユーザ削除に失敗しました。');
  885. });
  886. };
  887. // Importer management
  888. actions.importer = {};
  889. actions.importer.index = function (req, res) {
  890. var settingForm;
  891. settingForm = Config.setupCofigFormData('crowi', req.config);
  892. return res.render('admin/importer', {
  893. settingForm: settingForm,
  894. });
  895. };
  896. actions.api = {};
  897. actions.api.appSetting = function (req, res) {
  898. var form = req.form.settingForm;
  899. if (req.form.isValid) {
  900. debug('form content', form);
  901. // mail setting ならここで validation
  902. if (form['mail:from']) {
  903. validateMailSetting(req, form, function (err, data) {
  904. debug('Error validate mail setting: ', err, data);
  905. if (err) {
  906. req.form.errors.push('SMTPを利用したテストメール送信に失敗しました。設定をみなおしてください。');
  907. return res.json({
  908. status: false,
  909. message: req.form.errors.join('\n')
  910. });
  911. }
  912. return saveSetting(req, res, form);
  913. });
  914. } else {
  915. return saveSetting(req, res, form);
  916. }
  917. } else {
  918. return res.json({
  919. status: false,
  920. message: req.form.errors.join('\n')
  921. });
  922. }
  923. };
  924. actions.api.securitySetting = function (req, res) {
  925. const form = req.form.settingForm;
  926. if (req.form.isValid) {
  927. debug('form content', form);
  928. return saveSetting(req, res, form);
  929. } else {
  930. return res.json({
  931. status: false,
  932. message: req.form.errors.join('\n')
  933. });
  934. }
  935. };
  936. actions.api.securityPassportLdapSetting = function (req, res) {
  937. var form = req.form.settingForm;
  938. if (!req.form.isValid) {
  939. return res.json({
  940. status: false,
  941. message: req.form.errors.join('\n')
  942. });
  943. }
  944. debug('form content', form);
  945. return saveSettingAsync(form)
  946. .then(() => {
  947. const config = crowi.getConfig();
  948. // reset strategy
  949. crowi.passportService.resetLdapStrategy();
  950. // setup strategy
  951. if (Config.isEnabledPassportLdap(config)) {
  952. crowi.passportService.setupLdapStrategy(true);
  953. }
  954. return;
  955. })
  956. .then(() => {
  957. res.json({
  958. status: true
  959. });
  960. });
  961. };
  962. actions.api.securityPassportGoogleSetting = async (req, res) => {
  963. const form = req.form.settingForm;
  964. if (!req.form.isValid) {
  965. return res.json({
  966. status: false,
  967. message: req.form.errors.join('\n')
  968. });
  969. }
  970. debug('form content', form);
  971. await saveSettingAsync(form);
  972. const config = await crowi.getConfig();
  973. // reset strategy
  974. await crowi.passportService.resetGoogleStrategy();
  975. // setup strategy
  976. if (Config.isEnabledPassportGoogle(config)) {
  977. try {
  978. await crowi.passportService.setupGoogleStrategy(true);
  979. } catch (err) {
  980. // reset
  981. await crowi.passportService.resetGoogleStrategy();
  982. return res.json({
  983. status: false,
  984. message: err.message
  985. });
  986. }
  987. }
  988. return res.json({
  989. status: true
  990. });
  991. };
  992. actions.api.securityPassportGitHubSetting = async (req, res) => {
  993. const form = req.form.settingForm;
  994. if (!req.form.isValid) {
  995. return res.json({
  996. status: false,
  997. message: req.form.errors.join('\n')
  998. });
  999. }
  1000. debug('form content', form);
  1001. await saveSettingAsync(form);
  1002. const config = await crowi.getConfig();
  1003. // reset strategy
  1004. await crowi.passportService.resetGitHubStrategy();
  1005. // setup strategy
  1006. if (Config.isEnabledPassportGitHub(config)) {
  1007. try {
  1008. await crowi.passportService.setupGitHubStrategy(true);
  1009. } catch (err) {
  1010. // reset
  1011. await crowi.passportService.resetGitHubStrategy();
  1012. return res.json({
  1013. status: false,
  1014. message: err.message
  1015. });
  1016. }
  1017. }
  1018. return res.json({
  1019. status: true
  1020. });
  1021. };
  1022. actions.api.securityPassportTwitterSetting = async (req, res) => {
  1023. const form = req.form.settingForm;
  1024. if (!req.form.isValid) {
  1025. return res.json({
  1026. status: false,
  1027. message: req.form.errors.join('\n')
  1028. });
  1029. }
  1030. debug('form content', form);
  1031. await saveSettingAsync(form);
  1032. const config = await crowi.getConfig();
  1033. // reset strategy
  1034. await crowi.passportService.resetTwitterStrategy();
  1035. // setup strategy
  1036. if (Config.isEnabledPassportTwitter(config)) {
  1037. try {
  1038. await crowi.passportService.setupTwitterStrategy(true);
  1039. } catch (err) {
  1040. // reset
  1041. await crowi.passportService.resetTwitterStrategy();
  1042. return res.json({
  1043. status: false,
  1044. message: err.message
  1045. });
  1046. }
  1047. }
  1048. return res.json({
  1049. status: true
  1050. });
  1051. };
  1052. actions.api.customizeSetting = function (req, res) {
  1053. const form = req.form.settingForm;
  1054. if (req.form.isValid) {
  1055. debug('form content', form);
  1056. return saveSetting(req, res, form);
  1057. } else {
  1058. return res.json({
  1059. status: false,
  1060. message: req.form.errors.join('\n')
  1061. });
  1062. }
  1063. };
  1064. actions.api.customizeSetting = function (req, res) {
  1065. const form = req.form.settingForm;
  1066. if (req.form.isValid) {
  1067. debug('form content', form);
  1068. return saveSetting(req, res, form);
  1069. } else {
  1070. return res.json({
  1071. status: false,
  1072. message: req.form.errors.join('\n')
  1073. });
  1074. }
  1075. };
  1076. // app.post('/_api/admin/notifications.add' , admin.api.notificationAdd);
  1077. actions.api.notificationAdd = function (req, res) {
  1078. var UpdatePost = crowi.model('UpdatePost');
  1079. var pathPattern = req.body.pathPattern;
  1080. var channel = req.body.channel;
  1081. debug('notification.add', pathPattern, channel);
  1082. UpdatePost.create(pathPattern, channel, req.user)
  1083. .then(function (doc) {
  1084. debug('Successfully save updatePost', doc);
  1085. // fixme: うーん
  1086. doc.creator = doc.creator._id.toString();
  1087. return res.json(ApiResponse.success({
  1088. updatePost: doc
  1089. }));
  1090. }).catch(function (err) {
  1091. debug('Failed to save updatePost', err);
  1092. return res.json(ApiResponse.error());
  1093. });
  1094. };
  1095. // app.post('/_api/admin/notifications.remove' , admin.api.notificationRemove);
  1096. actions.api.notificationRemove = function (req, res) {
  1097. var UpdatePost = crowi.model('UpdatePost');
  1098. var id = req.body.id;
  1099. UpdatePost.remove(id)
  1100. .then(function () {
  1101. debug('Successfully remove updatePost');
  1102. return res.json(ApiResponse.success({}));
  1103. }).catch(function (err) {
  1104. debug('Failed to remove updatePost', err);
  1105. return res.json(ApiResponse.error());
  1106. });
  1107. };
  1108. // app.get('/_api/admin/users.search' , admin.api.userSearch);
  1109. actions.api.usersSearch = function (req, res) {
  1110. const User = crowi.model('User');
  1111. const email = req.query.email;
  1112. User.findUsersByPartOfEmail(email, {})
  1113. .then(users => {
  1114. const result = {
  1115. data: users
  1116. };
  1117. return res.json(ApiResponse.success(result));
  1118. }).catch(err => {
  1119. return res.json(ApiResponse.error());
  1120. });
  1121. };
  1122. actions.api.toggleIsEnabledForGlobalNotification = async (req, res) => {
  1123. const id = req.query.id;
  1124. const isEnabled = (req.query.isEnabled == 'true');
  1125. try {
  1126. if (isEnabled) {
  1127. await GlobalNotificationSetting.enable(id);
  1128. } else {
  1129. await GlobalNotificationSetting.disable(id);
  1130. }
  1131. return res.json(ApiResponse.success());
  1132. } catch (err) {
  1133. return res.json(ApiResponse.error());
  1134. }
  1135. };
  1136. /**
  1137. * save settings, update config cache, and response json
  1138. *
  1139. * @param {*} req
  1140. * @param {*} res
  1141. */
  1142. actions.api.importerSetting = async (req, res) => {
  1143. const form = req.form.settingForm;
  1144. if (!req.form.isValid) {
  1145. return res.json({
  1146. status: false,
  1147. message: req.form.errors.join('\n')
  1148. });
  1149. }
  1150. await saveSetting(req, res, form);
  1151. await importer.initialize();
  1152. };
  1153. /**
  1154. * Import all posts from esa
  1155. *
  1156. * @param {*} req
  1157. * @param {*} res
  1158. */
  1159. actions.api.importDataFromEsa = async (req, res) => {
  1160. const user = req.user;
  1161. const errors = await importer.importDataFromEsa(user);
  1162. if (errors.length > 0) {
  1163. return res.json({
  1164. status: false,
  1165. message: `<br> - ${errors.join('<br> - ')}`
  1166. });
  1167. }
  1168. return res.json({
  1169. status: true
  1170. });
  1171. };
  1172. /**
  1173. * Import all posts from qiita
  1174. *
  1175. * @param {*} req
  1176. * @param {*} res
  1177. */
  1178. actions.api.importDataFromQiita = async (req, res) => {
  1179. try {
  1180. const user = req.user;
  1181. const errors = await importer.importDataFromQiita(user);
  1182. if (errors) {
  1183. return res.json({
  1184. status: false,
  1185. message: `<br> - ${errors.join('<br> - ')}`
  1186. });
  1187. }
  1188. return res.json({
  1189. status: true
  1190. });
  1191. } catch (err) {
  1192. return res.json({
  1193. status: false,
  1194. message: `${err}`
  1195. });
  1196. }
  1197. };
  1198. /**
  1199. * Test connection to esa and response result with json
  1200. *
  1201. * @param {*} req
  1202. * @param {*} res
  1203. */
  1204. actions.api.testEsaAPI = async (req, res) => {
  1205. try {
  1206. await importer.testConnectionToEsa();
  1207. return res.json({
  1208. status: true
  1209. });
  1210. } catch (err) {
  1211. return res.json({
  1212. status: false,
  1213. message: `${err}`
  1214. });
  1215. }
  1216. };
  1217. /**
  1218. * Test connection to qiita and response result with json
  1219. *
  1220. * @param {*} req
  1221. * @param {*} res
  1222. */
  1223. actions.api.testQiitaAPI = async (req, res) => {
  1224. try {
  1225. await importer.testConnectionToQiita();
  1226. return res.json({
  1227. status: true
  1228. });
  1229. } catch (err) {
  1230. return res.json({
  1231. status: false,
  1232. message: `${err}`
  1233. });
  1234. }
  1235. };
  1236. /**
  1237. * save settings, update config cache, and response json
  1238. *
  1239. * @param {any} req
  1240. * @param {any} res
  1241. * @param {any} form
  1242. */
  1243. function saveSetting(req, res, form) {
  1244. Config.updateNamespaceByArray('crowi', form, function (err, config) {
  1245. Config.updateConfigCache('crowi', config);
  1246. return res.json({
  1247. status: true
  1248. });
  1249. });
  1250. }
  1251. /**
  1252. * save settings, update config cache ONLY. (this method don't response json)
  1253. *
  1254. * @param {any} form
  1255. * @returns
  1256. */
  1257. function saveSettingAsync(form) {
  1258. return new Promise((resolve, reject) => {
  1259. Config.updateNamespaceByArray('crowi', form, (err, config) => {
  1260. if (err) {
  1261. return reject(err);
  1262. }
  1263. Config.updateConfigCache('crowi', config);
  1264. return resolve();
  1265. });
  1266. });
  1267. }
  1268. function validateMailSetting(req, form, callback) {
  1269. var mailer = crowi.mailer;
  1270. var option = {
  1271. host: form['mail:smtpHost'],
  1272. port: form['mail:smtpPort'],
  1273. };
  1274. if (form['mail:smtpUser'] && form['mail:smtpPassword']) {
  1275. option.auth = {
  1276. user: form['mail:smtpUser'],
  1277. pass: form['mail:smtpPassword'],
  1278. };
  1279. }
  1280. if (option.port === 465) {
  1281. option.secure = true;
  1282. }
  1283. var smtpClient = mailer.createSMTPClient(option);
  1284. debug('mailer setup for validate SMTP setting', smtpClient);
  1285. smtpClient.sendMail({
  1286. from: form['mail:from'],
  1287. to: req.user.email,
  1288. subject: 'Wiki管理設定のアップデートによるメール通知',
  1289. text: 'このメールは、WikiのSMTP設定のアップデートにより送信されています。'
  1290. }, callback);
  1291. }
  1292. return actions;
  1293. };