UNPKG

periodicjs.ext.asyncadmin

Version:

An authentication extension for periodicjs that uses passport to authenticate user sessions.

921 lines (873 loc) 27.5 kB
'use strict'; var path = require('path'), // request = require('superagent'), async = require('async'), fs = require('fs-extra'), // npm = require('npm'), os = require('os'), // semver = require('semver'), str2json = require('string-to-json'), merge = require('utils-merge'), CronJob = require('cron').CronJob, admin_ext_settings, CoreUtilities, CoreController, CoreExtension, CoreMailer, appSettings, dbSettings, mongoose, AppDBSetting, appenvironment, logger, restartfile = path.join(process.cwd(), '/content/config/restart.json'), changedemailtemplate, io = global.io; /** * send setting update email * @param {object} options - contains email options and nodemailer transport * @param {Function} callbackk async callback */ var sendSettingEmail = function (options, callback) { if (appSettings.adminnotificationemail_bcc) { options.adminnotificationemail_bcc = appSettings.adminnotificationemail_bcc; } console.log({ to: appSettings.adminnotificationemail, cc: options.user.email, bcc: options.adminnotificationemail_bcc, from: appSettings.serverfromemail, }); CoreMailer.sendEmail({ appenvironment: appenvironment, to: appSettings.adminnotificationemail, cc: options.user.email, bcc: options.adminnotificationemail_bcc, replyTo: options.replyTo, generatetextemail: true, // replyTo: 'Promise Financial [Do Not Reply] <no-reply@promisefin.com>', from: appSettings.serverfromemail, subject: (options.subject) ? options.subject : appSettings.name + ' -Admin Email Notification', // bcc: ilsConfig.emailtosalesforce, emailtemplatefilepath: options.emailtemplate, emailtemplatedata: options.emaildata }, function (err, emailstatus) { if (err) { CoreController.logError({ err: err, req: options.req }); } else { logger.debug('settings change email sent', emailstatus); } if (callback) { callback(err, emailstatus); } }); }; /** * restarts application response handler and send notification email * @param {object} req * @param {object} res * @return {object} reponds with an error page or sends user to authenicated in resource */ var restart_app = function (req, res) { CoreController.handleDocumentQueryRender({ req: req, res: res, redirecturl: '/p-admin/settings', responseData: { result: 'success', data: 'restarted' } }); var d = new Date(); sendSettingEmail({ user: req.user, emaildata: { user: req.user, hostname: req.headers.host, appname: appSettings.name, appenvironment: appSettings.application.environment, appport: appSettings.application.port, settingmessage: 'Your application was restarted from the admin interface - ' + d, }, subject: appSettings.name + '[env:' + appSettings.application.environment + '] Application Restart Notification', emailtemplate: changedemailtemplate, }, function (err, status) { CoreUtilities.restart_app({ restartfile: restartfile }); if (err) { logger.error(err); } else { console.info('email status', status); } }); }; /** * placeholder response for updating application * @param {object} req * @param {object} res * @return {object} reponds with an error page or sends user to authenicated in resource */ var update_app = function (req, res) { CoreController.handleDocumentQueryRender({ req: req, res: res, redirecturl: '/p-admin/settings', responseData: { result: 'success', data: 'restarted' } }); }; /** * load the extensions configuration files from the installed config folder in content/config/extensions/[extension]/[config files] * @param {object} req * @param {object} res * @param {Function} next */ var load_extension_settings = function (req, res, next) { var extname = req.params.id, ext_default_config_file_path = path.resolve(process.cwd(), 'node_modules/', extname, 'config'), ext_installed_config_file_path = path.resolve(process.cwd(), 'content/config/extensions/', extname); /** * load config files into array of filejson * @param {Function} callback async callbackk * @return {array} array of file data objects */ var loadconfigfiles = function (callback) { var configfilesJSON = []; fs.readdir(ext_installed_config_file_path, function (err, configfiles) { if (configfiles && configfiles.length > 0) { async.each(configfiles, function (configFile, cb) { if (path.extname(configFile) === '.json') { fs.readJson(path.resolve(ext_installed_config_file_path, configFile), function (err, data) { if (err) { cb(err); } else { configfilesJSON.push({ name: configFile, filedata: data }); cb(null); } }); } else { fs.readFile(path.resolve(ext_installed_config_file_path, configFile), 'utf8', function (err, data) { if (err) { cb(err); } else { configfilesJSON.push({ name: configFile, filedata: data }); cb(null); } }); } }, function (err) { if (err) { callback(err); } else { callback(null, configfilesJSON); } }); } else { callback(null, configfilesJSON); } }); }; req.controllerData = (req.controllerData) ? req.controllerData : {}; async.waterfall([ function (cb) { cb(null, { ext_default_config_file_path: ext_default_config_file_path, ext_installed_config_file_path: ext_installed_config_file_path }); }, CoreExtension.getExtensionConfigFiles, CoreExtension.copyMissingConfigFiles, loadconfigfiles ], function (err, result) { if (err) { CoreController.handleDocumentQueryErrorResponse({ err: err, res: res, req: req }); } else { // console.log('err', err, 'result', result); req.controllerData.extconfigfiles = result; next(); } }); }; /** * save data from theme page post * @param {object} req * @param {object} res */ var update_theme_filedata = function (req, res) { var updateThemeFileData = CoreUtilities.removeEmptyObjectValues(req.body), themeconffile = path.resolve(process.cwd(), 'content/themes/', updateThemeFileData.themename, updateThemeFileData.filename), jsonParseError; delete updateThemeFileData._csrf; try { updateThemeFileData.filedata = (path.extname(updateThemeFileData.filename) === '.json') ? JSON.parse(updateThemeFileData.filedata) : updateThemeFileData.filedata; } catch (e) { jsonParseError = e; } if (path.extname(updateThemeFileData.filename) === '.json') { logger.warn('write json'); fs.writeJson(themeconffile, updateThemeFileData.filedata, function (err) { if (err) { CoreController.handleDocumentQueryErrorResponse({ err: err, res: res, req: req }); } else if (jsonParseError) { CoreController.handleDocumentQueryErrorResponse({ err: 'JSON Parse Error: ' + jsonParseError, res: res, req: req }); } else { CoreController.handleDocumentQueryRender({ req: req, res: res, redirecturl: '/p-admin/theme/' + updateThemeFileData.themename, responseData: { result: 'success', data: 'updated theme file and restarted application' }, callback: function () { CoreUtilities.restart_app({ restartfile: restartfile }); } }); } }); } else { logger.warn('write file'); fs.outputFile(themeconffile, updateThemeFileData.filedata, function (err) { if (err) { CoreController.handleDocumentQueryErrorResponse({ err: err, res: res, req: req }); } else if (jsonParseError) { CoreController.handleDocumentQueryErrorResponse({ err: 'JSON Parse Error: ' + jsonParseError, res: res, req: req }); } else { CoreController.handleDocumentQueryRender({ req: req, res: res, redirecturl: '/p-admin/theme/' + updateThemeFileData.themename, responseData: { result: 'success', data: 'updated theme file and restarted application' }, callback: function () { CoreUtilities.restart_app({ restartfile: restartfile }); } }); } }); } }; /** * save data from config page post * @param {object} req * @param {object} res */ var update_ext_filedata = function (req, res) { var updateConfigFileData = CoreUtilities.removeEmptyObjectValues(req.body), extconffile = path.resolve(process.cwd(), 'content/config/extensions/', updateConfigFileData.extname, updateConfigFileData.filename), jsonParseError; delete updateConfigFileData._csrf; try { updateConfigFileData.filedata = (path.extname(updateConfigFileData.filename) === '.json') ? JSON.parse(updateConfigFileData.filedata) : updateConfigFileData.filedata; } catch (e) { jsonParseError = e; } if (path.extname(updateConfigFileData.filename) === '.json') { logger.warn('write json'); fs.writeJson(extconffile, updateConfigFileData.filedata, function (err) { if (err) { CoreController.handleDocumentQueryErrorResponse({ err: err, res: res, req: req }); } else if (jsonParseError) { CoreController.handleDocumentQueryErrorResponse({ err: 'JSON Parse Error: ' + jsonParseError, res: res, req: req }); } else { CoreController.handleDocumentQueryRender({ req: req, res: res, redirecturl: '/p-admin/extension/' + updateConfigFileData.extname, responseData: { result: 'success', data: 'updated config file and restarted application' }, callback: function () { CoreUtilities.restart_app({ restartfile: restartfile }); } }); } }); } else { logger.warn('write file'); fs.outputFile(extconffile, updateConfigFileData.filedata, function (err) { if (err) { CoreController.handleDocumentQueryErrorResponse({ err: err, res: res, req: req }); } else if (jsonParseError) { CoreController.handleDocumentQueryErrorResponse({ err: 'JSON Parse Error: ' + jsonParseError, res: res, req: req }); } else { CoreController.handleDocumentQueryRender({ req: req, res: res, redirecturl: '/p-admin/extension/' + updateConfigFileData.extname, responseData: { result: 'success', data: 'updated config file and restarted application' }, callback: function () { CoreUtilities.restart_app({ restartfile: restartfile }); } }); } }); } }; /** * load app configuration information * @param {object} req * @param {object} res * @param {object} next async callback * @return {object} reponds with an error page or sends user to authenicated in resource */ var load_app_settings = function (req, res, next) { req.controllerData = (req.controllerData) ? req.controllerData : {}; var appsettings = { readonly: { application: appSettings.application, cookies: appSettings.cookies, crsf: appSettings.crsf, database: dbSettings.url, expressCompression: appSettings.expressCompression, theme: appSettings.theme, templatefileextension: appSettings.templatefileextension, templateengine: appSettings.templateengine, sessions: appSettings.sessions, node_modules: appSettings.node_modules, version: appSettings.version, }, environment: appSettings.application.environment, configuration: { adminnotificationemail: appSettings.adminnotificationemail, serverfromemail: appSettings.serverfromemail, debug: appSettings.debug, periodic_cache_status: appSettings.periodic_cache_status, homepage: appSettings.homepage, name: appSettings.name, } }; async.parallel({ env_config: function (asynccb) { fs.readJson(path.join(process.cwd(), 'content/config/environment', appenvironment + '.json'), asynccb); }, default_config: function (asynccb) { fs.readJson(path.join(process.cwd(), 'content/config/environment/default.json'), asynccb); }, global_config: function (asynccb) { fs.readJson(path.join(process.cwd(), 'content/config/config.json'), asynccb); } }, function (err, results) { if (err) { next(err); } else { var configReturn = {}; for (var key in appSettings) { configReturn[key] = appSettings[key]; } req.controllerData.config = results; req.controllerData.config.app_config = configReturn; req.controllerData.config.app_config.themeSettings = '[theme config]'; req.controllerData.config.app_config.extconf = '[extension config]'; req.controllerData.appsettings = appsettings; next(); } }); }; /** * load theme configuration information * @param {object} req * @param {object} res * @param {object} next async callback * @return {object} reponds with an error page or sends user to authenicated in resource */ var load_theme_settings = function (req, res, next) { var themesettings = { readonly: {}, environment: appSettings.application.environment, configuration: {} }; if (appSettings.themeSettings && appSettings.themeSettings.settings) { themesettings.configuration = appSettings.themeSettings.settings[appSettings.application.environment]; themesettings.readonly = { name: appSettings.themeSettings.name, periodicCompatibility: appSettings.themeSettings.periodicCompatibility, author: appSettings.themeSettings.author, url: appSettings.themeSettings.url, templatefileextension: appSettings.templatefileextension, templateengine: appSettings.templateengine, themepath: appSettings.themepath }; } req.controllerData = (req.controllerData) ? req.controllerData : {}; req.controllerData.themesettings = themesettings; next(); }; /** * form upload handler to update app settings, and sends notification email * @param {object} req * @param {object} res * @param {object} next async callback * @return {object} reponds with an error page or sends user to authenicated in resource */ var update_app_settings = function (req, res) { var updatedAppSettings = CoreUtilities.removeEmptyObjectValues(req.body), appsettingsfile = path.join(process.cwd(), 'content/config/environment/' + appSettings.application.environment + '.json'); updatedAppSettings = CoreUtilities.replaceBooleanStringObjectValues(updatedAppSettings); delete updatedAppSettings._csrf; fs.ensureFileSync(appsettingsfile); fs.readJson(appsettingsfile, function (err, appconfig) { if (err) { CoreController.handleDocumentQueryErrorResponse({ err: err, res: res, req: req }); } else { updatedAppSettings = str2json.convert(updatedAppSettings); var originalconfig = appconfig || {}, mergedconfig = merge(originalconfig, updatedAppSettings); async.series({ update_app_setting: function (asynccb) { fs.writeJson(appsettingsfile, mergedconfig, asynccb); }, send_email_notification: function (asynccb) { sendSettingEmail({ user: req.user, req: req, emaildata: { user: req.user, hostname: req.headers.host, appname: appSettings.name, appenvironment: appSettings.application.environment, appport: appSettings.application.port, settingmessage: '<p>Your application was configuration was changed from the admin interface - ' + new Date() + '</p><p><pre>' + JSON.stringify(mergedconfig, null, '\t') + '</pre></p>', }, subject: appSettings.name + '[env:' + appSettings.application.environment + '] Application Configuration Change Notification', emailtemplate: changedemailtemplate, }, asynccb); }, send_server_response: function (asynccb) { CoreController.handleDocumentQueryRender({ req: req, res: res, redirecturl: '/p-admin/settings', responseData: { result: 'success', data: 'app config updated' }, callback: asynccb }); } }, function (err, results) { if (err) { CoreController.handleDocumentQueryErrorResponse({ err: err, res: res, req: req }); } else { logger.debug('update_app_settings async series results', results); CoreUtilities.restart_app({ restartfile: restartfile }); } }); } }); }; /** * form upload handler to update theme settings, and sends notification email * @param {object} req * @param {object} res * @param {object} next async callback * @return {object} reponds with an error page or sends user to authenicated in resource */ var update_theme_settings = function (req, res) { var updatedThemeSettings = JSON.parse(req.body['themesettings-codemirror']), //CoreUtilities.removeEmptyObjectValues(req.body), themesettingsfile = path.join(process.cwd(), 'content/config/themes', appSettings.theme, 'periodicjs.theme.json'), originalsettings, mergedsettings, newthemeconfig; updatedThemeSettings = CoreUtilities.replaceBooleanStringObjectValues(updatedThemeSettings); delete updatedThemeSettings._csrf; async.series({ read_theme_files: function (asynccb) { fs.readJson(themesettingsfile, function (err, themeconfig) { if (err) { asynccb(err); } else { // updatedThemeSettings = str2json.convert(updatedThemeSettings); originalsettings = themeconfig.settings[appenvironment]; mergedsettings = merge(originalsettings, updatedThemeSettings); newthemeconfig = themeconfig; newthemeconfig.settings[appSettings.application.environment] = mergedsettings; asynccb(null, 'updated with new theme settings'); } }); }, write_new_settings: function (asynccb) { fs.writeJson(themesettingsfile, newthemeconfig, { spaces: 2 }, function (err) { asynccb(err, 'saved new theme settings'); }); }, send_server_response: function (asynccb) { CoreController.handleDocumentQueryRender({ req: req, res: res, redirecturl: '/p-admin/settings', responseData: { result: 'success', data: 'theme config updated' }, callback: asynccb }); }, send_email_notification: function (asynccb) { sendSettingEmail({ req: req, user: req.user, emaildata: { user: req.user, hostname: req.headers.host, appname: appSettings.name, appenvironment: appenvironment, appport: appSettings.application.port, settingmessage: '<p>Your theme [' + appenvironment + '] configuration was changed from the admin interface - ' + new Date() + '</p><p><pre>' + JSON.stringify(newthemeconfig.settings[appenvironment], null, '\t') + '</pre></p>', }, subject: appSettings.name + '[env:' + appenvironment + '] Application Theme Setting Change Notification', emailtemplate: changedemailtemplate, }, asynccb); } }, function (err, results) { if (err) { CoreController.handleDocumentQueryErrorResponse({ err: err, res: res, req: req }); } else { logger.debug('update_theme_settings async results', results); CoreUtilities.restart_app({ restartfile: restartfile }); } }); }; /** * form upload handler to update theme settings, and sends notification email * @param {object} req * @param {object} res * @param {object} next async callback * @return {object} reponds with an error page or sends user to authenicated in resource */ var update_config_json_files = function (req, res) { var bodyjson, configfilejsonpath, configname; if (req.body['defaultconfig-codemirror']) { bodyjson = JSON.parse(req.body['defaultconfig-codemirror']); configfilejsonpath = path.join(process.cwd(), '/content/config/environment/default.json'); configname = 'default.json'; } else if (req.body['envconfig-codemirror']) { bodyjson = JSON.parse(req.body['envconfig-codemirror']); configfilejsonpath = path.join(process.cwd(), '/content/config/environment/' + appenvironment + '.json'); configname = appenvironment + '.json'; } else if (req.body['globalconfig-codemirror']) { bodyjson = JSON.parse(req.body['globalconfig-codemirror']); configfilejsonpath = path.join(process.cwd(), '/content/config/config.json'); configname = 'config.json'; } async.series({ write_new_settings: function (asynccb) { fs.writeJson(configfilejsonpath, bodyjson, { spaces: 2 }, function (err) { asynccb(err, 'saved new theme settings'); }); }, send_server_response: function (asynccb) { CoreController.handleDocumentQueryRender({ req: req, res: res, redirecturl: '/p-admin/settings', responseData: { result: 'success', data: configname + ' updated' }, callback: asynccb }); }, send_email_notification: function (asynccb) { if (bodyjson.cookies && bodyjson.cookies.cookieParser) { bodyjson.cookies.cookieParser = '[redacted]'; } if (bodyjson.session_secret) { bodyjson.session_secret = '[redacted]'; } sendSettingEmail({ req: req, user: req.user, emaildata: { user: req.user, hostname: req.headers.host, appname: appSettings.name, appenvironment: appenvironment, appport: appSettings.application.port, settingmessage: '<p>Your ' + configname + ' configuration was changed from the admin interface - ' + new Date() + '</p><p><pre>' + JSON.stringify(bodyjson, null, '\t') + '</pre></p>', }, subject: appSettings.name + '[env:' + appenvironment + '] ' + configname + ' Change Notification', emailtemplate: changedemailtemplate, }, asynccb); } }, function (err, results) { if (err) { CoreController.handleDocumentQueryErrorResponse({ err: err, res: res, req: req }); } else { logger.debug('update_theme_settings async results', results); CoreUtilities.restart_app({ restartfile: restartfile }); } }); }; var send_setting_server_callback = function (options) { try { if (io && io.engine) { io.sockets.emit('server_callback', { functionName: options.functionName, functionData: options.functionData }); } } catch (e) { logger.error('asyncadmin - send_server_callback e', e); } }; var checkOutdatedModulesAndPeriodic = function (options, callback) { var send_outdated_emails = (options) ? options.send_outdated_emails : (admin_ext_settings && admin_ext_settings.settings) ? admin_ext_settings.settings.send_cron_check_email : true; var asyncadmin_outdated_log_file_path = path.resolve(process.cwd(), 'content/config/outdated_log.json'); if (send_outdated_emails === undefined) { send_outdated_emails = true; } fs.readJSON(asyncadmin_outdated_log_file_path, function (err, result) { if (err) { logger.error(err); if (callback) { callback(err); } } else if (result.calculate_outdated_versions && Object.keys(result.calculate_outdated_versions).length > 0) { if (callback) { callback(null, result); } logger.warn('asyncadmin - WARNING: Your Instance is out of date', result.calculate_outdated_versions); var alerthtml = '<ul>'; for (var key in result.calculate_outdated_versions) { var ext = result.calculate_outdated_versions[key]; alerthtml += '<li>' + ext.name + ': current(' + ext.latest_version + '), installed(' + ext.installed_version + ')</li>'; } alerthtml += '<ul>'; if (send_outdated_emails) { sendSettingEmail({ // req: {}, user: { username: 'application-cron', email: appSettings.adminnotificationemail }, emaildata: { user: { username: 'application-cron', email: appSettings.adminnotificationemail }, hostname: appSettings.homepage, appname: appSettings.name, appenvironment: appenvironment, appport: appSettings.application.port, settingmessage: '<p>Your ' + appSettings.name + ' application dependencies are outdated - ' + new Date() + '</p><div>' + alerthtml + '</div>', }, subject: appSettings.name + '[env:' + appenvironment + ' - ' + os.hostname() + '] Dependency warning notification', emailtemplate: changedemailtemplate, }, function () {}); } send_setting_server_callback({ functionName: 'showServerModal', functionData: '<div class="ts-text-xl"><span class="ts-text-error-color">Node (' + os.hostname() + ') dependencies outdated warning</span></div><div>' + alerthtml + '</div>' }); } }); }; var get_outdated_modules = function (req, res, next) { req.controllerData = (req.controllerData) ? req.controllerData : {}; checkOutdatedModulesAndPeriodic({}, function (err, outdated_modules) { if (err) { next(err); } else { req.controllerData.outdated_modules = outdated_modules; next(); } }); }; var useCronTasks = function () { try { var crontime_to_use = (admin_ext_settings && admin_ext_settings.settings && admin_ext_settings.settings.check_dependency_cron) ? admin_ext_settings.settings.check_dependency_cron : '00 00 06 * * 1-5', check_outdated_dependencies = new CronJob({ cronTime: crontime_to_use, onTick: checkOutdatedModulesAndPeriodic, onComplete: function () {} //, // start: true }); check_outdated_dependencies.start(); } catch (e) { logger.error('setupLoanCronTasks e', e); } }; /** * settings controller * @module settingsController * @{@link https://github.com/typesettin/periodicjs.ext.admin} * @author Yaw Joseph Etse * @copyright Copyright (c) 2014 Typesettin. All rights reserved. * @license MIT * @requires module:async * @requires module:path * @requires module:string-to-json * @requires module:utils-merge * @requires module:ejs * @requires module:periodicjs.core.utilities * @requires module:periodicjs.core.controller * @requires module:periodicjs.core.mailer * @param {object} resources variable injection from current periodic instance with references to the active logger and mongo session * @return {object} settings */ var controller = function (resources) { logger = resources.logger; mongoose = resources.mongoose; appSettings = resources.settings; dbSettings = resources.db; CoreController = resources.core.controller; CoreUtilities = resources.core.utilities; CoreExtension = resources.core.extension; CoreMailer = resources.core.mailer; AppDBSetting = mongoose.model('Setting'); appenvironment = appSettings.application.environment; admin_ext_settings = resources.app.controller.extension.asyncadmin.adminExtSettings; CoreController.getPluginViewDefaultTemplate({ viewname: 'p-admin/email/settings/notification', themefileext: appSettings.templatefileextension }, function (err, templatepath) { if (templatepath === 'p-admin/email/settings/notification') { templatepath = path.resolve(process.cwd(), 'node_modules/periodicjs.ext.asyncadmin/views', templatepath + '.' + appSettings.templatefileextension); } changedemailtemplate = templatepath; } ); checkOutdatedModulesAndPeriodic(); useCronTasks(); return { load_extension_settings: load_extension_settings, update_config_json_files: update_config_json_files, update_ext_filedata: update_ext_filedata, update_theme_filedata: update_theme_filedata, load_app_settings: load_app_settings, load_theme_settings: load_theme_settings, restart_app: restart_app, update_app: update_app, update_theme_settings: update_theme_settings, update_app_settings: update_app_settings, get_outdated_modules: get_outdated_modules }; }; module.exports = controller;