From a227cbe32804a7d6e9881c6ce4f7d874ca6a36be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 16 Dec 2019 08:44:55 -0500 Subject: [PATCH] refactor: async/await middleware --- src/middleware/maintenance.js | 56 +++++------ src/middleware/ratelimit.js | 10 +- src/middleware/render.js | 184 ++++++++++++++-------------------- src/middleware/user.js | 180 +++++++++++++-------------------- 4 files changed, 175 insertions(+), 255 deletions(-) diff --git a/src/middleware/maintenance.js b/src/middleware/maintenance.js index e98a75b9ef..00b34d4be3 100644 --- a/src/middleware/maintenance.js +++ b/src/middleware/maintenance.js @@ -1,44 +1,38 @@ 'use strict'; -var async = require('async'); -var nconf = require('nconf'); -var meta = require('../meta'); -var user = require('../user'); +const util = require('util'); +const nconf = require('nconf'); +const meta = require('../meta'); +const user = require('../user'); module.exports = function (middleware) { - middleware.maintenanceMode = function maintenanceMode(req, res, callback) { + middleware.maintenanceMode = async function maintenanceMode(req, res, next) { if (!meta.config.maintenanceMode) { - return setImmediate(callback); + return setImmediate(next); } - var url = req.url.replace(nconf.get('relative_path'), ''); + const url = req.url.replace(nconf.get('relative_path'), ''); if (url.startsWith('/login') || url.startsWith('/api/login')) { - return setImmediate(callback); + return setImmediate(next); } - var data; - async.waterfall([ - function (next) { - user.isAdministrator(req.uid, next); - }, - function (isAdmin, next) { - if (isAdmin) { - return callback(); - } - res.status(meta.config.maintenanceModeStatus); - data = { - site_title: meta.config.title || 'NodeBB', - message: meta.config.maintenanceModeMessage, - }; - if (res.locals.isAPI) { - return res.json(data); - } + const isAdmin = await user.isAdministrator(req.uid); + if (isAdmin) { + return setImmediate(next); + } + + res.status(meta.config.maintenanceModeStatus); - middleware.buildHeader(req, res, next); - }, - function () { - res.render('503', data); - }, - ], callback); + const data = { + site_title: meta.config.title || 'NodeBB', + message: meta.config.maintenanceModeMessage, + }; + + if (res.locals.isAPI) { + return res.json(data); + } + const buildHeaderAsync = util.promisify(middleware.buildHeader); + await buildHeaderAsync(req, res); + res.render('503', data); }; }; diff --git a/src/middleware/ratelimit.js b/src/middleware/ratelimit.js index 504cb0acd7..89f6cccdc4 100644 --- a/src/middleware/ratelimit.js +++ b/src/middleware/ratelimit.js @@ -1,11 +1,11 @@ 'use strict'; -var winston = require('winston'); +const winston = require('winston'); -var ratelimit = module.exports; +const ratelimit = module.exports; -var allowedCalls = 100; -var timeframe = 10000; +const allowedCalls = 100; +const timeframe = 10000; ratelimit.isFlooding = function (socket) { socket.callsPerSecond = socket.callsPerSecond || 0; @@ -14,7 +14,7 @@ ratelimit.isFlooding = function (socket) { socket.callsPerSecond += 1; - var now = Date.now(); + const now = Date.now(); socket.elapsedTime += now - socket.lastCallTime; if (socket.callsPerSecond > allowedCalls && socket.elapsedTime < timeframe) { diff --git a/src/middleware/render.js b/src/middleware/render.js index c33d6a8eac..23579bb70f 100644 --- a/src/middleware/render.js +++ b/src/middleware/render.js @@ -1,118 +1,89 @@ 'use strict'; -var async = require('async'); -var nconf = require('nconf'); -var validator = require('validator'); -var winston = require('winston'); +const util = require('util'); +const nconf = require('nconf'); +const validator = require('validator'); +const winston = require('winston'); -var plugins = require('../plugins'); -var meta = require('../meta'); -var translator = require('../translator'); -var widgets = require('../widgets'); -var utils = require('../utils'); +const plugins = require('../plugins'); +const meta = require('../meta'); +const translator = require('../translator'); +const widgets = require('../widgets'); +const utils = require('../utils'); module.exports = function (middleware) { + const renderHeaderFooterAsync = util.promisify(renderHeaderFooter); + middleware.processRender = function processRender(req, res, next) { // res.render post-processing, modified from here: https://gist.github.com/mrlannigan/5051687 - var render = res.render; - res.render = function renderOverride(template, options, fn) { - var self = this; - var req = this.req; - var defaultFn = function (err, str) { - if (err) { - return next(err); - } - self.send(str); - }; + const render = res.render; + res.render = async function renderOverride(template, options, fn) { + const self = this; + const req = this.req; options = options || {}; if (typeof options === 'function') { fn = options; options = {}; } - if (typeof fn !== 'function') { - fn = defaultFn; + + options.loggedIn = req.uid > 0; + options.relative_path = nconf.get('relative_path'); + options.template = { name: template, [template]: true }; + options.url = (req.baseUrl + req.path.replace(/^\/api/, '')); + options.bodyClass = buildBodyClass(req, res, options); + + const buildResult = await plugins.fireHook('filter:' + template + '.build', { req: req, res: res, templateData: options }); + const templateToRender = buildResult.templateData.templateToRender || template; + + const renderResult = await plugins.fireHook('filter:middleware.render', { req: req, res: res, templateData: buildResult.templateData }); + options = renderResult.templateData; + options._header = { + tags: await meta.tags.parse(req, renderResult, res.locals.metaTags, res.locals.linkTags), + }; + options.widgets = await widgets.render(req.uid, { + template: template + '.tpl', + url: options.url, + templateData: options, + req: req, + res: res, + }); + res.locals.template = template; + options._locals = undefined; + + if (res.locals.isAPI) { + if (req.route && req.route.path === '/api/') { + options.title = '[[pages:home]]'; + } + req.app.set('json spaces', global.env === 'development' || req.query.pretty ? 4 : 0); + return res.json(options); } + const ajaxifyData = JSON.stringify(options).replace(/<\//g, '<\\/'); + + const renderAsync = util.promisify((templateToRender, options, next) => render.call(self, templateToRender, options, next)); + + const results = await utils.promiseParallel({ + header: renderHeaderFooterAsync('renderHeader', req, res, options), + content: renderAsync(templateToRender, options), + footer: renderHeaderFooterAsync('renderFooter', req, res, options), + }); + + const str = results.header + + (res.locals.postHeader || '') + + results.content + '' + + (res.locals.preFooter || '') + + results.footer; - var ajaxifyData; - var templateToRender; - async.waterfall([ - function (next) { - options.loggedIn = req.uid > 0; - options.relative_path = nconf.get('relative_path'); - options.template = { name: template }; - options.template[template] = true; - options.url = (req.baseUrl + req.path.replace(/^\/api/, '')); - options.bodyClass = buildBodyClass(req, res, options); - plugins.fireHook('filter:' + template + '.build', { req: req, res: res, templateData: options }, next); - }, - function (data, next) { - templateToRender = data.templateData.templateToRender || template; - plugins.fireHook('filter:middleware.render', { req: req, res: res, templateData: data.templateData }, next); - }, - function parseTags(data, next) { - meta.tags.parse(req, data, res.locals.metaTags, res.locals.linkTags, function (err, tags) { - options._header = { - tags: tags, - }; - next(err, data); - }); - }, - function (data, next) { - options = data.templateData; - - widgets.render(req.uid, { - template: template + '.tpl', - url: options.url, - templateData: options, - req: req, - res: res, - }, next); - }, - function (data, next) { - options.widgets = data; - - res.locals.template = template; - options._locals = undefined; - - if (res.locals.isAPI) { - if (req.route && req.route.path === '/api/') { - options.title = '[[pages:home]]'; - } - req.app.set('json spaces', global.env === 'development' || req.query.pretty ? 4 : 0); - return res.json(options); - } - - ajaxifyData = JSON.stringify(options).replace(/<\//g, '<\\/'); - - async.parallel({ - header: function (next) { - renderHeaderFooter('renderHeader', req, res, options, next); - }, - content: function (next) { - render.call(self, templateToRender, options, next); - }, - footer: function (next) { - renderHeaderFooter('renderFooter', req, res, options, next); - }, - }, next); - }, - function (results, next) { - var str = results.header + - (res.locals.postHeader || '') + - results.content + '' + - (res.locals.preFooter || '') + - results.footer; - - translate(str, req, res, next); - }, - function (translated, next) { - translated = translated.replace('', function () { - return ''; - }); - next(null, translated); - }, - ], fn); + let translated = await translate(str, req, res); + translated = translated.replace('', function () { + return ''; + }); + + if (typeof fn !== 'function') { + self.send(translated); + } else { + fn(null, translated); + } }; next(); @@ -128,20 +99,19 @@ module.exports = function (middleware) { } } - function translate(str, req, res, next) { - var language = (res.locals.config && res.locals.config.userLang) || 'en-GB'; + async function translate(str, req, res) { + let language = (res.locals.config && res.locals.config.userLang) || 'en-GB'; if (res.locals.renderAdminHeader) { language = (res.locals.config && res.locals.config.acpLang) || 'en-GB'; } language = req.query.lang ? validator.escape(String(req.query.lang)) : language; - translator.translate(str, language, function (translated) { - next(null, translator.unescape(translated)); - }); + const translated = await translator.translate(str, language); + return translator.unescape(translated); } function buildBodyClass(req, res, templateData) { - var clean = req.path.replace(/^\/api/, '').replace(/^\/|\/$/g, ''); - var parts = clean.split('/').slice(0, 3); + const clean = req.path.replace(/^\/api/, '').replace(/^\/|\/$/g, ''); + const parts = clean.split('/').slice(0, 3); parts.forEach(function (p, index) { try { p = utils.slugify(decodeURIComponent(p)); diff --git a/src/middleware/user.js b/src/middleware/user.js index 000991a9c8..9e794a6a9e 100644 --- a/src/middleware/user.js +++ b/src/middleware/user.js @@ -1,17 +1,17 @@ 'use strict'; -var async = require('async'); -var nconf = require('nconf'); -var winston = require('winston'); +const util = require('util'); +const nconf = require('nconf'); +const winston = require('winston'); -var meta = require('../meta'); -var user = require('../user'); -var privileges = require('../privileges'); -var plugins = require('../plugins'); +const meta = require('../meta'); +const user = require('../user'); +const privileges = require('../privileges'); +const plugins = require('../plugins'); -var auth = require('../routes/authentication'); +const auth = require('../routes/authentication'); -var controllers = { +const controllers = { helpers: require('../controllers/helpers'), }; @@ -49,6 +49,8 @@ module.exports = function (middleware) { }); }; + const authenticateAsync = util.promisify(middleware.authenticate); + middleware.authenticateOrGuest = function authenticateOrGuest(req, res, next) { authenticate(req, res, next, next); }; @@ -61,30 +63,21 @@ module.exports = function (middleware) { ensureSelfOrMethod(user.isPrivileged, req, res, next); }; - function ensureSelfOrMethod(method, req, res, next) { + async function ensureSelfOrMethod(method, req, res, next) { /* The "self" part of this middleware hinges on you having used middleware.exposeUid prior to invoking this middleware. */ - async.waterfall([ - function (next) { - if (!req.loggedIn) { - return setImmediate(next, null, false); - } - - if (req.uid === parseInt(res.locals.uid, 10)) { - return setImmediate(next, null, true); - } - - method(req.uid, next); - }, - function (allowed, next) { - if (!allowed) { - return controllers.helpers.notAllowed(req, res); - } - next(); - }, - ], next); + if (!req.loggedIn) { + return controllers.helpers.notAllowed(req, res); + } + if (req.uid === parseInt(res.locals.uid, 10)) { + return setImmediate(next); + } + const allowed = await method(req.uid); + if (!allowed) { + return controllers.helpers.notAllowed(req, res); + } } middleware.checkGlobalPrivacySettings = function checkGlobalPrivacySettings(req, res, next) { @@ -92,110 +85,73 @@ module.exports = function (middleware) { middleware.canViewUsers(req, res, next); }; - middleware.canViewUsers = function canViewUsers(req, res, next) { + middleware.canViewUsers = async function canViewUsers(req, res, next) { if (parseInt(res.locals.uid, 10) === req.uid) { return next(); } - privileges.global.can('view:users', req.uid, function (err, canView) { - if (err || canView) { - return next(err); - } - controllers.helpers.notAllowed(req, res); - }); + const canView = await privileges.global.can('view:users', req.uid); + if (canView) { + return next(); + } + controllers.helpers.notAllowed(req, res); }; - middleware.canViewGroups = function canViewGroups(req, res, next) { - privileges.global.can('view:groups', req.uid, function (err, canView) { - if (err || canView) { - return next(err); - } - controllers.helpers.notAllowed(req, res); - }); + middleware.canViewGroups = async function canViewGroups(req, res, next) { + const canView = await privileges.global.can('view:groups', req.uid); + if (canView) { + return next(); + } + controllers.helpers.notAllowed(req, res); }; - middleware.checkAccountPermissions = function checkAccountPermissions(req, res, next) { + middleware.checkAccountPermissions = async function checkAccountPermissions(req, res, next) { // This middleware ensures that only the requested user and admins can pass - async.waterfall([ - function (next) { - middleware.authenticate(req, res, next); - }, - function (next) { - user.getUidByUserslug(req.params.userslug, next); - }, - function (uid, next) { - privileges.users.canEdit(req.uid, uid, next); - }, - function (allowed, next) { - if (allowed) { - return next(null, allowed); - } + await authenticateAsync(req, res); + const uid = await user.getUidByUserslug(req.params.userslug); + let allowed = await privileges.users.canEdit(req.uid, uid); + if (allowed) { + return next(); + } - // For the account/info page only, allow plain moderators through - if (/user\/.+\/info$/.test(req.path)) { - privileges.global.can('view:users:info', req.uid, next); - } else { - next(null, false); - } - }, - function (allowed) { - if (allowed) { - return next(); - } - controllers.helpers.notAllowed(req, res); - }, - ], next); + if (/user\/.+\/info$/.test(req.path)) { + allowed = await privileges.global.can('view:users:info', req.uid); + } + if (allowed) { + return next(); + } + controllers.helpers.notAllowed(req, res); }; - middleware.redirectToAccountIfLoggedIn = function redirectToAccountIfLoggedIn(req, res, next) { + middleware.redirectToAccountIfLoggedIn = async function redirectToAccountIfLoggedIn(req, res, next) { if (req.session.forceLogin || req.uid <= 0) { return next(); } - - async.waterfall([ - function (next) { - user.getUserField(req.uid, 'userslug', next); - }, - function (userslug) { - controllers.helpers.redirect(res, '/user/' + userslug); - }, - ], next); + const userslug = await user.getUserField(req.uid, 'userslug'); + controllers.helpers.redirect(res, '/user/' + userslug); }; - middleware.redirectUidToUserslug = function redirectUidToUserslug(req, res, next) { - var uid = parseInt(req.params.uid, 10); + middleware.redirectUidToUserslug = async function redirectUidToUserslug(req, res, next) { + const uid = parseInt(req.params.uid, 10); if (uid <= 0) { return next(); } - async.waterfall([ - function (next) { - user.getUserField(uid, 'userslug', next); - }, - function (userslug) { - if (!userslug) { - return next(); - } - var path = req.path.replace(/^\/api/, '') - .replace('uid', 'user') - .replace(uid, function () { return userslug; }); - controllers.helpers.redirect(res, path); - }, - ], next); + const userslug = await user.getUserField(uid, 'userslug'); + if (!userslug) { + return next(); + } + const path = req.path.replace(/^\/api/, '') + .replace('uid', 'user') + .replace(uid, function () { return userslug; }); + controllers.helpers.redirect(res, path); }; - middleware.redirectMeToUserslug = function redirectMeToUserslug(req, res, next) { - var uid = req.uid; - async.waterfall([ - function (next) { - user.getUserField(uid, 'userslug', next); - }, - function (userslug) { - if (!userslug) { - return controllers.helpers.notAllowed(req, res); - } - var path = req.path.replace(/^(\/api)?\/me/, '/user/' + userslug); - controllers.helpers.redirect(res, path); - }, - ], next); + middleware.redirectMeToUserslug = async function redirectMeToUserslug(req, res) { + const userslug = await user.getUserField(req.uid, 'userslug'); + if (!userslug) { + return controllers.helpers.notAllowed(req, res); + } + const path = req.path.replace(/^(\/api)?\/me/, '/user/' + userslug); + controllers.helpers.redirect(res, path); }; middleware.isAdmin = async function isAdmin(req, res, next) {