admin.js 44 KB

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