/* eslint-disable no-use-before-define */ module.exports = function(crowi, app) { const debug = require('debug')('growi:routes:admin'); const logger = require('@alias/logger')('growi:routes:admin'); const models = crowi.models; const Page = models.Page; const User = models.User; const ExternalAccount = models.ExternalAccount; const UserGroup = models.UserGroup; const UserGroupRelation = models.UserGroupRelation; const GlobalNotificationSetting = models.GlobalNotificationSetting; const GlobalNotificationMailSetting = models.GlobalNotificationMailSetting; const GlobalNotificationSlackSetting = models.GlobalNotificationSlackSetting; // eslint-disable-line no-unused-vars const { configManager, aclService, slackNotificationService, customizeService, } = crowi; const recommendedWhitelist = require('@commons/service/xss/recommended-whitelist'); const PluginUtils = require('../plugins/plugin-utils'); const ApiResponse = require('../util/apiResponse'); const importer = require('../util/importer')(crowi); const searchEvent = crowi.event('search'); const pluginUtils = new PluginUtils(); const MAX_PAGE_LIST = 50; const actions = {}; const { check } = require('express-validator/check'); const api = {}; function createPager(total, limit, page, pagesCount, maxPageList) { const pager = { page, pagesCount, pages: [], total, previous: null, previousDots: false, next: null, nextDots: false, }; if (page > 1) { pager.previous = page - 1; } if (page < pagesCount) { pager.next = page + 1; } let pagerMin = Math.max(1, Math.ceil(page - maxPageList / 2)); let pagerMax = Math.min(pagesCount, Math.floor(page + maxPageList / 2)); if (pagerMin === 1) { if (MAX_PAGE_LIST < pagesCount) { pagerMax = MAX_PAGE_LIST; } else { pagerMax = pagesCount; } } if (pagerMax === pagesCount) { if ((pagerMax - MAX_PAGE_LIST) < 1) { pagerMin = 1; } else { pagerMin = pagerMax - MAX_PAGE_LIST; } } pager.previousDots = null; if (pagerMin > 1) { pager.previousDots = true; } pager.nextDots = null; if (pagerMax < pagesCount) { pager.nextDots = true; } for (let i = pagerMin; i <= pagerMax; i++) { pager.pages.push(i); } return pager; } actions.index = function(req, res) { return res.render('admin/index', { plugins: pluginUtils.listPlugins(crowi.rootDir), }); }; // app.get('/admin/app' , admin.app.index); actions.app = {}; actions.app.index = function(req, res) { return res.render('admin/app'); }; actions.app.settingUpdate = function(req, res) { }; // app.get('/admin/security' , admin.security.index); actions.security = {}; actions.security.index = function(req, res) { const isWikiModeForced = aclService.isWikiModeForced(); const guestModeValue = aclService.getGuestModeValue(); return res.render('admin/security', { isWikiModeForced, guestModeValue, }); }; // app.get('/admin/markdown' , admin.markdown.index); actions.markdown = {}; actions.markdown.index = function(req, res) { const markdownSetting = configManager.getConfigByPrefix('markdown', 'markdown:'); return res.render('admin/markdown', { markdownSetting, recommendedWhitelist, }); }; // app.post('/admin/markdown/lineBreaksSetting' , admin.markdown.lineBreaksSetting); actions.markdown.lineBreaksSetting = async function(req, res) { const markdownSetting = req.form.markdownSetting; if (req.form.isValid) { await configManager.updateConfigsInTheSameNamespace('markdown', markdownSetting); req.flash('successMessage', ['Successfully updated!']); } else { req.flash('errorMessage', req.form.errors); } return res.redirect('/admin/markdown'); }; // app.post('/admin/markdown/presentationSetting' , admin.markdown.presentationSetting); actions.markdown.presentationSetting = async function(req, res) { const markdownSetting = req.form.markdownSetting; if (req.form.isValid) { await configManager.updateConfigsInTheSameNamespace('markdown', markdownSetting); req.flash('successMessage', ['Successfully updated!']); } else { req.flash('errorMessage', req.form.errors); } return res.redirect('/admin/markdown'); }; // app.post('/admin/markdown/xss-setting' , admin.markdown.xssSetting); actions.markdown.xssSetting = async function(req, res) { const xssSetting = req.form.markdownSetting; xssSetting['markdown:xss:tagWhiteList'] = csvToArray(xssSetting['markdown:xss:tagWhiteList']); xssSetting['markdown:xss:attrWhiteList'] = csvToArray(xssSetting['markdown:xss:attrWhiteList']); if (req.form.isValid) { await configManager.updateConfigsInTheSameNamespace('markdown', xssSetting); req.flash('successMessage', ['Successfully updated!']); } else { req.flash('errorMessage', req.form.errors); } return res.redirect('/admin/markdown'); }; const csvToArray = (string) => { const array = string.split(','); return array.map((item) => { return item.trim() }); }; // app.get('/admin/customize' , admin.customize.index); actions.customize = {}; actions.customize.index = function(req, res) { const settingForm = configManager.getConfigByPrefix('crowi', 'customize:'); /* eslint-disable quote-props, no-multi-spaces */ const highlightJsCssSelectorOptions = { 'github': { name: '[Light] GitHub', border: false }, 'github-gist': { name: '[Light] GitHub Gist', border: true }, 'atom-one-light': { name: '[Light] Atom One Light', border: true }, 'xcode': { name: '[Light] Xcode', border: true }, 'vs': { name: '[Light] Vs', border: true }, 'atom-one-dark': { name: '[Dark] Atom One Dark', border: false }, 'hybrid': { name: '[Dark] Hybrid', border: false }, 'monokai': { name: '[Dark] Monokai', border: false }, 'tomorrow-night': { name: '[Dark] Tomorrow Night', border: false }, 'vs2015': { name: '[Dark] Vs 2015', border: false }, }; /* eslint-enable quote-props, no-multi-spaces */ return res.render('admin/customize', { settingForm, highlightJsCssSelectorOptions, }); }; // app.get('/admin/notification' , admin.notification.index); actions.notification = {}; actions.notification.index = async(req, res) => { const UpdatePost = crowi.model('UpdatePost'); let slackSetting = configManager.getConfigByPrefix('notification', 'slack:'); const hasSlackIwhUrl = !!configManager.getConfig('notification', 'slack:incomingWebhookUrl'); const hasSlackToken = !!configManager.getConfig('notification', 'slack:token'); if (!hasSlackIwhUrl) { slackSetting['slack:incomingWebhookUrl'] = ''; } if (req.session.slackSetting) { slackSetting = req.session.slackSetting; req.session.slackSetting = null; } const globalNotifications = await GlobalNotificationSetting.findAll(); const userNotifications = await UpdatePost.findAll(); return res.render('admin/notification', { userNotifications, slackSetting, hasSlackIwhUrl, hasSlackToken, globalNotifications, }); }; // app.post('/admin/notification/slackSetting' , admin.notification.slackauth); actions.notification.slackSetting = async function(req, res) { const slackSetting = req.form.slackSetting; if (req.form.isValid) { await configManager.updateConfigsInTheSameNamespace('notification', slackSetting); req.flash('successMessage', ['Successfully Updated!']); // Re-setup crowi.setupSlack().then(() => { }); } else { req.flash('errorMessage', req.form.errors); } return res.redirect('/admin/notification'); }; // app.get('/admin/notification/slackAuth' , admin.notification.slackauth); actions.notification.slackAuth = function(req, res) { const code = req.query.code; if (!code || !slackNotificationService.hasSlackConfig()) { return res.redirect('/admin/notification'); } const slack = crowi.slack; slack.getOauthAccessToken(code) .then(async(data) => { debug('oauth response', data); try { await configManager.updateConfigsInTheSameNamespace('notification', { 'slack:token': data.access_token }); req.flash('successMessage', ['Successfully Connected!']); } catch (err) { req.flash('errorMessage', ['Failed to save access_token. Please try again.']); } return res.redirect('/admin/notification'); }) .catch((err) => { debug('oauth response ERROR', err); req.flash('errorMessage', ['Failed to fetch access_token. Please do connect again.']); return res.redirect('/admin/notification'); }); }; // app.post('/admin/notification/slackIwhSetting' , admin.notification.slackIwhSetting); actions.notification.slackIwhSetting = async function(req, res) { const slackIwhSetting = req.form.slackIwhSetting; if (req.form.isValid) { await configManager.updateConfigsInTheSameNamespace('notification', slackIwhSetting); req.flash('successMessage', ['Successfully Updated!']); // Re-setup crowi.setupSlack().then(() => { return res.redirect('/admin/notification#slack-incoming-webhooks'); }); } else { req.flash('errorMessage', req.form.errors); return res.redirect('/admin/notification#slack-incoming-webhooks'); } }; // app.post('/admin/notification/slackSetting/disconnect' , admin.notification.disconnectFromSlack); actions.notification.disconnectFromSlack = async function(req, res) { await configManager.updateConfigsInTheSameNamespace('notification', { 'slack:token': '' }); req.flash('successMessage', ['Successfully Disconnected!']); return res.redirect('/admin/notification'); }; actions.globalNotification = {}; actions.globalNotification.detail = async(req, res) => { const notificationSettingId = req.params.id; const renderVars = {}; if (notificationSettingId) { try { renderVars.setting = await GlobalNotificationSetting.findOne({ _id: notificationSettingId }); } catch (err) { logger.error(`Error in finding a global notification setting with {_id: ${notificationSettingId}}`); } } return res.render('admin/global-notification-detail', renderVars); }; actions.globalNotification.create = (req, res) => { const form = req.form.notificationGlobal; let setting; switch (form.notifyToType) { case 'mail': setting = new GlobalNotificationMailSetting(crowi); setting.toEmail = form.toEmail; break; // case 'slack': // setting = new GlobalNotificationSlackSetting(crowi); // setting.slackChannels = form.slackChannels; // break; default: logger.error('GlobalNotificationSetting Type Error: undefined type'); req.flash('errorMessage', 'Error occurred in creating a new global notification setting: undefined notification type'); return res.redirect('/admin/notification#global-notification'); } setting.triggerPath = form.triggerPath; setting.triggerEvents = getNotificationEvents(form); setting.save(); return res.redirect('/admin/notification#global-notification'); }; actions.globalNotification.update = async(req, res) => { const form = req.form.notificationGlobal; const setting = await GlobalNotificationSetting.findOne({ _id: form.id }); switch (form.notifyToType) { case 'mail': setting.toEmail = form.toEmail; break; // case 'slack': // setting.slackChannels = form.slackChannels; // break; default: logger.error('GlobalNotificationSetting Type Error: undefined type'); req.flash('errorMessage', 'Error occurred in updating the global notification setting: undefined notification type'); return res.redirect('/admin/notification#global-notification'); } setting.triggerPath = form.triggerPath; setting.triggerEvents = getNotificationEvents(form); setting.save(); return res.redirect('/admin/notification#global-notification'); }; actions.globalNotification.remove = async(req, res) => { const id = req.params.id; try { await GlobalNotificationSetting.findOneAndRemove({ _id: id }); return res.redirect('/admin/notification#global-notification'); } catch (err) { req.flash('errorMessage', 'Error in deleting global notification setting'); return res.redirect('/admin/notification#global-notification'); } }; const getNotificationEvents = (form) => { const triggerEvents = []; const triggerEventKeys = Object.keys(form).filter((key) => { return key.match(/^triggerEvent/) }); triggerEventKeys.forEach((key) => { if (form[key]) { triggerEvents.push(form[key]); } }); return triggerEvents; }; actions.search = {}; actions.search.index = function(req, res) { const search = crowi.getSearcher(); if (!search) { return res.redirect('/admin'); } return res.render('admin/search', {}); }; actions.user = {}; actions.user.index = async function(req, res) { const activeUsers = await User.countListByStatus(User.STATUS_ACTIVE); const userUpperLimit = aclService.userUpperLimit(); const isUserCountExceedsUpperLimit = await User.isUserCountExceedsUpperLimit(); const page = parseInt(req.query.page) || 1; const result = await User.findUsersWithPagination({ page, select: User.USER_PUBLIC_FIELDS, populate: User.IMAGE_POPULATION, }); const pager = createPager(result.total, result.limit, result.page, result.pages, MAX_PAGE_LIST); return res.render('admin/users', { users: result.docs, pager, activeUsers, userUpperLimit, isUserCountExceedsUpperLimit, }); }; actions.user.invite = function(req, res) { const form = req.form.inviteForm; const toSendEmail = form.sendEmail || false; if (req.form.isValid) { User.createUsersByInvitation(form.emailList.split('\n'), toSendEmail, (err, userList) => { if (err) { req.flash('errorMessage', req.form.errors.join('\n')); } else { req.flash('createdUser', userList); } return res.redirect('/admin/users'); }); } else { req.flash('errorMessage', req.form.errors.join('\n')); return res.redirect('/admin/users'); } }; actions.user.makeAdmin = function(req, res) { const id = req.params.id; User.findById(id, (err, userData) => { userData.makeAdmin((err, userData) => { if (err === null) { req.flash('successMessage', `${userData.name}さんのアカウントを管理者に設定しました。`); } else { req.flash('errorMessage', '更新に失敗しました。'); debug(err, userData); } return res.redirect('/admin/users'); }); }); }; actions.user.removeFromAdmin = function(req, res) { const id = req.params.id; User.findById(id, (err, userData) => { userData.removeFromAdmin((err, userData) => { if (err === null) { req.flash('successMessage', `${userData.name}さんのアカウントを管理者から外しました。`); } else { req.flash('errorMessage', '更新に失敗しました。'); debug(err, userData); } return res.redirect('/admin/users'); }); }); }; actions.user.activate = async function(req, res) { // check user upper limit const isUserCountExceedsUpperLimit = await User.isUserCountExceedsUpperLimit(); if (isUserCountExceedsUpperLimit) { req.flash('errorMessage', 'ユーザーが上限に達したため有効化できません。'); return res.redirect('/admin/users'); } const id = req.params.id; User.findById(id, (err, userData) => { userData.statusActivate((err, userData) => { if (err === null) { req.flash('successMessage', `${userData.name}さんのアカウントを有効化しました`); } else { req.flash('errorMessage', '更新に失敗しました。'); debug(err, userData); } return res.redirect('/admin/users'); }); }); }; actions.user.suspend = function(req, res) { const id = req.params.id; User.findById(id, (err, userData) => { userData.statusSuspend((err, userData) => { if (err === null) { req.flash('successMessage', `${userData.name}さんのアカウントを利用停止にしました`); } else { req.flash('errorMessage', '更新に失敗しました。'); debug(err, userData); } return res.redirect('/admin/users'); }); }); }; actions.user.remove = function(req, res) { const id = req.params.id; let username = ''; return new Promise((resolve, reject) => { User.findById(id, (err, userData) => { username = userData.username; return resolve(userData); }); }) .then((userData) => { return new Promise((resolve, reject) => { userData.statusDelete((err, userData) => { if (err) { reject(err); } resolve(userData); }); }); }) .then((userData) => { // remove all External Accounts return ExternalAccount.remove({ user: userData }).then(() => { return userData }); }) .then((userData) => { return Page.removeByPath(`/user/${username}`).then(() => { return userData }); }) .then((userData) => { req.flash('successMessage', `${username} さんのアカウントを削除しました`); return res.redirect('/admin/users'); }) .catch((err) => { req.flash('errorMessage', '削除に失敗しました。'); return res.redirect('/admin/users'); }); }; // これやったときの relation の挙動未確認 actions.user.removeCompletely = function(req, res) { // ユーザーの物理削除 const id = req.params.id; User.removeCompletelyById(id, (err, removed) => { if (err) { debug('Error while removing user.', err, id); req.flash('errorMessage', '完全な削除に失敗しました。'); } else { req.flash('successMessage', '削除しました'); } return res.redirect('/admin/users'); }); }; // app.post('/_api/admin/users.resetPassword' , admin.api.usersResetPassword); actions.user.resetPassword = async function(req, res) { const id = req.body.user_id; const User = crowi.model('User'); try { const newPassword = await User.resetPasswordByRandomString(id); const user = await User.findById(id); const result = { user: user.toObject(), newPassword }; return res.json(ApiResponse.success(result)); } catch (err) { debug('Error on reseting password', err); return res.json(ApiResponse.error(err)); } }; actions.externalAccount = {}; actions.externalAccount.index = function(req, res) { const page = parseInt(req.query.page) || 1; ExternalAccount.findAllWithPagination({ page }) .then((result) => { const pager = createPager(result.total, result.limit, result.page, result.pages, MAX_PAGE_LIST); return res.render('admin/external-accounts', { accounts: result.docs, pager, }); }); }; actions.externalAccount.remove = async function(req, res) { const id = req.params.id; let account = null; try { account = await ExternalAccount.findByIdAndRemove(id); if (account == null) { throw new Error('削除に失敗しました。'); } } catch (err) { req.flash('errorMessage', err.message); return res.redirect('/admin/users/external-accounts'); } req.flash('successMessage', `外部アカウント '${account.providerType}/${account.accountId}' を削除しました`); return res.redirect('/admin/users/external-accounts'); }; actions.userGroup = {}; actions.userGroup.index = function(req, res) { const page = parseInt(req.query.page) || 1; const isAclEnabled = aclService.isAclEnabled(); const renderVar = { userGroups: [], userGroupRelations: new Map(), pager: null, isAclEnabled, }; UserGroup.findUserGroupsWithPagination({ page }) .then((result) => { const pager = createPager(result.total, result.limit, result.page, result.pages, MAX_PAGE_LIST); const userGroups = result.docs; renderVar.userGroups = userGroups; renderVar.pager = pager; return userGroups.map((userGroup) => { return new Promise((resolve, reject) => { UserGroupRelation.findAllRelationForUserGroup(userGroup) .then((relations) => { return resolve({ id: userGroup._id, relatedUsers: relations.map((relation) => { return relation.relatedUser; }), }); }); }); }); }) .then((allRelationsPromise) => { return Promise.all(allRelationsPromise); }) .then((relations) => { for (const relation of relations) { renderVar.userGroupRelations[relation.id] = relation.relatedUsers; } debug('in findUserGroupsWithPagination findAllRelationForUserGroupResult', renderVar.userGroupRelations); return res.render('admin/user-groups', renderVar); }) .catch((err) => { debug('Error on find all relations', err); return res.json(ApiResponse.error('Error')); }); }; // グループ詳細 actions.userGroup.detail = async function(req, res) { const userGroupId = req.params.id; const renderVar = { userGroup: null, userGroupRelations: [], notRelatedusers: [], relatedPages: [], }; const userGroup = await UserGroup.findOne({ _id: userGroupId }); if (userGroup == null) { logger.error('no userGroup is exists. ', userGroupId); req.flash('errorMessage', 'グループがありません'); return res.redirect('/admin/user-groups'); } renderVar.userGroup = userGroup; const resolves = await Promise.all([ // get all user and group relations UserGroupRelation.findAllRelationForUserGroup(userGroup), // get all not related users for group UserGroupRelation.findUserByNotRelatedGroup(userGroup), // get all related pages Page.find({ grant: Page.GRANT_USER_GROUP, grantedGroup: { $in: [userGroup] } }), ]); renderVar.userGroupRelations = resolves[0]; renderVar.notRelatedusers = resolves[1]; renderVar.relatedPages = resolves[2]; return res.render('admin/user-group-detail', renderVar); }; // actions.userGroup.update = function(req, res) { const userGroupId = req.params.userGroupId; const name = crowi.xss.process(req.body.name); UserGroup.findById(userGroupId) .then((userGroupData) => { if (userGroupData == null) { req.flash('errorMessage', 'グループの検索に失敗しました。'); return new Promise(); } // 名前存在チェック return UserGroup.isRegisterableName(name) .then((isRegisterableName) => { // 既に存在するグループ名に更新しようとした場合はエラー if (!isRegisterableName) { req.flash('errorMessage', 'グループ名が既に存在します。'); } else { return userGroupData.updateName(name) .then(() => { req.flash('successMessage', 'グループ名を更新しました。'); }) .catch((err) => { req.flash('errorMessage', 'グループ名の更新に失敗しました。'); }); } }); }) .then(() => { return res.redirect(`/admin/user-group-detail/${userGroupId}`); }); }; actions.userGroupRelation = {}; actions.userGroupRelation.index = function(req, res) { }; actions.userGroupRelation.create = function(req, res) { const User = crowi.model('User'); const UserGroup = crowi.model('UserGroup'); const UserGroupRelation = crowi.model('UserGroupRelation'); // req params const userName = req.body.user_name; const userGroupId = req.body.user_group_id; let user = null; let userGroup = null; Promise.all([ // ユーザグループをIDで検索 UserGroup.findById(userGroupId), // ユーザを名前で検索 User.findUserByUsername(userName), ]) .then((resolves) => { userGroup = resolves[0]; user = resolves[1]; // Relation を作成 UserGroupRelation.createRelation(userGroup, user); }) .then((result) => { return res.redirect(`/admin/user-group-detail/${userGroup.id}`); }) .catch((err) => { debug('Error on create user-group relation', err); req.flash('errorMessage', 'Error on create user-group relation'); return res.redirect(`/admin/user-group-detail/${userGroup.id}`); }); }; actions.userGroupRelation.remove = function(req, res) { const UserGroupRelation = crowi.model('UserGroupRelation'); const userGroupId = req.params.id; const relationId = req.params.relationId; UserGroupRelation.removeById(relationId) .then(() => { return res.redirect(`/admin/user-group-detail/${userGroupId}`); }) .catch((err) => { debug('Error on remove user-group-relation', err); req.flash('errorMessage', 'グループのユーザ削除に失敗しました。'); }); }; // Importer management actions.importer = {}; actions.importer.api = api; api.validators = {}; api.validators.importer = {}; actions.importer.index = function(req, res) { const settingForm = configManager.getConfigByPrefix('crowi', 'importer:'); return res.render('admin/importer', { settingForm, }); }; api.validators.importer.esa = function() { const validator = [ check('importer:esa:team_name').not().isEmpty().withMessage('Error. Empty esa:team_name'), check('importer:esa:access_token').not().isEmpty().withMessage('Error. Empty esa:access_token'), ]; return validator; }; api.validators.importer.qiita = function() { const validator = [ check('importer:qiita:team_name').not().isEmpty().withMessage('Error. Empty qiita:team_name'), check('importer:qiita:access_token').not().isEmpty().withMessage('Error. Empty qiita:access_token'), ]; return validator; }; actions.api = {}; actions.api.appSetting = async function(req, res) { const form = req.form.settingForm; if (req.form.isValid) { debug('form content', form); // mail setting ならここで validation if (form['mail:from']) { validateMailSetting(req, form, async(err, data) => { debug('Error validate mail setting: ', err, data); if (err) { req.form.errors.push('SMTPを利用したテストメール送信に失敗しました。設定をみなおしてください。'); return res.json({ status: false, message: req.form.errors.join('\n') }); } await configManager.updateConfigsInTheSameNamespace('crowi', form); return res.json({ status: true }); }); } else { await configManager.updateConfigsInTheSameNamespace('crowi', form); return res.json({ status: true }); } } else { return res.json({ status: false, message: req.form.errors.join('\n') }); } }; actions.api.asyncAppSetting = async(req, res) => { const form = req.form.settingForm; if (!req.form.isValid) { return res.json({ status: false, message: req.form.errors.join('\n') }); } debug('form content', form); try { await configManager.updateConfigsInTheSameNamespace('crowi', form); return res.json({ status: true }); } catch (err) { logger.error(err); return res.json({ status: false }); } }; actions.api.securitySetting = async function(req, res) { if (!req.form.isValid) { return res.json({ status: false, message: req.form.errors.join('\n') }); } const form = req.form.settingForm; if (aclService.isWikiModeForced()) { logger.debug('security:restrictGuestMode will not be changed because wiki mode is forced to set'); delete form['security:restrictGuestMode']; } try { await configManager.updateConfigsInTheSameNamespace('crowi', form); return res.json({ status: true }); } catch (err) { logger.error(err); return res.json({ status: false }); } }; actions.api.securityPassportLdapSetting = function(req, res) { const form = req.form.settingForm; if (!req.form.isValid) { return res.json({ status: false, message: req.form.errors.join('\n') }); } debug('form content', form); return configManager.updateConfigsInTheSameNamespace('crowi', form) .then(() => { // reset strategy crowi.passportService.resetLdapStrategy(); // setup strategy if (configManager.getConfig('crowi', 'security:passport-ldap:isEnabled')) { crowi.passportService.setupLdapStrategy(true); } return; }) .then(() => { res.json({ status: true }); }); }; actions.api.securityPassportSamlSetting = async(req, res) => { const form = req.form.settingForm; validateSamlSettingForm(req.form, req.t); if (!req.form.isValid) { return res.json({ status: false, message: req.form.errors.join('\n') }); } debug('form content', form); await configManager.updateConfigsInTheSameNamespace('crowi', form); // reset strategy await crowi.passportService.resetSamlStrategy(); // setup strategy if (configManager.getConfig('crowi', 'security:passport-saml:isEnabled')) { try { await crowi.passportService.setupSamlStrategy(true); } catch (err) { // reset await crowi.passportService.resetSamlStrategy(); return res.json({ status: false, message: err.message }); } } return res.json({ status: true }); }; actions.api.securityPassportBasicSetting = async(req, res) => { const form = req.form.settingForm; if (!req.form.isValid) { return res.json({ status: false, message: req.form.errors.join('\n') }); } debug('form content', form); await configManager.updateConfigsInTheSameNamespace('crowi', form); // reset strategy await crowi.passportService.resetBasicStrategy(); // setup strategy if (configManager.getConfig('crowi', 'security:passport-basic:isEnabled')) { try { await crowi.passportService.setupBasicStrategy(true); } catch (err) { // reset await crowi.passportService.resetBasicStrategy(); return res.json({ status: false, message: err.message }); } } return res.json({ status: true }); }; actions.api.securityPassportGoogleSetting = async(req, res) => { const form = req.form.settingForm; if (!req.form.isValid) { return res.json({ status: false, message: req.form.errors.join('\n') }); } debug('form content', form); await configManager.updateConfigsInTheSameNamespace('crowi', form); // reset strategy await crowi.passportService.resetGoogleStrategy(); // setup strategy if (configManager.getConfig('crowi', 'security:passport-google:isEnabled')) { try { await crowi.passportService.setupGoogleStrategy(true); } catch (err) { // reset await crowi.passportService.resetGoogleStrategy(); return res.json({ status: false, message: err.message }); } } return res.json({ status: true }); }; actions.api.securityPassportGitHubSetting = async(req, res) => { const form = req.form.settingForm; if (!req.form.isValid) { return res.json({ status: false, message: req.form.errors.join('\n') }); } debug('form content', form); await configManager.updateConfigsInTheSameNamespace('crowi', form); // reset strategy await crowi.passportService.resetGitHubStrategy(); // setup strategy if (configManager.getConfig('crowi', 'security:passport-github:isEnabled')) { try { await crowi.passportService.setupGitHubStrategy(true); } catch (err) { // reset await crowi.passportService.resetGitHubStrategy(); return res.json({ status: false, message: err.message }); } } return res.json({ status: true }); }; actions.api.securityPassportTwitterSetting = async(req, res) => { const form = req.form.settingForm; if (!req.form.isValid) { return res.json({ status: false, message: req.form.errors.join('\n') }); } debug('form content', form); await configManager.updateConfigsInTheSameNamespace('crowi', form); // reset strategy await crowi.passportService.resetTwitterStrategy(); // setup strategy if (configManager.getConfig('crowi', 'security:passport-twitter:isEnabled')) { try { await crowi.passportService.setupTwitterStrategy(true); } catch (err) { // reset await crowi.passportService.resetTwitterStrategy(); return res.json({ status: false, message: err.message }); } } return res.json({ status: true }); }; actions.api.securityPassportOidcSetting = async(req, res) => { const form = req.form.settingForm; if (!req.form.isValid) { return res.json({ status: false, message: req.form.errors.join('\n') }); } debug('form content', form); await configManager.updateConfigsInTheSameNamespace('crowi', form); // reset strategy await crowi.passportService.resetOidcStrategy(); // setup strategy if (configManager.getConfig('crowi', 'security:passport-oidc:isEnabled')) { try { await crowi.passportService.setupOidcStrategy(true); } catch (err) { // reset await crowi.passportService.resetOidcStrategy(); return res.json({ status: false, message: err.message }); } } return res.json({ status: true }); }; actions.api.customizeSetting = async function(req, res) { const form = req.form.settingForm; if (req.form.isValid) { debug('form content', form); await configManager.updateConfigsInTheSameNamespace('crowi', form); customizeService.initCustomCss(); customizeService.initCustomTitle(); return res.json({ status: true }); } return res.json({ status: false, message: req.form.errors.join('\n') }); }; // app.post('/_api/admin/notifications.add' , admin.api.notificationAdd); actions.api.notificationAdd = function(req, res) { const UpdatePost = crowi.model('UpdatePost'); const pathPattern = req.body.pathPattern; const channel = req.body.channel; debug('notification.add', pathPattern, channel); UpdatePost.create(pathPattern, channel, req.user) .then((doc) => { debug('Successfully save updatePost', doc); // fixme: うーん doc.creator = doc.creator._id.toString(); return res.json(ApiResponse.success({ updatePost: doc })); }) .catch((err) => { debug('Failed to save updatePost', err); return res.json(ApiResponse.error()); }); }; // app.post('/_api/admin/notifications.remove' , admin.api.notificationRemove); actions.api.notificationRemove = function(req, res) { const UpdatePost = crowi.model('UpdatePost'); const id = req.body.id; UpdatePost.remove(id) .then(() => { debug('Successfully remove updatePost'); return res.json(ApiResponse.success({})); }) .catch((err) => { debug('Failed to remove updatePost', err); return res.json(ApiResponse.error()); }); }; // app.get('/_api/admin/users.search' , admin.api.userSearch); actions.api.usersSearch = function(req, res) { const User = crowi.model('User'); const email = req.query.email; User.findUsersByPartOfEmail(email, {}) .then((users) => { const result = { data: users, }; return res.json(ApiResponse.success(result)); }) .catch((err) => { return res.json(ApiResponse.error()); }); }; actions.api.toggleIsEnabledForGlobalNotification = async(req, res) => { const id = req.query.id; const isEnabled = (req.query.isEnabled === 'true'); try { if (isEnabled) { await GlobalNotificationSetting.enable(id); } else { await GlobalNotificationSetting.disable(id); } return res.json(ApiResponse.success()); } catch (err) { return res.json(ApiResponse.error()); } }; /** * save esa settings, update config cache, and response json * * @param {*} req * @param {*} res */ actions.api.importerSettingEsa = async(req, res) => { const form = req.body; const { validationResult } = require('express-validator'); const errors = validationResult(req); if (!errors.isEmpty()) { return res.json(ApiResponse.error('esa.io form is blank')); } await configManager.updateConfigsInTheSameNamespace('crowi', form); importer.initializeEsaClient(); // let it run in the back aftert res return res.json(ApiResponse.success()); }; /** * save qiita settings, update config cache, and response json * * @param {*} req * @param {*} res */ actions.api.importerSettingQiita = async(req, res) => { const form = req.body; const { validationResult } = require('express-validator'); const errors = validationResult(req); if (!errors.isEmpty()) { // return res.json(ApiResponse.error('Invalid comment.')); // return res.status(422).json({ errors: errors.array() }); console.log('validator', errors); return res.json(ApiResponse.error('Qiita form is blank')); } await configManager.updateConfigsInTheSameNamespace('crowi', form); importer.initializeQiitaClient(); // let it run in the back aftert res return res.json(ApiResponse.success()); }; /** * Import all posts from esa * * @param {*} req * @param {*} res */ actions.api.importDataFromEsa = async(req, res) => { const user = req.user; let errors; try { errors = await importer.importDataFromEsa(user); } catch (err) { errors = [err]; } if (errors.length > 0) { return res.json({ status: false, message: `
- ${errors.join('
- ')}` }); } return res.json({ status: true }); }; /** * Import all posts from qiita * * @param {*} req * @param {*} res */ actions.api.importDataFromQiita = async(req, res) => { const user = req.user; let errors; try { errors = await importer.importDataFromQiita(user); } catch (err) { errors = [err]; } if (errors.length > 0) { return res.json({ status: false, message: `
- ${errors.join('
- ')}` }); } return res.json({ status: true }); }; /** * Test connection to esa and response result with json * * @param {*} req * @param {*} res */ actions.api.testEsaAPI = async(req, res) => { try { await importer.testConnectionToEsa(); return res.json({ status: true }); } catch (err) { return res.json({ status: false, message: `${err}` }); } }; /** * Test connection to qiita and response result with json * * @param {*} req * @param {*} res */ actions.api.testQiitaAPI = async(req, res) => { try { await importer.testConnectionToQiita(); return res.json({ status: true }); } catch (err) { return res.json({ status: false, message: `${err}` }); } }; actions.api.searchBuildIndex = async function(req, res) { const search = crowi.getSearcher(); if (!search) { return res.json(ApiResponse.error('ElasticSearch Integration is not set up.')); } // first, delete index try { await search.deleteIndex(); } catch (err) { logger.warn('Delete index Error, but if it is initialize, its ok.', err); } // second, create index try { await search.buildIndex(); } catch (err) { logger.error('Error', err); return res.json(ApiResponse.error(err)); } searchEvent.on('addPageProgress', (total, current, skip) => { crowi.getIo().sockets.emit('admin:addPageProgress', { total, current, skip }); }); searchEvent.on('finishAddPage', (total, current, skip) => { crowi.getIo().sockets.emit('admin:finishAddPage', { total, current, skip }); }); // add all page search .addAllPages() .then(() => { debug('Data is successfully indexed. ------------------ ✧✧'); }) .catch((err) => { logger.error('Error', err); }); return res.json(ApiResponse.success()); }; function validateMailSetting(req, form, callback) { const mailer = crowi.mailer; const option = { host: form['mail:smtpHost'], port: form['mail:smtpPort'], }; if (form['mail:smtpUser'] && form['mail:smtpPassword']) { option.auth = { user: form['mail:smtpUser'], pass: form['mail:smtpPassword'], }; } if (option.port === 465) { option.secure = true; } const smtpClient = mailer.createSMTPClient(option); debug('mailer setup for validate SMTP setting', smtpClient); smtpClient.sendMail({ from: form['mail:from'], to: req.user.email, subject: 'Wiki管理設定のアップデートによるメール通知', text: 'このメールは、WikiのSMTP設定のアップデートにより送信されています。', }, callback); } /** * validate setting form values for SAML * * This validation checks, for the value of each mandatory items, * whether it from the environment variables is empty and form value to update it is empty. */ function validateSamlSettingForm(form, t) { for (const key of crowi.passportService.mandatoryConfigKeysForSaml) { const formValue = form.settingForm[key]; if (configManager.getConfigFromEnvVars('crowi', key) === null && formValue === '') { const formItemName = t(`security_setting.form_item_name.${key}`); form.errors.push(t('form_validation.required', formItemName)); } } } return actions; };