Merge commit 'c1d8b9bb5a9697c8d86ac518456ab1ae6fe45234' into v1.14.x

This commit is contained in:
Misty (Bot)
2020-06-04 18:25:34 +00:00
60 changed files with 609 additions and 578 deletions

View File

@@ -32,6 +32,9 @@
"view-profile": "View Profile",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "View Profile",
"user-edit": "Edit Profile",

View File

@@ -32,6 +32,9 @@
"view-profile": "Преглед на профила",
"start-new-chat": "Започване на нов разговор",
"go-to-target": "Преглед на целта на доклада",
"delete-post": "Изтриване на публикацията",
"purge-post": "Изчистване на публикацията",
"restore-post": "Възстановяване на публикацията",
"user-view": "Преглед на профила",
"user-edit": "Редактиране на профила",

View File

@@ -32,6 +32,9 @@
"view-profile": "View Profile",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "View Profile",
"user-edit": "Edit Profile",

View File

@@ -32,6 +32,9 @@
"view-profile": "Zobrazit profil",
"start-new-chat": "Začít novou konverzaci",
"go-to-target": "Zobrazit cílové označení",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "Zobrazit profil",
"user-edit": "Upravit profil",

View File

@@ -32,6 +32,9 @@
"view-profile": "View Profile",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "View Profile",
"user-edit": "Edit Profile",

View File

@@ -32,6 +32,9 @@
"view-profile": "Profil ansehen",
"start-new-chat": "Neuen Chat beginnen",
"go-to-target": "Meldungsziel ansehen",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "Profil ansehen",
"user-edit": "Profil bearbeiten",

View File

@@ -32,6 +32,9 @@
"view-profile": "View Profile",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "View Profile",
"user-edit": "Edit Profile",

View File

@@ -32,6 +32,9 @@
"view-profile": "View Profile",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "View Profile",
"user-edit": "Edit Profile",

View File

@@ -32,6 +32,9 @@
"view-profile": "View Profile",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "View Profile",
"user-edit": "Edit Profile",

View File

@@ -32,6 +32,9 @@
"view-profile": "Ver perfil",
"start-new-chat": "Empezar nuevo chat",
"go-to-target": "Ver objetivo marcado",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "Ver perfil",
"user-edit": "Editar perfil",

View File

@@ -32,6 +32,9 @@
"view-profile": "View Profile",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "View Profile",
"user-edit": "Edit Profile",

View File

@@ -32,6 +32,9 @@
"view-profile": "نمایش پروفایل",
"start-new-chat": "شروع چت جدید",
"go-to-target": "مشاهده محتوای گزارش شده",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "نمایش پروفایل",
"user-edit": "ویرایش پروفایل",

View File

@@ -32,6 +32,9 @@
"view-profile": "View Profile",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "View Profile",
"user-edit": "Edit Profile",

View File

@@ -32,6 +32,9 @@
"view-profile": "Voir le profil",
"start-new-chat": "Démarrer un nouveau Chat",
"go-to-target": "Voir le signalement cible",
"delete-post": "Supprimer les messages",
"purge-post": "Supprimer définitivement",
"restore-post": "Restaurer les messages",
"user-view": "Voir le profil",
"user-edit": "Éditer le profil",

View File

@@ -32,6 +32,9 @@
"view-profile": "Ver perfil",
"start-new-chat": "Comezar novo chat",
"go-to-target": "Ver contido marcado",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "Ver perfil",
"user-edit": "Editar perfil",

View File

@@ -32,6 +32,9 @@
"view-profile": "צפה בפרופיל",
"start-new-chat": "התחל שיחה חדשה",
"go-to-target": "צפה במטרת הסימון",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "צפה בפרופיל",
"user-edit": "ערוך פרופיל",

View File

@@ -32,6 +32,9 @@
"view-profile": "Pogledaj profil",
"start-new-chat": "Pokreni novi razgovor",
"go-to-target": "Pogledaj metu zastave",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "Pogledaj profil",
"user-edit": "Uredi profil",

View File

@@ -32,6 +32,9 @@
"view-profile": "Profil megtekintése",
"start-new-chat": "Új chat indítása",
"go-to-target": "View Flag Target",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "View Profile",
"user-edit": "Edit Profile",

View File

@@ -32,6 +32,9 @@
"view-profile": "View Profile",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "View Profile",
"user-edit": "Edit Profile",

View File

@@ -32,6 +32,9 @@
"view-profile": "Vedi Profilo",
"start-new-chat": "Inizia Nuova Chat",
"go-to-target": "Visualizza oggetto segnalazione",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "Vedi Profilo",
"user-edit": "Modifica Profilo",

View File

@@ -32,6 +32,9 @@
"view-profile": "プロフィールを見る",
"start-new-chat": "新しいチャットを開始",
"go-to-target": "フラグのターゲットを表示",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "プロフィールを見る",
"user-edit": "プロフィールを編集",

View File

@@ -32,6 +32,9 @@
"view-profile": "프로필 보기",
"start-new-chat": "새로운 채팅 시작",
"go-to-target": "신고된 게시물 바로가기",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "프로필 보기",
"user-edit": "프로필 수정",

View File

@@ -32,6 +32,9 @@
"view-profile": "View Profile",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "View Profile",
"user-edit": "Edit Profile",

View File

@@ -32,6 +32,9 @@
"view-profile": "Skatīt profilu",
"start-new-chat": "Sākt jaunu sarunu",
"go-to-target": "Skatīt atzīmēto rakstu",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "Skatīt profilu",
"user-edit": "Rediģēt profilu",

View File

@@ -32,6 +32,9 @@
"view-profile": "View Profile",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "View Profile",
"user-edit": "Edit Profile",

View File

@@ -32,6 +32,9 @@
"view-profile": "View Profile",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "View Profile",
"user-edit": "Edit Profile",

View File

@@ -32,6 +32,9 @@
"view-profile": "Profiel bekijken",
"start-new-chat": "Begin een nieuwe chat",
"go-to-target": "Bekijk markering doel",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "Profiel bekijken",
"user-edit": "Profiel wijzigen",

View File

@@ -32,6 +32,9 @@
"view-profile": "Zobacz profil",
"start-new-chat": "Rozpocznij nowy czat",
"go-to-target": "Zobacz cel flagowania",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "Zobacz profil",
"user-edit": "Edytuj profil",

View File

@@ -32,6 +32,9 @@
"view-profile": "Ver Perfil",
"start-new-chat": "Iniciar Novo Chat",
"go-to-target": "Ver o Sinalizado",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "Ver Perfil",
"user-edit": "Editar Perfil",

View File

@@ -32,6 +32,9 @@
"view-profile": "Ver Perfil",
"start-new-chat": "Iniciar Nova Conversa",
"go-to-target": "Ver Alvo da Denúncia",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "Ver Perfil",
"user-edit": "Editar Perfil",

View File

@@ -32,6 +32,9 @@
"view-profile": "View Profile",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "View Profile",
"user-edit": "Edit Profile",

View File

@@ -32,6 +32,9 @@
"view-profile": "Просмотреть профиль",
"start-new-chat": "Начать новый чат",
"go-to-target": "Показать предмет жалобы",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "Открыть профиль",
"user-edit": "Изменить профиль",

View File

@@ -32,6 +32,9 @@
"view-profile": "View Profile",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "View Profile",
"user-edit": "Edit Profile",

View File

@@ -32,6 +32,9 @@
"view-profile": "View Profile",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "View Profile",
"user-edit": "Edit Profile",

View File

@@ -32,6 +32,9 @@
"view-profile": "Zobraziť profil",
"start-new-chat": "Začať novú konverzáciu",
"go-to-target": "Zobraziť cieľové označenie",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "Zobraziť profil",
"user-edit": "Upraviť profil",

View File

@@ -32,6 +32,9 @@
"view-profile": "View Profile",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "View Profile",
"user-edit": "Edit Profile",

View File

@@ -32,6 +32,9 @@
"view-profile": "Погледај профил",
"start-new-chat": "Започни ново ћаскање",
"go-to-target": "Погледај циљ означавања",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "Погледај профил",
"user-edit": "Уреди профил",

View File

@@ -32,6 +32,9 @@
"view-profile": "Visa profil",
"start-new-chat": "Påbörja ny chatt",
"go-to-target": "Visa flaggans ämne",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "Visa profil",
"user-edit": "Redigera profil",

View File

@@ -32,6 +32,9 @@
"view-profile": "ดูโปรไฟล์",
"start-new-chat": "เริ่มแชทใหม่",
"go-to-target": "ดูเป้าหมายการปักธง",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "ดูโปรไฟล์",
"user-edit": "แก้ไขโปรไฟล์",

View File

@@ -32,6 +32,9 @@
"view-profile": "Profili Gör",
"start-new-chat": "Yeni Sohbet Başlat",
"go-to-target": "Şikayet Edilen İçeriği Gör",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "Profili Gör",
"user-edit": "Profili Düzenle",

View File

@@ -32,6 +32,9 @@
"view-profile": "View Profile",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "View Profile",
"user-edit": "Edit Profile",

View File

@@ -32,6 +32,9 @@
"view-profile": "查看个人资料",
"start-new-chat": "开始新会话",
"go-to-target": "查看举报目标",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "查看资料",
"user-edit": "编辑资料",

View File

@@ -32,6 +32,9 @@
"view-profile": "查看個人資料",
"start-new-chat": "開始新聊天對話",
"go-to-target": "查看舉報目標",
"delete-post": "Delete Post",
"purge-post": "Purge Post",
"restore-post": "Restore Post",
"user-view": "查看資料",
"user-edit": "編輯資料",

View File

@@ -36,7 +36,7 @@ define('search', ['navigator', 'translator', 'storage'], function (nav, translat
Search.quick = function (query, options, callback) {
callback = callback || function () {};
var template = options.template || 'partials/quick-search-results';
$(window).trigger('action:search.quick', { data: query });
$(window).trigger('action:search.quick.start', { data: query });
query.searchOnly = 1;
Search.api(query, function (data) {
if (options.hideOnNoMatches && !data.posts.length) {
@@ -52,7 +52,7 @@ define('search', ['navigator', 'translator', 'storage'], function (nav, translat
} else {
options.resultEl.addClass('hidden').find('#quick-search-results-container').html('');
}
$(window).trigger('action:search.quick.complete', { });
$(window).trigger('action:search.quick.complete', { data: data });
callback();
});
});

View File

@@ -52,7 +52,7 @@ Analytics.increment = function (keys, callback) {
}
};
Analytics.pageView = function (payload) {
Analytics.pageView = async function (payload) {
pageViews += 1;
if (payload.uid > 0) {
@@ -71,20 +71,16 @@ Analytics.pageView = function (payload) {
ipCache.set(payload.ip + nconf.get('secret'), hash);
}
db.sortedSetScore('ip:recent', hash, function (err, score) {
if (err) {
return;
}
if (!score) {
uniqueIPCount += 1;
}
var today = new Date();
today.setHours(today.getHours(), 0, 0, 0);
if (!score || score < today.getTime()) {
uniquevisitors += 1;
db.sortedSetAdd('ip:recent', Date.now(), hash);
}
});
const score = await db.sortedSetScore('ip:recent', hash);
if (!score) {
uniqueIPCount += 1;
}
const today = new Date();
today.setHours(today.getHours(), 0, 0, 0);
if (!score || score < today.getTime()) {
uniquevisitors += 1;
await db.sortedSetAdd('ip:recent', Date.now(), hash);
}
}
};

View File

@@ -6,6 +6,7 @@ const validator = require('validator');
const meta = require('../meta');
const plugins = require('../plugins');
const middleware = require('../middleware');
exports.handle404 = function handle404(req, res) {
const relativePath = nconf.get('relative_path');
@@ -36,14 +37,12 @@ exports.handle404 = function handle404(req, res) {
}
};
exports.send404 = function (req, res) {
exports.send404 = async function (req, res) {
res.status(404);
const path = String(req.path || '');
if (res.locals.isAPI) {
return res.json({ path: validator.escape(path.replace(/^\/api/, '')), title: '[[global:404.title]]' });
}
const middleware = require('../middleware');
middleware.buildHeader(req, res, function () {
res.render('404', { path: validator.escape(path), title: '[[global:404.title]]' });
});
await middleware.buildHeaderAsync(req, res);
res.render('404', { path: validator.escape(path), title: '[[global:404.title]]' });
};

View File

@@ -4,8 +4,9 @@ var nconf = require('nconf');
var winston = require('winston');
var validator = require('validator');
var plugins = require('../plugins');
var middleware = require('../middleware');
exports.handleURIErrors = function handleURIErrors(err, req, res, next) {
exports.handleURIErrors = async function handleURIErrors(err, req, res, next) {
// Handle cases where malformed URIs are passed in
if (err instanceof URIError) {
const cleanPath = req.path.replace(new RegExp('^' + nconf.get('relative_path')), '');
@@ -23,10 +24,8 @@ exports.handleURIErrors = function handleURIErrors(err, req, res, next) {
error: '[[global:400.title]]',
});
} else {
var middleware = require('../middleware');
middleware.buildHeader(req, res, function () {
res.status(400).render('400', { error: validator.escape(String(err.message)) });
});
await middleware.buildHeaderAsync(req, res);
res.status(400).render('400', { error: validator.escape(String(err.message)) });
}
}
} else {
@@ -46,7 +45,7 @@ exports.handleErrors = function handleErrors(err, req, res, next) { // eslint-di
res.status(403).type('text/plain').send(err.message);
},
};
var defaultHandler = function () {
var defaultHandler = async function () {
// Display NodeBB error page
var status = parseInt(err.status, 10);
if ((status === 302 || status === 308) && err.path) {
@@ -61,10 +60,8 @@ exports.handleErrors = function handleErrors(err, req, res, next) { // eslint-di
if (res.locals.isAPI) {
res.json({ path: validator.escape(path), error: err.message });
} else {
var middleware = require('../middleware');
middleware.buildHeader(req, res, function () {
res.render('500', { path: validator.escape(path), error: validator.escape(String(err.message)) });
});
await middleware.buildHeaderAsync(req, res);
res.render('500', { path: validator.escape(path), error: validator.escape(String(err.message)) });
}
};

View File

@@ -2,7 +2,6 @@
const nconf = require('nconf');
const validator = require('validator');
const winston = require('winston');
const querystring = require('querystring');
const _ = require('lodash');
@@ -15,21 +14,19 @@ const middleware = require('../middleware');
const helpers = module.exports;
helpers.noScriptErrors = function (req, res, error, httpStatus) {
helpers.noScriptErrors = async function (req, res, error, httpStatus) {
if (req.body.noscript !== 'true') {
return res.status(httpStatus).send(error);
}
const middleware = require('../middleware');
const httpStatusString = httpStatus.toString();
middleware.buildHeader(req, res, function () {
res.status(httpStatus).render(httpStatusString, {
path: req.path,
loggedIn: req.loggedIn,
error: error,
returnLink: true,
title: '[[global:' + httpStatusString + '.title]]',
});
await middleware.buildHeaderAsync(req, res);
res.status(httpStatus).render(httpStatusString, {
path: req.path,
loggedIn: req.loggedIn,
error: error,
returnLink: true,
title: '[[global:' + httpStatusString + '.title]]',
});
};
@@ -104,41 +101,37 @@ helpers.buildTerms = function (url, term, query) {
}];
};
helpers.notAllowed = function (req, res, error) {
plugins.fireHook('filter:helpers.notAllowed', {
helpers.notAllowed = async function (req, res, error) {
const data = await plugins.fireHook('filter:helpers.notAllowed', {
req: req,
res: res,
error: error,
}, function (err) {
if (err) {
return winston.error(err);
}
if (req.loggedIn || req.uid === -1) {
if (res.locals.isAPI) {
res.status(403).json({
path: req.path.replace(/^\/api/, ''),
loggedIn: req.loggedIn,
error: error,
title: '[[global:403.title]]',
});
} else {
middleware.buildHeader(req, res, function () {
res.status(403).render('403', {
path: req.path,
loggedIn: req.loggedIn,
error: error,
title: '[[global:403.title]]',
});
});
}
} else if (res.locals.isAPI) {
req.session.returnTo = req.url.replace(/^\/api/, '');
res.status(401).json('not-authorized');
} else {
req.session.returnTo = req.url;
res.redirect(nconf.get('relative_path') + '/login');
}
});
if (req.loggedIn || req.uid === -1) {
if (res.locals.isAPI) {
res.status(403).json({
path: req.path.replace(/^\/api/, ''),
loggedIn: req.loggedIn,
error: data.error,
title: '[[global:403.title]]',
});
} else {
await middleware.buildHeaderAsync(req, res);
res.status(403).render('403', {
path: req.path,
loggedIn: req.loggedIn,
error: data.error,
title: '[[global:403.title]]',
});
}
} else if (res.locals.isAPI) {
req.session.returnTo = req.url.replace(/^\/api/, '');
res.status(401).json('not-authorized');
} else {
req.session.returnTo = req.url;
res.redirect(nconf.get('relative_path') + '/login');
}
};
helpers.redirect = function (res, url) {

View File

@@ -1,6 +1,5 @@
'use strict';
var async = require('async');
var winston = require('winston');
var jsesc = require('jsesc');
var nconf = require('nconf');
@@ -9,7 +8,9 @@ var semver = require('semver');
var user = require('../user');
var meta = require('../meta');
var plugins = require('../plugins');
var utils = require('../../public/src/utils');
var versions = require('../admin/versions');
var helpers = require('./helpers');
var controllers = {
api: require('../controllers/api'),
@@ -18,112 +19,88 @@ var controllers = {
module.exports = function (middleware) {
middleware.admin = {};
middleware.admin.isAdmin = function (req, res, next) {
middleware.admin.isAdmin = helpers.try(async function (req, res, next) {
winston.warn('[middleware.admin.isAdmin] deprecation warning, no need to use this from plugins!');
middleware.isAdmin(req, res, next);
};
await middleware.isAdmin(req, res, next);
});
middleware.admin.buildHeader = function (req, res, next) {
middleware.admin.buildHeader = helpers.try(async function (req, res, next) {
res.locals.renderAdminHeader = true;
res.locals.config = await controllers.api.loadConfig(req);
next();
});
async.waterfall([
function (next) {
controllers.api.loadConfig(req, next);
},
function (config, next) {
res.locals.config = config;
next();
},
], next);
};
middleware.admin.renderHeader = function (req, res, data, next) {
middleware.admin.renderHeader = async (req, res, data) => {
var custom_header = {
plugins: [],
authentication: [],
};
res.locals.config = res.locals.config || {};
async.waterfall([
function (next) {
async.parallel({
userData: function (next) {
user.getUserFields(req.uid, ['username', 'userslug', 'email', 'picture', 'email:confirmed'], next);
},
scripts: function (next) {
getAdminScripts(next);
},
custom_header: function (next) {
plugins.fireHook('filter:admin.header.build', custom_header, next);
},
configs: function (next) {
meta.configs.list(next);
},
latestVersion: function (next) {
versions.getLatestVersion(function (err, result) {
if (err) {
winston.error('[acp] Failed to fetch latest version', err);
}
next(null, err ? null : result);
});
},
}, next);
},
function (results, next) {
var userData = results.userData;
userData.uid = req.uid;
userData['email:confirmed'] = userData['email:confirmed'] === 1;
const results = await utils.promiseParallel({
userData: user.getUserFields(req.uid, ['username', 'userslug', 'email', 'picture', 'email:confirmed']),
scripts: getAdminScripts(),
custom_header: plugins.fireHook('filter:admin.header.build', custom_header),
configs: meta.configs.list(),
latestVersion: getLatestVersion(),
});
var acpPath = req.path.slice(1).split('/');
acpPath.forEach(function (path, i) {
acpPath[i] = path.charAt(0).toUpperCase() + path.slice(1);
});
acpPath = acpPath.join(' > ');
var userData = results.userData;
userData.uid = req.uid;
userData['email:confirmed'] = userData['email:confirmed'] === 1;
var version = nconf.get('version');
var acpPath = req.path.slice(1).split('/');
acpPath.forEach(function (path, i) {
acpPath[i] = path.charAt(0).toUpperCase() + path.slice(1);
});
acpPath = acpPath.join(' > ');
res.locals.config.userLang = res.locals.config.acpLang || res.locals.config.userLang;
var templateValues = {
config: res.locals.config,
configJSON: jsesc(JSON.stringify(res.locals.config), { isScriptContext: true }),
relative_path: res.locals.config.relative_path,
adminConfigJSON: encodeURIComponent(JSON.stringify(results.configs)),
user: userData,
userJSON: jsesc(JSON.stringify(userData), { isScriptContext: true }),
plugins: results.custom_header.plugins,
authentication: results.custom_header.authentication,
scripts: results.scripts,
'cache-buster': meta.config['cache-buster'] || '',
env: !!process.env.NODE_ENV,
title: (acpPath || 'Dashboard') + ' | NodeBB Admin Control Panel',
bodyClass: data.bodyClass,
version: version,
latestVersion: results.latestVersion,
upgradeAvailable: results.latestVersion && semver.gt(results.latestVersion, version),
};
var version = nconf.get('version');
templateValues.template = { name: res.locals.template };
templateValues.template[res.locals.template] = true;
res.locals.config.userLang = res.locals.config.acpLang || res.locals.config.userLang;
var templateValues = {
config: res.locals.config,
configJSON: jsesc(JSON.stringify(res.locals.config), { isScriptContext: true }),
relative_path: res.locals.config.relative_path,
adminConfigJSON: encodeURIComponent(JSON.stringify(results.configs)),
user: userData,
userJSON: jsesc(JSON.stringify(userData), { isScriptContext: true }),
plugins: results.custom_header.plugins,
authentication: results.custom_header.authentication,
scripts: results.scripts,
'cache-buster': meta.config['cache-buster'] || '',
env: !!process.env.NODE_ENV,
title: (acpPath || 'Dashboard') + ' | NodeBB Admin Control Panel',
bodyClass: data.bodyClass,
version: version,
latestVersion: results.latestVersion,
upgradeAvailable: results.latestVersion && semver.gt(results.latestVersion, version),
};
req.app.render('admin/header', templateValues, next);
},
], next);
templateValues.template = { name: res.locals.template };
templateValues.template[res.locals.template] = true;
return await req.app.renderAsync('admin/header', templateValues);
};
function getAdminScripts(callback) {
async.waterfall([
function (next) {
plugins.fireHook('filter:admin.scripts.get', [], next);
},
function (scripts, next) {
next(null, scripts.map(function (script) {
return { src: script };
}));
},
], callback);
async function getAdminScripts() {
const scripts = await plugins.fireHook('filter:admin.scripts.get', []);
return scripts.map(function (script) {
return { src: script };
});
}
middleware.admin.renderFooter = function (req, res, data, next) {
req.app.render('admin/footer', data, next);
async function getLatestVersion() {
try {
const result = await versions.getLatestVersion();
return result;
} catch (err) {
winston.error('[acp] Failed to fetch latest version' + err.stack);
}
return null;
}
middleware.admin.renderFooter = async function (req, res, data) {
return await req.app.renderAsync('admin/footer', data);
};
};

View File

@@ -1,9 +1,9 @@
'use strict';
var async = require('async');
var nconf = require('nconf');
var jsesc = require('jsesc');
var _ = require('lodash');
var util = require('util');
var db = require('../database');
var user = require('../user');
@@ -16,6 +16,7 @@ var translator = require('../translator');
var privileges = require('../privileges');
var languages = require('../languages');
var utils = require('../utils');
var helpers = require('./helpers');
var controllers = {
api: require('../controllers/api'),
@@ -23,36 +24,20 @@ var controllers = {
};
module.exports = function (middleware) {
middleware.buildHeader = function buildHeader(req, res, next) {
middleware.buildHeader = helpers.try(async function buildHeader(req, res, next) {
res.locals.renderHeader = true;
res.locals.isAPI = false;
async.waterfall([
function (next) {
if (req.uid >= 0) {
middleware.applyCSRF(req, res, next);
} else {
setImmediate(next);
}
},
function (next) {
async.parallel({
config: function (next) {
controllers.api.loadConfig(req, next);
},
plugins: function (next) {
plugins.fireHook('filter:middleware.buildHeader', { req: req, locals: res.locals }, next);
},
}, next);
},
function (results, next) {
res.locals.config = results.config;
// Return no arguments
setImmediate(next);
},
], next);
};
const [config] = await Promise.all([
controllers.api.loadConfig(req),
plugins.fireHook('filter:middleware.buildHeader', { req: req, locals: res.locals }),
]);
res.locals.config = config;
next();
});
middleware.generateHeader = function generateHeader(req, res, data, callback) {
middleware.buildHeaderAsync = util.promisify(middleware.buildHeader);
async function generateHeader(req, res, data) {
var registrationType = meta.config.registrationType || 'normal';
res.locals.config = res.locals.config || {};
var templateValues = {
@@ -73,207 +58,162 @@ module.exports = function (middleware) {
templateValues.configJSON = jsesc(JSON.stringify(res.locals.config), { isScriptContext: true });
async.waterfall([
function (next) {
async.parallel({
isAdmin: function (next) {
user.isAdministrator(req.uid, next);
},
isGlobalMod: function (next) {
user.isGlobalModerator(req.uid, next);
},
isModerator: function (next) {
user.isModeratorOfAnyCategory(req.uid, next);
},
privileges: function (next) {
privileges.global.get(req.uid, next);
},
user: function (next) {
user.getUserData(req.uid, next);
},
isEmailConfirmSent: function (next) {
if (!meta.config.requireEmailConfirmation || req.uid <= 0) {
return next(null, false);
}
db.get('uid:' + req.uid + ':confirm:email:sent', next);
},
languageDirection: function (next) {
translator.translate('[[language:dir]]', res.locals.config.userLang, function (translated) {
next(null, translated);
});
},
browserTitle: function (next) {
translator.translate(controllers.helpers.buildTitle(translator.unescape(data.title)), function (translated) {
next(null, translated);
});
},
navigation: async.apply(navigation.get, req.uid),
banned: async.apply(user.bans.isBanned, req.uid),
banReason: async.apply(user.bans.getReason, req.uid),
const results = await utils.promiseParallel({
isAdmin: user.isAdministrator(req.uid),
isGlobalMod: user.isGlobalModerator(req.uid),
isModerator: user.isModeratorOfAnyCategory(req.uid),
privileges: privileges.global.get(req.uid),
user: user.getUserData(req.uid),
isEmailConfirmSent: (!meta.config.requireEmailConfirmation || req.uid <= 0) ? false : await db.get('uid:' + req.uid + ':confirm:email:sent'),
languageDirection: translator.translate('[[language:dir]]', res.locals.config.userLang),
browserTitle: translator.translate(controllers.helpers.buildTitle(translator.unescape(data.title))),
navigation: navigation.get(req.uid),
banned: user.bans.isBanned(req.uid),
banReason: user.bans.getReason(req.uid),
unreadData: async.apply(topics.getUnreadData, { uid: req.uid }),
unreadChatCount: async.apply(messaging.getUnreadCount, req.uid),
unreadNotificationCount: async.apply(user.notifications.getUnreadCount, req.uid),
}, next);
},
function (results, next) {
if (results.banned) {
req.logout();
return res.redirect('/');
}
const unreadData = {
'': {},
new: {},
watched: {},
unreplied: {},
};
results.user.unreadData = unreadData;
results.user.isAdmin = results.isAdmin;
results.user.isGlobalMod = results.isGlobalMod;
results.user.isMod = !!results.isModerator;
results.user.privileges = results.privileges;
results.user[results.user.status] = true;
results.user.email = String(results.user.email);
results.user['email:confirmed'] = results.user['email:confirmed'] === 1;
results.user.isEmailConfirmSent = !!results.isEmailConfirmSent;
templateValues.bootswatchSkin = (parseInt(meta.config.disableCustomUserSkins, 10) !== 1 ? res.locals.config.bootswatchSkin : '') || meta.config.bootswatchSkin || '';
templateValues.config.bootswatchSkin = templateValues.bootswatchSkin || 'noskin'; // TODO remove in v1.12.0+
const unreadCounts = results.unreadData.counts;
var unreadCount = {
topic: unreadCounts[''] || 0,
newTopic: unreadCounts.new || 0,
watchedTopic: unreadCounts.watched || 0,
unrepliedTopic: unreadCounts.unreplied || 0,
chat: results.unreadChatCount || 0,
notification: results.unreadNotificationCount || 0,
};
Object.keys(unreadCount).forEach(function (key) {
if (unreadCount[key] > 99) {
unreadCount[key] = '99+';
}
});
const tidsByFilter = results.unreadData.tidsByFilter;
results.navigation = results.navigation.map(function (item) {
function modifyNavItem(item, route, filter, content) {
if (item && item.originalRoute === route) {
unreadData[filter] = _.zipObject(tidsByFilter[filter], tidsByFilter[filter].map(() => true));
item.content = content;
if (unreadCounts[filter] > 0) {
item.iconClass += ' unread-count';
}
}
}
modifyNavItem(item, '/unread', '', unreadCount.topic);
modifyNavItem(item, '/unread?filter=new', 'new', unreadCount.newTopic);
modifyNavItem(item, '/unread?filter=watched', 'watched', unreadCount.watchedTopic);
modifyNavItem(item, '/unread?filter=unreplied', 'unreplied', unreadCount.unrepliedTopic);
return item;
});
templateValues.browserTitle = results.browserTitle;
templateValues.navigation = results.navigation;
templateValues.unreadCount = unreadCount;
templateValues.isAdmin = results.user.isAdmin;
templateValues.isGlobalMod = results.user.isGlobalMod;
templateValues.showModMenu = results.user.isAdmin || results.user.isGlobalMod || results.user.isMod;
templateValues.canChat = results.canChat && meta.config.disableChat !== 1;
templateValues.user = results.user;
templateValues.userJSON = jsesc(JSON.stringify(results.user), { isScriptContext: true });
templateValues.useCustomCSS = meta.config.useCustomCSS && meta.config.customCSS;
templateValues.customCSS = templateValues.useCustomCSS ? (meta.config.renderedCustomCSS || '') : '';
templateValues.useCustomHTML = meta.config.useCustomHTML;
templateValues.customHTML = templateValues.useCustomHTML ? meta.config.customHTML : '';
templateValues.maintenanceHeader = meta.config.maintenanceMode && !results.isAdmin;
templateValues.defaultLang = meta.config.defaultLang || 'en-GB';
templateValues.userLang = res.locals.config.userLang;
templateValues.languageDirection = results.languageDirection;
templateValues.template = { name: res.locals.template };
templateValues.template[res.locals.template] = true;
if (data.hasOwnProperty('_header')) {
templateValues.metaTags = data._header.tags.meta;
templateValues.linkTags = data._header.tags.link;
}
if (req.route && req.route.path === '/') {
modifyTitle(templateValues);
}
plugins.fireHook('filter:middleware.renderHeader', {
req: req,
res: res,
templateValues: templateValues,
data: data,
}, next);
},
], function (err, data) {
callback(err, data.templateValues);
unreadData: topics.getUnreadData({ uid: req.uid }),
unreadChatCount: messaging.getUnreadCount(req.uid),
unreadNotificationCount: user.notifications.getUnreadCount(req.uid),
});
};
middleware.renderHeader = function renderHeader(req, res, data, callback) {
async.waterfall([
async.apply(middleware.generateHeader, req, res, data),
function (templateValues, next) {
req.app.render('header', templateValues, next);
},
], callback);
};
if (results.banned) {
req.logout();
return res.redirect('/');
}
middleware.renderFooter = function renderFooter(req, res, data, callback) {
async.waterfall([
function (next) {
plugins.fireHook('filter:middleware.renderFooter', {
req: req,
res: res,
templateValues: data,
}, next);
},
function (data, next) {
async.parallel({
scripts: async.apply(plugins.fireHook, 'filter:scripts.get', []),
timeagoLocale: (next) => {
async.waterfall([
async.apply(languages.listCodes),
(languageCodes, next) => {
const userLang = res.locals.config.userLang;
const timeagoCode = utils.userLangToTimeagoCode(userLang);
const unreadData = {
'': {},
new: {},
watched: {},
unreplied: {},
};
if (languageCodes.includes(userLang) && languages.timeagoCodes.includes(timeagoCode)) {
const pathToLocaleFile = '/vendor/jquery/timeago/locales/jquery.timeago.' + timeagoCode + '.js';
next(null, (nconf.get('relative_path') + '/assets' + pathToLocaleFile));
} else {
next(null, false);
}
},
], next);
},
}, function (err, results) {
next(err, data, results);
});
},
function (data, results, next) {
if (results.timeagoLocale) {
results.scripts.push(results.timeagoLocale);
results.user.unreadData = unreadData;
results.user.isAdmin = results.isAdmin;
results.user.isGlobalMod = results.isGlobalMod;
results.user.isMod = !!results.isModerator;
results.user.privileges = results.privileges;
results.user[results.user.status] = true;
results.user.email = String(results.user.email);
results.user['email:confirmed'] = results.user['email:confirmed'] === 1;
results.user.isEmailConfirmSent = !!results.isEmailConfirmSent;
templateValues.bootswatchSkin = (parseInt(meta.config.disableCustomUserSkins, 10) !== 1 ? res.locals.config.bootswatchSkin : '') || meta.config.bootswatchSkin || '';
templateValues.config.bootswatchSkin = templateValues.bootswatchSkin || 'noskin'; // TODO remove in v1.12.0+
const unreadCounts = results.unreadData.counts;
const unreadCount = {
topic: unreadCounts[''] || 0,
newTopic: unreadCounts.new || 0,
watchedTopic: unreadCounts.watched || 0,
unrepliedTopic: unreadCounts.unreplied || 0,
chat: results.unreadChatCount || 0,
notification: results.unreadNotificationCount || 0,
};
Object.keys(unreadCount).forEach(function (key) {
if (unreadCount[key] > 99) {
unreadCount[key] = '99+';
}
});
const tidsByFilter = results.unreadData.tidsByFilter;
results.navigation = results.navigation.map(function (item) {
function modifyNavItem(item, route, filter, content) {
if (item && item.originalRoute === route) {
unreadData[filter] = _.zipObject(tidsByFilter[filter], tidsByFilter[filter].map(() => true));
item.content = content;
if (unreadCounts[filter] > 0) {
item.iconClass += ' unread-count';
}
}
data.templateValues.scripts = results.scripts.map(function (script) {
return { src: script };
});
}
modifyNavItem(item, '/unread', '', unreadCount.topic);
modifyNavItem(item, '/unread?filter=new', 'new', unreadCount.newTopic);
modifyNavItem(item, '/unread?filter=watched', 'watched', unreadCount.watchedTopic);
modifyNavItem(item, '/unread?filter=unreplied', 'unreplied', unreadCount.unrepliedTopic);
return item;
});
data.templateValues.useCustomJS = meta.config.useCustomJS;
data.templateValues.customJS = data.templateValues.useCustomJS ? meta.config.customJS : '';
data.templateValues.isSpider = req.uid === -1;
req.app.render('footer', data.templateValues, next);
},
], callback);
templateValues.browserTitle = results.browserTitle;
templateValues.navigation = results.navigation;
templateValues.unreadCount = unreadCount;
templateValues.isAdmin = results.user.isAdmin;
templateValues.isGlobalMod = results.user.isGlobalMod;
templateValues.showModMenu = results.user.isAdmin || results.user.isGlobalMod || results.user.isMod;
templateValues.canChat = results.canChat && meta.config.disableChat !== 1;
templateValues.user = results.user;
templateValues.userJSON = jsesc(JSON.stringify(results.user), { isScriptContext: true });
templateValues.useCustomCSS = meta.config.useCustomCSS && meta.config.customCSS;
templateValues.customCSS = templateValues.useCustomCSS ? (meta.config.renderedCustomCSS || '') : '';
templateValues.useCustomHTML = meta.config.useCustomHTML;
templateValues.customHTML = templateValues.useCustomHTML ? meta.config.customHTML : '';
templateValues.maintenanceHeader = meta.config.maintenanceMode && !results.isAdmin;
templateValues.defaultLang = meta.config.defaultLang || 'en-GB';
templateValues.userLang = res.locals.config.userLang;
templateValues.languageDirection = results.languageDirection;
templateValues.template = { name: res.locals.template };
templateValues.template[res.locals.template] = true;
if (data.hasOwnProperty('_header')) {
templateValues.metaTags = data._header.tags.meta;
templateValues.linkTags = data._header.tags.link;
}
if (req.route && req.route.path === '/') {
modifyTitle(templateValues);
}
const hookReturn = await plugins.fireHook('filter:middleware.renderHeader', {
req: req,
res: res,
templateValues: templateValues,
data: data,
});
return hookReturn.templateValues;
}
middleware.renderHeader = async function renderHeader(req, res, data) {
return await req.app.renderAsync('header', await generateHeader(req, res, data));
};
middleware.renderFooter = async function renderFooter(req, res, templateValues) {
const data = await plugins.fireHook('filter:middleware.renderFooter', {
req: req,
res: res,
templateValues: templateValues,
});
const results = await utils.promiseParallel({
scripts: plugins.fireHook('filter:scripts.get', []),
timeagoLocale: (async () => {
const languageCodes = await languages.listCodes();
const userLang = res.locals.config.userLang;
const timeagoCode = utils.userLangToTimeagoCode(userLang);
if (languageCodes.includes(userLang) && languages.timeagoCodes.includes(timeagoCode)) {
const pathToLocaleFile = '/vendor/jquery/timeago/locales/jquery.timeago.' + timeagoCode + '.js';
return nconf.get('relative_path') + '/assets' + pathToLocaleFile;
}
return false;
})(),
});
if (results.timeagoLocale) {
results.scripts.push(results.timeagoLocale);
}
data.templateValues.scripts = results.scripts.map(function (script) {
return { src: script };
});
data.templateValues.useCustomJS = meta.config.useCustomJS;
data.templateValues.customJS = data.templateValues.useCustomJS ? meta.config.customJS : '';
data.templateValues.isSpider = req.uid === -1;
return await req.app.renderAsync('footer', data.templateValues);
};
function modifyTitle(obj) {

View File

@@ -6,9 +6,10 @@ var _ = require('lodash');
var meta = require('../meta');
var languages = require('../languages');
var helpers = require('./helpers');
module.exports = function (middleware) {
middleware.addHeaders = function addHeaders(req, res, next) {
middleware.addHeaders = helpers.try(function addHeaders(req, res, next) {
var headers = {
'X-Powered-By': encodeURI(meta.config['powered-by'] || 'NodeBB'),
'X-Frame-Options': meta.config['allow-from-uri'] ? 'ALLOW-FROM ' + encodeURI(meta.config['allow-from-uri']) : 'SAMEORIGIN',
@@ -61,31 +62,30 @@ module.exports = function (middleware) {
}
next();
};
});
let langs = [];
middleware.autoLocale = function autoLocale(req, res, next) {
middleware.autoLocale = helpers.try(async function autoLocale(req, res, next) {
if (parseInt(req.uid, 10) > 0 || !meta.config.autoDetectLang || req.query.lang) {
return next();
}
const langs = await listCodes();
const lang = req.acceptsLanguages(langs);
if (!lang) {
return next();
}
req.query.lang = lang;
next();
};
languages.listCodes(function (err, codes) {
if (err) {
winston.error('[middleware/autoLocale] Could not retrieve languages codes list!');
codes = [];
}
winston.verbose('[middleware/autoLocale] Retrieves languages list for middleware');
var defaultLang = meta.config.defaultLang || 'en-GB';
langs = _.uniq([defaultLang, ...codes]);
});
async function listCodes() {
const defaultLang = meta.config.defaultLang || 'en-GB';
try {
const codes = await languages.listCodes();
winston.verbose('[middleware/autoLocale] Retrieves languages list for middleware');
return _.uniq([defaultLang, ...codes]);
} catch (err) {
winston.error('[middleware/autoLocale] Could not retrieve languages codes list! ' + err.stack);
return [defaultLang];
}
}
};

22
src/middleware/helpers.js Normal file
View File

@@ -0,0 +1,22 @@
'use strict';
const helpers = module.exports;
helpers.try = function (middleware) {
if (middleware && middleware.constructor && middleware.constructor.name === 'AsyncFunction') {
return async function (req, res, next) {
try {
await middleware(req, res, next);
} catch (err) {
next(err);
}
};
}
return function (req, res, next) {
try {
middleware(req, res, next);
} catch (err) {
next(err);
}
};
};

View File

@@ -8,6 +8,7 @@ var nconf = require('nconf');
var ensureLoggedIn = require('connect-ensure-login');
var toobusy = require('toobusy-js');
var LRU = require('lru-cache');
var util = require('util');
var plugins = require('../plugins');
var meta = require('../meta');
@@ -15,6 +16,7 @@ var user = require('../user');
var groups = require('../groups');
var analytics = require('../analytics');
var privileges = require('../privileges');
var helpers = require('./helpers');
var controllers = {
api: require('../controllers/api'),
@@ -31,7 +33,7 @@ middleware.regexes = {
timestampedUpload: /^\d+-.+$/,
};
middleware.applyCSRF = csrf({
const csurfMiddleware = csrf({
cookie: nconf.get('url_parsed').protocol === 'https:' ? {
secure: true,
sameSite: 'Strict',
@@ -39,6 +41,14 @@ middleware.applyCSRF = csrf({
} : true,
});
middleware.applyCSRF = function (req, res, next) {
if (req.uid >= 0) {
csurfMiddleware(req, res, next);
} else {
next();
}
};
middleware.ensureLoggedIn = ensureLoggedIn.ensureLoggedIn(nconf.get('relative_path') + '/login');
require('./admin')(middleware);
@@ -51,56 +61,39 @@ require('./headers')(middleware);
middleware.stripLeadingSlashes = function stripLeadingSlashes(req, res, next) {
var target = req.originalUrl.replace(nconf.get('relative_path'), '');
if (target.startsWith('//')) {
res.redirect(nconf.get('relative_path') + target.replace(/^\/+/, '/'));
} else {
setImmediate(next);
return res.redirect(nconf.get('relative_path') + target.replace(/^\/+/, '/'));
}
next();
};
middleware.pageView = function pageView(req, res, next) {
analytics.pageView({
ip: req.ip,
uid: req.uid,
middleware.pageView = helpers.try(async function pageView(req, res, next) {
const promises = [
analytics.pageView({ ip: req.ip, uid: req.uid }),
];
if (req.loggedIn) {
promises.push(user.updateOnlineUsers(req.uid));
promises.push(user.updateLastOnlineTime(req.uid));
}
await Promise.all(promises);
plugins.fireHook('action:middleware.pageView', { req: req });
next();
});
middleware.pluginHooks = helpers.try(async function pluginHooks(req, res, next) {
// TODO: Deprecate in v2.0
await async.each(plugins.loadedHooks['filter:router.page'] || [], function (hookObj, next) {
hookObj.method(req, res, next);
});
plugins.fireHook('action:middleware.pageView', { req: req });
if (req.loggedIn) {
if (req.path.startsWith('/api/users') || req.path.startsWith('/users')) {
async.parallel([
async.apply(user.updateOnlineUsers, req.uid),
async.apply(user.updateLastOnlineTime, req.uid),
], next);
} else {
user.updateOnlineUsers(req.uid);
user.updateLastOnlineTime(req.uid);
setImmediate(next);
}
} else {
setImmediate(next);
}
};
middleware.pluginHooks = async function pluginHooks(req, res, next) {
// TODO: Deprecate in v2.0
try {
await async.each(plugins.loadedHooks['filter:router.page'] || [], function (hookObj, next) {
hookObj.method(req, res, next);
});
await plugins.fireHook('response:router.page', {
req: req,
res: res,
});
} catch (err) {
return next(err);
}
await plugins.fireHook('response:router.page', {
req: req,
res: res,
});
if (!res.headersSent) {
next();
}
};
});
middleware.validateFiles = function validateFiles(req, res, next) {
if (!Array.isArray(req.files.files) || !req.files.files.length) {
@@ -131,36 +124,28 @@ middleware.routeTouchIcon = function routeTouchIcon(req, res) {
});
};
middleware.privateTagListing = function privateTagListing(req, res, next) {
privileges.global.can('view:tags', req.uid, function (err, canView) {
if (err || canView) {
return next(err);
}
controllers.helpers.notAllowed(req, res);
});
};
middleware.privateTagListing = helpers.try(async function privateTagListing(req, res, next) {
const canView = await privileges.global.can('view:tags', req.uid);
if (!canView) {
return controllers.helpers.notAllowed(req, res);
}
next();
});
middleware.exposeGroupName = function exposeGroupName(req, res, next) {
expose('groupName', groups.getGroupNameByGroupSlug, 'slug', req, res, next);
};
middleware.exposeGroupName = helpers.try(async function exposeGroupName(req, res, next) {
await expose('groupName', groups.getGroupNameByGroupSlug, 'slug', req, res, next);
});
middleware.exposeUid = function exposeUid(req, res, next) {
expose('uid', user.getUidByUserslug, 'userslug', req, res, next);
};
middleware.exposeUid = helpers.try(async function exposeUid(req, res, next) {
await expose('uid', user.getUidByUserslug, 'userslug', req, res, next);
});
function expose(exposedField, method, field, req, res, next) {
async function expose(exposedField, method, field, req, res, next) {
if (!req.params.hasOwnProperty(field)) {
return next();
}
async.waterfall([
function (next) {
method(req.params[field], next);
},
function (id, next) {
res.locals[exposedField] = id;
next();
},
], next);
res.locals[exposedField] = await method(req.params[field]);
next();
}
middleware.privateUploads = function privateUploads(req, res, next) {
@@ -188,10 +173,13 @@ middleware.busyCheck = function busyCheck(req, res, next) {
}
};
middleware.applyBlacklist = function applyBlacklist(req, res, next) {
meta.blacklist.test(req.ip, function (err) {
middleware.applyBlacklist = async function applyBlacklist(req, res, next) {
try {
await meta.blacklist.test(req.ip);
next();
} catch (err) {
next(err);
});
}
};
middleware.delayLoading = function delayLoading(req, res, next) {
@@ -207,25 +195,19 @@ middleware.delayLoading = function delayLoading(req, res, next) {
setTimeout(next, 1000);
};
middleware.buildSkinAsset = function buildSkinAsset(req, res, next) {
middleware.buildSkinAsset = helpers.try(async function buildSkinAsset(req, res, next) {
// If this middleware is reached, a skin was requested, so it is built on-demand
var target = path.basename(req.originalUrl).match(/(client-[a-z]+)/);
if (target) {
async.waterfall([
async.apply(plugins.prepareForBuild, ['client side styles']),
async.apply(meta.css.buildBundle, target[0], true),
], function (err, css) {
if (err) {
return next();
}
require('../meta/minifier').killAll();
res.status(200).type('text/css').send(css);
});
} else {
setImmediate(next);
const target = path.basename(req.originalUrl).match(/(client-[a-z]+)/);
if (!target) {
return next();
}
};
await plugins.prepareForBuild(['client side styles']);
const buildBundle = util.promisify(meta.css.buildBundle);
const css = await buildBundle(target[0], true);
require('../meta/minifier').killAll();
res.status(200).type('text/css').send(css);
});
middleware.trimUploadTimestamps = function trimUploadTimestamps(req, res, next) {
// Check match
@@ -238,19 +220,18 @@ middleware.trimUploadTimestamps = function trimUploadTimestamps(req, res, next)
next();
};
middleware.validateAuth = function validateAuth(req, res, next) {
plugins.fireHook('static:auth.validate', {
user: res.locals.user,
strategy: res.locals.strategy,
}, function (err) {
if (err) {
return req.session.regenerate(function () {
req.uid = 0;
req.loggedIn = false;
next(err);
});
}
middleware.validateAuth = helpers.try(async function validateAuth(req, res, next) {
try {
await plugins.fireHook('static:auth.validate', {
user: res.locals.user,
strategy: res.locals.strategy,
});
next();
});
};
} catch (err) {
const regenerateSession = util.promisify(cb => req.session.regenerate(cb));
await regenerateSession();
req.uid = 0;
req.loggedIn = false;
next(err);
}
});

View File

@@ -4,11 +4,12 @@ const util = require('util');
const nconf = require('nconf');
const meta = require('../meta');
const user = require('../user');
const helpers = require('./helpers');
module.exports = function (middleware) {
middleware.maintenanceMode = async function maintenanceMode(req, res, next) {
middleware.maintenanceMode = helpers.try(async function maintenanceMode(req, res, next) {
if (!meta.config.maintenanceMode) {
return setImmediate(next);
return next();
}
const hooksAsync = util.promisify(middleware.pluginHooks);
@@ -16,12 +17,12 @@ module.exports = function (middleware) {
const url = req.url.replace(nconf.get('relative_path'), '');
if (url.startsWith('/login') || url.startsWith('/api/login')) {
return setImmediate(next);
return next();
}
const isAdmin = await user.isAdministrator(req.uid);
if (isAdmin) {
return setImmediate(next);
return next();
}
res.status(meta.config.maintenanceModeStatus);
@@ -34,8 +35,7 @@ module.exports = function (middleware) {
if (res.locals.isAPI) {
return res.json(data);
}
const buildHeaderAsync = util.promisify(middleware.buildHeader);
await buildHeaderAsync(req, res);
await middleware.buildHeaderAsync(req, res);
res.render('503', data);
};
});
};

View File

@@ -12,8 +12,6 @@ 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
const render = res.render;
@@ -63,9 +61,9 @@ module.exports = function (middleware) {
const renderAsync = util.promisify((templateToRender, options, next) => render.call(self, templateToRender, options, next));
const results = await utils.promiseParallel({
header: renderHeaderFooterAsync('renderHeader', req, res, options),
header: renderHeaderFooter('renderHeader', req, res, options),
content: renderAsync(templateToRender, options),
footer: renderHeaderFooterAsync('renderFooter', req, res, options),
footer: renderHeaderFooter('renderFooter', req, res, options),
});
const str = results.header +
@@ -89,14 +87,13 @@ module.exports = function (middleware) {
next();
};
function renderHeaderFooter(method, req, res, options, next) {
async function renderHeaderFooter(method, req, res, options) {
if (res.locals.renderHeader) {
middleware[method](req, res, options, next);
return await middleware[method](req, res, options);
} else if (res.locals.renderAdminHeader) {
middleware.admin[method](req, res, options, next);
} else {
next(null, '');
return await middleware.admin[method](req, res, options);
}
return '';
}
async function translate(str, req, res) {

View File

@@ -1,6 +1,5 @@
'use strict';
const util = require('util');
const nconf = require('nconf');
const winston = require('winston');
@@ -8,7 +7,7 @@ const meta = require('../meta');
const user = require('../user');
const privileges = require('../privileges');
const plugins = require('../plugins');
const helpers = require('./helpers');
const auth = require('../routes/authentication');
const controllers = {
@@ -16,9 +15,9 @@ const controllers = {
};
module.exports = function (middleware) {
async function authenticate(req, res, next, callback) {
async function authenticate(req, res) {
if (req.loggedIn) {
return next();
return true;
}
await plugins.fireHook('response:middleware.authenticate', {
@@ -28,35 +27,35 @@ module.exports = function (middleware) {
});
if (!res.headersSent) {
auth.setAuthVars(req, res, function () {
if (req.loggedIn && req.user && req.user.uid) {
return next();
}
callback();
});
auth.setAuthVars(req);
}
return !res.headersSent;
}
middleware.authenticate = function middlewareAuthenticate(req, res, next) {
authenticate(req, res, next, function () {
controllers.helpers.notAllowed(req, res, next);
});
};
middleware.authenticate = helpers.try(async function middlewareAuthenticate(req, res, next) {
if (!await authenticate(req, res)) {
return;
}
if (!req.loggedIn) {
return controllers.helpers.notAllowed(req, res);
}
next();
});
const authenticateAsync = util.promisify(middleware.authenticate);
middleware.authenticateOrGuest = helpers.try(async function authenticateOrGuest(req, res, next) {
if (!await authenticate(req, res)) {
return;
}
next();
});
middleware.authenticateOrGuest = function authenticateOrGuest(req, res, next) {
authenticate(req, res, next, next);
};
middleware.ensureSelfOrGlobalPrivilege = helpers.try(async function ensureSelfOrGlobalPrivilege(req, res, next) {
await ensureSelfOrMethod(user.isAdminOrGlobalMod, req, res, next);
});
middleware.ensureSelfOrGlobalPrivilege = function ensureSelfOrGlobalPrivilege(req, res, next) {
ensureSelfOrMethod(user.isAdminOrGlobalMod, req, res, next);
};
middleware.ensureSelfOrPrivileged = function ensureSelfOrPrivileged(req, res, next) {
ensureSelfOrMethod(user.isPrivileged, req, res, next);
};
middleware.ensureSelfOrPrivileged = helpers.try(async function ensureSelfOrPrivileged(req, res, next) {
await ensureSelfOrMethod(user.isPrivileged, req, res, next);
});
async function ensureSelfOrMethod(method, req, res, next) {
/*
@@ -67,7 +66,7 @@ module.exports = function (middleware) {
return controllers.helpers.notAllowed(req, res);
}
if (req.uid === parseInt(res.locals.uid, 10)) {
return setImmediate(next);
return next();
}
const allowed = await method(req.uid);
if (!allowed) {
@@ -77,12 +76,12 @@ module.exports = function (middleware) {
return next();
}
middleware.checkGlobalPrivacySettings = function checkGlobalPrivacySettings(req, res, next) {
middleware.checkGlobalPrivacySettings = helpers.try(async function checkGlobalPrivacySettings(req, res, next) {
winston.warn('[middleware], checkGlobalPrivacySettings deprecated, use canViewUsers or canViewGroups');
middleware.canViewUsers(req, res, next);
};
await middleware.canViewUsers(req, res, next);
});
middleware.canViewUsers = async function canViewUsers(req, res, next) {
middleware.canViewUsers = helpers.try(async function canViewUsers(req, res, next) {
if (parseInt(res.locals.uid, 10) === req.uid) {
return next();
}
@@ -91,19 +90,24 @@ module.exports = function (middleware) {
return next();
}
controllers.helpers.notAllowed(req, res);
};
});
middleware.canViewGroups = async function canViewGroups(req, res, next) {
middleware.canViewGroups = helpers.try(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 = async function checkAccountPermissions(req, res, next) {
middleware.checkAccountPermissions = helpers.try(async function checkAccountPermissions(req, res, next) {
// This middleware ensures that only the requested user and admins can pass
await authenticateAsync(req, res);
if (!await authenticate(req, res)) {
return;
}
if (!req.loggedIn) {
return controllers.helpers.notAllowed(req, res);
}
const uid = await user.getUidByUserslug(req.params.userslug);
let allowed = await privileges.users.canEdit(req.uid, uid);
if (allowed) {
@@ -117,17 +121,17 @@ module.exports = function (middleware) {
return next();
}
controllers.helpers.notAllowed(req, res);
};
});
middleware.redirectToAccountIfLoggedIn = async function redirectToAccountIfLoggedIn(req, res, next) {
middleware.redirectToAccountIfLoggedIn = helpers.try(async function redirectToAccountIfLoggedIn(req, res, next) {
if (req.session.forceLogin || req.uid <= 0) {
return next();
}
const userslug = await user.getUserField(req.uid, 'userslug');
controllers.helpers.redirect(res, '/user/' + userslug);
};
});
middleware.redirectUidToUserslug = async function redirectUidToUserslug(req, res, next) {
middleware.redirectUidToUserslug = helpers.try(async function redirectUidToUserslug(req, res, next) {
const uid = parseInt(req.params.uid, 10);
if (uid <= 0) {
return next();
@@ -140,18 +144,18 @@ module.exports = function (middleware) {
.replace('uid', 'user')
.replace(uid, function () { return userslug; });
controllers.helpers.redirect(res, path);
};
});
middleware.redirectMeToUserslug = async function redirectMeToUserslug(req, res) {
middleware.redirectMeToUserslug = helpers.try(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) {
middleware.isAdmin = helpers.try(async function isAdmin(req, res, next) {
const isAdmin = await user.isAdministrator(req.uid);
if (!isAdmin) {
return controllers.helpers.notAllowed(req, res);
@@ -186,7 +190,7 @@ module.exports = function (middleware) {
} else {
res.redirect(nconf.get('relative_path') + '/login?local=1');
}
};
});
middleware.requireUser = function (req, res, next) {
if (req.loggedIn) {

View File

@@ -8,13 +8,7 @@ module.exports = function (app, middleware, controllers) {
var router = express.Router();
app.use('/api', router);
router.get('/config', function (req, res, next) {
if (req.uid >= 0) {
middleware.applyCSRF(req, res, next);
} else {
setImmediate(next);
}
}, controllers.api.getConfig);
router.get('/config', middleware.applyCSRF, controllers.api.getConfig);
router.get('/me', controllers.user.getCurrentUser);
router.get('/user/uid/:uid', middleware.canViewUsers, controllers.user.getUserByUID);

View File

@@ -23,14 +23,17 @@ Auth.initialize = function (app, middleware) {
passportSessionMiddleware(req, res, next);
});
app.use(Auth.setAuthVars);
app.use(function (req, res, next) {
Auth.setAuthVars(req, res);
next();
});
Auth.app = app;
Auth.middleware = middleware;
};
Auth.setAuthVars = function setAuthVars(req, res, next) {
var isSpider = req.isSpider();
Auth.setAuthVars = function setAuthVars(req) {
const isSpider = req.isSpider();
req.loggedIn = !isSpider && !!req.user;
if (req.user) {
req.uid = parseInt(req.user.uid, 10);
@@ -39,7 +42,6 @@ Auth.setAuthVars = function setAuthVars(req, res, next) {
} else {
req.uid = 0;
}
next();
};
Auth.getLoginStrategies = function () {

View File

@@ -5,7 +5,7 @@ var helpers = module.exports;
helpers.setupPageRoute = function (router, name, middleware, middlewares, controller) {
middlewares = [middleware.maintenanceMode, middleware.registrationComplete, middleware.pageView, middleware.pluginHooks].concat(middlewares);
router.get(name, middleware.busyCheck, middleware.buildHeader, middlewares, helpers.tryRoute(controller));
router.get(name, middleware.busyCheck, middleware.applyCSRF, middleware.buildHeader, middlewares, helpers.tryRoute(controller));
router.get('/api' + name, middlewares, helpers.tryRoute(controller));
};

View File

@@ -56,7 +56,7 @@ function topicRoutes(app, middleware, controllers) {
function postRoutes(app, middleware, controllers) {
const middlewares = [middleware.maintenanceMode, middleware.registrationComplete, middleware.pluginHooks];
app.get('/post/:pid', middleware.busyCheck, middleware.buildHeader, middlewares, controllers.posts.redirectToPost);
app.get('/post/:pid', middleware.busyCheck, middlewares, controllers.posts.redirectToPost);
app.get('/api/post/:pid', middlewares, controllers.posts.redirectToPost);
}