mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-08 02:52:39 -05:00
✨ Private RSS feed (#9088)
refs #9001 When a blog is in private mode there is now an unguessable URL that allows access to the RSS feed for internal use, commenting systems, etc. - add public hash for private blogging - auto generate on bootstrap if missing - global hash, we can re-use in the future - update private blogging middleware to detect the private RSS URL and rewrite it so that the normal rss route/code is used for display - if a normal `/rss/` route is accessed with a private session return a 404
This commit is contained in:
parent
7be165da07
commit
7800ed3d8b
4 changed files with 136 additions and 3 deletions
|
@ -4,6 +4,7 @@ var fs = require('fs'),
|
|||
path = require('path'),
|
||||
config = require('../../../config'),
|
||||
utils = require('../../../utils'),
|
||||
errors = require('../../../errors'),
|
||||
i18n = require('../../../i18n'),
|
||||
settingsCache = require('../../../settings/cache'),
|
||||
privateRoute = '/' + config.get('routeKeywords').private + '/',
|
||||
|
@ -57,7 +58,25 @@ privateBlogging = {
|
|||
});
|
||||
}
|
||||
|
||||
privateBlogging.authenticatePrivateSession(req, res, next);
|
||||
// CASE: Allow private RSS feed urls.
|
||||
// Any url which contains the hash and the postfix /rss is allowed to access a private rss feed without
|
||||
// a session. As soon as a path matches, we rewrite the url. Even Express uses rewriting when using `app.use()`.
|
||||
if (req.url.indexOf(settingsCache.get('public_hash') + '/rss') !== -1) {
|
||||
req.url = req.url.replace(settingsCache.get('public_hash') + '/', '');
|
||||
return next();
|
||||
}
|
||||
|
||||
// NOTE: Redirect to /private if the session does not exist.
|
||||
privateBlogging.authenticatePrivateSession(req, res, function onSessionVerified() {
|
||||
// CASE: RSS is disabled for private blogging e.g. they create overhead
|
||||
if (req.path.lastIndexOf('/rss/', 0) === 0 || req.path.lastIndexOf('/rss/') === req.url.length - 5) {
|
||||
return next(new errors.NotFoundError({
|
||||
message: i18n.t('errors.errors.pageNotFound')
|
||||
}));
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
},
|
||||
|
||||
authenticatePrivateSession: function authenticatePrivateSession(req, res, next) {
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
var should = require('should'), // jshint ignore:line
|
||||
sinon = require('sinon'),
|
||||
crypto = require('crypto'),
|
||||
settingsCache = require('../../../settings/cache'),
|
||||
fs = require('fs'),
|
||||
errors = require('../../../errors'),
|
||||
settingsCache = require('../../../settings/cache'),
|
||||
privateBlogging = require('../lib/middleware'),
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
|
@ -223,6 +224,114 @@ describe('Private Blogging', function () {
|
|||
privateBlogging.authenticateProtection(req, res, next);
|
||||
res.redirect.called.should.be.true();
|
||||
});
|
||||
|
||||
it('filterPrivateRoutes should 404 for /rss/ requests', function () {
|
||||
var salt = Date.now().toString();
|
||||
req.url = req.path = '/rss/';
|
||||
|
||||
req.session = {
|
||||
token: hash('rightpassword', salt),
|
||||
salt: salt
|
||||
};
|
||||
|
||||
res.isPrivateBlog = true;
|
||||
res.redirect = sandbox.spy();
|
||||
|
||||
privateBlogging.filterPrivateRoutes(req, res, next);
|
||||
next.called.should.be.true();
|
||||
(next.firstCall.args[0] instanceof errors.NotFoundError).should.eql(true);
|
||||
});
|
||||
|
||||
it('filterPrivateRoutes should 404 for /rss requests', function () {
|
||||
var salt = Date.now().toString();
|
||||
req.url = req.path = '/rss';
|
||||
|
||||
req.session = {
|
||||
token: hash('rightpassword', salt),
|
||||
salt: salt
|
||||
};
|
||||
|
||||
res.isPrivateBlog = true;
|
||||
res.redirect = sandbox.spy();
|
||||
|
||||
privateBlogging.filterPrivateRoutes(req, res, next);
|
||||
next.called.should.be.true();
|
||||
(next.firstCall.args[0] instanceof errors.NotFoundError).should.eql(true);
|
||||
});
|
||||
|
||||
it('filterPrivateRoutes should 404 for tag rss requests', function () {
|
||||
var salt = Date.now().toString();
|
||||
req.url = req.path = '/tag/welcome/rss/';
|
||||
|
||||
req.session = {
|
||||
token: hash('rightpassword', salt),
|
||||
salt: salt
|
||||
};
|
||||
|
||||
res.isPrivateBlog = true;
|
||||
res.redirect = sandbox.spy();
|
||||
|
||||
privateBlogging.filterPrivateRoutes(req, res, next);
|
||||
next.called.should.be.true();
|
||||
(next.firstCall.args[0] instanceof errors.NotFoundError).should.eql(true);
|
||||
});
|
||||
|
||||
it('filterPrivateRoutes: allow private /rss/ feed', function () {
|
||||
settingsStub.withArgs('public_hash').returns('777aaa');
|
||||
|
||||
req.url = req.originalUrl = req.path = '/777aaa/rss/';
|
||||
req.params = {};
|
||||
|
||||
res.isPrivateBlog = true;
|
||||
res.locals = {};
|
||||
|
||||
privateBlogging.filterPrivateRoutes(req, res, next);
|
||||
next.called.should.be.true();
|
||||
req.url.should.eql('/rss/');
|
||||
});
|
||||
|
||||
it('filterPrivateRoutes: allow private /rss feed', function () {
|
||||
settingsStub.withArgs('public_hash').returns('777aaa');
|
||||
|
||||
req.url = req.originalUrl = req.path = '/777aaa/rss';
|
||||
req.params = {};
|
||||
|
||||
res.isPrivateBlog = true;
|
||||
res.locals = {};
|
||||
|
||||
privateBlogging.filterPrivateRoutes(req, res, next);
|
||||
next.called.should.be.true();
|
||||
req.url.should.eql('/rss');
|
||||
});
|
||||
|
||||
it('filterPrivateRoutes: allow private rss feed e.g. tags', function () {
|
||||
settingsStub.withArgs('public_hash').returns('777aaa');
|
||||
|
||||
req.url = req.originalUrl = req.path = '/tag/getting-started/777aaa/rss/';
|
||||
req.params = {};
|
||||
|
||||
res.isPrivateBlog = true;
|
||||
res.locals = {};
|
||||
|
||||
privateBlogging.filterPrivateRoutes(req, res, next);
|
||||
next.called.should.be.true();
|
||||
req.url.should.eql('/tag/getting-started/rss/');
|
||||
});
|
||||
|
||||
it('[failure] filterPrivateRoutes: allow private rss feed e.g. tags', function () {
|
||||
settingsStub.withArgs('public_hash').returns('777aaa');
|
||||
|
||||
req.url = req.originalUrl = req.path = '/tag/getting-started/rss/';
|
||||
req.params = {};
|
||||
|
||||
res.isPrivateBlog = true;
|
||||
res.locals = {};
|
||||
|
||||
res.redirect = sandbox.spy();
|
||||
|
||||
privateBlogging.filterPrivateRoutes(req, res, next);
|
||||
res.redirect.called.should.be.true();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -104,6 +104,9 @@
|
|||
},
|
||||
"password": {
|
||||
"defaultValue": ""
|
||||
},
|
||||
"public_hash": {
|
||||
"defaultValue": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ var Settings,
|
|||
Promise = require('bluebird'),
|
||||
_ = require('lodash'),
|
||||
uuid = require('uuid'),
|
||||
crypto = require('crypto'),
|
||||
ghostBookshelf = require('./base'),
|
||||
errors = require('../errors'),
|
||||
events = require('../events'),
|
||||
|
@ -19,7 +20,8 @@ function parseDefaultSettings() {
|
|||
var defaultSettingsInCategories = require('../data/schema/').defaultSettings,
|
||||
defaultSettingsFlattened = {},
|
||||
dynamicDefault = {
|
||||
db_hash: uuid.v4()
|
||||
db_hash: uuid.v4(),
|
||||
public_hash: crypto.randomBytes(15).toString('hex')
|
||||
};
|
||||
|
||||
_.each(defaultSettingsInCategories, function each(settings, categoryName) {
|
||||
|
|
Loading…
Add table
Reference in a new issue