diff --git a/Gruntfile.js b/Gruntfile.js index 753504a298..8227ebbd23 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -183,7 +183,8 @@ var _ = require('lodash'), // #### All Unit tests unit: { src: [ - 'core/test/unit/**/*_spec.js' + 'core/test/unit/**/*_spec.js', + 'core/server/apps/**/tests/*_spec.js' ] }, diff --git a/core/server/apps/index.js b/core/server/apps/index.js index ab49b06506..ff27352c3c 100644 --- a/core/server/apps/index.js +++ b/core/server/apps/index.js @@ -5,6 +5,7 @@ var _ = require('lodash'), api = require('../api'), loader = require('./loader'), i18n = require('../i18n'), + config = require('../config'), // Holds the available apps availableApps = {}; @@ -20,13 +21,13 @@ function getInstalledApps() { return Promise.reject(e); } - return installed; + return installed.concat(config.internalApps); }); } function saveInstalledApps(installedApps) { return getInstalledApps().then(function (currentInstalledApps) { - var updatedAppsInstalled = _.uniq(installedApps.concat(currentInstalledApps)); + var updatedAppsInstalled = _.difference(_.uniq(installedApps.concat(currentInstalledApps)), config.internalApps); return api.settings.edit({settings: [{key: 'installedApps', value: updatedAppsInstalled}]}, {context: {internal: true}}); }); @@ -42,6 +43,8 @@ module.exports = { var aApps = response.settings[0]; appsToLoad = JSON.parse(aApps.value) || []; + + appsToLoad = appsToLoad.concat(config.internalApps); }); } catch (e) { errors.logError( diff --git a/core/server/apps/loader.js b/core/server/apps/loader.js index 40231f3a06..719f69a1ff 100644 --- a/core/server/apps/loader.js +++ b/core/server/apps/loader.js @@ -10,8 +10,16 @@ var path = require('path'), i18n = require('../i18n'), loader; +function isInternalApp(name) { + return _.contains(config.internalApps, name); +} + // Get the full path to an app by name function getAppAbsolutePath(name) { + if (isInternalApp(name)) { + return path.join(config.paths.corePath, '/server/apps/', name); + } + return path.join(config.paths.appPath, name); } @@ -20,19 +28,25 @@ function getAppAbsolutePath(name) { function getAppRelativePath(name, relativeTo) { relativeTo = relativeTo || __dirname; - return path.relative(relativeTo, getAppAbsolutePath(name)); + var relativePath = path.relative(relativeTo, getAppAbsolutePath(name)); + + if (relativePath.charAt(0) !== '.') { + relativePath = './' + relativePath; + } + + return relativePath; } // Load apps through a pseudo sandbox -function loadApp(appPath) { - var sandbox = new AppSandbox(); +function loadApp(appPath, isInternal) { + var sandbox = new AppSandbox({internal: isInternal}); return sandbox.loadApp(appPath); } function getAppByName(name, permissions) { // Grab the app class to instantiate - var AppClass = loadApp(getAppRelativePath(name)), + var AppClass = loadApp(getAppRelativePath(name), isInternalApp(name)), appProxy = new AppProxy({ name: name, permissions: permissions diff --git a/core/server/apps/private-blogging/index.js b/core/server/apps/private-blogging/index.js new file mode 100644 index 0000000000..d8d16f69dd --- /dev/null +++ b/core/server/apps/private-blogging/index.js @@ -0,0 +1,30 @@ +var config = require('../../config'), + errors = require('../../errors'), + i18n = require('../../i18n'), + middleware = require('./lib/middleware'), + router = require('./lib/router'); + +module.exports = { + activate: function activate() { + if (config.paths.subdir) { + var paths = config.paths.subdir.split('/'); + + if (paths.pop() === config.routeKeywords.private) { + errors.logErrorAndExit( + new Error(i18n.t('errors.config.urlCannotContainPrivateSubdir.error')), + i18n.t('errors.config.urlCannotContainPrivateSubdir.description'), + i18n.t('errors.config.urlCannotContainPrivateSubdir.help') + ); + } + } + }, + + setupMiddleware: function setupMiddleware(blogApp) { + blogApp.use(middleware.checkIsPrivate); + blogApp.use(middleware.filterPrivateRoutes); + }, + + setupRoutes: function setupRoutes(blogRouter) { + blogRouter.use('/' + config.routeKeywords.private + '/', router); + } +}; diff --git a/core/server/middleware/private-blogging.js b/core/server/apps/private-blogging/lib/middleware.js similarity index 67% rename from core/server/middleware/private-blogging.js rename to core/server/apps/private-blogging/lib/middleware.js index e2c5b466e9..c89c6e4ac6 100644 --- a/core/server/middleware/private-blogging.js +++ b/core/server/apps/private-blogging/lib/middleware.js @@ -1,14 +1,16 @@ var _ = require('lodash'), fs = require('fs'), - config = require('../config'), + config = require('../../../config'), crypto = require('crypto'), path = require('path'), - api = require('../api'), + api = require('../../../api'), Promise = require('bluebird'), - errors = require('../errors'), + errors = require('../../../errors'), session = require('cookie-session'), - utils = require('../utils'), - i18n = require('../i18n'), + utils = require('../../../utils'), + i18n = require('../../../i18n'), + privateRoute = '/' + config.routeKeywords.private + '/', + protectedSecurity = [], privateBlogging; function verifySessionHash(salt, hash) { @@ -45,7 +47,7 @@ privateBlogging = { }, filterPrivateRoutes: function filterPrivateRoutes(req, res, next) { - if (res.isAdmin || !res.isPrivateBlog || req.url.lastIndexOf('/private/', 0) === 0) { + if (res.isAdmin || !res.isPrivateBlog || req.url.lastIndexOf(privateRoute, 0) === 0) { return next(); } @@ -55,7 +57,7 @@ privateBlogging = { (req.path.lastIndexOf('/sitemap', 0) === 0 && req.path.lastIndexOf('.xml') === req.path.length - 4)) { return errors.error404(req, res, next); } else if (req.url.lastIndexOf('/robots.txt', 0) === 0) { - fs.readFile(path.join(config.paths.corePath, 'shared', 'private-robots.txt'), function readFile(err, buf) { + fs.readFile(path.resolve(__dirname, '../', 'robots.txt'), function readFile(err, buf) { if (err) { return next(err); } @@ -80,7 +82,7 @@ privateBlogging = { if (isVerified) { return next(); } else { - url = config.urlFor({relativeUrl: '/private/'}); + url = config.urlFor({relativeUrl: privateRoute}); url += req.url === '/' ? '' : '?r=' + encodeURIComponent(req.url); return res.redirect(url); } @@ -133,6 +135,46 @@ privateBlogging = { return next(); } }); + }, + + spamPrevention: function spamPrevention(req, res, next) { + var currentTime = process.hrtime()[0], + remoteAddress = req.connection.remoteAddress, + rateProtectedPeriod = config.rateProtectedPeriod || 3600, + rateProtectedAttempts = config.rateProtectedAttempts || 10, + ipCount = '', + message = i18n.t('errors.middleware.spamprevention.tooManyAttempts'), + deniedRateLimit = '', + password = req.body.password; + + if (password) { + protectedSecurity.push({ip: remoteAddress, time: currentTime}); + } else { + res.error = { + message: i18n.t('errors.middleware.spamprevention.noPassword') + }; + return next(); + } + + // filter entries that are older than rateProtectedPeriod + protectedSecurity = _.filter(protectedSecurity, function filter(logTime) { + return (logTime.time + rateProtectedPeriod > currentTime); + }); + + ipCount = _.chain(protectedSecurity).countBy('ip').value(); + deniedRateLimit = (ipCount[remoteAddress] > rateProtectedAttempts); + + if (deniedRateLimit) { + errors.logError( + i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error', {rfa: rateProtectedAttempts, rfp: rateProtectedPeriod}), + i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.context') + ); + message += rateProtectedPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater'); + res.error = { + message: message + }; + } + return next(); } }; diff --git a/core/server/apps/private-blogging/lib/router.js b/core/server/apps/private-blogging/lib/router.js new file mode 100644 index 0000000000..e5789f2547 --- /dev/null +++ b/core/server/apps/private-blogging/lib/router.js @@ -0,0 +1,39 @@ +var path = require('path'), + express = require('express'), + middleware = require('./middleware'), + templates = require('../../../controllers/frontend/templates'), + setResponseContext = require('../../../controllers/frontend/context'), + privateRouter = express.Router(); + +function controller(req, res) { + var defaultView = path.resolve(__dirname, 'views', 'private.hbs'), + paths = templates.getActiveThemePaths(req.app.get('activeTheme')), + data = {}; + + if (res.error) { + data.error = res.error; + } + + setResponseContext(req, res); + if (paths.hasOwnProperty('private.hbs')) { + return res.render('private', data); + } else { + return res.render(defaultView, data); + } +} + +// password-protected frontend route +privateRouter.route('/') + .get( + middleware.isPrivateSessionAuth, + controller + ) + .post( + middleware.isPrivateSessionAuth, + middleware.spamPrevention, + middleware.authenticateProtection, + controller + ); + +module.exports = privateRouter; +module.exports.controller = controller; diff --git a/core/server/views/private.hbs b/core/server/apps/private-blogging/lib/views/private.hbs similarity index 100% rename from core/server/views/private.hbs rename to core/server/apps/private-blogging/lib/views/private.hbs diff --git a/core/shared/private-robots.txt b/core/server/apps/private-blogging/robots.txt similarity index 100% rename from core/shared/private-robots.txt rename to core/server/apps/private-blogging/robots.txt diff --git a/core/server/apps/private-blogging/tests/controller_spec.js b/core/server/apps/private-blogging/tests/controller_spec.js new file mode 100644 index 0000000000..f5bbe57b0b --- /dev/null +++ b/core/server/apps/private-blogging/tests/controller_spec.js @@ -0,0 +1,83 @@ +/*globals describe, beforeEach, afterEach, it*/ +var privateController = require('../lib/router').controller, + path = require('path'), + sinon = require('sinon'), + configUtils = require('../../../../test/utils/configUtils'), + sandbox = sinon.sandbox.create(); + +describe('Private Controller', function () { + var res, req, defaultPath; + + // Helper function to prevent unit tests + // from failing via timeout when they + // should just immediately fail + function failTest(done) { + return function (err) { + done(err); + }; + } + + beforeEach(function () { + res = { + locals: {version: ''}, + render: sandbox.spy() + }; + + req = { + app: {get: function () { return 'casper'; }}, + route: {path: '/private/?r=/'}, + query: {r: ''}, + params: {} + }; + + defaultPath = path.join(configUtils.config.paths.appRoot, '/core/server/apps/private-blogging/lib/views/private.hbs'); + + configUtils.set({ + theme: { + permalinks: '/:slug/' + } + }); + }); + + afterEach(function () { + sandbox.restore(); + configUtils.restore(); + }); + + it('Should render default password page when theme has no password template', function (done) { + configUtils.set({paths: {availableThemes: {casper: {}}}}); + + res.render = function (view) { + view.should.eql(defaultPath); + done(); + }; + + privateController(req, res, failTest(done)); + }); + + it('Should render theme password page when it exists', function (done) { + configUtils.set({paths: {availableThemes: {casper: { + 'private.hbs': '/content/themes/casper/private.hbs' + }}}}); + + res.render = function (view) { + view.should.eql('private'); + done(); + }; + + privateController(req, res, failTest(done)); + }); + + it('Should render with error when error is passed in', function (done) { + configUtils.set({paths: {availableThemes: {casper: {}}}}); + res.error = 'Test Error'; + + res.render = function (view, context) { + view.should.eql(defaultPath); + context.should.eql({error: 'Test Error'}); + done(); + }; + + privateController(req, res, failTest(done)); + }); +}); diff --git a/core/test/unit/middleware/private-blogging_spec.js b/core/server/apps/private-blogging/tests/middleware_spec.js similarity index 81% rename from core/test/unit/middleware/private-blogging_spec.js rename to core/server/apps/private-blogging/tests/middleware_spec.js index 635c56ec49..507f1e329d 100644 --- a/core/test/unit/middleware/private-blogging_spec.js +++ b/core/server/apps/private-blogging/tests/middleware_spec.js @@ -1,11 +1,11 @@ -/*globals describe, beforeEach, afterEach, it*/ +/*globals describe, beforeEach, afterEach, before, it*/ var crypto = require('crypto'), should = require('should'), sinon = require('sinon'), Promise = require('bluebird'), - privateBlogging = require('../../../server/middleware/private-blogging'), - api = require('../../../server/api'), - errors = require('../../../server/errors'), + privateBlogging = require('../lib/middleware'), + api = require('../../../api'), + errors = require('../../../errors'), fs = require('fs'); should.equal(true, true); @@ -268,4 +268,77 @@ describe('Private Blogging', function () { }); }); }); + + describe('spamPrevention', function () { + var error = null, + res, req, spyNext; + + before(function () { + spyNext = sinon.spy(function (param) { + error = param; + }); + }); + + beforeEach(function () { + res = sinon.spy(); + req = { + connection: { + remoteAddress: '10.0.0.0' + }, + body: { + password: 'password' + } + }; + }); + + it ('sets an error when there is no password', function (done) { + req.body = {}; + + privateBlogging.spamPrevention(req, res, spyNext); + res.error.message.should.equal('No password entered'); + spyNext.calledOnce.should.be.true(); + + done(); + }); + + it ('sets and error message after 10 tries', function (done) { + var ndx; + + for (ndx = 0; ndx < 10; ndx = ndx + 1) { + privateBlogging.spamPrevention(req, res, spyNext); + } + + should.not.exist(res.error); + privateBlogging.spamPrevention(req, res, spyNext); + should.exist(res.error); + should.exist(res.error.message); + + done(); + }); + + it ('allows more tries after an hour', function (done) { + var ndx, + stub = sinon.stub(process, 'hrtime', function () { + return [10, 10]; + }); + + for (ndx = 0; ndx < 11; ndx = ndx + 1) { + privateBlogging.spamPrevention(req, res, spyNext); + } + + should.exist(res.error); + process.hrtime.restore(); + stub = sinon.stub(process, 'hrtime', function () { + return [3610000, 10]; + }); + + res = sinon.spy(); + + privateBlogging.spamPrevention(req, res, spyNext); + should.not.exist(res.error); + + process.hrtime.restore(); + done(); + }); + }); }); diff --git a/core/server/apps/sandbox.js b/core/server/apps/sandbox.js index 582ce0728c..cba73b60ea 100644 --- a/core/server/apps/sandbox.js +++ b/core/server/apps/sandbox.js @@ -32,6 +32,12 @@ AppSandbox.prototype.loadModule = function loadModuleSandboxed(modulePath) { // Instantiate a Node Module class currentModule = new Module(modulePath, parentModulePath); + if (this.opts.internal) { + currentModule.load(currentModule.id); + + return currentModule.exports; + } + // Grab the original modules require function nodeRequire = currentModule.require; diff --git a/core/server/config/index.js b/core/server/config/index.js index 10b477b117..9786ecaf48 100644 --- a/core/server/config/index.js +++ b/core/server/config/index.js @@ -192,6 +192,7 @@ ConfigManager.prototype.set = function (config) { preview: 'p', private: 'private' }, + internalApps: ['private-blogging'], slugs: { // Used by generateSlug to generate slugs for posts, tags, users, .. // reserved slugs are reserved but can be extended/removed by apps diff --git a/core/server/controllers/frontend/index.js b/core/server/controllers/frontend/index.js index aa1cd61cd4..099fd64b2f 100644 --- a/core/server/controllers/frontend/index.js +++ b/core/server/controllers/frontend/index.js @@ -6,7 +6,6 @@ var _ = require('lodash'), api = require('../../api'), - path = require('path'), config = require('../../config'), errors = require('../../errors'), filters = require('../../filters'), @@ -143,22 +142,6 @@ frontendControllers = { return next(); } }).catch(handleError(next)); - }, - private: function private(req, res) { - var defaultPage = path.resolve(config.paths.adminViews, 'private.hbs'), - paths = templates.getActiveThemePaths(req.app.get('activeTheme')), - data = {}; - - if (res.error) { - data.error = res.error; - } - - setResponseContext(req, res); - if (paths.hasOwnProperty('private.hbs')) { - return res.render('private', data); - } else { - return res.render(defaultPage, data); - } } }; diff --git a/core/server/middleware/index.js b/core/server/middleware/index.js index 66bd609648..5a35207257 100644 --- a/core/server/middleware/index.js +++ b/core/server/middleware/index.js @@ -18,7 +18,6 @@ var bodyParser = require('body-parser'), checkSSL = require('./check-ssl'), decideIsAdmin = require('./decide-is-admin'), oauth = require('./oauth'), - privateBlogging = require('./private-blogging'), redirectToSetup = require('./redirect-to-setup'), serveSharedFile = require('./serve-shared-file'), spamPrevention = require('./spam-prevention'), @@ -26,6 +25,7 @@ var bodyParser = require('body-parser'), themeHandler = require('./theme-handler'), uncapitalise = require('./uncapitalise'), cors = require('./cors'), + privateBlogging = require('../apps/private-blogging'), ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy, BearerStrategy = require('passport-http-bearer').Strategy, @@ -37,7 +37,6 @@ middleware = { upload: multer({dest: tmpdir()}), cacheControl: cacheControl, spamPrevention: spamPrevention, - privateBlogging: privateBlogging, oauth: oauth, api: { authenticateClient: auth.authenticateClient, @@ -111,9 +110,8 @@ setupMiddleware = function setupMiddleware(blogApp, adminApp) { // Theme only config blogApp.use(staticTheme()); - // Check if password protected blog - blogApp.use(privateBlogging.checkIsPrivate); // check if the blog is protected - blogApp.use(privateBlogging.filterPrivateRoutes); + // setup middleware for private blogs + privateBlogging.setupMiddleware(blogApp); // Serve sitemap.xsl file blogApp.use(serveSharedFile('sitemap.xsl', 'text/xsl', utils.ONE_DAY_S)); @@ -158,8 +156,8 @@ setupMiddleware = function setupMiddleware(blogApp, adminApp) { adminApp.use(routes.admin()); blogApp.use('/ghost', adminApp); - // Set up Frontend routes - blogApp.use(routes.frontend(middleware)); + // Set up Frontend routes (including private blogging routes) + blogApp.use(routes.frontend()); // ### Error handling // 404 Handler diff --git a/core/server/middleware/spam-prevention.js b/core/server/middleware/spam-prevention.js index 24bda693d6..2b6e199f92 100644 --- a/core/server/middleware/spam-prevention.js +++ b/core/server/middleware/spam-prevention.js @@ -12,7 +12,6 @@ var _ = require('lodash'), i18n = require('../i18n'), loginSecurity = [], forgottenSecurity = [], - protectedSecurity = [], spamPrevention; spamPrevention = { @@ -116,46 +115,6 @@ spamPrevention = { next(); }, - protected: function protected(req, res, next) { - var currentTime = process.hrtime()[0], - remoteAddress = req.connection.remoteAddress, - rateProtectedPeriod = config.rateProtectedPeriod || 3600, - rateProtectedAttempts = config.rateProtectedAttempts || 10, - ipCount = '', - message = i18n.t('errors.middleware.spamprevention.tooManyAttempts'), - deniedRateLimit = '', - password = req.body.password; - - if (password) { - protectedSecurity.push({ip: remoteAddress, time: currentTime}); - } else { - res.error = { - message: i18n.t('errors.middleware.spamprevention.noPassword') - }; - return next(); - } - - // filter entries that are older than rateProtectedPeriod - protectedSecurity = _.filter(protectedSecurity, function filter(logTime) { - return (logTime.time + rateProtectedPeriod > currentTime); - }); - - ipCount = _.chain(protectedSecurity).countBy('ip').value(); - deniedRateLimit = (ipCount[remoteAddress] > rateProtectedAttempts); - - if (deniedRateLimit) { - errors.logError( - i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error', {rfa: rateProtectedAttempts, rfp: rateProtectedPeriod}), - i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.context') - ); - message += rateProtectedPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater'); - res.error = { - message: message - }; - } - return next(); - }, - resetCounter: function resetCounter(email) { loginSecurity = _.filter(loginSecurity, function filter(logTime) { return (logTime.email !== email); diff --git a/core/server/routes/frontend.js b/core/server/routes/frontend.js index 5c9cc7f216..6ceffe5fc4 100644 --- a/core/server/routes/frontend.js +++ b/core/server/routes/frontend.js @@ -1,16 +1,16 @@ -var frontend = require('../controllers/frontend'), - channels = require('../controllers/frontend/channels'), - config = require('../config'), - express = require('express'), - utils = require('../utils'), +var frontend = require('../controllers/frontend'), + channels = require('../controllers/frontend/channels'), + config = require('../config'), + express = require('express'), + utils = require('../utils'), + privateBlogging = require('../apps/private-blogging'), frontendRoutes; -frontendRoutes = function frontendRoutes(middleware) { +frontendRoutes = function frontendRoutes() { var router = express.Router(), subdir = config.paths.subdir, - routeKeywords = config.routeKeywords, - privateRouter = express.Router(); + routeKeywords = config.routeKeywords; // ### Admin routes router.get(/^\/(logout|signout)\/$/, function redirectToSignout(req, res) { @@ -25,31 +25,18 @@ frontendRoutes = function frontendRoutes(middleware) { utils.redirect301(res, subdir + '/ghost/'); }); - // password-protected frontend route - privateRouter.route('/') - .get( - middleware.privateBlogging.isPrivateSessionAuth, - frontend.private - ) - .post( - middleware.privateBlogging.isPrivateSessionAuth, - middleware.spamPrevention.protected, - middleware.privateBlogging.authenticateProtection, - frontend.private - ); - // Post Live Preview router.get('/' + routeKeywords.preview + '/:uuid', frontend.preview); - // Private - router.use('/' + routeKeywords.private + '/', privateRouter); - // Channels router.use(channels.router()); // Default router.get('*', frontend.single); + // @TODO: this can be removed once the proper app route hooks have been set up. + privateBlogging.setupRoutes(router); + return router; }; diff --git a/core/server/translations/en.json b/core/server/translations/en.json index 1e9656707c..801367731c 100644 --- a/core/server/translations/en.json +++ b/core/server/translations/en.json @@ -145,6 +145,11 @@ "description": "Your site url in config.js cannot contain a subdirectory called ghost.", "help": "Please rename the subdirectory before restarting" }, + "urlCannotContainPrivateSubdir": { + "error": "private subdirectory not allowed", + "description": "Your site url in config.js cannot contain a subdirectory called private.", + "help": "Please rename the subdirectory before restarting" + }, "dbConfigInvalid": { "error": "invalid database configuration", "description": "Your database configuration in config.js is invalid.", diff --git a/core/test/unit/controllers/frontend/index_spec.js b/core/test/unit/controllers/frontend/index_spec.js index 7506194cf0..1ec42f48ae 100644 --- a/core/test/unit/controllers/frontend/index_spec.js +++ b/core/test/unit/controllers/frontend/index_spec.js @@ -4,7 +4,6 @@ var moment = require('moment'), sinon = require('sinon'), Promise = require('bluebird'), _ = require('lodash'), - path = require('path'), // Stuff we are testing api = require('../../../../server/api'), @@ -730,69 +729,6 @@ describe('Frontend Controller', function () { }); }); - describe('private', function () { - var res, req, defaultPath; - - beforeEach(function () { - res = { - locals: {version: ''}, - render: sandbox.spy() - }; - - req = { - app: {get: function () { return 'casper'; }}, - route: {path: '/private/?r=/'}, - query: {r: ''}, - params: {} - }; - - defaultPath = path.join(configUtils.config.paths.appRoot, '/core/server/views/private.hbs'); - - configUtils.set({ - theme: { - permalinks: '/:slug/' - } - }); - }); - - it('Should render default password page when theme has no password template', function (done) { - configUtils.set({paths: {availableThemes: {casper: {}}}}); - - res.render = function (view) { - view.should.eql(defaultPath); - done(); - }; - - frontend.private(req, res, failTest(done)); - }); - - it('Should render theme password page when it exists', function (done) { - configUtils.set({paths: {availableThemes: {casper: { - 'private.hbs': '/content/themes/casper/private.hbs' - }}}}); - - res.render = function (view) { - view.should.eql('private'); - done(); - }; - - frontend.private(req, res, failTest(done)); - }); - - it('Should render with error when error is passed in', function (done) { - configUtils.set({paths: {availableThemes: {casper: {}}}}); - res.error = 'Test Error'; - - res.render = function (view, context) { - view.should.eql(defaultPath); - context.should.eql({error: 'Test Error'}); - done(); - }; - - frontend.private(req, res, failTest(done)); - }); - }); - describe('preview', function () { var req, res, mockPosts = [{ posts: [{ diff --git a/core/test/unit/middleware/spam-prevention_spec.js b/core/test/unit/middleware/spam-prevention_spec.js index 2c941a2636..d1d8d99ac7 100644 --- a/core/test/unit/middleware/spam-prevention_spec.js +++ b/core/test/unit/middleware/spam-prevention_spec.js @@ -151,70 +151,4 @@ describe('Middleware: spamPrevention', function () { done(); }); }); - - describe('protected', function () { - var res; - - beforeEach(function () { - res = sinon.spy(); - req = { - connection: { - remoteAddress: '10.0.0.0' - }, - body: { - password: 'password' - } - }; - }); - - it ('sets an error when there is no password', function (done) { - req.body = {}; - - middleware.spamPrevention.protected(req, res, spyNext); - res.error.message.should.equal('No password entered'); - spyNext.calledOnce.should.be.true(); - - done(); - }); - - it ('sets and error message after 10 tries', function (done) { - var ndx; - - for (ndx = 0; ndx < 10; ndx = ndx + 1) { - middleware.spamPrevention.protected(req, res, spyNext); - } - - should.not.exist(res.error); - middleware.spamPrevention.protected(req, res, spyNext); - should.exist(res.error); - should.exist(res.error.message); - - done(); - }); - - it ('allows more tries after an hour', function (done) { - var ndx, - stub = sinon.stub(process, 'hrtime', function () { - return [10, 10]; - }); - - for (ndx = 0; ndx < 11; ndx = ndx + 1) { - middleware.spamPrevention.protected(req, res, spyNext); - } - - should.exist(res.error); - process.hrtime.restore(); - stub = sinon.stub(process, 'hrtime', function () { - return [3610000, 10]; - }); - - res = sinon.spy(); - - middleware.spamPrevention.protected(req, res, spyNext); - should.not.exist(res.error); - - process.hrtime.restore(); - done(); - }); - }); });