mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-13 22:41:32 -05:00
6cf586aae6
closes #1837 - moved admin theme static resource service above 'checkSSL', otherwise when forceAdminSSL is true it will try to redirect them to HTTPS, and error pages will be unstyled
303 lines
11 KiB
JavaScript
303 lines
11 KiB
JavaScript
// # 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
|
|
|
|
var middleware = require('./middleware'),
|
|
express = require('express'),
|
|
_ = require('underscore'),
|
|
url = require('url'),
|
|
when = require('when'),
|
|
slashes = require('connect-slashes'),
|
|
errors = require('../errorHandling'),
|
|
api = require('../api'),
|
|
fs = require('fs'),
|
|
path = require('path'),
|
|
hbs = require('express-hbs'),
|
|
config = require('../config'),
|
|
storage = require('../storage'),
|
|
packageInfo = require('../../../package.json'),
|
|
BSStore = require('../bookshelf-session'),
|
|
models = require('../models'),
|
|
|
|
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;
|
|
|
|
// ##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;
|
|
// relative path from the URL, not including subdir
|
|
res.locals.relativeUrl = req.path.replace(config.paths().subdir, '');
|
|
|
|
if (res.isAdmin) {
|
|
res.locals.csrfToken = req.csrfToken();
|
|
when.all([
|
|
api.users.read({id: req.session.user}),
|
|
api.notifications.browse()
|
|
]).then(function (values) {
|
|
var currentUser = values[0],
|
|
notifications = values[1];
|
|
|
|
_.extend(res.locals, {
|
|
currentUser: {
|
|
name: currentUser.name,
|
|
email: currentUser.email,
|
|
image: currentUser.image
|
|
},
|
|
messages: notifications
|
|
});
|
|
next();
|
|
}).otherwise(function () {
|
|
// Only show passive notifications
|
|
api.notifications.browse().then(function (notifications) {
|
|
_.extend(res.locals, {
|
|
messages: _.reject(notifications, function (notification) {
|
|
return notification.status !== 'passive';
|
|
})
|
|
});
|
|
next();
|
|
});
|
|
});
|
|
} else {
|
|
next();
|
|
}
|
|
}
|
|
|
|
// ### InitViews Middleware
|
|
// Initialise Theme or Admin Views
|
|
function initViews(req, res, next) {
|
|
/*jslint unparam:true*/
|
|
|
|
if (!res.isAdmin) {
|
|
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')));
|
|
} else {
|
|
expressServer.engine('hbs', expressServer.get('admin view engine'));
|
|
expressServer.set('views', config.paths().adminViews);
|
|
}
|
|
|
|
next();
|
|
}
|
|
|
|
// ### Activate Theme
|
|
// Helper for manageAdminAndTheme
|
|
function activateTheme(activeTheme) {
|
|
var hbsOptions,
|
|
themePartials = path.join(config.paths().themePath, activeTheme, 'partials'),
|
|
stackLocation = _.indexOf(expressServer.stack, _.find(expressServer.stack, function (stackItem) {
|
|
return stackItem.route === config.paths().subdir && stackItem.handle.name === 'settingEnabled';
|
|
}));
|
|
|
|
// clear the view cache
|
|
expressServer.cache = {};
|
|
expressServer.disable(expressServer.get('activeTheme'));
|
|
expressServer.set('activeTheme', activeTheme);
|
|
expressServer.enable(expressServer.get('activeTheme'));
|
|
if (stackLocation) {
|
|
expressServer.stack[stackLocation].handle = middleware.whenEnabled(expressServer.get('activeTheme'), middleware.staticTheme());
|
|
}
|
|
|
|
// set view engine
|
|
hbsOptions = { partialsDir: [ config.paths().helperTemplates ] };
|
|
|
|
fs.stat(themePartials, function (err, stats) {
|
|
// Check that the theme has a partials directory before trying to use it
|
|
if (!err && stats && stats.isDirectory()) {
|
|
hbsOptions.partialsDir.push(themePartials);
|
|
}
|
|
});
|
|
|
|
expressServer.set('theme view engine', hbs.express3(hbsOptions));
|
|
|
|
// Update user error template
|
|
errors.updateActiveTheme(activeTheme, config.paths().availableThemes[activeTheme].hasOwnProperty('error'));
|
|
}
|
|
|
|
// ### 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) {
|
|
res.isAdmin = req.url.lastIndexOf(config.paths().subdir + '/ghost/', 0) === 0;
|
|
|
|
if (res.isAdmin) {
|
|
expressServer.enable('admin');
|
|
expressServer.disable(expressServer.get('activeTheme'));
|
|
} else {
|
|
expressServer.enable(expressServer.get('activeTheme'));
|
|
expressServer.disable('admin');
|
|
}
|
|
api.settings.read('activeTheme').then(function (activeTheme) {
|
|
// Check if the theme changed
|
|
if (activeTheme.value !== expressServer.get('activeTheme')) {
|
|
// 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
|
|
return errors.throwError('The currently active theme ' + activeTheme.value + ' is missing.');
|
|
}
|
|
} else {
|
|
activateTheme(activeTheme.value);
|
|
}
|
|
}
|
|
next();
|
|
}).otherwise(function (err) {
|
|
// Trying to start up without the active theme present, setup a simple hbs instance
|
|
// and render an error page straight away.
|
|
expressServer.engine('hbs', hbs.express3());
|
|
next(err);
|
|
});
|
|
}
|
|
|
|
// 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) {
|
|
return res.redirect(config.paths().subdir + '/ghost/signup/');
|
|
}
|
|
next();
|
|
}).otherwise(function (err) {
|
|
return next(new Error(err));
|
|
});
|
|
}
|
|
|
|
function isSSLrequired(isAdmin) {
|
|
var forceSSL = url.parse(config().url).protocol === 'https:' ? true : false,
|
|
forceAdminSSL = (isAdmin && config().forceAdminSSL);
|
|
if (forceSSL || forceAdminSSL) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Check to see if we should use SSL
|
|
// and redirect if needed
|
|
function checkSSL(req, res, next) {
|
|
if (isSSLrequired(res.isAdmin)) {
|
|
if (!req.secure) {
|
|
return res.redirect(301, url.format({
|
|
protocol: 'https:',
|
|
hostname: url.parse(config().url).hostname,
|
|
pathname: req.path,
|
|
query: req.query
|
|
}));
|
|
}
|
|
}
|
|
next();
|
|
}
|
|
|
|
module.exports = function (server, dbHash) {
|
|
var subdir = config.paths().subdir,
|
|
corePath = config.paths().corePath,
|
|
cookie;
|
|
|
|
// Cache express server instance
|
|
expressServer = server;
|
|
middleware.cacheServer(expressServer);
|
|
|
|
// Make sure 'req.secure' is valid for proxied requests
|
|
// (X-Forwarded-Proto header will be checked, if present)
|
|
expressServer.enable('trust proxy');
|
|
|
|
// Logging configuration
|
|
if (expressServer.get('env') !== 'development') {
|
|
expressServer.use(express.logger());
|
|
} else {
|
|
expressServer.use(express.logger('dev'));
|
|
}
|
|
|
|
// Favicon
|
|
expressServer.use(subdir, express.favicon(corePath + '/shared/favicon.ico'));
|
|
|
|
// 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}));
|
|
expressServer.use(subdir + '/content/images', storage.get_storage().serve());
|
|
expressServer.use(subdir + '/ghost/scripts', express['static'](path.join(corePath, '/built/scripts'), {maxAge: ONE_YEAR_MS}));
|
|
|
|
// First determine whether we're serving admin or theme content
|
|
expressServer.use(manageAdminAndTheme);
|
|
|
|
|
|
// Admin only config
|
|
expressServer.use(subdir + '/ghost', middleware.whenEnabled('admin', express['static'](path.join(corePath, '/client/assets'), {maxAge: ONE_YEAR_MS})));
|
|
|
|
// Force SSL
|
|
// NOTE: Importantly this is _after_ the check above for admin-theme static resources,
|
|
// which do not need HTTPS. In fact, if HTTPS is forced on them, then 404 page might
|
|
// not display properly when HTTPS is not available!
|
|
expressServer.use(checkSSL);
|
|
|
|
// Theme only config
|
|
expressServer.use(subdir, middleware.whenEnabled(expressServer.get('activeTheme'), middleware.staticTheme()));
|
|
|
|
// Add in all trailing slashes
|
|
expressServer.use(slashes(true, {headers: {'Cache-Control': 'public, max-age=' + ONE_YEAR_S}}));
|
|
|
|
// Body parsing
|
|
expressServer.use(express.json());
|
|
expressServer.use(express.urlencoded());
|
|
|
|
// ### Sessions
|
|
// we need the trailing slash in the cookie path. Session handling *must* be after the slash handling
|
|
cookie = {
|
|
path: subdir + '/ghost/',
|
|
maxAge: 12 * ONE_HOUR_MS
|
|
};
|
|
|
|
// 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;
|
|
}
|
|
|
|
expressServer.use(express.cookieParser());
|
|
expressServer.use(express.session({
|
|
store: new BSStore(models),
|
|
proxy: true,
|
|
secret: dbHash,
|
|
cookie: cookie
|
|
}));
|
|
|
|
|
|
//enable express csrf protection
|
|
expressServer.use(middleware.conditionalCSRF);
|
|
|
|
|
|
// local data
|
|
expressServer.use(ghostLocals);
|
|
// So on every request we actually clean out redundant passive notifications from the server side
|
|
expressServer.use(middleware.cleanNotifications);
|
|
// Initialise the views
|
|
expressServer.use(initViews);
|
|
|
|
|
|
// ### Caching
|
|
expressServer.use(middleware.cacheControl('public'));
|
|
expressServer.use('/api/', middleware.cacheControl('private'));
|
|
expressServer.use('/ghost/', middleware.cacheControl('private'));
|
|
|
|
// ### Routing
|
|
expressServer.use(subdir, expressServer.router);
|
|
|
|
// ### Error handling
|
|
// 404 Handler
|
|
expressServer.use(errors.error404);
|
|
|
|
// 500 Handler
|
|
expressServer.use(errors.error500);
|
|
};
|
|
|
|
// Export middleware functions directly
|
|
module.exports.middleware = middleware;
|
|
// Expose middleware functions in this file as well
|
|
module.exports.middleware.redirectToSignup = redirectToSignup;
|