0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-27 22:49:56 -05:00
ghost/test/unit/apps/private-blogging/middleware_spec.js

351 lines
13 KiB
JavaScript
Raw Normal View History

const errors = require('@tryghost/errors');
const should = require('should');
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');
function hash(password, salt) {
const hasher = crypto.createHash('sha256');
hasher.update(password + salt, 'utf8');
return hasher.digest('hex');
}
describe('Private Blogging', function () {
let settingsStub;
afterEach(function () {
sinon.restore();
});
describe('passProtect', function () {
let req;
let res;
let next;
beforeEach(function () {
req = {
query: {}
};
res = {};
settingsStub = sinon.stub(settingsCache, 'get');
next = sinon.spy();
});
it('checkIsPrivate should call next if not private', function () {
settingsStub.withArgs('is_private').returns(false);
privateBlogging.checkIsPrivate(req, res, next);
next.called.should.be.true();
res.isPrivateBlog.should.be.false();
});
it('checkIsPrivate should load session if private', function () {
settingsStub.withArgs('is_private').returns(true);
privateBlogging.checkIsPrivate(req, res, next);
res.isPrivateBlog.should.be.true();
});
describe('not private', function () {
beforeEach(function () {
res.isPrivateBlog = false;
});
it('filterPrivateRoutes should call next if not private', function () {
privateBlogging.filterPrivateRoutes(req, res, next);
next.called.should.be.true();
});
it('redirectPrivateToHomeIfLoggedIn should redirect if blog is not private', function () {
res = {
redirect: sinon.spy(),
isPrivateBlog: false
};
privateBlogging.redirectPrivateToHomeIfLoggedIn(req, res, next);
res.redirect.called.should.be.true();
});
});
describe('private', function () {
beforeEach(function () {
res = {
status: function () {
return this;
},
send: function () {
},
set: function () {
},
redirect: sinon.spy(),
isPrivateBlog: true
};
req.session = {};
});
it('filterPrivateRoutes should call next if is the "private" route', function () {
req.path = req.url = '/private/';
privateBlogging.filterPrivateRoutes(req, res, next);
next.called.should.be.true();
});
it('filterPrivateRoutes: sitemap redirects to /private', function () {
req.path = req.url = '/sitemap.xml';
🎨 configurable logging with bunyan (#7431) - 🛠 add bunyan and prettyjson, remove morgan - ✨ add logging module - GhostLogger class that handles setup of bunyan - PrettyStream for stdout - ✨ config for logging - @TODO: testing level fatal? - ✨ log each request via GhostLogger (express middleware) - @TODO: add errors to output - 🔥 remove errors.updateActiveTheme - we can read the value from config - 🔥 remove 15 helper functions in core/server/errors/index.js - all these functions get replaced by modules: 1. logging 2. error middleware handling for html/json 3. error creation (which will be part of PR #7477) - ✨ add express error handler for html/json - one true error handler for express responses - contains still some TODO's, but they are not high priority for first implementation/integration - this middleware only takes responsibility of either rendering html responses or return json error responses - 🎨 use new express error handler in middleware/index - 404 and 500 handling - 🎨 return error instead of error message in permissions/index.js - the rule for error handling should be: if you call a unit, this unit should return a custom Ghost error - 🎨 wrap serve static module - rule: if you call a module/unit, you should always wrap this error - it's always the same rule - so the caller never has to worry about what comes back - it's always a clear error instance - in this case: we return our notfounderror if serve static does not find the resource - this avoid having checks everywhere - 🎨 replace usages of errors/index.js functions and adapt tests - use logging.error, logging.warn - make tests green - remove some usages of logging and throwing api errors -> because when a request is involved, logging happens automatically - 🐛 return errorDetails to Ghost-Admin - errorDetails is used for Theme error handling - 🎨 use 500er error for theme is missing error in theme-handler - 🎨 extend file rotation to 1w
2016-10-04 17:33:43 +02:00
privateBlogging.filterPrivateRoutes(req, res, next);
res.redirect.calledOnce.should.be.true();
});
it('filterPrivateRoutes: sitemap with params redirects to /private', function () {
req.url = '/sitemap.xml?weird=param';
req.path = '/sitemap.xml';
🎨 configurable logging with bunyan (#7431) - 🛠 add bunyan and prettyjson, remove morgan - ✨ add logging module - GhostLogger class that handles setup of bunyan - PrettyStream for stdout - ✨ config for logging - @TODO: testing level fatal? - ✨ log each request via GhostLogger (express middleware) - @TODO: add errors to output - 🔥 remove errors.updateActiveTheme - we can read the value from config - 🔥 remove 15 helper functions in core/server/errors/index.js - all these functions get replaced by modules: 1. logging 2. error middleware handling for html/json 3. error creation (which will be part of PR #7477) - ✨ add express error handler for html/json - one true error handler for express responses - contains still some TODO's, but they are not high priority for first implementation/integration - this middleware only takes responsibility of either rendering html responses or return json error responses - 🎨 use new express error handler in middleware/index - 404 and 500 handling - 🎨 return error instead of error message in permissions/index.js - the rule for error handling should be: if you call a unit, this unit should return a custom Ghost error - 🎨 wrap serve static module - rule: if you call a module/unit, you should always wrap this error - it's always the same rule - so the caller never has to worry about what comes back - it's always a clear error instance - in this case: we return our notfounderror if serve static does not find the resource - this avoid having checks everywhere - 🎨 replace usages of errors/index.js functions and adapt tests - use logging.error, logging.warn - make tests green - remove some usages of logging and throwing api errors -> because when a request is involved, logging happens automatically - 🐛 return errorDetails to Ghost-Admin - errorDetails is used for Theme error handling - 🎨 use 500er error for theme is missing error in theme-handler - 🎨 extend file rotation to 1w
2016-10-04 17:33:43 +02:00
privateBlogging.filterPrivateRoutes(req, res, next);
res.redirect.calledOnce.should.be.true();
});
it('filterPrivateRoutes: rss redirects to /private', function () {
req.path = req.url = '/rss/';
🎨 configurable logging with bunyan (#7431) - 🛠 add bunyan and prettyjson, remove morgan - ✨ add logging module - GhostLogger class that handles setup of bunyan - PrettyStream for stdout - ✨ config for logging - @TODO: testing level fatal? - ✨ log each request via GhostLogger (express middleware) - @TODO: add errors to output - 🔥 remove errors.updateActiveTheme - we can read the value from config - 🔥 remove 15 helper functions in core/server/errors/index.js - all these functions get replaced by modules: 1. logging 2. error middleware handling for html/json 3. error creation (which will be part of PR #7477) - ✨ add express error handler for html/json - one true error handler for express responses - contains still some TODO's, but they are not high priority for first implementation/integration - this middleware only takes responsibility of either rendering html responses or return json error responses - 🎨 use new express error handler in middleware/index - 404 and 500 handling - 🎨 return error instead of error message in permissions/index.js - the rule for error handling should be: if you call a unit, this unit should return a custom Ghost error - 🎨 wrap serve static module - rule: if you call a module/unit, you should always wrap this error - it's always the same rule - so the caller never has to worry about what comes back - it's always a clear error instance - in this case: we return our notfounderror if serve static does not find the resource - this avoid having checks everywhere - 🎨 replace usages of errors/index.js functions and adapt tests - use logging.error, logging.warn - make tests green - remove some usages of logging and throwing api errors -> because when a request is involved, logging happens automatically - 🐛 return errorDetails to Ghost-Admin - errorDetails is used for Theme error handling - 🎨 use 500er error for theme is missing error in theme-handler - 🎨 extend file rotation to 1w
2016-10-04 17:33:43 +02:00
privateBlogging.filterPrivateRoutes(req, res, next);
res.redirect.calledOnce.should.be.true();
});
it('filterPrivateRoutes: author rss redirects to /private', function () {
req.path = req.url = '/author/halfdan/rss/';
🎨 configurable logging with bunyan (#7431) - 🛠 add bunyan and prettyjson, remove morgan - ✨ add logging module - GhostLogger class that handles setup of bunyan - PrettyStream for stdout - ✨ config for logging - @TODO: testing level fatal? - ✨ log each request via GhostLogger (express middleware) - @TODO: add errors to output - 🔥 remove errors.updateActiveTheme - we can read the value from config - 🔥 remove 15 helper functions in core/server/errors/index.js - all these functions get replaced by modules: 1. logging 2. error middleware handling for html/json 3. error creation (which will be part of PR #7477) - ✨ add express error handler for html/json - one true error handler for express responses - contains still some TODO's, but they are not high priority for first implementation/integration - this middleware only takes responsibility of either rendering html responses or return json error responses - 🎨 use new express error handler in middleware/index - 404 and 500 handling - 🎨 return error instead of error message in permissions/index.js - the rule for error handling should be: if you call a unit, this unit should return a custom Ghost error - 🎨 wrap serve static module - rule: if you call a module/unit, you should always wrap this error - it's always the same rule - so the caller never has to worry about what comes back - it's always a clear error instance - in this case: we return our notfounderror if serve static does not find the resource - this avoid having checks everywhere - 🎨 replace usages of errors/index.js functions and adapt tests - use logging.error, logging.warn - make tests green - remove some usages of logging and throwing api errors -> because when a request is involved, logging happens automatically - 🐛 return errorDetails to Ghost-Admin - errorDetails is used for Theme error handling - 🎨 use 500er error for theme is missing error in theme-handler - 🎨 extend file rotation to 1w
2016-10-04 17:33:43 +02:00
privateBlogging.filterPrivateRoutes(req, res, next);
res.redirect.calledOnce.should.be.true();
});
it('filterPrivateRoutes: tag rss redirects to /private', function () {
req.path = req.url = '/tag/slimer/rss/';
🎨 configurable logging with bunyan (#7431) - 🛠 add bunyan and prettyjson, remove morgan - ✨ add logging module - GhostLogger class that handles setup of bunyan - PrettyStream for stdout - ✨ config for logging - @TODO: testing level fatal? - ✨ log each request via GhostLogger (express middleware) - @TODO: add errors to output - 🔥 remove errors.updateActiveTheme - we can read the value from config - 🔥 remove 15 helper functions in core/server/errors/index.js - all these functions get replaced by modules: 1. logging 2. error middleware handling for html/json 3. error creation (which will be part of PR #7477) - ✨ add express error handler for html/json - one true error handler for express responses - contains still some TODO's, but they are not high priority for first implementation/integration - this middleware only takes responsibility of either rendering html responses or return json error responses - 🎨 use new express error handler in middleware/index - 404 and 500 handling - 🎨 return error instead of error message in permissions/index.js - the rule for error handling should be: if you call a unit, this unit should return a custom Ghost error - 🎨 wrap serve static module - rule: if you call a module/unit, you should always wrap this error - it's always the same rule - so the caller never has to worry about what comes back - it's always a clear error instance - in this case: we return our notfounderror if serve static does not find the resource - this avoid having checks everywhere - 🎨 replace usages of errors/index.js functions and adapt tests - use logging.error, logging.warn - make tests green - remove some usages of logging and throwing api errors -> because when a request is involved, logging happens automatically - 🐛 return errorDetails to Ghost-Admin - errorDetails is used for Theme error handling - 🎨 use 500er error for theme is missing error in theme-handler - 🎨 extend file rotation to 1w
2016-10-04 17:33:43 +02:00
privateBlogging.filterPrivateRoutes(req, res, next);
res.redirect.calledOnce.should.be.true();
});
it('filterPrivateRoutes: rss plus something redirects to /private', function () {
req.path = req.url = '/rss/sometag';
🎨 configurable logging with bunyan (#7431) - 🛠 add bunyan and prettyjson, remove morgan - ✨ add logging module - GhostLogger class that handles setup of bunyan - PrettyStream for stdout - ✨ config for logging - @TODO: testing level fatal? - ✨ log each request via GhostLogger (express middleware) - @TODO: add errors to output - 🔥 remove errors.updateActiveTheme - we can read the value from config - 🔥 remove 15 helper functions in core/server/errors/index.js - all these functions get replaced by modules: 1. logging 2. error middleware handling for html/json 3. error creation (which will be part of PR #7477) - ✨ add express error handler for html/json - one true error handler for express responses - contains still some TODO's, but they are not high priority for first implementation/integration - this middleware only takes responsibility of either rendering html responses or return json error responses - 🎨 use new express error handler in middleware/index - 404 and 500 handling - 🎨 return error instead of error message in permissions/index.js - the rule for error handling should be: if you call a unit, this unit should return a custom Ghost error - 🎨 wrap serve static module - rule: if you call a module/unit, you should always wrap this error - it's always the same rule - so the caller never has to worry about what comes back - it's always a clear error instance - in this case: we return our notfounderror if serve static does not find the resource - this avoid having checks everywhere - 🎨 replace usages of errors/index.js functions and adapt tests - use logging.error, logging.warn - make tests green - remove some usages of logging and throwing api errors -> because when a request is involved, logging happens automatically - 🐛 return errorDetails to Ghost-Admin - errorDetails is used for Theme error handling - 🎨 use 500er error for theme is missing error in theme-handler - 🎨 extend file rotation to 1w
2016-10-04 17:33:43 +02:00
privateBlogging.filterPrivateRoutes(req, res, next);
res.redirect.calledOnce.should.be.true();
});
it('filterPrivateRoutes should render custom robots.txt', function () {
req.url = req.path = '/robots.txt';
res.writeHead = sinon.spy();
res.end = sinon.spy();
sinon.stub(fs, 'readFile').callsFake(function (file, cb) {
cb(null, 'User-agent: * Disallow: /');
});
privateBlogging.filterPrivateRoutes(req, res, next);
res.writeHead.called.should.be.true();
res.end.called.should.be.true();
});
it('doLoginToPrivateSite should call next if error', function () {
res.error = 'Test Error';
privateBlogging.doLoginToPrivateSite(req, res, next);
next.called.should.be.true();
});
describe('with hash verification', function () {
beforeEach(function () {
settingsStub.withArgs('password').returns('rightpassword');
});
it('authenticatePrivateSession should return next if hash is verified', function () {
const salt = Date.now().toString();
req.session = {
token: hash('rightpassword', salt),
salt: salt
};
privateBlogging.authenticatePrivateSession(req, res, next);
next.called.should.be.true();
});
it('authenticatePrivateSession should redirect if hash is not verified', function () {
req.url = '/welcome';
req.session = {
token: 'wrongpassword',
salt: Date.now().toString()
};
res.redirect = sinon.spy();
privateBlogging.authenticatePrivateSession(req, res, next);
res.redirect.called.should.be.true();
});
it('redirectPrivateToHomeIfLoggedIn should redirect if hash is verified', function () {
const salt = Date.now().toString();
req.session = {
token: hash('rightpassword', salt),
salt: salt
};
res.redirect = sinon.spy();
privateBlogging.redirectPrivateToHomeIfLoggedIn(req, res, next);
res.redirect.called.should.be.true();
});
it('redirectPrivateToHomeIfLoggedIn should return next if hash is not verified', function () {
req.session = {
token: 'wrongpassword',
salt: Date.now().toString()
};
privateBlogging.redirectPrivateToHomeIfLoggedIn(req, res, next);
next.called.should.be.true();
});
it('doLoginToPrivateSite should return next if password is incorrect', function () {
req.body = {password: 'wrongpassword'};
privateBlogging.doLoginToPrivateSite(req, res, next);
res.error.should.not.be.empty();
next.called.should.be.true();
});
it('doLoginToPrivateSite should redirect if password is correct', function () {
req.body = {password: 'rightpassword'};
req.session = {};
res.redirect = sinon.spy();
privateBlogging.doLoginToPrivateSite(req, res, next);
res.redirect.called.should.be.true();
});
it('doLoginToPrivateSite should redirect to "/" if r param is a full url', function () {
req.body = {password: 'rightpassword'};
req.session = {};
req.query = {
r: encodeURIComponent('http://britney.com')
};
res.redirect = sinon.spy();
privateBlogging.doLoginToPrivateSite(req, res, next);
res.redirect.called.should.be.true();
res.redirect.args[0][0].should.be.equal('/');
});
it('filterPrivateRoutes should 404 for /rss/ requests', function () {
const salt = Date.now().toString();
req.url = req.path = '/rss/';
req.session = {
token: hash('rightpassword', salt),
salt: salt
};
res.isPrivateBlog = true;
res.redirect = sinon.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 () {
const salt = Date.now().toString();
req.url = req.path = '/tag/welcome/rss/';
req.session = {
token: hash('rightpassword', salt),
salt: salt
};
res.isPrivateBlog = true;
res.redirect = sinon.spy();
privateBlogging.filterPrivateRoutes(req, res, next);
next.called.should.be.true();
(next.firstCall.args[0] instanceof errors.NotFoundError).should.eql(true);
});
it('filterPrivateRoutes should return next if tag contains rss', function () {
const salt = Date.now().toString();
req.url = req.path = '/tag/rss-test/';
req.session = {
token: hash('rightpassword', salt),
salt: salt
};
res.isPrivateBlog = true;
res.redirect = sinon.spy();
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 () {
const salt = Date.now().toString();
req.url = req.path = '/ab/';
req.session = {
token: hash('rightpassword', salt),
salt: salt
};
res.isPrivateBlog = true;
res.redirect = sinon.spy();
privateBlogging.filterPrivateRoutes(req, res, next);
next.called.should.be.true();
next.firstCall.args.length.should.equal(0);
});
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 = sinon.spy();
privateBlogging.filterPrivateRoutes(req, res, next);
res.redirect.called.should.be.true();
});
});
});
});
});