diff --git a/app.js b/app.js index 90f5ef046e..7a0b7bc2a5 100644 --- a/app.js +++ b/app.js @@ -26,7 +26,10 @@ if (require.main !== module) { } var nconf = require('nconf'); -nconf.argv().env('__'); +nconf.argv().env({ + separator: '__', + lowerCase: true, +}); var url = require('url'); var async = require('async'); diff --git a/loader.js b/loader.js index 214f785eb9..f897fd79ce 100644 --- a/loader.js +++ b/loader.js @@ -142,7 +142,7 @@ function getPorts() { process.exit(); } var urlObject = url.parse(_url); - var port = nconf.get('port') || nconf.get('PORT') || urlObject.port || 4567; + var port = nconf.get('port') || urlObject.port || 4567; if (!Array.isArray(port)) { port = [port]; } diff --git a/package.json b/package.json index f092c45508..0a0b28dd14 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "nodebb-plugin-emoji-extended": "1.1.1", "nodebb-plugin-emoji-one": "1.2.1", "nodebb-plugin-markdown": "7.1.1", - "nodebb-plugin-mentions": "2.0.3", + "nodebb-plugin-mentions": "2.1.1", "nodebb-plugin-soundpack-default": "1.0.0", "nodebb-plugin-spam-be-gone": "0.5.0", "nodebb-rewards-essentials": "0.0.9", diff --git a/public/language/ja/admin/general/dashboard.json b/public/language/ja/admin/general/dashboard.json index ed544afb51..124d26c394 100644 --- a/public/language/ja/admin/general/dashboard.json +++ b/public/language/ja/admin/general/dashboard.json @@ -20,12 +20,12 @@ "stats.all": "全て", "updates": "更新", - "running-version": "NodeBB v %1 を実行しています。", + "running-version": "NodeBB v%1 を実行しています。", "keep-updated": "常に最新のセキュリティパッチとバグ修正のためにNodeBBが最新であることを確認してください。", - "up-to-date": "

あなたは最新の状態です。 ", + "up-to-date": "

あなたは最新の状態です。

", "upgrade-available": "

新しいバージョン (v%1) がリリースされました。NodeBBのアップグレードを検討してください。

", "prerelease-upgrade-available": "

これはNodeBBの旧リリースのバージョンです。新しいバージョン(v%1)がリリースされました。 NodeBBのアップグレードを検討してください。", - "prerelease-warning": "

これはNodeBBのプレリリース版です。意図しないバグが発生することがあります。

", + "prerelease-warning": "

これはNodeBBのプレリリース版です。意図しないバグが発生することがあります。

", "running-in-development": "フォーラムが開発モードで動作しています。フォーラムの動作が脆弱かもしれませんので、管理者に問い合わせてください。", "notices": "通知", diff --git a/public/src/client/category.js b/public/src/client/category.js index fd757e7803..a91f32b03e 100644 --- a/public/src/client/category.js +++ b/public/src/client/category.js @@ -15,8 +15,8 @@ define('forum/category', [ ], function (infinitescroll, share, navigator, categoryTools, sort, components, translator, topicSelect, pagination, storage) { var Category = {}; - $(window).on('action:ajaxify.end', function (ev, data) { - if (data.tpl_url !== 'category') { + $(window).on('action:ajaxify.start', function (ev, data) { + if (data.url && !data.url.startsWith('category/')) { navigator.disable(); removeListeners(); diff --git a/public/src/client/topic.js b/public/src/client/topic.js index 3651c8be2b..08a707f029 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -23,16 +23,14 @@ define('forum/topic', [ Topic.replaceURLTimeout = 0; } - if (ajaxify.currentPage !== data.url) { + if (data.url && !data.url.startsWith('topic/')) { navigator.disable(); components.get('navbar/title').find('span').text('').hide(); app.removeAlert('bookmark'); events.removeListeners(); $(window).off('keydown', onKeyDown); - } - if (data.url && !data.url.startsWith('topic/')) { require(['search'], function (search) { if (search.topicDOM.active) { search.topicDOM.end(); diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index 707a9a5b0f..90b845538e 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -199,7 +199,7 @@ define('forum/topic/postTools', [ var selectedNode = getSelectedNode(); showStaleWarning(function () { - var username = getUserName(button); + var username = getUserSlug(button); if (getData(button, 'data-uid') === '0' || !getData(button, 'data-userslug')) { username = ''; } @@ -231,7 +231,7 @@ define('forum/topic/postTools', [ var selectedNode = getSelectedNode(); showStaleWarning(function () { - var username = getUserName(button); + var username = getUserSlug(button); var toPid = getData(button, 'data-pid'); function quote(text) { @@ -284,7 +284,7 @@ define('forum/topic/postTools', [ selectedText = range.toString(); var postEl = $(content).parents('[component="post"]'); selectedPid = postEl.attr('data-pid'); - username = getUserName($(content)); + username = getUserSlug($(content)); range.detach(); } return { text: selectedText, pid: selectedPid, username: username }; @@ -309,22 +309,22 @@ define('forum/topic/postTools', [ return button.parents('[data-pid]').attr(data); } - function getUserName(button) { - var username = ''; + function getUserSlug(button) { + var slug = ''; var post = button.parents('[data-pid]'); if (button.attr('component') === 'topic/reply') { - return username; + return slug; } if (post.length) { - username = post.attr('data-username').replace(/\s/g, '-'); + slug = post.attr('data-userslug'); } if (post.length && post.attr('data-uid') !== '0') { - username = '@' + username; + slug = '@' + slug; } - return username; + return slug; } function togglePostDelete(button, tid) { diff --git a/public/src/modules/settings.js b/public/src/modules/settings.js index 93583d6ed9..5dfa5def2e 100644 --- a/public/src/modules/settings.js +++ b/public/src/modules/settings.js @@ -470,6 +470,9 @@ define('settings', function () { } } + // Save loaded settings into ajaxify.data for use client-side + ajaxify.data.settings = values; + $(formEl).deserialize(values); $(formEl).find('input[type="checkbox"]').each(function () { $(this).parents('.mdl-switch').toggleClass('is-checked', $(this).is(':checked')); @@ -510,6 +513,9 @@ define('settings', function () { // Remove unsaved flag to re-enable ajaxify app.flags._unsaved = false; + // Also save to local ajaxify.data + ajaxify.data.settings = values; + if (typeof callback === 'function') { callback(err); } else if (err) { diff --git a/src/controllers/category.js b/src/controllers/category.js index 867f17c5e9..96e3d8c2bf 100644 --- a/src/controllers/category.js +++ b/src/controllers/category.js @@ -154,12 +154,10 @@ categoryController.get = function (req, res, callback) { categoryData.description = translator.escape(categoryData.description); categoryData.privileges = userPrivileges; categoryData.showSelect = categoryData.privileges.editable; - + categoryData.rssFeedUrl = nconf.get('url') + '/category/' + categoryData.cid + '.rss'; if (parseInt(req.uid, 10)) { categories.markAsRead([cid], req.uid); - categoryData.rssFeedUrl = nconf.get('url') + '/category/' + categoryData.cid + '.rss?uid=' + req.uid + '&token=' + rssToken; - } else { - categoryData.rssFeedUrl = nconf.get('url') + '/category/' + categoryData.cid + '.rss'; + categoryData.rssFeedUrl += '?uid=' + req.uid + '&token=' + rssToken; } addTags(categoryData, res); diff --git a/src/controllers/topics.js b/src/controllers/topics.js index c702ba90fc..a794d40c8a 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -15,7 +15,7 @@ var helpers = require('./helpers'); var pagination = require('../pagination'); var utils = require('../utils'); -var topicsController = {}; +var topicsController = module.exports; topicsController.get = function (req, res, callback) { var tid = req.params.topic_id; @@ -23,6 +23,7 @@ topicsController.get = function (req, res, callback) { var pageCount = 1; var userPrivileges; var settings; + var rssToken; if ((req.params.post_index && !utils.isNumber(req.params.post_index)) || !utils.isNumber(tid)) { return callback(); @@ -40,6 +41,9 @@ topicsController.get = function (req, res, callback) { topic: function (next) { topics.getTopicData(tid, next); }, + rssToken: function (next) { + user.auth.getFeedToken(req.uid, next); + }, }, next); }, function (results, next) { @@ -48,6 +52,7 @@ topicsController.get = function (req, res, callback) { } userPrivileges = results.privileges; + rssToken = results.rssToken; if (!userPrivileges['topics:read'] || (parseInt(results.topic.deleted, 10) && !userPrivileges.view_deleted)) { return helpers.notAllowed(req, res); @@ -129,167 +134,173 @@ topicsController.get = function (req, res, callback) { plugins.fireHook('filter:controllers.topic.get', { topicData: topicData, uid: req.uid }, next); }, function (data, next) { - var breadcrumbs = [ - { - text: data.topicData.category.name, - url: nconf.get('relative_path') + '/category/' + data.topicData.category.slug, - }, - { - text: data.topicData.title, - }, - ]; - - helpers.buildCategoryBreadcrumbs(data.topicData.category.parentCid, function (err, crumbs) { - if (err) { - return next(err); - } - data.topicData.breadcrumbs = crumbs.concat(breadcrumbs); - next(null, data.topicData); - }); + buildBreadcrumbs(data.topicData, next); }, - function (topicData, next) { - function findPost(index) { - for (var i = 0; i < topicData.posts.length; i += 1) { - if (parseInt(topicData.posts[i].index, 10) === parseInt(index, 10)) { - return topicData.posts[i]; - } - } + function (topicData) { + topicData.privileges = userPrivileges; + topicData.topicStaleDays = parseInt(meta.config.topicStaleDays, 10) || 60; + topicData['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1; + topicData['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1; + topicData['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1; + topicData.bookmarkThreshold = parseInt(meta.config.bookmarkThreshold, 10) || 5; + topicData.postEditDuration = parseInt(meta.config.postEditDuration, 10) || 0; + topicData.postDeleteDuration = parseInt(meta.config.postDeleteDuration, 10) || 0; + topicData.scrollToMyPost = settings.scrollToMyPost; + topicData.rssFeedUrl = nconf.get('relative_path') + '/topic/' + topicData.tid + '.rss'; + if (req.uid) { + topicData.rssFeedUrl += '?uid=' + req.uid + '&token=' + rssToken; } - var description = ''; - var postAtIndex = findPost(Math.max(0, req.params.post_index - 1)); + topicData.postIndex = req.params.post_index; + topicData.pagination = pagination.create(currentPage, pageCount, req.query); + topicData.pagination.rel.forEach(function (rel) { + rel.href = nconf.get('url') + '/topic/' + topicData.slug + rel.href; + res.locals.linkTags.push(rel); + }); - if (postAtIndex && postAtIndex.content) { - description = S(postAtIndex.content).decodeHTMLEntities().stripTags().s; + req.session.tids_viewed = req.session.tids_viewed || {}; + if (!req.session.tids_viewed[tid] || req.session.tids_viewed[tid] < Date.now() - 3600000) { + topics.increaseViewCount(tid); + req.session.tids_viewed[tid] = Date.now(); } - if (description.length > 255) { - description = description.substr(0, 255) + '...'; - } + addTags(topicData, req, res); - var ogImageUrl = ''; - if (topicData.thumb) { - ogImageUrl = topicData.thumb; - } else if (postAtIndex && postAtIndex.user && postAtIndex.user.picture) { - ogImageUrl = postAtIndex.user.picture; - } else if (meta.config['og:image']) { - ogImageUrl = meta.config['og:image']; - } else if (meta.config['brand:logo']) { - ogImageUrl = meta.config['brand:logo']; - } else { - ogImageUrl = '/logo.png'; - } - - if (typeof ogImageUrl === 'string' && ogImageUrl.indexOf('http') === -1) { - ogImageUrl = nconf.get('url') + ogImageUrl; + if (req.uid) { + topics.markAsRead([tid], req.uid, function (err, markedRead) { + if (err) { + return callback(err); + } + if (markedRead) { + topics.pushUnreadCount(req.uid); + topics.markTopicNotificationsRead([tid], req.uid); + } + }); } - description = description.replace(/\n/g, ' '); - - res.locals.metaTags = [ - { - name: 'title', - content: topicData.titleRaw, - }, - { - name: 'description', - content: description, - }, - { - property: 'og:title', - content: topicData.titleRaw, - }, - { - property: 'og:description', - content: description, - }, - { - property: 'og:type', - content: 'article', - }, - { - property: 'og:image', - content: ogImageUrl, - noEscape: true, - }, - { - property: 'og:image:url', - content: ogImageUrl, - noEscape: true, - }, - { - property: 'article:published_time', - content: utils.toISOString(topicData.timestamp), - }, - { - property: 'article:modified_time', - content: utils.toISOString(topicData.lastposttime), - }, - { - property: 'article:section', - content: topicData.category ? topicData.category.name : '', - }, - ]; - - res.locals.linkTags = [ - { - rel: 'alternate', - type: 'application/rss+xml', - href: nconf.get('url') + '/topic/' + tid + '.rss', - }, - ]; + res.render('topic', topicData); + }, + ], callback); +}; - if (topicData.category) { - res.locals.linkTags.push({ - rel: 'up', - href: nconf.get('url') + '/category/' + topicData.category.slug, - }); - } +function buildBreadcrumbs(topicData, callback) { + var breadcrumbs = [ + { + text: topicData.category.name, + url: nconf.get('relative_path') + '/category/' + topicData.category.slug, + }, + { + text: topicData.title, + }, + ]; + async.waterfall([ + function (next) { + helpers.buildCategoryBreadcrumbs(topicData.category.parentCid, next); + }, + function (crumbs, next) { + topicData.breadcrumbs = crumbs.concat(breadcrumbs); next(null, topicData); }, - ], function (err, data) { - if (err) { - return callback(err); + ], callback); +} + +function addTags(topicData, req, res) { + function findPost(index) { + for (var i = 0; i < topicData.posts.length; i += 1) { + if (parseInt(topicData.posts[i].index, 10) === parseInt(index, 10)) { + return topicData.posts[i]; + } } + } + var description = ''; + var postAtIndex = findPost(Math.max(0, req.params.post_index - 1)); - data.privileges = userPrivileges; - data.topicStaleDays = parseInt(meta.config.topicStaleDays, 10) || 60; - data['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1; - data['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1; - data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1; - data.bookmarkThreshold = parseInt(meta.config.bookmarkThreshold, 10) || 5; - data.postEditDuration = parseInt(meta.config.postEditDuration, 10) || 0; - data.postDeleteDuration = parseInt(meta.config.postDeleteDuration, 10) || 0; - data.scrollToMyPost = settings.scrollToMyPost; - data.rssFeedUrl = nconf.get('relative_path') + '/topic/' + data.tid + '.rss'; - data.postIndex = req.params.post_index; - data.pagination = pagination.create(currentPage, pageCount, req.query); - data.pagination.rel.forEach(function (rel) { - rel.href = nconf.get('url') + '/topic/' + data.slug + rel.href; - res.locals.linkTags.push(rel); - }); + if (postAtIndex && postAtIndex.content) { + description = S(postAtIndex.content).decodeHTMLEntities().stripTags().s; + } - req.session.tids_viewed = req.session.tids_viewed || {}; - if (!req.session.tids_viewed[tid] || req.session.tids_viewed[tid] < Date.now() - 3600000) { - topics.increaseViewCount(tid); - req.session.tids_viewed[tid] = Date.now(); - } + if (description.length > 255) { + description = description.substr(0, 255) + '...'; + } - if (req.uid) { - topics.markAsRead([tid], req.uid, function (err, markedRead) { - if (err) { - return callback(err); - } - if (markedRead) { - topics.pushUnreadCount(req.uid); - topics.markTopicNotificationsRead([tid], req.uid); - } - }); - } + var ogImageUrl = ''; + if (topicData.thumb) { + ogImageUrl = topicData.thumb; + } else if (postAtIndex && postAtIndex.user && postAtIndex.user.picture) { + ogImageUrl = postAtIndex.user.picture; + } else if (meta.config['og:image']) { + ogImageUrl = meta.config['og:image']; + } else if (meta.config['brand:logo']) { + ogImageUrl = meta.config['brand:logo']; + } else { + ogImageUrl = '/logo.png'; + } - res.render('topic', data); - }); -}; + if (typeof ogImageUrl === 'string' && ogImageUrl.indexOf('http') === -1) { + ogImageUrl = nconf.get('url') + ogImageUrl; + } + + description = description.replace(/\n/g, ' '); + res.locals.metaTags = [ + { + name: 'title', + content: topicData.titleRaw, + }, + { + name: 'description', + content: description, + }, + { + property: 'og:title', + content: topicData.titleRaw, + }, + { + property: 'og:description', + content: description, + }, + { + property: 'og:type', + content: 'article', + }, + { + property: 'og:image', + content: ogImageUrl, + noEscape: true, + }, + { + property: 'og:image:url', + content: ogImageUrl, + noEscape: true, + }, + { + property: 'article:published_time', + content: utils.toISOString(topicData.timestamp), + }, + { + property: 'article:modified_time', + content: utils.toISOString(topicData.lastposttime), + }, + { + property: 'article:section', + content: topicData.category ? topicData.category.name : '', + }, + ]; + + res.locals.linkTags = [ + { + rel: 'alternate', + type: 'application/rss+xml', + href: topicData.rssFeedUrl, + }, + ]; + + if (topicData.category) { + res.locals.linkTags.push({ + rel: 'up', + href: nconf.get('url') + '/category/' + topicData.category.slug, + }); + } +} topicsController.teaser = function (req, res, next) { var tid = req.params.topic_id; @@ -355,5 +366,3 @@ topicsController.pagination = function (req, res, callback) { res.json(paginationData); }); }; - -module.exports = topicsController; diff --git a/src/database/mongo.js b/src/database/mongo.js index 14dcd330ac..c79ee81bdc 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -206,18 +206,18 @@ mongoModule.info = function (db, callback) { stats.mem = results.serverStatus.mem; stats.mem = results.serverStatus.mem; - stats.mem.resident = (stats.mem.resident / 1024).toFixed(2); - stats.mem.virtual = (stats.mem.virtual / 1024).toFixed(2); - stats.mem.mapped = (stats.mem.mapped / 1024).toFixed(2); + stats.mem.resident = (stats.mem.resident / 1024).toFixed(3); + stats.mem.virtual = (stats.mem.virtual / 1024).toFixed(3); + stats.mem.mapped = (stats.mem.mapped / 1024).toFixed(3); stats.collectionData = results.listCollections; stats.network = results.serverStatus.network; stats.raw = JSON.stringify(stats, null, 4); stats.avgObjSize = stats.avgObjSize.toFixed(2); - stats.dataSize = (stats.dataSize / scale).toFixed(2); - stats.storageSize = (stats.storageSize / scale).toFixed(2); - stats.fileSize = stats.fileSize ? (stats.fileSize / scale).toFixed(2) : 0; - stats.indexSize = (stats.indexSize / scale).toFixed(2); + stats.dataSize = (stats.dataSize / scale).toFixed(3); + stats.storageSize = (stats.storageSize / scale).toFixed(3); + stats.fileSize = stats.fileSize ? (stats.fileSize / scale).toFixed(3) : 0; + stats.indexSize = (stats.indexSize / scale).toFixed(3); stats.storageEngine = results.serverStatus.storageEngine ? results.serverStatus.storageEngine.name : 'mmapv1'; stats.host = results.serverStatus.host; stats.version = results.serverStatus.version; diff --git a/src/database/redis.js b/src/database/redis.js index 75129b6c8b..13d87c27bd 100644 --- a/src/database/redis.js +++ b/src/database/redis.js @@ -152,7 +152,7 @@ redisModule.info = function (cxn, callback) { redisData[parts[0]] = parts[1]; } }); - redisData.used_memory_human = (redisData.used_memory / (1024 * 1024 * 1024)).toFixed(2); + redisData.used_memory_human = (redisData.used_memory / (1024 * 1024 * 1024)).toFixed(3); redisData.raw = JSON.stringify(redisData, null, 4); redisData.redis = true; diff --git a/src/meta/settings.js b/src/meta/settings.js index 18eae100c3..d157a4cd95 100644 --- a/src/meta/settings.js +++ b/src/meta/settings.js @@ -36,7 +36,9 @@ Settings.set = function (hash, values, callback) { }; Settings.setOne = function (hash, field, value, callback) { - db.setObjectField('settings:' + hash, field, value, callback); + var data = {}; + data[field] = value; + Settings.set(hash, data, callback); }; Settings.setOnEmpty = function (hash, values, callback) { @@ -54,7 +56,7 @@ Settings.setOnEmpty = function (hash, values, callback) { }); if (Object.keys(empty).length) { - db.setObject('settings:' + hash, empty, next); + Settings.set(hash, empty, next); } else { next(); } diff --git a/src/routes/feeds.js b/src/routes/feeds.js index f2a772a7bd..62b8e6f650 100644 --- a/src/routes/feeds.js +++ b/src/routes/feeds.js @@ -26,7 +26,7 @@ module.exports = function (app, middleware) { app.get('/tags/:tag.rss', middleware.maintenanceMode, generateForTag); }; -function validateTokenIfRequiresLogin(requiresLogin, req, res, callback) { +function validateTokenIfRequiresLogin(requiresLogin, cid, req, res, callback) { var uid = req.query.uid; var token = req.query.token; @@ -38,23 +38,31 @@ function validateTokenIfRequiresLogin(requiresLogin, req, res, callback) { return helpers.notAllowed(req, res); } - user.getUserField(uid, 'rss_token', function (err, _token) { - if (err) { - return callback(err); - } - - if (token === _token) { - return callback(); - } - - user.auth.logAttempt(uid, req.ip, function (err) { - if (err) { - return callback(err); + async.waterfall([ + function (next) { + user.getUserField(uid, 'rss_token', next); + }, + function (_token, next) { + if (token === _token) { + async.waterfall([ + function (next) { + privileges.categories.get(cid, uid, next); + }, + function (privileges, next) { + if (!privileges.read) { + return helpers.notAllowed(req, res); + } + next(); + }, + ], callback); + return; } - - return helpers.notAllowed(req, res); - }); - }); + user.auth.logAttempt(uid, req.ip, next); + }, + function () { + helpers.notAllowed(req, res); + }, + ], callback); } function generateForTopic(req, res, callback) { @@ -64,6 +72,7 @@ function generateForTopic(req, res, callback) { var tid = req.params.topic_id; var userPrivileges; + var topic; async.waterfall([ function (next) { async.parallel({ @@ -79,15 +88,12 @@ function generateForTopic(req, res, callback) { if (!results.topic || (parseInt(results.topic.deleted, 10) && !results.privileges.view_deleted)) { return controllers404.send404(req, res); } - - validateTokenIfRequiresLogin(!results.privileges['topics:read'], req, res, function (err) { - if (err) { - return next(err); - } - - userPrivileges = results.privileges; - topics.getTopicWithPosts(results.topic, 'tid:' + tid + ':posts', req.uid, 0, 25, false, next); - }); + userPrivileges = results.privileges; + topic = results.topic; + validateTokenIfRequiresLogin(!results.privileges['topics:read'], results.topic.cid, req, res, next); + }, + function (next) { + topics.getTopicWithPosts(topic, 'tid:' + tid + ':posts', req.uid || req.query.uid || 0, 0, 25, false, next); }, function (topicData) { topics.modifyPostsByPrivilege(topicData, userPrivileges); @@ -130,40 +136,12 @@ function generateForTopic(req, res, callback) { ], callback); } -function generateForUserTopics(req, res, callback) { - if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) { - return controllers404.send404(req, res); - } - - var userslug = req.params.userslug; - - async.waterfall([ - function (next) { - user.getUidByUserslug(userslug, next); - }, - function (uid, next) { - if (!uid) { - return callback(); - } - user.getUserFields(uid, ['uid', 'username'], next); - }, - function (userData, next) { - generateForTopics({ - uid: req.uid, - title: 'Topics by ' + userData.username, - description: 'A list of topics that are posted by ' + userData.username, - feed_url: '/user/' + userslug + '/topics.rss', - site_url: '/user/' + userslug + '/topics', - }, 'uid:' + userData.uid + ':topics', req, res, next); - }, - ], callback); -} - function generateForCategory(req, res, next) { if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) { return controllers404.send404(req, res); } var cid = req.params.category_id; + var category; async.waterfall([ function (next) { @@ -178,25 +156,23 @@ function generateForCategory(req, res, next) { reverse: true, start: 0, stop: 25, - uid: req.uid, + uid: req.uid || req.query.uid || 0, }, next); }, }, next); }, function (results, next) { - validateTokenIfRequiresLogin(!results.privileges.read, req, res, function (err) { - if (err) { - return next(err); - } - - generateTopicsFeed({ - uid: req.uid, - title: results.category.name, - description: results.category.description, - feed_url: '/category/' + cid + '.rss', - site_url: '/category/' + results.category.cid, - }, results.category.topics, next); - }); + category = results.category; + validateTokenIfRequiresLogin(!results.privileges.read, cid, req, res, next); + }, + function (next) { + generateTopicsFeed({ + uid: req.uid || req.query.uid || 0, + title: category.name, + description: category.description, + feed_url: '/category/' + cid + '.rss', + site_url: '/category/' + category.cid, + }, category.topics, next); }, function (feed) { sendFeed(feed, res); @@ -330,12 +306,13 @@ function generateForRecentPosts(req, res, next) { ], next); } -function generateForCategoryRecentPosts(req, res, next) { +function generateForCategoryRecentPosts(req, res, callback) { if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) { return controllers404.send404(req, res); } var cid = req.params.category_id; - + var category; + var posts; async.waterfall([ function (next) { async.parallel({ @@ -346,31 +323,29 @@ function generateForCategoryRecentPosts(req, res, next) { categories.getCategoryData(cid, next); }, posts: function (next) { - categories.getRecentReplies(cid, req.uid, 20, next); + categories.getRecentReplies(cid, req.uid || req.query.uid || 0, 20, next); }, }, next); }, function (results, next) { if (!results.category) { - return next(); + return controllers404.send404(req, res); } + category = results.category; + posts = results.posts; + validateTokenIfRequiresLogin(!results.privileges.read, cid, req, res, next); + }, + function () { + var feed = generateForPostsFeed({ + title: category.name + ' Recent Posts', + description: 'A list of recent posts from ' + category.name, + feed_url: '/category/' + cid + '/recentposts.rss', + site_url: '/category/' + cid + '/recentposts', + }, posts); - validateTokenIfRequiresLogin(!results.privileges.read, req, res, function (err) { - if (err) { - return next(err); - } - - var feed = generateForPostsFeed({ - title: results.category.name + ' Recent Posts', - description: 'A list of recent posts from ' + results.category.name, - feed_url: '/category/' + cid + '/recentposts.rss', - site_url: '/category/' + cid + '/recentposts', - }, results.posts); - - sendFeed(feed, res); - }); + sendFeed(feed, res); }, - ], next); + ], callback); } function generateForPostsFeed(feedOptions, posts) { @@ -397,6 +372,35 @@ function generateForPostsFeed(feedOptions, posts) { return feed; } +function generateForUserTopics(req, res, callback) { + if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) { + return controllers404.send404(req, res); + } + + var userslug = req.params.userslug; + + async.waterfall([ + function (next) { + user.getUidByUserslug(userslug, next); + }, + function (uid, next) { + if (!uid) { + return callback(); + } + user.getUserFields(uid, ['uid', 'username'], next); + }, + function (userData, next) { + generateForTopics({ + uid: req.uid, + title: 'Topics by ' + userData.username, + description: 'A list of topics that are posted by ' + userData.username, + feed_url: '/user/' + userslug + '/topics.rss', + site_url: '/user/' + userslug + '/topics', + }, 'uid:' + userData.uid + ':topics', req, res, next); + }, + ], callback); +} + function generateForTag(req, res, next) { if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) { return controllers404.send404(req, res); diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js index bae1832782..df169aba0e 100644 --- a/src/socket.io/admin.js +++ b/src/socket.io/admin.js @@ -187,7 +187,10 @@ SocketAdmin.config.setMultiple = function (socket, data, callback) { logger.monitorConfig({ io: index.server }, setting); } } - setImmediate(next); + data.type = 'config-change'; + data.uid = socket.uid; + data.ip = socket.ip; + events.log(data, next); }, ], callback); }; @@ -201,7 +204,19 @@ SocketAdmin.settings.get = function (socket, data, callback) { }; SocketAdmin.settings.set = function (socket, data, callback) { - meta.settings.set(data.hash, data.values, callback); + async.waterfall([ + function (next) { + meta.settings.set(data.hash, data.values, next); + }, + function (next) { + var eventData = data.values; + eventData.type = 'settings-change'; + eventData.uid = socket.uid; + eventData.ip = socket.ip; + eventData.hash = data.hash; + events.log(eventData, next); + }, + ], callback); }; SocketAdmin.settings.clearSitemapCache = function (socket, data, callback) { diff --git a/src/start.js b/src/start.js index 8d4e465a55..2195a0ec29 100644 --- a/src/start.js +++ b/src/start.js @@ -98,7 +98,7 @@ function setupConfigs() { nconf.set('secure', urlObject.protocol === 'https:'); nconf.set('use_port', !!urlObject.port); nconf.set('relative_path', relativePath); - nconf.set('port', urlObject.port || nconf.get('port') || nconf.get('PORT') || (nconf.get('PORT_ENV_VAR') ? nconf.get(nconf.get('PORT_ENV_VAR')) : false) || 4567); + nconf.set('port', urlObject.port || nconf.get('port') || (nconf.get('PORT_ENV_VAR') ? nconf.get(nconf.get('PORT_ENV_VAR')) : false) || 4567); nconf.set('upload_url', '/assets/uploads'); } diff --git a/src/user/auth.js b/src/user/auth.js index fc1d995bf6..4347c860d1 100644 --- a/src/user/auth.js +++ b/src/user/auth.js @@ -52,19 +52,23 @@ module.exports = function (User) { if (!uid) { return callback(); } - - User.getUserField(uid, 'rss_token', function (err, token) { - if (err) { - return callback(err); - } - - if (!token) { - token = utils.generateUUID(); - User.setUserField(uid, 'rss_token', token); - } - - callback(false, token); - }); + var token; + async.waterfall([ + function (next) { + User.getUserField(uid, 'rss_token', next); + }, + function (_token, next) { + token = _token || utils.generateUUID(); + if (!_token) { + User.setUserField(uid, 'rss_token', token, next); + } else { + next(); + } + }, + function (next) { + next(null, token); + }, + ], callback); }; User.auth.clearLoginAttempts = function (uid) { diff --git a/src/webserver.js b/src/webserver.js index 2d26045b8b..828aa7b3da 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -133,8 +133,8 @@ function setupExpressApp(app, callback) { app.use(compression()); - app.get('/ping', ping); - app.get('/sping', ping); + app.get(relativePath + '/ping', ping); + app.get(relativePath + '/sping', ping); setupFavicon(app); @@ -231,6 +231,7 @@ function setupAutoLocale(app, callback) { function listen(callback) { callback = callback || function () { }; + console.log('derp', nconf.get('port')); var port = parseInt(nconf.get('port'), 10); var isSocket = isNaN(port); var socketPath = isSocket ? nconf.get('port') : ''; diff --git a/test/feeds.js b/test/feeds.js index e8e3f8dfa4..4ed9fe0937 100644 --- a/test/feeds.js +++ b/test/feeds.js @@ -12,6 +12,7 @@ var groups = require('../src/groups'); var user = require('../src/user'); var meta = require('../src/meta'); var privileges = require('../src/privileges'); +var helpers = require('./helpers'); describe('feeds', function () { var tid; @@ -113,4 +114,81 @@ describe('feeds', function () { }); }); }); + + describe('private feeds and tokens', function () { + var jar; + var rssToken; + before(function (done) { + helpers.loginUser('foo', 'barbar', function (err, _jar) { + assert.ifError(err); + jar = _jar; + done(); + }); + }); + + it('should load feed if its not private', function (done) { + request(nconf.get('url') + '/category/' + cid + '.rss', { }, function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert(body); + done(); + }); + }); + + + it('should not allow access if uid or token is missing', function (done) { + privileges.categories.rescind(['read'], cid, 'guests', function (err) { + assert.ifError(err); + async.parallel({ + test1: function (next) { + request(nconf.get('url') + '/category/' + cid + '.rss?uid=' + fooUid, { }, next); + }, + test2: function (next) { + request(nconf.get('url') + '/category/' + cid + '.rss?token=sometoken', { }, next); + }, + }, function (err, results) { + assert.ifError(err); + assert.equal(results.test1[0].statusCode, 200); + assert.equal(results.test2[0].statusCode, 200); + assert(results.test1[0].body.indexOf('Login to your account') !== -1); + assert(results.test2[0].body.indexOf('Login to your account') !== -1); + done(); + }); + }); + }); + + it('should not allow access if token is wrong', function (done) { + request(nconf.get('url') + '/category/' + cid + '.rss?uid=' + fooUid + '&token=sometoken', { }, function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert(body.indexOf('Login to your account') !== -1); + done(); + }); + }); + + it('should allow access if token is correct', function (done) { + request(nconf.get('url') + '/api/category/' + cid, { jar: jar, json: true }, function (err, res, body) { + assert.ifError(err); + rssToken = body.rssFeedUrl.split('token')[1].slice(1); + request(nconf.get('url') + '/category/' + cid + '.rss?uid=' + fooUid + '&token=' + rssToken, { }, function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert(body); + done(); + }); + }); + }); + + it('should not allow access if token is correct but has no privilege', function (done) { + privileges.categories.rescind(['read'], cid, 'registered-users', function (err) { + assert.ifError(err); + request(nconf.get('url') + '/category/' + cid + '.rss?uid=' + fooUid + '&token=' + rssToken, { }, function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert(body.indexOf('Login to your account') !== -1); + done(); + }); + }); + }); + }); }); diff --git a/test/mocks/databasemock.js b/test/mocks/databasemock.js index 27dab2b647..c43b01a900 100644 --- a/test/mocks/databasemock.js +++ b/test/mocks/databasemock.js @@ -110,7 +110,7 @@ before(function (done) { nconf.set('secure', urlObject.protocol === 'https:'); nconf.set('use_port', !!urlObject.port); nconf.set('relative_path', relativePath); - nconf.set('port', urlObject.port || nconf.get('port') || nconf.get('PORT') || (nconf.get('PORT_ENV_VAR') ? nconf.get(nconf.get('PORT_ENV_VAR')) : false) || 4567); + nconf.set('port', urlObject.port || nconf.get('port') || (nconf.get('PORT_ENV_VAR') ? nconf.get(nconf.get('PORT_ENV_VAR')) : false) || 4567); nconf.set('upload_path', path.join(nconf.get('base_dir'), nconf.get('upload_path'))); nconf.set('core_templates_path', path.join(__dirname, '../../src/views'));