Skip to content

Commit

Permalink
Theming updates for password protection
Browse files Browse the repository at this point in the history
refs #4993, #5073

- Removed nonexistent helpers siteDescription and bodyClass from admin templates
- Changed password.hbs to private.hbs to match the route name
- added a new input_password helper for rendering the password input with the correct properties
- removed the forward input as this can be handled via urls only
- moved 'private' to routeKeywords
- added 'private' context
- minor update to text next to the password in settings
  • Loading branch information
ErisDS committed May 13, 2015
1 parent 1f0fb3c commit c3dda5e
Show file tree
Hide file tree
Showing 12 changed files with 97 additions and 47 deletions.
2 changes: 1 addition & 1 deletion core/client/app/templates/settings/general.hbs
Expand Up @@ -98,7 +98,7 @@
{{#if model.isPrivate}}
<div class="form-group">
{{input name="general[password]" type="text" value=model.password}}
<p>This password will be needed to access your blog. All search engine optimization and social features are now disabled.</p>
<p>This password will be needed to access your blog. All search engine optimization and social features are now disabled. This password is stored in plaintext.</p>
</div>
{{/if}}
</fieldset>
Expand Down
3 changes: 2 additions & 1 deletion core/server/config/index.js
Expand Up @@ -206,7 +206,8 @@ ConfigManager.prototype.set = function (config) {
tag: 'tag',
author: 'author',
page: 'page',
preview: 'p'
preview: 'p',
private: 'private'
},
slugs: {
// Used by generateSlug to generate slugs for posts, tags, users, ..
Expand Down
18 changes: 10 additions & 8 deletions core/server/controllers/frontend.js
Expand Up @@ -71,7 +71,8 @@ function setResponseContext(req, res, data) {
var contexts = [],
pageParam = req.params.page !== undefined ? parseInt(req.params.page, 10) : 1,
tagPattern = new RegExp('^\\/' + config.routeKeywords.tag + '\\/'),
authorPattern = new RegExp('^\\/' + config.routeKeywords.author + '\\/');
authorPattern = new RegExp('^\\/' + config.routeKeywords.author + '\\/'),
privatePattern = new RegExp('^\\/' + config.routeKeywords.private + '\\/');

// paged context
if (!isNaN(pageParam) && pageParam > 1) {
Expand All @@ -85,6 +86,8 @@ function setResponseContext(req, res, data) {
contexts.push('index');
} else if (/\/rss\/(:page\/)?$/.test(req.route.path)) {
contexts.push('rss');
} else if (privatePattern.test(req.route.path)) {
contexts.push('private');
} else if (tagPattern.test(req.route.path)) {
contexts.push('tag');
} else if (authorPattern.test(req.route.path)) {
Expand Down Expand Up @@ -137,7 +140,6 @@ function renderPost(req, res) {
response = formatResponse(post);

setResponseContext(req, res, response);

res.render(view, response);
});
};
Expand Down Expand Up @@ -406,16 +408,16 @@ frontendControllers = {
},
rss: rss,
private: function (req, res) {
var defaultPage = path.resolve(config.paths.adminViews, 'password.hbs');
var defaultPage = path.resolve(config.paths.adminViews, 'private.hbs');
return getActiveThemePaths().then(function (paths) {
var data = {
forward: req.query.r
};
var data = {};
if (res.error) {
data.error = res.error;
}
if (paths.hasOwnProperty('password.hbs')) {
return res.render('password', data);

setResponseContext(req, res);
if (paths.hasOwnProperty('private.hbs')) {
return res.render('private', data);
} else {
return res.render(defaultPage, data);
}
Expand Down
15 changes: 10 additions & 5 deletions core/server/helpers/index.js
Expand Up @@ -23,22 +23,25 @@ coreHelpers.excerpt = require('./excerpt');
coreHelpers.foreach = require('./foreach');
coreHelpers.ghost_foot = require('./ghost_foot');
coreHelpers.ghost_head = require('./ghost_head');
coreHelpers.image = require('./image');
coreHelpers.is = require('./is');
coreHelpers.has = require('./has');
coreHelpers.meta_description = require('./meta_description');
coreHelpers.meta_title = require('./meta_title');
coreHelpers.navigation = require('./navigation');
coreHelpers.page_url = require('./page_url');
coreHelpers.pageUrl = require('./page_url').deprecated;
coreHelpers.pagination = require('./pagination');
coreHelpers.plural = require('./plural');
coreHelpers.post_class = require('./post_class');
coreHelpers.prev_post = require('./prev_next');
coreHelpers.next_post = require('./prev_next');
coreHelpers.tags = require('./tags');
coreHelpers.title = require('./title');
coreHelpers.url = require('./url');
coreHelpers.image = require('./image');
coreHelpers.prev_post = require('./prev_next');
coreHelpers.next_post = require('./prev_next');

// Specialist helpers for certain templates
coreHelpers.input_password = require('./input_password');
coreHelpers.page_url = require('./page_url');
coreHelpers.pageUrl = require('./page_url').deprecated;

coreHelpers.helperMissing = function (arg) {
if (arguments.length === 2) {
Expand Down Expand Up @@ -94,6 +97,7 @@ registerHelpers = function (adminHbs) {
registerThemeHelper('excerpt', coreHelpers.excerpt);
registerThemeHelper('foreach', coreHelpers.foreach);
registerThemeHelper('is', coreHelpers.is);
registerThemeHelper('input_password', coreHelpers.input_password);
registerThemeHelper('has', coreHelpers.has);
registerThemeHelper('navigation', coreHelpers.navigation);
registerThemeHelper('page_url', coreHelpers.page_url);
Expand All @@ -116,6 +120,7 @@ registerHelpers = function (adminHbs) {

// Register admin helpers
registerAdminHelper('asset', coreHelpers.asset);
registerAdminHelper('input_password', coreHelpers.input_password);
};

module.exports = coreHelpers;
Expand Down
24 changes: 24 additions & 0 deletions core/server/helpers/input_password.js
@@ -0,0 +1,24 @@
// # Input Password Helper
// Usage: `{{input_password}}`
//
// Password input used on private.hbs for password-protected blogs
//
// We use the name meta_title to match the helper for consistency:
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers

var hbs = require('express-hbs'),
utils = require('./utils'),
input_password;

input_password = function () {
var output = utils.inputTemplate({
type: 'password',
name: 'password',
className: 'private-login-password',
extras: 'autofocus="autofocus"'
});

return new hbs.handlebars.SafeString(output);
};

module.exports = input_password;
1 change: 1 addition & 0 deletions core/server/helpers/utils.js
Expand Up @@ -5,6 +5,7 @@ utils = {
assetTemplate: _.template('<%= source %>?v=<%= version %>'),
linkTemplate: _.template('<a href="<%= url %>"><%= text %></a>'),
scriptTemplate: _.template('<script src="<%= source %>?v=<%= version %>"></script>'),
inputTemplate: _.template('<input class="<%= className %>" type="<%= type %>" name="<%= name %>" <%= extras %> />'),
isProduction: process.env.NODE_ENV === 'production'
};

Expand Down
7 changes: 4 additions & 3 deletions core/server/middleware/middleware.js
Expand Up @@ -395,7 +395,7 @@ middleware = {
if (isVerified) {
return next();
} else {
return res.redirect(config.urlFor({relativeUrl: '/private/'}) + '?r=' + encodeURI(req.url));
return res.redirect(config.urlFor({relativeUrl: '/private/'}) + '?r=' + encodeURIComponent(req.url));
}
});
},
Expand Down Expand Up @@ -470,14 +470,15 @@ middleware = {
return api.settings.read({context: {internal: true}, key: 'password'}).then(function (response) {
var pass = response.settings[0],
hasher = crypto.createHash('sha256'),
salt = Date.now().toString();
salt = Date.now().toString(),
forward = req.query && req.query.r ? req.query.r : '/';

if (pass.value === bodyPass) {
hasher.update(bodyPass + salt, 'utf8');
req.session.token = hasher.digest('hex');
req.session.salt = salt;

return res.redirect(config.urlFor({relativeUrl: decodeURI(req.body.forward)}));
return res.redirect(config.urlFor({relativeUrl: decodeURIComponent(forward)}));
} else {
res.error = {
message: 'Wrong password'
Expand Down
4 changes: 2 additions & 2 deletions core/server/routes/frontend.js
Expand Up @@ -29,11 +29,11 @@ frontendRoutes = function (middleware) {
});

// password-protected frontend route
router.get('/private/',
router.get('/' + routeKeywords.private + '/',
middleware.isPrivateSessionAuth,
frontend.private
);
router.post('/private/',
router.post('/' + routeKeywords.private + '/',
middleware.isPrivateSessionAuth,
middleware.spamProtectedPrevention,
middleware.authenticateProtection,
Expand Down
Expand Up @@ -7,8 +7,6 @@

<title>Ghost - Private Blog Access</title>

<meta name="description" content="{{siteDescription}}">
<meta name="author" content="">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
Expand All @@ -26,10 +24,9 @@
<div class="vertical">
<form id="setup" class="setup-form private-login" method="post" novalidate="novalidate">
<h1>This blog is private</h1>
<input type="hidden" name="forward" value="{{forward}}">
<div class="form-group">
<span class="input-icon icon-lock">
<input class="private-login-password" type="password" name="password" autofocus="autofocus" />
{{input_password}}
</span>
<button class="btn btn-green private-login-button" type="submit">
Enter
Expand All @@ -49,6 +46,5 @@
</div>
</aside>
{{/if}}
<script src="{{asset "ghost.js" ghost="true" minifyInProduction="true"}}"></script>
</body>
</html>
5 changes: 2 additions & 3 deletions core/server/views/user-error.hbs
Expand Up @@ -6,8 +6,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />

<title>{{code}}{{message}}</title>
<meta name="description" content="{{siteDescription}}">
<meta name="author" content="">

<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
Expand All @@ -19,7 +18,7 @@
<link rel="stylesheet" type='text/css' href='//fonts.googleapis.com/css?family=Open+Sans:400,300,700'>
<link rel="stylesheet" href="{{asset "ghost.css" ghost="true" minifyInProduction="true"}}" />
</head>
<body class="{{bodyClass}}">
<body>
<main role="main" id="main">
<div id="container">
<section class="error-content error-404 js-error-container">
Expand Down
32 changes: 13 additions & 19 deletions core/test/unit/frontend_spec.js
Expand Up @@ -1442,25 +1442,26 @@ describe('Frontend Controller', function () {
describe('private', function () {
var req, res, config, defaultPath;

defaultPath = '/core/server/views/password.hbs';
defaultPath = '/core/server/views/private.hbs';

beforeEach(function () {
res = {
locals: {verson: ''},
locals: {version: ''},
render: sandbox.spy()
},
req = {
query: {
r: ''
}
route: {path: '/private/?r=/'},
query: {r: ''},
params: {}
},
config = {
paths: {
adminViews: '/core/server/views',
availableThemes: {
casper: {}
}
}
},
routeKeywords: {private: 'private'}
};

apiSettingsStub = sandbox.stub(api.settings, 'read');
Expand All @@ -1477,28 +1478,20 @@ describe('Frontend Controller', function () {

frontend.private(req, res, done).then(function () {
res.render.calledWith(defaultPath).should.be.true;
res.locals.context.should.containEql('private');
done();
}).catch(done);
});

it('Should render theme password page when it exists', function (done) {
config.paths.availableThemes.casper = {
'password.hbs': '/content/themes/casper/password.hbs'
'private.hbs': '/content/themes/casper/private.hbs'
};
frontend.__set__('config', config);

frontend.private(req, res, done).then(function () {
res.render.calledWith('password').should.be.true;
done();
}).catch(done);
});

it('Should render with forward data when it is passed in', function (done) {
frontend.__set__('config', config);
req.query.r = '/test-redirect/';

frontend.private(req, res, done).then(function () {
res.render.calledWith(defaultPath, {forward: '/test-redirect/'}).should.be.true;
res.render.calledWith('private').should.be.true;
res.locals.context.should.containEql('private');
done();
}).catch(done);
});
Expand All @@ -1508,7 +1501,8 @@ describe('Frontend Controller', function () {
res.error = 'Test Error';

frontend.private(req, res, done).then(function () {
res.render.calledWith(defaultPath, {forward: '', error: 'Test Error'}).should.be.true;
res.render.calledWith(defaultPath, {error: 'Test Error'}).should.be.true;
res.locals.context.should.containEql('private');
done();
}).catch(done);
});
Expand Down
27 changes: 27 additions & 0 deletions core/test/unit/server_helpers/input_password_spec.js
@@ -0,0 +1,27 @@
/*globals describe, before, it*/
/*jshint expr:true*/
var should = require('should'),
hbs = require('express-hbs'),
utils = require('./utils'),

// Stuff we are testing
handlebars = hbs.handlebars,
helpers = require('../../../server/helpers');

describe('{{input_password}} helper', function () {
before(function () {
utils.loadHelpers();
});

it('has loaded input_password helper', function () {
should.exist(handlebars.helpers.input_password);
});

it('returns the correct input', function () {
var markup = '<input class="private-login-password" type="password" name="password" autofocus="autofocus" />',
rendered = helpers.input_password();
should.exist(rendered);

String(rendered).should.equal(markup);
});
});

0 comments on commit c3dda5e

Please sign in to comment.