diff --git a/src/controllers/category.js b/src/controllers/category.js index cb5b08f9a3..867f17c5e9 100644 --- a/src/controllers/category.js +++ b/src/controllers/category.js @@ -22,6 +22,7 @@ categoryController.get = function (req, res, callback) { var pageCount = 1; var userPrivileges; var settings; + var rssToken; if ((req.params.topic_index && !utils.isNumber(req.params.topic_index)) || !utils.isNumber(cid)) { return callback(); @@ -39,10 +40,14 @@ categoryController.get = function (req, res, callback) { userSettings: function (next) { user.getSettings(req.uid, next); }, + rssToken: function (next) { + user.auth.getFeedToken(req.uid, next); + }, }, next); }, function (results, next) { userPrivileges = results.privileges; + rssToken = results.rssToken; if (!results.categoryData.slug || (results.categoryData && parseInt(results.categoryData.disabled, 10) === 1)) { return callback(); @@ -150,14 +155,16 @@ categoryController.get = function (req, res, callback) { categoryData.privileges = userPrivileges; categoryData.showSelect = categoryData.privileges.editable; - addTags(categoryData, res); - 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'; } + addTags(categoryData, res); + categoryData['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1; - categoryData.rssFeedUrl = nconf.get('relative_path') + '/category/' + categoryData.cid + '.rss'; categoryData.title = translator.escape(categoryData.name); pageCount = Math.max(1, Math.ceil(categoryData.topic_count / settings.topicsPerPage)); categoryData.pagination = pagination.create(currentPage, pageCount, req.query); @@ -220,7 +227,7 @@ function addTags(categoryData, res) { { rel: 'alternate', type: 'application/rss+xml', - href: nconf.get('url') + '/category/' + categoryData.cid + '.rss', + href: categoryData.rssFeedUrl, }, { rel: 'up', diff --git a/src/routes/feeds.js b/src/routes/feeds.js index e1fd92cd59..f2a772a7bd 100644 --- a/src/routes/feeds.js +++ b/src/routes/feeds.js @@ -26,6 +26,37 @@ module.exports = function (app, middleware) { app.get('/tags/:tag.rss', middleware.maintenanceMode, generateForTag); }; +function validateTokenIfRequiresLogin(requiresLogin, req, res, callback) { + var uid = req.query.uid; + var token = req.query.token; + + if (!requiresLogin) { + return callback(); + } + + if (!uid || !token) { + 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); + } + + return helpers.notAllowed(req, res); + }); + }); +} + function generateForTopic(req, res, callback) { if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) { return controllers404.send404(req, res); @@ -48,11 +79,15 @@ function generateForTopic(req, res, callback) { if (!results.topic || (parseInt(results.topic.deleted, 10) && !results.privileges.view_deleted)) { return controllers404.send404(req, res); } - if (!results.privileges['topics:read']) { - return helpers.notAllowed(req, res); - } - userPrivileges = results.privileges; - topics.getTopicWithPosts(results.topic, 'tid:' + tid + ':posts', req.uid, 0, 25, false, next); + + 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); + }); }, function (topicData) { topics.modifyPostsByPrivilege(topicData, userPrivileges); @@ -149,16 +184,19 @@ function generateForCategory(req, res, next) { }, next); }, function (results, next) { - if (!results.privileges.read) { - return helpers.notAllowed(req, res); - } - 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); + 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); + }); }, function (feed) { sendFeed(feed, res); @@ -317,18 +355,20 @@ function generateForCategoryRecentPosts(req, res, next) { return next(); } - if (!results.privileges.read) { - return helpers.notAllowed(req, res); - } + 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); + 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); } @@ -381,4 +421,3 @@ function sendFeed(feed, res) { var xml = feed.xml(); res.type('xml').set('Content-Length', Buffer.byteLength(xml)).send(xml); } - diff --git a/src/user/auth.js b/src/user/auth.js index 0a5c06fcb6..fc1d995bf6 100644 --- a/src/user/auth.js +++ b/src/user/auth.js @@ -6,6 +6,7 @@ var db = require('../database'); var meta = require('../meta'); var events = require('../events'); var batch = require('../batch'); +var utils = require('../utils'); module.exports = function (User) { User.auth = {}; @@ -47,6 +48,25 @@ module.exports = function (User) { ], callback); }; + User.auth.getFeedToken = function (uid, callback) { + 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); + }); + }; + User.auth.clearLoginAttempts = function (uid) { db.delete('loginAttempts:' + uid); }; diff --git a/src/user/profile.js b/src/user/profile.js index d6f7ac5a4b..d3067b6945 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -283,7 +283,10 @@ module.exports = function (User) { }, function (hashedPassword, next) { async.parallel([ - async.apply(User.setUserField, data.uid, 'password', hashedPassword), + async.apply(User.setUserFields, data.uid, { + password: hashedPassword, + rss_token: utils.generateUUID(), + }), async.apply(User.reset.updateExpiry, data.uid), async.apply(User.auth.revokeAllSessions, data.uid), ], function (err) {