2020-05-25 03:49:38 -05:00
|
|
|
const errors = require('@tryghost/errors');
|
2020-06-15 20:13:35 +01:00
|
|
|
const should = require('should');
|
2020-04-29 16:44:27 +01:00
|
|
|
const sinon = require('sinon');
|
|
|
|
const crypto = require('crypto');
|
|
|
|
const fs = require('fs-extra');
|
|
|
|
const settingsCache = require('../../../../core/server/services/settings/cache');
|
|
|
|
const privateBlogging = require('../../../../core/frontend/apps/private-blogging/lib/middleware');
|
2015-07-15 18:01:23 +02:00
|
|
|
|
|
|
|
function hash(password, salt) {
|
2020-04-29 16:44:27 +01:00
|
|
|
const hasher = crypto.createHash('sha256');
|
2015-07-15 18:01:23 +02:00
|
|
|
hasher.update(password + salt, 'utf8');
|
|
|
|
return hasher.digest('hex');
|
|
|
|
}
|
|
|
|
|
|
|
|
describe('Private Blogging', function () {
|
2020-04-29 16:44:27 +01:00
|
|
|
let settingsStub;
|
2015-07-15 18:01:23 +02:00
|
|
|
|
|
|
|
afterEach(function () {
|
2019-01-21 17:53:44 +01:00
|
|
|
sinon.restore();
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('passProtect', function () {
|
2020-04-29 16:44:27 +01:00
|
|
|
let req;
|
|
|
|
let res;
|
|
|
|
let next;
|
2015-07-15 18:01:23 +02:00
|
|
|
|
|
|
|
beforeEach(function () {
|
2018-10-08 16:31:08 +07:00
|
|
|
req = {
|
|
|
|
query: {}
|
|
|
|
};
|
2016-02-14 22:48:20 +00:00
|
|
|
res = {};
|
2019-01-21 17:53:44 +01:00
|
|
|
settingsStub = sinon.stub(settingsCache, 'get');
|
|
|
|
next = sinon.spy();
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
|
2017-10-03 13:40:53 +02:00
|
|
|
it('checkIsPrivate should call next if not private', function () {
|
|
|
|
settingsStub.withArgs('is_private').returns(false);
|
2015-07-15 18:01:23 +02:00
|
|
|
|
2017-10-03 13:40:53 +02:00
|
|
|
privateBlogging.checkIsPrivate(req, res, next);
|
|
|
|
next.called.should.be.true();
|
|
|
|
res.isPrivateBlog.should.be.false();
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
|
2017-10-03 13:40:53 +02:00
|
|
|
it('checkIsPrivate should load session if private', function () {
|
|
|
|
settingsStub.withArgs('is_private').returns(true);
|
2015-07-15 18:01:23 +02:00
|
|
|
|
2017-10-03 13:40:53 +02:00
|
|
|
privateBlogging.checkIsPrivate(req, res, next);
|
|
|
|
res.isPrivateBlog.should.be.true();
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('not private', function () {
|
|
|
|
beforeEach(function () {
|
|
|
|
res.isPrivateBlog = false;
|
|
|
|
});
|
|
|
|
|
|
|
|
it('filterPrivateRoutes should call next if not private', function () {
|
|
|
|
privateBlogging.filterPrivateRoutes(req, res, next);
|
2016-02-07 21:27:01 +00:00
|
|
|
next.called.should.be.true();
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
|
2020-06-15 20:55:59 +01:00
|
|
|
it('redirectPrivateToHomeIfLoggedIn should redirect if blog is not private', function () {
|
2015-07-15 18:01:23 +02:00
|
|
|
res = {
|
2019-01-21 17:53:44 +01:00
|
|
|
redirect: sinon.spy(),
|
2015-07-15 18:01:23 +02:00
|
|
|
isPrivateBlog: false
|
|
|
|
};
|
2020-06-15 20:55:59 +01:00
|
|
|
privateBlogging.redirectPrivateToHomeIfLoggedIn(req, res, next);
|
2016-02-07 21:27:01 +00:00
|
|
|
res.redirect.called.should.be.true();
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('private', function () {
|
|
|
|
beforeEach(function () {
|
|
|
|
res = {
|
|
|
|
status: function () {
|
|
|
|
return this;
|
|
|
|
},
|
2017-03-21 08:24:11 +00:00
|
|
|
send: function () {
|
|
|
|
},
|
|
|
|
set: function () {
|
|
|
|
},
|
2019-01-21 17:53:44 +01:00
|
|
|
redirect: sinon.spy(),
|
2015-07-15 18:01:23 +02:00
|
|
|
isPrivateBlog: true
|
|
|
|
};
|
2017-09-11 15:09:19 +02:00
|
|
|
|
|
|
|
req.session = {};
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
it('filterPrivateRoutes should call next if is the "private" route', function () {
|
2016-01-07 15:03:39 +01:00
|
|
|
req.path = req.url = '/private/';
|
2015-07-15 18:01:23 +02:00
|
|
|
privateBlogging.filterPrivateRoutes(req, res, next);
|
2016-02-07 21:27:01 +00:00
|
|
|
next.called.should.be.true();
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
|
2017-09-11 15:09:19 +02:00
|
|
|
it('filterPrivateRoutes: sitemap redirects to /private', function () {
|
2016-01-07 15:03:39 +01:00
|
|
|
req.path = req.url = '/sitemap.xml';
|
2016-10-04 17:33:43 +02:00
|
|
|
|
2017-10-03 13:40:53 +02:00
|
|
|
privateBlogging.filterPrivateRoutes(req, res, next);
|
|
|
|
res.redirect.calledOnce.should.be.true();
|
2016-01-07 15:03:39 +01:00
|
|
|
});
|
|
|
|
|
2017-09-11 15:09:19 +02:00
|
|
|
it('filterPrivateRoutes: sitemap with params redirects to /private', function () {
|
2016-01-07 15:03:39 +01:00
|
|
|
req.url = '/sitemap.xml?weird=param';
|
|
|
|
req.path = '/sitemap.xml';
|
2016-10-04 17:33:43 +02:00
|
|
|
|
2017-10-03 13:40:53 +02:00
|
|
|
privateBlogging.filterPrivateRoutes(req, res, next);
|
|
|
|
res.redirect.calledOnce.should.be.true();
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
|
2017-09-11 15:09:19 +02:00
|
|
|
it('filterPrivateRoutes: rss redirects to /private', function () {
|
2016-01-07 15:03:39 +01:00
|
|
|
req.path = req.url = '/rss/';
|
2016-10-04 17:33:43 +02:00
|
|
|
|
2017-10-03 13:40:53 +02:00
|
|
|
privateBlogging.filterPrivateRoutes(req, res, next);
|
|
|
|
res.redirect.calledOnce.should.be.true();
|
2016-01-07 15:03:39 +01:00
|
|
|
});
|
|
|
|
|
2017-09-11 15:09:19 +02:00
|
|
|
it('filterPrivateRoutes: author rss redirects to /private', function () {
|
2016-01-07 15:03:39 +01:00
|
|
|
req.path = req.url = '/author/halfdan/rss/';
|
2016-10-04 17:33:43 +02:00
|
|
|
|
2017-10-03 13:40:53 +02:00
|
|
|
privateBlogging.filterPrivateRoutes(req, res, next);
|
|
|
|
res.redirect.calledOnce.should.be.true();
|
2016-01-07 15:03:39 +01:00
|
|
|
});
|
|
|
|
|
2017-09-11 15:09:19 +02:00
|
|
|
it('filterPrivateRoutes: tag rss redirects to /private', function () {
|
2016-01-07 15:03:39 +01:00
|
|
|
req.path = req.url = '/tag/slimer/rss/';
|
2016-10-04 17:33:43 +02:00
|
|
|
|
2017-10-03 13:40:53 +02:00
|
|
|
privateBlogging.filterPrivateRoutes(req, res, next);
|
|
|
|
res.redirect.calledOnce.should.be.true();
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
|
2017-09-11 15:09:19 +02:00
|
|
|
it('filterPrivateRoutes: rss plus something redirects to /private', function () {
|
2016-01-07 15:03:39 +01:00
|
|
|
req.path = req.url = '/rss/sometag';
|
2016-10-04 17:33:43 +02:00
|
|
|
|
2017-10-03 13:40:53 +02:00
|
|
|
privateBlogging.filterPrivateRoutes(req, res, next);
|
|
|
|
res.redirect.calledOnce.should.be.true();
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
it('filterPrivateRoutes should render custom robots.txt', function () {
|
2016-01-07 15:03:39 +01:00
|
|
|
req.url = req.path = '/robots.txt';
|
2019-01-21 17:53:44 +01:00
|
|
|
res.writeHead = sinon.spy();
|
|
|
|
res.end = sinon.spy();
|
|
|
|
sinon.stub(fs, 'readFile').callsFake(function (file, cb) {
|
2015-07-15 18:01:23 +02:00
|
|
|
cb(null, 'User-agent: * Disallow: /');
|
|
|
|
});
|
|
|
|
privateBlogging.filterPrivateRoutes(req, res, next);
|
2016-02-07 21:27:01 +00:00
|
|
|
res.writeHead.called.should.be.true();
|
|
|
|
res.end.called.should.be.true();
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
|
2020-06-15 20:55:59 +01:00
|
|
|
it('doLoginToPrivateSite should call next if error', function () {
|
2015-07-15 18:01:23 +02:00
|
|
|
res.error = 'Test Error';
|
2020-06-15 20:55:59 +01:00
|
|
|
privateBlogging.doLoginToPrivateSite(req, res, next);
|
2016-02-07 21:27:01 +00:00
|
|
|
next.called.should.be.true();
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('with hash verification', function () {
|
|
|
|
beforeEach(function () {
|
2017-10-03 13:40:53 +02:00
|
|
|
settingsStub.withArgs('password').returns('rightpassword');
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
|
2017-10-03 13:40:53 +02:00
|
|
|
it('authenticatePrivateSession should return next if hash is verified', function () {
|
2020-04-29 16:44:27 +01:00
|
|
|
const salt = Date.now().toString();
|
2015-07-15 18:01:23 +02:00
|
|
|
|
|
|
|
req.session = {
|
|
|
|
token: hash('rightpassword', salt),
|
|
|
|
salt: salt
|
|
|
|
};
|
|
|
|
|
2017-10-03 13:40:53 +02:00
|
|
|
privateBlogging.authenticatePrivateSession(req, res, next);
|
|
|
|
next.called.should.be.true();
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
|
2017-10-03 13:40:53 +02:00
|
|
|
it('authenticatePrivateSession should redirect if hash is not verified', function () {
|
2017-06-08 16:36:14 +01:00
|
|
|
req.url = '/welcome';
|
2015-07-15 18:01:23 +02:00
|
|
|
req.session = {
|
|
|
|
token: 'wrongpassword',
|
|
|
|
salt: Date.now().toString()
|
|
|
|
};
|
2019-01-21 17:53:44 +01:00
|
|
|
res.redirect = sinon.spy();
|
2015-07-15 18:01:23 +02:00
|
|
|
|
2017-10-03 13:40:53 +02:00
|
|
|
privateBlogging.authenticatePrivateSession(req, res, next);
|
|
|
|
res.redirect.called.should.be.true();
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
|
2020-06-15 20:55:59 +01:00
|
|
|
it('redirectPrivateToHomeIfLoggedIn should redirect if hash is verified', function () {
|
2020-04-29 16:44:27 +01:00
|
|
|
const salt = Date.now().toString();
|
2015-07-15 18:01:23 +02:00
|
|
|
|
|
|
|
req.session = {
|
|
|
|
token: hash('rightpassword', salt),
|
|
|
|
salt: salt
|
|
|
|
};
|
2019-01-21 17:53:44 +01:00
|
|
|
res.redirect = sinon.spy();
|
2015-07-15 18:01:23 +02:00
|
|
|
|
2020-06-15 20:55:59 +01:00
|
|
|
privateBlogging.redirectPrivateToHomeIfLoggedIn(req, res, next);
|
2017-10-03 13:40:53 +02:00
|
|
|
res.redirect.called.should.be.true();
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
|
2020-06-15 20:55:59 +01:00
|
|
|
it('redirectPrivateToHomeIfLoggedIn should return next if hash is not verified', function () {
|
2015-07-15 18:01:23 +02:00
|
|
|
req.session = {
|
|
|
|
token: 'wrongpassword',
|
|
|
|
salt: Date.now().toString()
|
|
|
|
};
|
|
|
|
|
2020-06-15 20:55:59 +01:00
|
|
|
privateBlogging.redirectPrivateToHomeIfLoggedIn(req, res, next);
|
2017-10-03 13:40:53 +02:00
|
|
|
next.called.should.be.true();
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
|
2020-06-15 20:55:59 +01:00
|
|
|
it('doLoginToPrivateSite should return next if password is incorrect', function () {
|
2015-07-15 18:01:23 +02:00
|
|
|
req.body = {password: 'wrongpassword'};
|
|
|
|
|
2020-06-15 20:55:59 +01:00
|
|
|
privateBlogging.doLoginToPrivateSite(req, res, next);
|
2017-10-03 13:40:53 +02:00
|
|
|
res.error.should.not.be.empty();
|
|
|
|
next.called.should.be.true();
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
|
2020-06-15 20:55:59 +01:00
|
|
|
it('doLoginToPrivateSite should redirect if password is correct', function () {
|
2015-07-15 18:01:23 +02:00
|
|
|
req.body = {password: 'rightpassword'};
|
|
|
|
req.session = {};
|
2019-01-21 17:53:44 +01:00
|
|
|
res.redirect = sinon.spy();
|
2015-07-15 18:01:23 +02:00
|
|
|
|
2020-06-15 20:55:59 +01:00
|
|
|
privateBlogging.doLoginToPrivateSite(req, res, next);
|
2017-10-03 13:40:53 +02:00
|
|
|
res.redirect.called.should.be.true();
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
2018-09-24 20:47:05 +07:00
|
|
|
|
2020-06-15 20:55:59 +01:00
|
|
|
it('doLoginToPrivateSite should redirect to "/" if r param is a full url', function () {
|
2018-09-24 20:47:05 +07:00
|
|
|
req.body = {password: 'rightpassword'};
|
|
|
|
req.session = {};
|
|
|
|
req.query = {
|
|
|
|
r: encodeURIComponent('http://britney.com')
|
|
|
|
};
|
2019-01-21 17:53:44 +01:00
|
|
|
res.redirect = sinon.spy();
|
2018-09-24 20:47:05 +07:00
|
|
|
|
2020-06-15 20:55:59 +01:00
|
|
|
privateBlogging.doLoginToPrivateSite(req, res, next);
|
2018-09-24 20:47:05 +07:00
|
|
|
res.redirect.called.should.be.true();
|
|
|
|
res.redirect.args[0][0].should.be.equal('/');
|
|
|
|
});
|
2017-10-05 12:07:32 +02:00
|
|
|
|
|
|
|
it('filterPrivateRoutes should 404 for /rss/ requests', function () {
|
2020-04-29 16:44:27 +01:00
|
|
|
const salt = Date.now().toString();
|
2017-10-05 12:07:32 +02:00
|
|
|
req.url = req.path = '/rss/';
|
|
|
|
|
|
|
|
req.session = {
|
|
|
|
token: hash('rightpassword', salt),
|
|
|
|
salt: salt
|
|
|
|
};
|
|
|
|
|
|
|
|
res.isPrivateBlog = true;
|
2019-01-21 17:53:44 +01:00
|
|
|
res.redirect = sinon.spy();
|
2017-10-05 12:07:32 +02:00
|
|
|
|
|
|
|
privateBlogging.filterPrivateRoutes(req, res, next);
|
|
|
|
next.called.should.be.true();
|
2020-05-25 03:49:38 -05:00
|
|
|
(next.firstCall.args[0] instanceof errors.NotFoundError).should.eql(true);
|
2017-10-05 12:07:32 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
it('filterPrivateRoutes should 404 for tag rss requests', function () {
|
2020-04-29 16:44:27 +01:00
|
|
|
const salt = Date.now().toString();
|
2017-10-05 12:07:32 +02:00
|
|
|
req.url = req.path = '/tag/welcome/rss/';
|
|
|
|
|
|
|
|
req.session = {
|
|
|
|
token: hash('rightpassword', salt),
|
2018-06-04 23:57:18 +07:00
|
|
|
salt: salt
|
|
|
|
};
|
|
|
|
|
|
|
|
res.isPrivateBlog = true;
|
2019-01-21 17:53:44 +01:00
|
|
|
res.redirect = sinon.spy();
|
2018-06-04 23:57:18 +07:00
|
|
|
|
|
|
|
privateBlogging.filterPrivateRoutes(req, res, next);
|
|
|
|
next.called.should.be.true();
|
2020-05-25 03:49:38 -05:00
|
|
|
(next.firstCall.args[0] instanceof errors.NotFoundError).should.eql(true);
|
2018-06-04 23:57:18 +07:00
|
|
|
});
|
|
|
|
|
|
|
|
it('filterPrivateRoutes should return next if tag contains rss', function () {
|
2020-04-29 16:44:27 +01:00
|
|
|
const salt = Date.now().toString();
|
2018-06-04 23:57:18 +07:00
|
|
|
req.url = req.path = '/tag/rss-test/';
|
|
|
|
|
|
|
|
req.session = {
|
|
|
|
token: hash('rightpassword', salt),
|
|
|
|
salt: salt
|
|
|
|
};
|
|
|
|
|
|
|
|
res.isPrivateBlog = true;
|
2019-01-21 17:53:44 +01:00
|
|
|
res.redirect = sinon.spy();
|
2018-06-04 23:57:18 +07:00
|
|
|
|
|
|
|
privateBlogging.filterPrivateRoutes(req, res, next);
|
|
|
|
next.called.should.be.true();
|
|
|
|
next.firstCall.args.length.should.equal(0);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('filterPrivateRoutes should not 404 for very short post url', function () {
|
2020-04-29 16:44:27 +01:00
|
|
|
const salt = Date.now().toString();
|
2018-06-04 23:57:18 +07:00
|
|
|
req.url = req.path = '/ab/';
|
|
|
|
|
|
|
|
req.session = {
|
|
|
|
token: hash('rightpassword', salt),
|
|
|
|
salt: salt
|
|
|
|
};
|
|
|
|
|
|
|
|
res.isPrivateBlog = true;
|
2019-01-21 17:53:44 +01:00
|
|
|
res.redirect = sinon.spy();
|
2018-06-04 23:57:18 +07:00
|
|
|
|
|
|
|
privateBlogging.filterPrivateRoutes(req, res, next);
|
|
|
|
next.called.should.be.true();
|
|
|
|
next.firstCall.args.length.should.equal(0);
|
|
|
|
});
|
|
|
|
|
2017-10-05 12:07:32 +02:00
|
|
|
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 = {};
|
|
|
|
|
2019-01-21 17:53:44 +01:00
|
|
|
res.redirect = sinon.spy();
|
2017-10-05 12:07:32 +02:00
|
|
|
|
|
|
|
privateBlogging.filterPrivateRoutes(req, res, next);
|
|
|
|
res.redirect.called.should.be.true();
|
|
|
|
});
|
2015-07-15 18:01:23 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|