mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-15 11:34:24 +03:00
f16dc290b7
addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
301 lines
10 KiB
JavaScript
301 lines
10 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('lodash'),
|
|
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
|
|
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;
|