'use strict'; var async = require('async'); var fs = require('fs'); var path = require('path'); var csrf = require('csurf'); var validator = require('validator'); var nconf = require('nconf'); var ensureLoggedIn = require('connect-ensure-login'); var toobusy = require('toobusy-js'); var plugins = require('../plugins'); var meta = require('../meta'); var user = require('../user'); var groups = require('../groups'); var analytics = require('../analytics'); var controllers = { api: require('./../controllers/api'), helpers: require('../controllers/helpers'), }; var middleware = {}; middleware.applyCSRF = csrf(); middleware.ensureLoggedIn = ensureLoggedIn.ensureLoggedIn(nconf.get('relative_path') + '/login'); require('./admin')(middleware); require('./header')(middleware); require('./render')(middleware); require('./maintenance')(middleware); require('./user')(middleware); require('./headers')(middleware); middleware.authenticate = function (req, res, next) { if (req.user) { return next(); } else if (plugins.hasListeners('action:middleware.authenticate')) { return plugins.fireHook('action:middleware.authenticate', { req: req, res: res, next: next, }); } controllers.helpers.notAllowed(req, res); }; middleware.ensureSelfOrGlobalPrivilege = function (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.uid) { return setImmediate(next, null, false); } if (req.uid === parseInt(res.locals.uid, 10)) { return setImmediate(next, null, true); } user.isAdminOrGlobalMod(req.uid, next); }, function (isAdminOrGlobalMod, next) { if (!isAdminOrGlobalMod) { return controllers.helpers.notAllowed(req, res); } next(); }, ], next); }; middleware.ensureSelfOrPrivileged = function (req, res, next) { /* The "self" part of this middleware hinges on you having used middleware.exposeUid prior to invoking this middleware. */ if (req.user) { if (parseInt(req.user.uid, 10) === parseInt(res.locals.uid, 10)) { return next(); } user.isPrivileged(req.uid, function (err, ok) { if (err) { return next(err); } else if (ok) { return next(); } controllers.helpers.notAllowed(req, res); }); } else { controllers.helpers.notAllowed(req, res); } }; middleware.pageView = function (req, res, next) { analytics.pageView({ ip: req.ip, path: req.path, uid: req.uid, }); plugins.fireHook('action:middleware.pageView', { req: req }); if (req.user) { user.updateLastOnlineTime(req.user.uid); if (req.path.startsWith('/api/users') || req.path.startsWith('/users')) { user.updateOnlineUsers(req.user.uid, next); } else { user.updateOnlineUsers(req.user.uid); next(); } } else { next(); } }; middleware.pluginHooks = function (req, res, next) { async.each(plugins.loadedHooks['filter:router.page'] || [], function (hookObj, next) { hookObj.method(req, res, next); }, function () { // If it got here, then none of the subscribed hooks did anything, or there were no hooks next(); }); }; middleware.validateFiles = function (req, res, next) { if (!Array.isArray(req.files.files) || !req.files.files.length) { return next(new Error(['[[error:invalid-files]]'])); } next(); }; middleware.prepareAPI = function (req, res, next) { res.locals.isAPI = true; next(); }; middleware.routeTouchIcon = function (req, res) { if (meta.config['brand:touchIcon'] && validator.isURL(meta.config['brand:touchIcon'])) { return res.redirect(meta.config['brand:touchIcon']); } return res.sendFile(path.join(__dirname, '../../public', meta.config['brand:touchIcon'] || '/logo.png'), { maxAge: req.app.enabled('cache') ? 5184000000 : 0, }); }; middleware.privateTagListing = function (req, res, next) { if (!req.user && parseInt(meta.config.privateTagListing, 10) === 1) { controllers.helpers.notAllowed(req, res); } else { next(); } }; middleware.exposeGroupName = function (req, res, next) { expose('groupName', groups.getGroupNameByGroupSlug, 'slug', req, res, next); }; middleware.exposeUid = function (req, res, next) { expose('uid', user.getUidByUserslug, 'userslug', req, res, next); }; function expose(exposedField, method, field, req, res, next) { if (!req.params.hasOwnProperty(field)) { return next(); } method(req.params[field], function (err, id) { if (err) { return next(err); } res.locals[exposedField] = id; next(); }); } middleware.privateUploads = function (req, res, next) { if (req.user || parseInt(meta.config.privateUploads, 10) !== 1) { return next(); } if (req.path.startsWith('/assets/uploads/files')) { return res.status(403).json('not-allowed'); } next(); }; middleware.busyCheck = function (req, res, next) { if (global.env === 'production' && (!meta.config.hasOwnProperty('eventLoopCheckEnabled') || parseInt(meta.config.eventLoopCheckEnabled, 10) === 1) && toobusy()) { analytics.increment('errors:503'); res.status(503).type('text/html').sendFile(path.join(__dirname, '../../public/503.html')); } else { next(); } }; middleware.applyBlacklist = function (req, res, next) { meta.blacklist.test(req.ip, function (err) { next(err); }); }; middleware.processTimeagoLocales = function (req, res) { var fallback = req.path.indexOf('-short') === -1 ? 'jquery.timeago.en.js' : 'jquery.timeago.en-short.js'; var localPath = path.join(__dirname, '../../public/vendor/jquery/timeago/locales', req.path); var exists; try { exists = fs.accessSync(localPath, fs.F_OK | fs.R_OK); } catch (e) { exists = false; } if (exists) { res.status(200).sendFile(localPath, { maxAge: req.app.enabled('cache') ? 5184000000 : 0, }); } else { res.status(200).sendFile(path.join(__dirname, '../../public/vendor/jquery/timeago/locales', fallback), { maxAge: req.app.enabled('cache') ? 5184000000 : 0, }); } }; module.exports = middleware;