mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
Merge pull request #6617 from acburdine/private-blogging-app
Move private-blogging into an internal app
This commit is contained in:
commit
b0ab3f0273
19 changed files with 332 additions and 238 deletions
|
@ -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'
|
||||
]
|
||||
},
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
30
core/server/apps/private-blogging/index.js
Normal file
30
core/server/apps/private-blogging/index.js
Normal file
|
@ -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);
|
||||
}
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
39
core/server/apps/private-blogging/lib/router.js
Normal file
39
core/server/apps/private-blogging/lib/router.js
Normal file
|
@ -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;
|
83
core/server/apps/private-blogging/tests/controller_spec.js
Normal file
83
core/server/apps/private-blogging/tests/controller_spec.js
Normal file
|
@ -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));
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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: [{
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue