mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-04 17:04:59 +03:00
Merge pull request #5265 from ErisDS/private-blog-theming
Theming updates for password protection
This commit is contained in:
commit
b71294e45d
@ -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>
|
||||
|
@ -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, ..
|
||||
|
@ -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) {
|
||||
@ -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)) {
|
||||
@ -137,7 +140,6 @@ function renderPost(req, res) {
|
||||
response = formatResponse(post);
|
||||
|
||||
setResponseContext(req, res, response);
|
||||
|
||||
res.render(view, response);
|
||||
});
|
||||
};
|
||||
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
@ -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);
|
||||
@ -116,6 +120,7 @@ registerHelpers = function (adminHbs) {
|
||||
|
||||
// Register admin helpers
|
||||
registerAdminHelper('asset', coreHelpers.asset);
|
||||
registerAdminHelper('input_password', coreHelpers.input_password);
|
||||
};
|
||||
|
||||
module.exports = coreHelpers;
|
||||
|
24
core/server/helpers/input_password.js
Normal file
24
core/server/helpers/input_password.js
Normal file
@ -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;
|
@ -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'
|
||||
};
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -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'
|
||||
|
@ -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,
|
||||
|
@ -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">
|
||||
@ -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
|
||||
@ -49,6 +46,5 @@
|
||||
</div>
|
||||
</aside>
|
||||
{{/if}}
|
||||
<script src="{{asset "ghost.js" ghost="true" minifyInProduction="true"}}"></script>
|
||||
</body>
|
||||
</html>
|
@ -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">
|
||||
@ -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">
|
||||
|
@ -1442,17 +1442,17 @@ 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: {
|
||||
@ -1460,7 +1460,8 @@ describe('Frontend Controller', function () {
|
||||
availableThemes: {
|
||||
casper: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
routeKeywords: {private: 'private'}
|
||||
};
|
||||
|
||||
apiSettingsStub = sandbox.stub(api.settings, 'read');
|
||||
@ -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);
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
|
27
core/test/unit/server_helpers/input_password_spec.js
Normal file
27
core/test/unit/server_helpers/input_password_spec.js
Normal file
@ -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);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user