Skip to content
Permalink
Browse files

added password protection

closes #4993
- brings password protection to the frontend of blogs
- adds testing for password protection
- upgrades bcrypt-js to 2.1.0
  • Loading branch information...
acburdine committed Mar 26, 2015
1 parent 498a0aa commit 548acf54bf4dd1b26045b09099c2838d5a946917
@@ -24,6 +24,10 @@ var FeatureController = Ember.Controller.extend(Ember.PromiseProxyMixin, {
}

return value;
}),

passProtectUI: Ember.computed('config.passProtectUI', 'labs.passProtectUI', function () {
return this.get('config.passProtectUI') || this.get('labs.passProtectUI');
})
});

@@ -1,6 +1,8 @@
import Ember from 'ember';
var SettingsController = Ember.Controller.extend({

needs: ['feature'],

showGeneral: Ember.computed('session.user.name', function () {
return this.get('session.user.isAuthor') || this.get('session.user.isEditor') ? false : true;
}),
@@ -21,6 +23,9 @@ var SettingsController = Ember.Controller.extend({
}),
showAbout: Ember.computed('session.user.name', function () {
return this.get('session.user.isAuthor') ? false : true;
}),
showPassProtection: Ember.computed('session.user.name', 'controllers.feature.passProtectUI', function () {
return this.get('session.user.isAuthor') || this.get('session.user.isEditor') || !this.get('controllers.feature.passProtectUI') ? false : true;
})
});

@@ -23,6 +23,16 @@ var LabsController = Ember.Controller.extend(Ember.Evented, {
});
},

usePassProtectUI: Ember.computed('controllers.feature.passProtectUI', function (key, value) {
// setter
if (arguments.length > 1) {
this.saveLabs('passProtectUI', value);
}

// getter
return this.get('controllers.feature.passProtectUI');
}),

actions: {
onUpload: function (file) {
var self = this,
@@ -0,0 +1,27 @@
import Ember from 'ember';

var SettingsPassProtectController = Ember.Controller.extend({

actions: {
save: function () {
var self = this;
if (this.get('model.isPrivate') && this.get('model.password') === '') {
self.notifications.closePassive();
self.notifications.showError('Password must have a value.');
return;
}

return this.get('model').save().then(function (model) {
self.notifications.closePassive();
self.notifications.showSuccess('Settings successfully saved.');

return model;
}).catch(function (errors) {
self.notifications.closePassive();
self.notifications.showErrors(errors);
});
}
}
});

export default SettingsPassProtectController;
@@ -19,7 +19,9 @@ var Setting = DS.Model.extend(NProgressSaveMixin, ValidationEngine, {
ghost_head: DS.attr('string'),
ghost_foot: DS.attr('string'),
labs: DS.attr('string'),
navigation: DS.attr('string')
navigation: DS.attr('string'),
isPrivate: DS.attr('boolean'),
password: DS.attr('string')
});

export default Setting;
@@ -43,6 +43,7 @@ Router.map(function () {
this.route('labs');
this.route('code-injection');
this.route('navigation');
this.route('pass-protect');
});

// Redirect debug to settings labs
@@ -0,0 +1,44 @@
import AuthenticatedRoute from 'ghost/routes/authenticated';
import loadingIndicator from 'ghost/mixins/loading-indicator';
import CurrentUserSettings from 'ghost/mixins/current-user-settings';
import styleBody from 'ghost/mixins/style-body';

var SettingsPassProtectRoute = AuthenticatedRoute.extend(styleBody, loadingIndicator, CurrentUserSettings, {
classNames: ['settings-view-pass'],

beforeModel: function () {
var feature = this.controllerFor('feature'),
self = this;

if (!feature) {
this.generateController('feature');
feature = this.controllerFor('feature');
}

return this.get('session.user')
.then(this.transitionAuthor())
.then(this.transitionEditor())
.then(function () {
return feature.then(function () {
if (!feature.get('passProtectUI')) {
return self.transitionTo('settings.general');
}
});
});
},

model: function () {
return this.store.find('setting', {type: 'blog,theme'}).then(function (records) {
return records.get('firstObject');
});
},

actions: {
save: function () {
this.get('controller').send('save');
}
}

});

export default SettingsPassProtectRoute;
@@ -27,6 +27,10 @@
{{gh-activating-list-item route="settings.code-injection" title="Code Injection" classNames="settings-nav-code icon-code"}}
{{/if}}

{{#if showPassProtection}}
{{gh-activating-list-item route="settings.pass-protect" title="Password Protection" classNames="settings-nav-pass icon-lock"}}
{{/if}}

{{#if showLabs}}
{{gh-activating-list-item route="settings.labs" title="Labs" classNames="settings-nav-labs icon-atom"}}
{{/if}}
@@ -44,4 +44,21 @@
</fieldset>
</form>

<hr>

<form>
<fieldset>
<div class="form-group for-checkbox">
<label for="labs-passProtectUI">Password Protection</label>
<label class="checkbox" for="labs-passProtectUI">
{{input id="labs-passProtectUI" name="labs[passProtectUI]" type="checkbox"
checked=usePassProtectUI}}
<span class="input-toggle-component"></span>
<p>Enable the password protection interface</p>
</label>
<p>A settings screen which enables you to add password protection to the front of your blog (work in progress)</p>
</div>
</fieldset>
</form>

</section>
@@ -0,0 +1,29 @@
<header class="settings-view-header">
{{#link-to "settings" class="btn btn-default btn-back"}}Back{{/link-to}}
<h2 class="page-title">Password protect your blog</h2>
<section class="page-actions">
<button type="button" class="btn btn-blue" {{action "save"}}>Save</button>
</section>
</header>

<section class="content settings-pass">
<form id="settings-pass" novalidate="novalidate">
<fieldset>
<div class="form-group for-checkbox">
<label for="blog-isPrivate">Make this blog private</label>
<label class="checkbox" for="blog-isPrivate">
{{input id="blog-isPrivate" name="labs[passProtectUI]" type="checkbox"
checked=model.isPrivate}}
<span class="input-toggle-component"></span>
<p>Enable password protection</p>
</label>
</div>
{{#if model.isPrivate}}
<div class="form-group">
{{input name="private[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>
</div>
{{/if}}
</fieldset>
</form>
</section>
@@ -0,0 +1,5 @@
import BaseView from 'ghost/views/settings/content-base';

var SettingsGeneralView = BaseView.extend();

export default SettingsGeneralView;
@@ -10,6 +10,7 @@ var _ = require('lodash'),
function getValidKeys() {
var validKeys = {
fileStorage: config.fileStorage === false ? false : true,
passProtectUI: config.passProtectUI === true ? true : false,
apps: config.apps === true ? true : false,
version: config.ghostVersion,
environment: process.env.NODE_ENV,
@@ -94,7 +94,7 @@ function urlPathForPost(post, permalinks) {
// Usage:
// urlFor('home', true) -> http://my-ghost-blog.com/
// E.g. /blog/ subdir
// urlFor({relativeUrl: '/my-static-page/') -> /blog/my-static-page/
// urlFor({relativeUrl: '/my-static-page/'}) -> /blog/my-static-page/
// E.g. if post object represents welcome post, and slugs are set to standard
// urlFor('post', {...}) -> /welcome-to-ghost/
// E.g. if post object represents welcome post, and slugs are set to date
@@ -14,6 +14,7 @@ var moment = require('moment'),
template = require('../helpers/template'),
errors = require('../errors'),
routeMatch = require('path-match')(),
path = require('path'),

frontendControllers,
staticPostPermalink;
@@ -409,7 +410,23 @@ frontendControllers = {
return handleError(next)(err);
});
},
rss: rss
rss: rss,
private: function (req, res) {
var defaultPage = path.resolve(config.paths.adminViews, 'password.hbs');
return getActiveThemePaths().then(function (paths) {
var data = {
forward: req.query.r
};
if (res.error) {
data.error = res.error;
}
if (paths.hasOwnProperty('password.hbs')) {
return res.render('password', data);
} else {
return res.render(defaultPage, data);
}
});
}
};

module.exports = frontendControllers;
@@ -73,6 +73,12 @@
},
"navigation": {
"defaultValue": "[{\"label\":\"Home\", \"url\":\"/\"}]"
},
"isPrivate": {
"defaultValue": "false"
},
"password": {
"defaultValue": ""
}
},
"theme": {
@@ -236,7 +236,6 @@ setupMiddleware = function (blogAppInstance, adminApp) {

// Favicon
blogApp.use(serveSharedFile('favicon.ico', 'image/x-icon', utils.ONE_DAY_S));
blogApp.use(serveSharedFile('sitemap.xsl', 'text/xsl', utils.ONE_DAY_S));

// Static assets
blogApp.use('/shared', express['static'](path.join(corePath, '/shared'), {maxAge: utils.ONE_HOUR_MS}));
@@ -261,6 +260,13 @@ setupMiddleware = function (blogAppInstance, adminApp) {
// Theme only config
blogApp.use(middleware.staticTheme());

// Check if password protected blog
blogApp.use(middleware.checkIsPrivate); // check if the blog is protected
blogApp.use(middleware.filterPrivateRoutes);

// Serve sitemap.xsl file
blogApp.use(serveSharedFile('sitemap.xsl', 'text/xsl', utils.ONE_DAY_S));

// Serve robots.txt if not found in theme
blogApp.use(serveSharedFile('robots.txt', 'text/plain', utils.ONE_HOUR_S));

@@ -302,7 +308,7 @@ setupMiddleware = function (blogAppInstance, adminApp) {
blogApp.use('/ghost', adminApp);

// Set up Frontend routes
blogApp.use(routes.frontend());
blogApp.use(routes.frontend(middleware));

// ### Error handling
// 404 Handler

0 comments on commit 548acf5

Please sign in to comment.
You can’t perform that action at this time.