2013-11-26 00:31:18 +04:00
|
|
|
// # Custom Middleware
|
|
|
|
// The following custom middleware functions cannot yet be unit tested, and as such are kept separate from
|
|
|
|
// the testable custom middleware functions in middleware.js
|
|
|
|
|
2013-11-12 10:03:25 +04:00
|
|
|
var middleware = require('./middleware'),
|
|
|
|
express = require('express'),
|
|
|
|
_ = require('underscore'),
|
2013-12-09 23:41:19 +04:00
|
|
|
url = require('url'),
|
2013-12-06 12:51:35 +04:00
|
|
|
when = require('when'),
|
2013-11-12 10:03:25 +04:00
|
|
|
slashes = require('connect-slashes'),
|
|
|
|
errors = require('../errorHandling'),
|
|
|
|
api = require('../api'),
|
2014-01-14 03:11:59 +04:00
|
|
|
fs = require('fs'),
|
2013-11-12 10:03:25 +04:00
|
|
|
path = require('path'),
|
|
|
|
hbs = require('express-hbs'),
|
2013-11-20 17:58:52 +04:00
|
|
|
config = require('../config'),
|
2013-11-12 10:03:25 +04:00
|
|
|
storage = require('../storage'),
|
|
|
|
packageInfo = require('../../../package.json'),
|
2013-11-26 07:22:59 +04:00
|
|
|
BSStore = require('../bookshelf-session'),
|
2013-12-06 18:13:15 +04:00
|
|
|
models = require('../models'),
|
2013-11-12 10:03:25 +04:00
|
|
|
|
2013-12-31 03:13:25 +04:00
|
|
|
expressServer,
|
|
|
|
ONE_HOUR_S = 60 * 60,
|
|
|
|
ONE_YEAR_S = 365 * 24 * ONE_HOUR_S,
|
|
|
|
ONE_HOUR_MS = ONE_HOUR_S * 1000,
|
|
|
|
ONE_YEAR_MS = 365 * 24 * ONE_HOUR_MS;
|
2013-11-12 10:03:25 +04:00
|
|
|
|
|
|
|
// ##Custom Middleware
|
|
|
|
|
|
|
|
// ### GhostLocals Middleware
|
|
|
|
// Expose the standard locals that every external page should have available,
|
|
|
|
// separating between the theme and the admin
|
|
|
|
function ghostLocals(req, res, next) {
|
|
|
|
// Make sure we have a locals value.
|
|
|
|
res.locals = res.locals || {};
|
|
|
|
res.locals.version = packageInfo.version;
|
2014-01-03 04:37:21 +04:00
|
|
|
// relative path from the URL, not including subdir
|
|
|
|
res.locals.relativeUrl = req.path.replace(config.paths().subdir, '');
|
2013-11-12 10:03:25 +04:00
|
|
|
|
|
|
|
if (res.isAdmin) {
|
2013-11-26 13:38:54 +04:00
|
|
|
res.locals.csrfToken = req.csrfToken();
|
2013-12-06 12:51:35 +04:00
|
|
|
when.all([
|
|
|
|
api.users.read({id: req.session.user}),
|
|
|
|
api.notifications.browse()
|
|
|
|
]).then(function (values) {
|
|
|
|
var currentUser = values[0],
|
|
|
|
notifications = values[1];
|
|
|
|
|
2013-11-12 10:03:25 +04:00
|
|
|
_.extend(res.locals, {
|
|
|
|
currentUser: {
|
|
|
|
name: currentUser.name,
|
|
|
|
email: currentUser.email,
|
|
|
|
image: currentUser.image
|
|
|
|
},
|
2013-12-06 12:51:35 +04:00
|
|
|
messages: notifications
|
2013-11-12 10:03:25 +04:00
|
|
|
});
|
|
|
|
next();
|
|
|
|
}).otherwise(function () {
|
|
|
|
// Only show passive notifications
|
2013-12-06 12:51:35 +04:00
|
|
|
api.notifications.browse().then(function (notifications) {
|
|
|
|
_.extend(res.locals, {
|
|
|
|
messages: _.reject(notifications, function (notification) {
|
|
|
|
return notification.status !== 'passive';
|
|
|
|
})
|
|
|
|
});
|
|
|
|
next();
|
2013-11-12 10:03:25 +04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
next();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ### InitViews Middleware
|
|
|
|
// Initialise Theme or Admin Views
|
|
|
|
function initViews(req, res, next) {
|
|
|
|
/*jslint unparam:true*/
|
|
|
|
|
|
|
|
if (!res.isAdmin) {
|
2013-12-02 03:31:55 +04:00
|
|
|
hbs.updateTemplateOptions({ data: {blog: config.theme()} });
|
|
|
|
expressServer.engine('hbs', expressServer.get('theme view engine'));
|
|
|
|
expressServer.set('views', path.join(config.paths().themePath, expressServer.get('activeTheme')));
|
2013-11-12 10:03:25 +04:00
|
|
|
} else {
|
2013-12-02 03:31:55 +04:00
|
|
|
expressServer.engine('hbs', expressServer.get('admin view engine'));
|
2013-12-06 18:13:15 +04:00
|
|
|
expressServer.set('views', config.paths().adminViews);
|
2013-11-12 10:03:25 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
next();
|
|
|
|
}
|
|
|
|
|
|
|
|
// ### Activate Theme
|
|
|
|
// Helper for manageAdminAndTheme
|
2013-12-06 12:51:35 +04:00
|
|
|
function activateTheme(activeTheme) {
|
2013-12-02 03:31:55 +04:00
|
|
|
var hbsOptions,
|
2014-01-14 03:11:59 +04:00
|
|
|
themePartials = path.join(config.paths().themePath, activeTheme, 'partials'),
|
2013-12-02 03:31:55 +04:00
|
|
|
stackLocation = _.indexOf(expressServer.stack, _.find(expressServer.stack, function (stackItem) {
|
Fix live theme switching not working on subdirectories
Closes #1770
- Previously, the middleware would check that the route on the stack was an empty string, which worked when there was no subdirectories
- When subdirectories were added, the proper route was only set when
updating the theme
- Because it was only set when updating, this explains themes working on
initial load, since the stack location was looking for an empty
string, which is what the middleware was initialized with
- However, once a new theme was set, it was still look for an empty
string, which would never exist, which caused the issue
- Now, the route is properly set on initialization of the middleware,
and then the `config.paths().subdir` property is used for the check
2013-12-29 03:08:57 +04:00
|
|
|
return stackItem.route === config.paths().subdir && stackItem.handle.name === 'settingEnabled';
|
2013-12-02 03:31:55 +04:00
|
|
|
}));
|
2013-11-12 10:03:25 +04:00
|
|
|
|
|
|
|
// clear the view cache
|
2013-12-06 18:13:15 +04:00
|
|
|
expressServer.cache = {};
|
|
|
|
expressServer.disable(expressServer.get('activeTheme'));
|
|
|
|
expressServer.set('activeTheme', activeTheme);
|
|
|
|
expressServer.enable(expressServer.get('activeTheme'));
|
2013-11-12 10:03:25 +04:00
|
|
|
if (stackLocation) {
|
2013-12-06 18:13:15 +04:00
|
|
|
expressServer.stack[stackLocation].handle = middleware.whenEnabled(expressServer.get('activeTheme'), middleware.staticTheme());
|
2013-11-12 10:03:25 +04:00
|
|
|
}
|
|
|
|
|
2013-12-02 03:31:55 +04:00
|
|
|
// set view engine
|
|
|
|
hbsOptions = { partialsDir: [ config.paths().helperTemplates ] };
|
2014-01-14 03:11:59 +04:00
|
|
|
|
|
|
|
fs.stat(themePartials, function (err, stats) {
|
2013-12-02 03:31:55 +04:00
|
|
|
// Check that the theme has a partials directory before trying to use it
|
2014-01-14 03:11:59 +04:00
|
|
|
if (!err && stats && stats.isDirectory()) {
|
|
|
|
hbsOptions.partialsDir.push(themePartials);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2013-12-02 03:31:55 +04:00
|
|
|
expressServer.set('theme view engine', hbs.express3(hbsOptions));
|
|
|
|
|
2013-11-12 10:03:25 +04:00
|
|
|
// Update user error template
|
2014-01-03 00:57:37 +04:00
|
|
|
errors.updateActiveTheme(activeTheme, config.paths().availableThemes[activeTheme].hasOwnProperty('error'));
|
2013-11-12 10:03:25 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// ### ManageAdminAndTheme Middleware
|
|
|
|
// Uses the URL to detect whether this response should be an admin response
|
|
|
|
// This is used to ensure the right content is served, and is not for security purposes
|
|
|
|
function manageAdminAndTheme(req, res, next) {
|
2013-12-28 20:01:08 +04:00
|
|
|
res.isAdmin = req.url.lastIndexOf(config.paths().subdir + '/ghost/', 0) === 0;
|
2013-11-17 22:40:26 +04:00
|
|
|
|
2013-11-12 10:03:25 +04:00
|
|
|
if (res.isAdmin) {
|
2013-12-06 18:13:15 +04:00
|
|
|
expressServer.enable('admin');
|
|
|
|
expressServer.disable(expressServer.get('activeTheme'));
|
2013-11-12 10:03:25 +04:00
|
|
|
} else {
|
2013-12-06 18:13:15 +04:00
|
|
|
expressServer.enable(expressServer.get('activeTheme'));
|
|
|
|
expressServer.disable('admin');
|
2013-11-12 10:03:25 +04:00
|
|
|
}
|
2013-12-06 12:51:35 +04:00
|
|
|
api.settings.read('activeTheme').then(function (activeTheme) {
|
|
|
|
// Check if the theme changed
|
2013-12-06 18:13:15 +04:00
|
|
|
if (activeTheme.value !== expressServer.get('activeTheme')) {
|
2013-12-06 12:51:35 +04:00
|
|
|
// Change theme
|
|
|
|
if (!config.paths().availableThemes.hasOwnProperty(activeTheme.value)) {
|
|
|
|
if (!res.isAdmin) {
|
|
|
|
// Throw an error if the theme is not available, but not on the admin UI
|
|
|
|
errors.logAndThrowError('The currently active theme ' + activeTheme.value + ' is missing.');
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
activateTheme(activeTheme.value);
|
2013-11-12 10:03:25 +04:00
|
|
|
}
|
|
|
|
}
|
2013-12-06 12:51:35 +04:00
|
|
|
next();
|
|
|
|
});
|
2013-11-12 10:03:25 +04:00
|
|
|
}
|
|
|
|
|
2013-12-06 18:13:15 +04:00
|
|
|
// Redirect to signup if no users are currently created
|
|
|
|
function redirectToSignup(req, res, next) {
|
|
|
|
/*jslint unparam:true*/
|
|
|
|
api.users.browse().then(function (users) {
|
|
|
|
if (users.length === 0) {
|
2013-12-28 20:01:08 +04:00
|
|
|
return res.redirect(config.paths().subdir + '/ghost/signup/');
|
2013-12-06 18:13:15 +04:00
|
|
|
}
|
|
|
|
next();
|
|
|
|
}).otherwise(function (err) {
|
|
|
|
return next(new Error(err));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2013-12-19 20:05:45 +04:00
|
|
|
function isSSLrequired(isAdmin) {
|
|
|
|
var forceSSL = url.parse(config().url).protocol === 'https:' ? true : false,
|
|
|
|
forceAdminSSL = (isAdmin && config().forceAdminSSL);
|
|
|
|
if (forceSSL || forceAdminSSL) {
|
|
|
|
return true;
|
2013-12-09 23:41:19 +04:00
|
|
|
}
|
2013-12-19 20:05:45 +04:00
|
|
|
return false;
|
2013-12-09 23:41:19 +04:00
|
|
|
}
|
|
|
|
|
2013-12-19 20:05:45 +04:00
|
|
|
// Check to see if we should use SSL
|
|
|
|
// and redirect if needed
|
2013-12-09 23:41:19 +04:00
|
|
|
function checkSSL(req, res, next) {
|
2013-12-19 20:05:45 +04:00
|
|
|
if (isSSLrequired(res.isAdmin)) {
|
|
|
|
// Check if X-Forarded-Proto headers are sent, if they are check for https.
|
|
|
|
// If they are not assume true to avoid infinite redirect loop.
|
|
|
|
// If the X-Forwarded-Proto header is missing and Express cannot automatically sense HTTPS the redirect will not be made.
|
|
|
|
var httpsHeader = req.header('X-Forwarded-Proto') !== undefined ? req.header('X-Forwarded-Proto').toLowerCase() === 'https' ? true : false : true;
|
|
|
|
if (!req.secure && !httpsHeader) {
|
|
|
|
return res.redirect(301, url.format({
|
|
|
|
protocol: 'https:',
|
|
|
|
hostname: url.parse(config().url).hostname,
|
|
|
|
pathname: req.path,
|
|
|
|
query: req.query
|
|
|
|
}));
|
|
|
|
}
|
2013-12-09 23:41:19 +04:00
|
|
|
}
|
|
|
|
next();
|
|
|
|
}
|
|
|
|
|
2013-12-06 18:13:15 +04:00
|
|
|
module.exports = function (server, dbHash) {
|
2013-12-31 03:13:25 +04:00
|
|
|
var subdir = config.paths().subdir,
|
2013-12-19 20:05:45 +04:00
|
|
|
corePath = config.paths().corePath,
|
|
|
|
cookie;
|
2013-11-12 10:03:25 +04:00
|
|
|
|
2013-12-06 18:13:15 +04:00
|
|
|
// Cache express server instance
|
|
|
|
expressServer = server;
|
|
|
|
middleware.cacheServer(expressServer);
|
|
|
|
|
2013-11-12 10:03:25 +04:00
|
|
|
// Logging configuration
|
2013-12-06 18:13:15 +04:00
|
|
|
if (expressServer.get('env') !== 'development') {
|
|
|
|
expressServer.use(express.logger());
|
2013-11-12 10:03:25 +04:00
|
|
|
} else {
|
2013-12-06 18:13:15 +04:00
|
|
|
expressServer.use(express.logger('dev'));
|
2013-11-12 10:03:25 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Favicon
|
2013-12-28 20:01:08 +04:00
|
|
|
expressServer.use(subdir, express.favicon(corePath + '/shared/favicon.ico'));
|
2013-11-12 10:03:25 +04:00
|
|
|
|
2013-12-31 03:13:25 +04:00
|
|
|
// Static assets
|
|
|
|
// For some reason send divides the max age number by 1000
|
|
|
|
expressServer.use(subdir + '/shared', express['static'](path.join(corePath, '/shared'), {maxAge: ONE_HOUR_MS}));
|
2013-12-28 20:01:08 +04:00
|
|
|
expressServer.use(subdir + '/content/images', storage.get_storage().serve());
|
2013-12-31 03:13:25 +04:00
|
|
|
expressServer.use(subdir + '/ghost/scripts', express['static'](path.join(corePath, '/built/scripts'), {maxAge: ONE_YEAR_MS}));
|
2013-11-12 10:03:25 +04:00
|
|
|
|
|
|
|
// First determine whether we're serving admin or theme content
|
2013-12-06 18:13:15 +04:00
|
|
|
expressServer.use(manageAdminAndTheme);
|
2013-11-12 10:03:25 +04:00
|
|
|
|
2013-12-09 23:41:19 +04:00
|
|
|
// Force SSL
|
2013-12-19 20:05:45 +04:00
|
|
|
expressServer.use(checkSSL);
|
2013-12-09 23:41:19 +04:00
|
|
|
|
2013-12-31 03:13:25 +04:00
|
|
|
|
2013-11-12 10:03:25 +04:00
|
|
|
// Admin only config
|
2013-12-31 03:13:25 +04:00
|
|
|
expressServer.use(subdir + '/ghost', middleware.whenEnabled('admin', express['static'](path.join(corePath, '/client/assets'), {maxAge: ONE_YEAR_MS})));
|
2013-11-12 10:03:25 +04:00
|
|
|
|
|
|
|
// Theme only config
|
Fix live theme switching not working on subdirectories
Closes #1770
- Previously, the middleware would check that the route on the stack was an empty string, which worked when there was no subdirectories
- When subdirectories were added, the proper route was only set when
updating the theme
- Because it was only set when updating, this explains themes working on
initial load, since the stack location was looking for an empty
string, which is what the middleware was initialized with
- However, once a new theme was set, it was still look for an empty
string, which would never exist, which caused the issue
- Now, the route is properly set on initialization of the middleware,
and then the `config.paths().subdir` property is used for the check
2013-12-29 03:08:57 +04:00
|
|
|
expressServer.use(subdir, middleware.whenEnabled(expressServer.get('activeTheme'), middleware.staticTheme()));
|
2013-11-12 10:03:25 +04:00
|
|
|
|
|
|
|
// Add in all trailing slashes
|
2013-12-31 03:13:25 +04:00
|
|
|
expressServer.use(slashes(true, {headers: {'Cache-Control': 'public, max-age=' + ONE_YEAR_S}}));
|
2013-11-12 10:03:25 +04:00
|
|
|
|
2013-12-31 03:13:25 +04:00
|
|
|
// Body parsing
|
2013-12-06 18:13:15 +04:00
|
|
|
expressServer.use(express.json());
|
|
|
|
expressServer.use(express.urlencoded());
|
2013-11-17 22:40:26 +04:00
|
|
|
|
2013-12-31 03:13:25 +04:00
|
|
|
// ### Sessions
|
2014-01-12 21:08:12 +04:00
|
|
|
// we need the trailing slash in the cookie path. Session handling *must* be after the slash handling
|
2013-12-19 20:05:45 +04:00
|
|
|
cookie = {
|
2014-01-12 21:08:12 +04:00
|
|
|
path: subdir + '/ghost/',
|
2013-12-31 03:13:25 +04:00
|
|
|
maxAge: 12 * ONE_HOUR_MS
|
2013-12-19 20:05:45 +04:00
|
|
|
};
|
|
|
|
|
|
|
|
// if SSL is forced, add secure flag to cookie
|
|
|
|
// parameter is true, since cookie is used with admin only
|
|
|
|
if (isSSLrequired(true)) {
|
|
|
|
cookie.secure = true;
|
|
|
|
}
|
|
|
|
|
2013-12-06 18:13:15 +04:00
|
|
|
expressServer.use(express.cookieParser());
|
|
|
|
expressServer.use(express.session({
|
|
|
|
store: new BSStore(models),
|
2013-12-19 20:05:45 +04:00
|
|
|
proxy: true,
|
2013-12-06 18:13:15 +04:00
|
|
|
secret: dbHash,
|
2013-12-19 20:05:45 +04:00
|
|
|
cookie: cookie
|
2013-11-24 18:29:36 +04:00
|
|
|
}));
|
2013-11-12 10:03:25 +04:00
|
|
|
|
2013-12-31 03:13:25 +04:00
|
|
|
|
2013-11-12 10:03:25 +04:00
|
|
|
//enable express csrf protection
|
2013-12-06 18:13:15 +04:00
|
|
|
expressServer.use(middleware.conditionalCSRF);
|
2013-12-31 03:13:25 +04:00
|
|
|
|
|
|
|
|
2013-11-12 10:03:25 +04:00
|
|
|
// local data
|
2013-12-06 18:13:15 +04:00
|
|
|
expressServer.use(ghostLocals);
|
2013-12-31 03:13:25 +04:00
|
|
|
// So on every request we actually clean out redundant passive notifications from the server side
|
2013-12-06 18:13:15 +04:00
|
|
|
expressServer.use(middleware.cleanNotifications);
|
2013-11-12 10:03:25 +04:00
|
|
|
// Initialise the views
|
2013-12-06 18:13:15 +04:00
|
|
|
expressServer.use(initViews);
|
2013-11-12 10:03:25 +04:00
|
|
|
|
2013-12-31 03:13:25 +04:00
|
|
|
|
|
|
|
// ### Caching
|
|
|
|
expressServer.use(middleware.cacheControl('public'));
|
|
|
|
expressServer.use('/api/', middleware.cacheControl('private'));
|
|
|
|
expressServer.use('/ghost/', middleware.cacheControl('private'));
|
|
|
|
|
|
|
|
// ### Routing
|
2013-12-28 20:01:08 +04:00
|
|
|
expressServer.use(subdir, expressServer.router);
|
2013-11-12 10:03:25 +04:00
|
|
|
|
|
|
|
// ### Error handling
|
|
|
|
// 404 Handler
|
2013-12-06 18:13:15 +04:00
|
|
|
expressServer.use(errors.error404);
|
2013-11-12 10:03:25 +04:00
|
|
|
|
|
|
|
// 500 Handler
|
2013-12-06 18:13:15 +04:00
|
|
|
expressServer.use(errors.error500);
|
2013-11-12 10:03:25 +04:00
|
|
|
};
|
|
|
|
|
|
|
|
// Export middleware functions directly
|
2013-11-17 22:40:26 +04:00
|
|
|
module.exports.middleware = middleware;
|
2013-12-06 18:13:15 +04:00
|
|
|
// Expose middleware functions in this file as well
|
2013-12-02 03:31:55 +04:00
|
|
|
module.exports.middleware.redirectToSignup = redirectToSignup;
|