mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-29 13:52:10 +03:00
🎉 🎨 ✨ Remove middleware/index.js (#7548)
closes #4172, closes #6948, refs #7491, refs #7488, refs #7542, refs #7484 * 🎨 Co-locate all admin-related code in /admin - move all the admin related code from controllers, routes and helpers into a single location - add error handling middleware explicitly to adminApp - re-order blogApp middleware to ensure the shared middleware is mounted after the adminApp - TODO: rethink the structure of /admin, this should probably be an internal app * 💄 Group global middleware together - There are only a few pieces of middleware which are "global" - These are needed for the admin, blog and api - Everything else is only needed in one or two places * ✨ Introduce a separate blogApp - create a brand-new blogApp - mount all blog/theme only middleware etc onto blogApp - mount error handling on blogApp only * 🎨 Separate error handling for HTML & API JSON - split JSON and HTML error handling into separate functions - re-introduce a way to not output the stack for certain errors - add more tests around errors & an assertion framework for checking JSON Errors - TODO: better 404 handling for static assets Rationale: The API is very different to the blog/admin panel: - It is intended to only ever serve JSON, never HTML responses - It is intended to always serve JSON Meanwhile the blog and admin panel have no need for JSON errors, when an error happens on those pages, we should serve HTML pages which are nicely formatted with the error & using the correct template * 🐛 Fix checkSSL to work for subapps - in order to make this work on a sub app we need to use the pattern `req.originalUrl || req.url` * 🔥 Get rid of decide-is-admin (part 1/2) - delete decide-is-admin & tests - add two small functions to apiApp and adminApp to set res.isAdmin - mount checkSSL on all the apps - TODO: deduplicate the calls to checkSSL by making blogApp a subApp :D - PART 2/2: finish cleaning this up by removing it from where it's not needed and giving it a more specific name Rationale: Now that we have both an adminApp and an apiApp, we can temporarily replace this weird path-matching middleware with middleware that sets res.isAdmin for api & admin * 🎨 Wire up prettyURLs on all Apps - prettyURLs is needed for all requests - it cannot be global because it has to live after asset middleware, and before routing - this does not result in duplicate redirects, but does result in duplicate checks - TODO: resolve extra middleware in stack by making blogApp a sub app * ⏱ Add debug to API setup * 🎨 Rename blogApp -> parentApp in middleware * 🎨 Co-locate all blog-related code in /blog - Move all of the blogApp code from middleware/index.js to blog/app.js - Move routes/frontend.js to blog/routes.js - Remove the routes/index.js and routes folder, this is empty now! - @TODO is blog the best name for this? 🤔 - @TODO sort out the big hunk of asset-related mess - @TODO also separate out the concept of theme from blog * 🎉 Replace middleware index with server/app.js - The final piece of the puzzle! 🎉 🎈 🎂 - We no longer have our horrendous middleware/index.js - Instead, we have a set of app.js files, which all use a familiar pattern * 💄 Error handling fixups
This commit is contained in:
parent
4abb9590a1
commit
4411f8254f
70
core/server/admin/app.js
Normal file
70
core/server/admin/app.js
Normal file
@ -0,0 +1,70 @@
|
||||
var debug = require('debug')('ghost:admin'),
|
||||
config = require('../config'),
|
||||
express = require('express'),
|
||||
adminHbs = require('express-hbs').create(),
|
||||
|
||||
// Admin only middleware
|
||||
redirectToSetup = require('../middleware/redirect-to-setup'),
|
||||
|
||||
// Global/shared middleware?
|
||||
cacheControl = require('../middleware/cache-control'),
|
||||
checkSSL = require('../middleware/check-ssl'),
|
||||
errorHandler = require('../middleware//error-handler'),
|
||||
maintenance = require('../middleware/maintenance'),
|
||||
prettyURLs = require('../middleware//pretty-urls'),
|
||||
serveStatic = require('express').static,
|
||||
utils = require('../utils');
|
||||
|
||||
module.exports = function setupAdminApp() {
|
||||
debug('Admin setup start');
|
||||
var adminApp = express();
|
||||
|
||||
// First determine whether we're serving admin or theme content
|
||||
// @TODO finish refactoring this away.
|
||||
adminApp.use(function setIsAdmin(req, res, next) {
|
||||
res.isAdmin = true;
|
||||
next();
|
||||
});
|
||||
|
||||
// @TODO replace all this with serving ember's index.html
|
||||
// Create a hbs instance for admin and init view engine
|
||||
adminApp.set('view engine', 'hbs');
|
||||
adminApp.set('views', config.get('paths').adminViews);
|
||||
adminApp.engine('hbs', adminHbs.express3({}));
|
||||
// Register our `asset` helper
|
||||
adminHbs.registerHelper('asset', require('../helpers/asset'));
|
||||
|
||||
// Admin assets
|
||||
// @TODO ensure this gets a local 404 error handler
|
||||
adminApp.use('/assets', serveStatic(
|
||||
config.get('paths').clientAssets,
|
||||
{maxAge: utils.ONE_YEAR_MS, fallthrough: false}
|
||||
));
|
||||
|
||||
// Render error page in case of maintenance
|
||||
adminApp.use(maintenance);
|
||||
|
||||
// Force SSL if required
|
||||
// must happen AFTER asset loading and BEFORE routing
|
||||
adminApp.use(checkSSL);
|
||||
|
||||
// Add in all trailing slashes & remove uppercase
|
||||
// must happen AFTER asset loading and BEFORE routing
|
||||
adminApp.use(prettyURLs);
|
||||
|
||||
// Cache headers go last before serving the request
|
||||
// Admin is currently set to not be cached at all
|
||||
adminApp.use(cacheControl('private'));
|
||||
// Special redirects for the admin (these should have their own cache-control headers)
|
||||
adminApp.use(redirectToSetup);
|
||||
|
||||
// Finally, routing
|
||||
adminApp.get('*', require('./controller'));
|
||||
|
||||
adminApp.use(errorHandler.pageNotFound);
|
||||
adminApp.use(errorHandler.handleHTMLResponse);
|
||||
|
||||
debug('Admin setup end');
|
||||
|
||||
return adminApp;
|
||||
};
|
69
core/server/admin/controller.js
Normal file
69
core/server/admin/controller.js
Normal file
@ -0,0 +1,69 @@
|
||||
var debug = require('debug')('ghost:admin:controller'),
|
||||
_ = require('lodash'),
|
||||
Promise = require('bluebird'),
|
||||
api = require('../api'),
|
||||
config = require('../config'),
|
||||
logging = require('../logging'),
|
||||
updateCheck = require('../update-check'),
|
||||
i18n = require('../i18n');
|
||||
|
||||
// Route: index
|
||||
// Path: /ghost/
|
||||
// Method: GET
|
||||
module.exports = function adminController(req, res) {
|
||||
/*jslint unparam:true*/
|
||||
debug('index called');
|
||||
|
||||
function renderIndex() {
|
||||
var configuration,
|
||||
fetch = {
|
||||
configuration: api.configuration.read().then(function (res) { return res.configuration[0]; }),
|
||||
client: api.clients.read({slug: 'ghost-admin'}).then(function (res) { return res.clients[0]; }),
|
||||
ghostAuth: api.clients.read({slug: 'ghost-auth'})
|
||||
.then(function (res) { return res.clients[0]; })
|
||||
.catch(function () {
|
||||
return;
|
||||
})
|
||||
};
|
||||
|
||||
return Promise.props(fetch).then(function renderIndex(result) {
|
||||
configuration = result.configuration;
|
||||
|
||||
configuration.clientId = {value: result.client.slug, type: 'string'};
|
||||
configuration.clientSecret = {value: result.client.secret, type: 'string'};
|
||||
|
||||
if (result.ghostAuth && config.get('auth:type') === 'ghost') {
|
||||
configuration.ghostAuthId = {value: result.ghostAuth.uuid, type: 'string'};
|
||||
}
|
||||
|
||||
debug('rendering default template');
|
||||
res.render('default', {
|
||||
configuration: configuration
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateCheck().then(function then() {
|
||||
return updateCheck.showUpdateNotification();
|
||||
}).then(function then(updateVersion) {
|
||||
if (!updateVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
var notification = {
|
||||
status: 'alert',
|
||||
type: 'info',
|
||||
location: 'upgrade.new-version-available',
|
||||
dismissible: false,
|
||||
message: i18n.t('notices.controllers.newVersionAvailable',
|
||||
{version: updateVersion, link: '<a href="http://support.ghost.org/how-to-upgrade/" target="_blank">Click here</a>'})};
|
||||
|
||||
return api.notifications.browse({context: {internal: true}}).then(function then(results) {
|
||||
if (!_.some(results.notifications, {message: notification.message})) {
|
||||
return api.notifications.add({notifications: [notification]}, {context: {internal: true}});
|
||||
}
|
||||
});
|
||||
}).finally(function noMatterWhat() {
|
||||
renderIndex();
|
||||
}).catch(logging.logError);
|
||||
};
|
1
core/server/admin/index.js
Normal file
1
core/server/admin/index.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./app');
|
@ -1,5 +1,6 @@
|
||||
// # API routes
|
||||
var express = require('express'),
|
||||
var debug = require('debug')('ghost:api'),
|
||||
express = require('express'),
|
||||
tmpdir = require('os').tmpdir,
|
||||
|
||||
// This essentially provides the controllers for the routes
|
||||
@ -20,6 +21,8 @@ var express = require('express'),
|
||||
// Shared
|
||||
bodyParser = require('body-parser'), // global, shared
|
||||
cacheControl = require('../middleware/cache-control'), // global, shared
|
||||
checkSSL = require('../middleware/check-ssl'),
|
||||
prettyURLs = require('../middleware/pretty-urls'),
|
||||
maintenance = require('../middleware/maintenance'), // global, shared
|
||||
errorHandler = require('../middleware/error-handler'), // global, shared
|
||||
|
||||
@ -209,8 +212,16 @@ function apiRoutes() {
|
||||
}
|
||||
|
||||
module.exports = function setupApiApp() {
|
||||
debug('API setup start');
|
||||
var apiApp = express();
|
||||
|
||||
// @TODO finish refactoring this away.
|
||||
apiApp.use(function setIsAdmin(req, res, next) {
|
||||
// Api === isAdmin for the purposes of the forceAdminSSL config option.
|
||||
res.isAdmin = true;
|
||||
next();
|
||||
});
|
||||
|
||||
// API middleware
|
||||
|
||||
// Body parsing
|
||||
@ -220,7 +231,13 @@ module.exports = function setupApiApp() {
|
||||
// send 503 json response in case of maintenance
|
||||
apiApp.use(maintenance);
|
||||
|
||||
// @TODO check SSL and pretty URLS is needed here too, once we've finished refactoring
|
||||
// Force SSL if required
|
||||
// must happen AFTER asset loading and BEFORE routing
|
||||
apiApp.use(checkSSL);
|
||||
|
||||
// Add in all trailing slashes & remove uppercase
|
||||
// must happen AFTER asset loading and BEFORE routing
|
||||
apiApp.use(prettyURLs);
|
||||
|
||||
// Check version matches for API requests, depends on res.locals.safeVersion being set
|
||||
// Therefore must come after themeHandler.ghostLocals, for now
|
||||
@ -233,8 +250,10 @@ module.exports = function setupApiApp() {
|
||||
apiApp.use(apiRoutes());
|
||||
|
||||
// API error handling
|
||||
// @TODO: split the API error handling into its own thing?
|
||||
apiApp.use(errorHandler);
|
||||
apiApp.use(errorHandler.resourceNotFound);
|
||||
apiApp.use(errorHandler.handleJSONResponse);
|
||||
|
||||
debug('API setup end');
|
||||
|
||||
return apiApp;
|
||||
};
|
||||
|
84
core/server/app.js
Normal file
84
core/server/app.js
Normal file
@ -0,0 +1,84 @@
|
||||
var debug = require('debug')('ghost:app'),
|
||||
express = require('express'),
|
||||
|
||||
// app requires
|
||||
config = require('./config'),
|
||||
logging = require('./logging'),
|
||||
|
||||
// middleware
|
||||
compress = require('compression'),
|
||||
netjet = require('netjet'),
|
||||
|
||||
// local middleware
|
||||
ghostLocals = require('./middleware/ghost-locals');
|
||||
|
||||
module.exports = function setupParentApp() {
|
||||
debug('ParentApp setup start');
|
||||
var parentApp = express();
|
||||
|
||||
// ## Global settings
|
||||
|
||||
// Make sure 'req.secure' is valid for proxied requests
|
||||
// (X-Forwarded-Proto header will be checked, if present)
|
||||
parentApp.enable('trust proxy');
|
||||
|
||||
/**
|
||||
* request logging
|
||||
*/
|
||||
parentApp.use(function expressLogging(req, res, next) {
|
||||
res.once('finish', function () {
|
||||
logging.request({req: req, res: res, err: req.err});
|
||||
});
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
if (debug.enabled) {
|
||||
// debug keeps a timer, so this is super useful
|
||||
parentApp.use((function () {
|
||||
var reqDebug = require('debug')('ghost:req');
|
||||
return function debugLog(req, res, next) {
|
||||
reqDebug('Request', req.originalUrl);
|
||||
next();
|
||||
};
|
||||
})());
|
||||
}
|
||||
|
||||
// enabled gzip compression by default
|
||||
if (config.get('server').compress !== false) {
|
||||
parentApp.use(compress());
|
||||
}
|
||||
|
||||
// Preload link headers
|
||||
if (config.get('preloadHeaders')) {
|
||||
parentApp.use(netjet({
|
||||
cache: {
|
||||
max: config.get('preloadHeaders')
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// This sets global res.locals which are needed everywhere
|
||||
parentApp.use(ghostLocals);
|
||||
|
||||
// @TODO where should this live?!
|
||||
// Load helpers
|
||||
require('./helpers').loadCoreHelpers();
|
||||
debug('Helpers done');
|
||||
|
||||
// Mount the apps on the parentApp
|
||||
// API
|
||||
// @TODO: finish refactoring the API app
|
||||
// @TODO: decide what to do with these paths - config defaults? config overrides?
|
||||
parentApp.use('/ghost/api/v0.1/', require('./api/app')());
|
||||
|
||||
// ADMIN
|
||||
parentApp.use('/ghost', require('./admin')());
|
||||
|
||||
// BLOG
|
||||
parentApp.use(require('./blog')());
|
||||
|
||||
debug('ParentApp setup end');
|
||||
|
||||
return parentApp;
|
||||
};
|
99
core/server/blog/app.js
Normal file
99
core/server/blog/app.js
Normal file
@ -0,0 +1,99 @@
|
||||
var debug = require('debug')('ghost:blog'),
|
||||
path = require('path'),
|
||||
|
||||
// App requires
|
||||
config = require('../config'),
|
||||
storage = require('../storage'),
|
||||
utils = require('../utils'),
|
||||
|
||||
// This should probably be an internal app
|
||||
sitemapHandler = require('../data/xml/sitemap/handler'),
|
||||
|
||||
// routes
|
||||
routes = require('./routes'),
|
||||
|
||||
// local middleware
|
||||
cacheControl = require('../middleware/cache-control'),
|
||||
checkSSL = require('../middleware/check-ssl'),
|
||||
errorHandler = require('../middleware/error-handler'),
|
||||
maintenance = require('../middleware/maintenance'),
|
||||
prettyURLs = require('../middleware/pretty-urls'),
|
||||
serveSharedFile = require('../middleware/serve-shared-file'),
|
||||
staticTheme = require('../middleware/static-theme'),
|
||||
themeHandler = require('../middleware/theme-handler');
|
||||
|
||||
module.exports = function setupBlogApp() {
|
||||
debug('Blog setup start');
|
||||
|
||||
var blogApp = require('express')();
|
||||
|
||||
// ## App - specific code
|
||||
// set the view engine
|
||||
blogApp.set('view engine', 'hbs');
|
||||
|
||||
// Theme middleware
|
||||
// rightly or wrongly currently comes before theme static assets
|
||||
// @TODO revisit where and when these are needed
|
||||
blogApp.use(themeHandler.updateActiveTheme);
|
||||
blogApp.use(themeHandler.configHbsForContext);
|
||||
debug('Themes done');
|
||||
|
||||
// Static content/assets
|
||||
// @TODO make sure all of these have a local 404 error handler
|
||||
// Favicon
|
||||
blogApp.use(serveSharedFile('favicon.ico', 'image/x-icon', utils.ONE_DAY_S));
|
||||
// Ghost-Url
|
||||
blogApp.use(serveSharedFile('shared/ghost-url.js', 'application/javascript', utils.ONE_HOUR_S));
|
||||
blogApp.use(serveSharedFile('shared/ghost-url.min.js', 'application/javascript', utils.ONE_HOUR_S));
|
||||
// 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));
|
||||
// Serve blog images using the storage adapter
|
||||
blogApp.use('/content/images', storage.getStorage().serve());
|
||||
|
||||
// Theme static assets/files
|
||||
blogApp.use(staticTheme());
|
||||
debug('Static content done');
|
||||
|
||||
// setup middleware for internal apps
|
||||
// @TODO: refactor this to be a proper app middleware hook for internal & external apps
|
||||
config.get('internalApps').forEach(function (appName) {
|
||||
var app = require(path.join(config.get('paths').internalAppPath, appName));
|
||||
if (app.hasOwnProperty('setupMiddleware')) {
|
||||
app.setupMiddleware(blogApp);
|
||||
}
|
||||
});
|
||||
|
||||
// site map - this should probably be refactored to be an internal app
|
||||
sitemapHandler(blogApp);
|
||||
debug('Internal apps done');
|
||||
|
||||
// send 503 error page in case of maintenance
|
||||
blogApp.use(maintenance);
|
||||
|
||||
// Force SSL if required
|
||||
// must happen AFTER asset loading and BEFORE routing
|
||||
blogApp.use(checkSSL);
|
||||
|
||||
// Add in all trailing slashes & remove uppercase
|
||||
// must happen AFTER asset loading and BEFORE routing
|
||||
blogApp.use(prettyURLs);
|
||||
|
||||
// ### Caching
|
||||
// Blog frontend is cacheable
|
||||
blogApp.use(cacheControl('public'));
|
||||
|
||||
debug('General middleware done');
|
||||
|
||||
// Set up Frontend routes (including private blogging routes)
|
||||
blogApp.use(routes());
|
||||
|
||||
// ### Error handlers
|
||||
blogApp.use(errorHandler.pageNotFound);
|
||||
blogApp.use(errorHandler.handleHTMLResponse);
|
||||
|
||||
debug('Blog setup end');
|
||||
|
||||
return blogApp;
|
||||
};
|
1
core/server/blog/index.js
Normal file
1
core/server/blog/index.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./app');
|
@ -1,77 +0,0 @@
|
||||
var debug = require('debug')('ghost:admin:controller'),
|
||||
_ = require('lodash'),
|
||||
Promise = require('bluebird'),
|
||||
api = require('../api'),
|
||||
config = require('../config'),
|
||||
errors = require('../errors'),
|
||||
logging = require('../logging'),
|
||||
updateCheck = require('../update-check'),
|
||||
i18n = require('../i18n'),
|
||||
adminControllers;
|
||||
|
||||
adminControllers = {
|
||||
// Route: index
|
||||
// Path: /ghost/
|
||||
// Method: GET
|
||||
index: function index(req, res) {
|
||||
debug('index called');
|
||||
/*jslint unparam:true*/
|
||||
|
||||
function renderIndex() {
|
||||
var configuration,
|
||||
fetch = {
|
||||
configuration: api.configuration.read().then(function (res) { return res.configuration[0]; }),
|
||||
client: api.clients.read({slug: 'ghost-admin'}).then(function (res) { return res.clients[0]; }),
|
||||
ghostAuth: api.clients.read({slug: 'ghost-auth'})
|
||||
.then(function (res) { return res.clients[0]; })
|
||||
.catch(function () {
|
||||
return;
|
||||
})
|
||||
};
|
||||
|
||||
return Promise.props(fetch).then(function renderIndex(result) {
|
||||
configuration = result.configuration;
|
||||
|
||||
configuration.clientId = {value: result.client.slug, type: 'string'};
|
||||
configuration.clientSecret = {value: result.client.secret, type: 'string'};
|
||||
|
||||
if (result.ghostAuth && config.get('auth:type') === 'ghost') {
|
||||
configuration.ghostAuthId = {value: result.ghostAuth.uuid, type: 'string'};
|
||||
}
|
||||
|
||||
debug('rendering default template');
|
||||
res.render('default', {
|
||||
configuration: configuration
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateCheck().then(function then() {
|
||||
return updateCheck.showUpdateNotification();
|
||||
}).then(function then(updateVersion) {
|
||||
if (!updateVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
var notification = {
|
||||
status: 'alert',
|
||||
type: 'info',
|
||||
location: 'upgrade.new-version-available',
|
||||
dismissible: false,
|
||||
message: i18n.t('notices.controllers.newVersionAvailable',
|
||||
{version: updateVersion, link: '<a href="http://support.ghost.org/how-to-upgrade/" target="_blank">Click here</a>'})};
|
||||
|
||||
return api.notifications.browse({context: {internal: true}}).then(function then(results) {
|
||||
if (!_.some(results.notifications, {message: notification.message})) {
|
||||
return api.notifications.add({notifications: [notification]}, {context: {internal: true}});
|
||||
}
|
||||
});
|
||||
}).finally(function noMatterWhat() {
|
||||
renderIndex();
|
||||
}).catch(function (err) {
|
||||
logging.error(new errors.GhostError({err: err}));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = adminControllers;
|
@ -84,15 +84,7 @@ function registerAsyncThemeHelper(name, fn) {
|
||||
registerAsyncHelper(hbs, name, fn);
|
||||
}
|
||||
|
||||
// Register a handlebars helper for admin
|
||||
function registerAdminHelper(name, fn) {
|
||||
coreHelpers.adminHbs.registerHelper(name, fn);
|
||||
}
|
||||
|
||||
registerHelpers = function (adminHbs) {
|
||||
// Expose hbs instance for admin
|
||||
coreHelpers.adminHbs = adminHbs;
|
||||
|
||||
registerHelpers = function () {
|
||||
// Register theme helpers
|
||||
registerThemeHelper('asset', coreHelpers.asset);
|
||||
registerThemeHelper('author', coreHelpers.author);
|
||||
@ -125,9 +117,6 @@ registerHelpers = function (adminHbs) {
|
||||
registerAsyncThemeHelper('next_post', coreHelpers.next_post);
|
||||
registerAsyncThemeHelper('prev_post', coreHelpers.prev_post);
|
||||
registerAsyncThemeHelper('get', coreHelpers.get);
|
||||
|
||||
// Register admin helpers
|
||||
registerAdminHelper('asset', coreHelpers.asset);
|
||||
};
|
||||
|
||||
module.exports = coreHelpers;
|
||||
|
@ -14,13 +14,11 @@ require('./overrides');
|
||||
|
||||
// Module dependencies
|
||||
var debug = require('debug')('ghost:boot:init'),
|
||||
express = require('express'),
|
||||
uuid = require('node-uuid'),
|
||||
Promise = require('bluebird'),
|
||||
i18n = require('./i18n'),
|
||||
api = require('./api'),
|
||||
config = require('./config'),
|
||||
middleware = require('./middleware'),
|
||||
db = require('./data/schema'),
|
||||
models = require('./models'),
|
||||
permissions = require('./permissions'),
|
||||
@ -109,12 +107,10 @@ function init(options) {
|
||||
}).then(function () {
|
||||
debug('Apps, XMLRPC, Slack done');
|
||||
|
||||
// Get reference to an express app instance.
|
||||
parentApp = express();
|
||||
// Setup our collection of express apps
|
||||
parentApp = require('./app')();
|
||||
|
||||
// ## Middleware and Routing
|
||||
middleware(parentApp);
|
||||
debug('Express done');
|
||||
debug('Express Apps done');
|
||||
|
||||
return auth.init(config.get('auth'))
|
||||
.then(function (response) {
|
||||
|
@ -44,7 +44,7 @@ checkSSL = function checkSSL(req, res, next) {
|
||||
forceAdminSSL: config.get('forceAdminSSL'),
|
||||
configUrlSSL: config.get('urlSSL'),
|
||||
configUrl: config.get('url'),
|
||||
reqUrl: req.url
|
||||
reqUrl: req.originalUrl || req.url
|
||||
});
|
||||
|
||||
if (response.isForbidden) {
|
||||
|
@ -1,17 +0,0 @@
|
||||
// # DecideIsAdmin Middleware
|
||||
// Usage: decideIsAdmin(request, result, next)
|
||||
// After:
|
||||
// Before:
|
||||
// App: Blog
|
||||
//
|
||||
// Helper function to determine if its an admin page.
|
||||
|
||||
var decideIsAdmin;
|
||||
|
||||
decideIsAdmin = function decideIsAdmin(req, res, next) {
|
||||
/*jslint unparam:true*/
|
||||
res.isAdmin = req.url.lastIndexOf('/ghost/', 0) === 0;
|
||||
next();
|
||||
};
|
||||
|
||||
module.exports = decideIsAdmin;
|
@ -4,14 +4,27 @@ var _ = require('lodash'),
|
||||
config = require('../config'),
|
||||
errors = require('../errors'),
|
||||
i18n = require('../i18n'),
|
||||
_private = {};
|
||||
_private = {},
|
||||
errorHandler = {};
|
||||
|
||||
_private.parseStack = function (stack) {
|
||||
/**
|
||||
* This function splits the stack into pieces, that are then rendered using the following handlebars code:
|
||||
* ```
|
||||
* {{#each stack}}
|
||||
* <li>
|
||||
* at
|
||||
* {{#if function}}<em class="error-stack-function">{{function}}</em>{{/if}}
|
||||
* <span class="error-stack-file">({{at}})</span>
|
||||
* </li>
|
||||
* {{/each}}
|
||||
* ```
|
||||
* @TODO revisit whether this is useful as part of #7491
|
||||
*/
|
||||
_private.parseStack = function parseStack(stack) {
|
||||
if (!_.isString(stack)) {
|
||||
return stack;
|
||||
}
|
||||
|
||||
// TODO: split out line numbers
|
||||
var stackRegex = /\s*at\s*(\w+)?\s*\(([^\)]+)\)\s*/i;
|
||||
|
||||
return (
|
||||
@ -35,64 +48,30 @@ _private.parseStack = function (stack) {
|
||||
);
|
||||
};
|
||||
|
||||
_private.handleHTMLResponse = function handleHTMLResponse(err, req, res) {
|
||||
return function handleHTMLResponse() {
|
||||
var availableTheme = config.get('paths').availableThemes[req.app.get('activeTheme')] || {},
|
||||
defaultTemplate = availableTheme['error.hbs'] ||
|
||||
path.resolve(config.get('paths').adminViews, 'user-error.hbs') ||
|
||||
'error';
|
||||
|
||||
res.render(defaultTemplate, {
|
||||
message: err.message,
|
||||
code: err.statusCode,
|
||||
stack: _private.parseStack(err.stack)
|
||||
}, function renderResponse(err, html) {
|
||||
if (!err) {
|
||||
return res.send(html);
|
||||
}
|
||||
|
||||
// And then try to explain things to the user...
|
||||
// Cheat and output the error using handlebars escapeExpression
|
||||
return res.status(500).send(
|
||||
'<h1>' + i18n.t('errors.errors.oopsErrorTemplateHasError') + '</h1>' +
|
||||
'<p>' + i18n.t('errors.errors.encounteredError') + '</p>' +
|
||||
'<pre>' + hbs.handlebars.Utils.escapeExpression(err.message || err) + '</pre>' +
|
||||
'<br ><p>' + i18n.t('errors.errors.whilstTryingToRender') + '</p>' +
|
||||
err.statusCode + ' ' + '<pre>' + hbs.handlebars.Utils.escapeExpression(err.message || err) + '</pre>'
|
||||
);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @TODO: jsonapi errors format
|
||||
*/
|
||||
_private.handleJSONResponse = function handleJSONResponse(err, req, res) {
|
||||
return function handleJSONResponse() {
|
||||
res.json({
|
||||
errors: [{
|
||||
message: err.message,
|
||||
errorType: err.errorType,
|
||||
errorDetails: err.errorDetails
|
||||
}]
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @TODO: test uncaught exception (wrap into custom error!)
|
||||
* Get an error ready to be shown the the user
|
||||
*
|
||||
* @TODO: support multiple errors
|
||||
* @TODO: decouple req.err
|
||||
*/
|
||||
module.exports = function errorHandler(err, req, res, next) {
|
||||
_private.prepareError = function prepareError(err, req, res, next) {
|
||||
if (_.isArray(err)) {
|
||||
err = err[0];
|
||||
}
|
||||
|
||||
if (!(err instanceof errors.GhostError)) {
|
||||
err = new errors.GhostError({
|
||||
err: err
|
||||
});
|
||||
// We need a special case for 404 errors
|
||||
// @TODO look at adding this to the GhostError class
|
||||
if (err.statusCode && err.statusCode === 404) {
|
||||
err = new errors.NotFoundError({
|
||||
err: err
|
||||
});
|
||||
} else {
|
||||
err = new errors.GhostError({
|
||||
err: err,
|
||||
statusCode: err.statusCode
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
req.err = err;
|
||||
@ -103,15 +82,75 @@ module.exports = function errorHandler(err, req, res, next) {
|
||||
'Cache-Control': 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'
|
||||
});
|
||||
|
||||
// @TODO: does this resolves all use cases?
|
||||
if (!req.headers.accept && !req.headers.hasOwnProperty('content-type')) {
|
||||
req.headers.accept = 'text/html';
|
||||
}
|
||||
next(err);
|
||||
};
|
||||
|
||||
// jscs:disable
|
||||
res.format({
|
||||
json: _private.handleJSONResponse(err, req, res, next),
|
||||
html: _private.handleHTMLResponse(err, req, res, next),
|
||||
'default': _private.handleHTMLResponse(err, req, res, next)
|
||||
_private.JSONErrorRenderer = function JSONErrorRenderer(err, req, res, /*jshint unused:false */ next) {
|
||||
// @TODO: jsonapi errors format ?
|
||||
res.json({
|
||||
errors: [{
|
||||
message: err.message,
|
||||
errorType: err.errorType,
|
||||
errorDetails: err.errorDetails
|
||||
}]
|
||||
});
|
||||
};
|
||||
|
||||
_private.HTMLErrorRenderer = function HTMLErrorRender(err, req, res, /*jshint unused:false */ next) {
|
||||
// @TODO reconsider this
|
||||
var availableTheme = config.get('paths').availableThemes[req.app.get('activeTheme')] || {},
|
||||
defaultTemplate = availableTheme['error.hbs'] ||
|
||||
path.resolve(config.get('paths').adminViews, 'user-error.hbs') ||
|
||||
'error',
|
||||
templateData = {
|
||||
message: err.message,
|
||||
code: err.statusCode
|
||||
};
|
||||
|
||||
// @TODO revisit use of config.get('env') as part of #7488
|
||||
if (err.statusCode === 500 && config.get('env') !== 'production') {
|
||||
templateData.stack = err.stack;
|
||||
}
|
||||
|
||||
res.render(defaultTemplate, templateData, function renderResponse(err, html) {
|
||||
if (!err) {
|
||||
return res.send(html);
|
||||
}
|
||||
|
||||
// And then try to explain things to the user...
|
||||
// Cheat and output the error using handlebars escapeExpression
|
||||
return res.status(500).send(
|
||||
'<h1>' + i18n.t('errors.errors.oopsErrorTemplateHasError') + '</h1>' +
|
||||
'<p>' + i18n.t('errors.errors.encounteredError') + '</p>' +
|
||||
'<pre>' + hbs.handlebars.Utils.escapeExpression(err.message || err) + '</pre>' +
|
||||
'<br ><p>' + i18n.t('errors.errors.whilstTryingToRender') + '</p>' +
|
||||
err.statusCode + ' ' + '<pre>' + hbs.handlebars.Utils.escapeExpression(err.message || err) + '</pre>'
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
errorHandler.resourceNotFound = function resourceNotFound(req, res, next) {
|
||||
// TODO, handle unknown resources & methods differently, so that we can also produce
|
||||
// 405 Method Not Allowed
|
||||
next(new errors.NotFoundError({message: i18n.t('errors.errors.resourceNotFound')}));
|
||||
};
|
||||
|
||||
errorHandler.pageNotFound = function pageNotFound(req, res, next) {
|
||||
next(new errors.NotFoundError({message: i18n.t('errors.errors.pageNotFound')}));
|
||||
};
|
||||
|
||||
errorHandler.handleJSONResponse = [
|
||||
// Make sure the error can be served
|
||||
_private.prepareError,
|
||||
// Render the error using JSON format
|
||||
_private.JSONErrorRenderer
|
||||
];
|
||||
|
||||
errorHandler.handleHTMLResponse = [
|
||||
// Make sure the error can be served
|
||||
_private.prepareError,
|
||||
// Render the error using HTML format
|
||||
_private.HTMLErrorRenderer
|
||||
];
|
||||
|
||||
module.exports = errorHandler;
|
||||
|
@ -1,190 +0,0 @@
|
||||
var debug = require('debug')('ghost:middleware'),
|
||||
express = require('express'),
|
||||
hbs = require('express-hbs'),
|
||||
path = require('path'),
|
||||
|
||||
// app requires
|
||||
config = require('../config'),
|
||||
errors = require('../errors'),
|
||||
helpers = require('../helpers'),
|
||||
i18n = require('../i18n'),
|
||||
logging = require('../logging'),
|
||||
routes = require('../routes'),
|
||||
storage = require('../storage'),
|
||||
utils = require('../utils'),
|
||||
|
||||
// This should probably be an internal app
|
||||
sitemapHandler = require('../data/xml/sitemap/handler'),
|
||||
|
||||
// middleware
|
||||
compress = require('compression'),
|
||||
netjet = require('netjet'),
|
||||
serveStatic = require('express').static,
|
||||
// local middleware
|
||||
cacheControl = require('./cache-control'),
|
||||
checkSSL = require('./check-ssl'),
|
||||
decideIsAdmin = require('./decide-is-admin'),
|
||||
errorHandler = require('./error-handler'),
|
||||
ghostLocals = require('./ghost-locals'),
|
||||
maintenance = require('./maintenance'),
|
||||
prettyURLs = require('./pretty-urls'),
|
||||
redirectToSetup = require('./redirect-to-setup'),
|
||||
serveSharedFile = require('./serve-shared-file'),
|
||||
staticTheme = require('./static-theme'),
|
||||
themeHandler = require('./theme-handler');
|
||||
|
||||
module.exports = function setupMiddleware(blogApp) {
|
||||
debug('Middleware start');
|
||||
|
||||
var adminApp = express(),
|
||||
adminHbs = hbs.create();
|
||||
|
||||
// ##Configuration
|
||||
|
||||
// enabled gzip compression by default
|
||||
if (config.get('server').compress !== false) {
|
||||
blogApp.use(compress());
|
||||
}
|
||||
|
||||
// ## View engine
|
||||
// set the view engine
|
||||
blogApp.set('view engine', 'hbs');
|
||||
|
||||
// Create a hbs instance for admin and init view engine
|
||||
adminApp.set('view engine', 'hbs');
|
||||
adminApp.engine('hbs', adminHbs.express3({}));
|
||||
debug('Views done');
|
||||
|
||||
// Load helpers
|
||||
helpers.loadCoreHelpers(adminHbs);
|
||||
debug('Helpers done');
|
||||
|
||||
// Make sure 'req.secure' is valid for proxied requests
|
||||
// (X-Forwarded-Proto header will be checked, if present)
|
||||
blogApp.enable('trust proxy');
|
||||
|
||||
/**
|
||||
* request logging
|
||||
*/
|
||||
blogApp.use(function expressLogging(req, res, next) {
|
||||
res.once('finish', function () {
|
||||
logging.request({req: req, res: res, err: req.err});
|
||||
});
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
if (debug.enabled) {
|
||||
// debug keeps a timer, so this is super useful
|
||||
blogApp.use((function () {
|
||||
var reqDebug = require('debug')('ghost:req');
|
||||
return function debugLog(req, res, next) {
|
||||
reqDebug('Request', req.originalUrl);
|
||||
next();
|
||||
};
|
||||
})());
|
||||
}
|
||||
|
||||
// Preload link headers
|
||||
if (config.get('preloadHeaders')) {
|
||||
blogApp.use(netjet({
|
||||
cache: {
|
||||
max: config.get('preloadHeaders')
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// This sets global res.locals which are needed everywhere
|
||||
blogApp.use(ghostLocals);
|
||||
|
||||
// First determine whether we're serving admin or theme content
|
||||
// @TODO refactor this horror away!
|
||||
blogApp.use(decideIsAdmin);
|
||||
|
||||
// Theme middleware
|
||||
// rightly or wrongly currently comes before theme static assets
|
||||
// @TODO revisit where and when these are needed
|
||||
blogApp.use(themeHandler.updateActiveTheme);
|
||||
blogApp.use(themeHandler.configHbsForContext);
|
||||
debug('Themes done');
|
||||
|
||||
// Static content/assets
|
||||
// Favicon
|
||||
blogApp.use(serveSharedFile('favicon.ico', 'image/x-icon', utils.ONE_DAY_S));
|
||||
// Ghost-Url
|
||||
blogApp.use(serveSharedFile('shared/ghost-url.js', 'application/javascript', utils.ONE_HOUR_S));
|
||||
blogApp.use(serveSharedFile('shared/ghost-url.min.js', 'application/javascript', utils.ONE_HOUR_S));
|
||||
// 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));
|
||||
// Serve blog images using the storage adapter
|
||||
blogApp.use('/content/images', storage.getStorage().serve());
|
||||
|
||||
// Admin assets
|
||||
// Admin only config
|
||||
blogApp.use('/ghost/assets', serveStatic(
|
||||
config.get('paths').clientAssets,
|
||||
{maxAge: utils.ONE_YEAR_MS}
|
||||
));
|
||||
|
||||
// Theme static assets/files
|
||||
blogApp.use(staticTheme());
|
||||
debug('Static content done');
|
||||
|
||||
// Force SSL
|
||||
// must happen AFTER asset loading and BEFORE routing
|
||||
blogApp.use(checkSSL);
|
||||
adminApp.set('views', config.get('paths').adminViews);
|
||||
|
||||
// setup middleware for internal apps
|
||||
// @TODO: refactor this to be a proper app middleware hook for internal & external apps
|
||||
config.get('internalApps').forEach(function (appName) {
|
||||
var app = require(path.join(config.get('paths').internalAppPath, appName));
|
||||
if (app.hasOwnProperty('setupMiddleware')) {
|
||||
app.setupMiddleware(blogApp);
|
||||
}
|
||||
});
|
||||
|
||||
// site map - this should probably be refactored to be an internal app
|
||||
sitemapHandler(blogApp);
|
||||
debug('Internal apps done');
|
||||
|
||||
// Add in all trailing slashes & remove uppercase
|
||||
// must happen AFTER asset loading and BEFORE routing
|
||||
blogApp.use(prettyURLs);
|
||||
|
||||
// ### Caching
|
||||
// Blog frontend is cacheable
|
||||
blogApp.use(cacheControl('public'));
|
||||
// Admin shouldn't be cached
|
||||
adminApp.use(cacheControl('private'));
|
||||
|
||||
debug('General middleware done');
|
||||
|
||||
// Load the API
|
||||
// @TODO: finish refactoring the API app
|
||||
// @TODO: decide what to do with these paths - config defaults? config overrides?
|
||||
blogApp.use('/ghost/api/v0.1/', require('../api/app')());
|
||||
|
||||
// Mount admin express app to /ghost and set up routes
|
||||
adminApp.use(redirectToSetup);
|
||||
adminApp.use(maintenance);
|
||||
adminApp.use(routes.admin());
|
||||
blogApp.use('/ghost', adminApp);
|
||||
debug('Admin app & api done');
|
||||
|
||||
// send 503 error page in case of maintenance
|
||||
blogApp.use(maintenance);
|
||||
|
||||
// Set up Frontend routes (including private blogging routes)
|
||||
blogApp.use(routes.frontend());
|
||||
|
||||
// ### Error handlers
|
||||
blogApp.use(function pageNotFound(req, res, next) {
|
||||
next(new errors.NotFoundError({message: i18n.t('errors.errors.pageNotFound')}));
|
||||
});
|
||||
|
||||
blogApp.use(errorHandler);
|
||||
debug('Middleware end');
|
||||
};
|
@ -1,14 +0,0 @@
|
||||
var admin = require('../controllers/admin'),
|
||||
express = require('express'),
|
||||
|
||||
adminRoutes;
|
||||
|
||||
adminRoutes = function () {
|
||||
var router = express.Router();
|
||||
|
||||
router.get('*', admin.index);
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
module.exports = adminRoutes;
|
@ -1,4 +0,0 @@
|
||||
module.exports = {
|
||||
admin: require('./admin'),
|
||||
frontend: require('./frontend')
|
||||
};
|
0
core/server/themes/index.js
Normal file
0
core/server/themes/index.js
Normal file
@ -474,7 +474,8 @@
|
||||
"whilstTryingToRender": "whilst trying to render an error page for the error: ",
|
||||
"renderingErrorPage": "Rendering Error Page",
|
||||
"caughtProcessingError": "Ghost caught a processing error in the middleware layer.",
|
||||
"pageNotFound": "Page not found"
|
||||
"pageNotFound": "Page not found",
|
||||
"resourceNotFound": "Resource not found"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
@ -61,6 +61,22 @@ describe('Admin Routing', function () {
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
describe('Assets', function () {
|
||||
it('should return 404 for unknown assets', function (done) {
|
||||
request.get('/ghost/assets/not-found.js')
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(404)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should retrieve built assets', function (done) {
|
||||
request.get('/ghost/assets/vendor.js')
|
||||
.expect('Cache-Control', testUtils.cacheRules.year)
|
||||
.expect(200)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Legacy Redirects', function () {
|
||||
it('should redirect /logout/ to /ghost/signout/', function (done) {
|
||||
request.get('/logout/')
|
||||
|
@ -26,24 +26,81 @@ describe('Unauthorized', function () {
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
describe('Unauthorized API', function () {
|
||||
it('can\'t retrieve posts', function (done) {
|
||||
request.get(testUtils.API.getApiQuery('posts/'))
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(401)
|
||||
.end(function firstRequest(err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
it('returns 401 error for known endpoint', function (done) {
|
||||
request.get(testUtils.API.getApiQuery('posts/'))
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(401)
|
||||
.end(function firstRequest(err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
res.should.be.json();
|
||||
var jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
// TODO: testUtils.API.checkResponseValue(jsonResponse, ['error']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
res.should.be.json();
|
||||
should.exist(res.body);
|
||||
res.body.should.be.a.JSONErrorResponse();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 404 error for unknown endpoint', function (done) {
|
||||
request.get(testUtils.API.getApiQuery('unknown/'))
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(404)
|
||||
.end(function firstRequest(err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
res.should.be.json();
|
||||
should.exist(res.body);
|
||||
res.body.should.be.a.JSONErrorResponse();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Authorized API', function () {
|
||||
var accesstoken;
|
||||
before(function (done) {
|
||||
// starting ghost automatically populates the db
|
||||
// TODO: prevent db init, and manage bringing up the DB with fixtures ourselves
|
||||
ghost().then(function (ghostServer) {
|
||||
request = supertest.agent(ghostServer.rootApp);
|
||||
}).then(function () {
|
||||
return testUtils.doAuth(request);
|
||||
}).then(function (token) {
|
||||
accesstoken = token;
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
after(function (done) {
|
||||
testUtils.clearData().then(function () {
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('serves a JSON 404 for an unknown endpoint', function (done) {
|
||||
request.get(testUtils.API.getApiQuery('unknown/'))
|
||||
.set('Authorization', 'Bearer ' + accesstoken)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(404)
|
||||
.end(function firstRequest(err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
res.should.be.json();
|
||||
should.exist(res.body);
|
||||
res.body.should.be.a.JSONErrorResponse();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -319,13 +319,6 @@ describe('Frontend Routing', function () {
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should retrieve built assets', function (done) {
|
||||
request.get('/ghost/assets/vendor.js')
|
||||
.expect('Cache-Control', testUtils.cacheRules.year)
|
||||
.expect(200)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should retrieve default robots.txt', function (done) {
|
||||
request.get('/robots.txt')
|
||||
.expect('Cache-Control', testUtils.cacheRules.hour)
|
||||
|
@ -25,7 +25,7 @@ describe('checkSSL', function () {
|
||||
});
|
||||
|
||||
it('should not require SSL (frontend)', function (done) {
|
||||
req.url = '/';
|
||||
req.originalUrl = '/';
|
||||
checkSSL(req, res, next);
|
||||
next.called.should.be.true();
|
||||
next.calledWith().should.be.true();
|
||||
@ -33,7 +33,7 @@ describe('checkSSL', function () {
|
||||
});
|
||||
|
||||
it('should require SSL (frontend)', function (done) {
|
||||
req.url = '/';
|
||||
req.originalUrl = '/';
|
||||
req.secure = true;
|
||||
checkSSL(req, res, next);
|
||||
next.called.should.be.true();
|
||||
@ -42,7 +42,7 @@ describe('checkSSL', function () {
|
||||
});
|
||||
|
||||
it('should not require SSL (admin)', function (done) {
|
||||
req.url = '/ghost';
|
||||
req.originalUrl = '/ghost';
|
||||
res.isAdmin = true;
|
||||
checkSSL(req, res, next);
|
||||
next.called.should.be.true();
|
||||
@ -51,7 +51,7 @@ describe('checkSSL', function () {
|
||||
});
|
||||
|
||||
it('should not redirect with SSL (admin)', function (done) {
|
||||
req.url = '/ghost';
|
||||
req.originalUrl = '/ghost';
|
||||
res.isAdmin = true;
|
||||
res.secure = true;
|
||||
|
||||
@ -62,7 +62,7 @@ describe('checkSSL', function () {
|
||||
});
|
||||
|
||||
it('should not redirect with force admin SSL (admin)', function (done) {
|
||||
req.url = '/ghost';
|
||||
req.originalUrl = '/ghost';
|
||||
res.isAdmin = true;
|
||||
req.secure = true;
|
||||
configUtils.set({
|
||||
@ -76,7 +76,7 @@ describe('checkSSL', function () {
|
||||
});
|
||||
|
||||
it('should redirect with force admin SSL (admin)', function (done) {
|
||||
req.url = '/ghost/';
|
||||
req.originalUrl = '/ghost/';
|
||||
res.isAdmin = true;
|
||||
res.redirect = {};
|
||||
req.secure = false;
|
||||
@ -97,7 +97,7 @@ describe('checkSSL', function () {
|
||||
});
|
||||
|
||||
it('should redirect to subdirectory with force admin SSL (admin)', function (done) {
|
||||
req.url = '/blog/ghost/';
|
||||
req.originalUrl = '/blog/ghost/';
|
||||
res.isAdmin = true;
|
||||
res.redirect = {};
|
||||
req.secure = false;
|
||||
@ -118,7 +118,7 @@ describe('checkSSL', function () {
|
||||
});
|
||||
|
||||
it('should redirect and keep query with force admin SSL (admin)', function (done) {
|
||||
req.url = '/ghost/';
|
||||
req.originalUrl = '/ghost/';
|
||||
req.query = {
|
||||
test: 'true'
|
||||
};
|
||||
@ -142,7 +142,7 @@ describe('checkSSL', function () {
|
||||
});
|
||||
|
||||
it('should redirect with with config.url being SSL (frontend)', function (done) {
|
||||
req.url = '/';
|
||||
req.originalUrl = '/';
|
||||
req.secure = false;
|
||||
res.redirect = {};
|
||||
configUtils.set({
|
||||
@ -162,7 +162,7 @@ describe('checkSSL', function () {
|
||||
});
|
||||
|
||||
it('should redirect to urlSSL (admin)', function (done) {
|
||||
req.url = '/ghost/';
|
||||
req.originalUrl = '/ghost/';
|
||||
res.isAdmin = true;
|
||||
res.redirect = {};
|
||||
req.secure = false;
|
||||
@ -183,7 +183,7 @@ describe('checkSSL', function () {
|
||||
});
|
||||
|
||||
it('should not redirect if redirect:false (admin)', function (done) {
|
||||
req.url = '/ghost/';
|
||||
req.originalUrl = '/ghost/';
|
||||
res.isAdmin = true;
|
||||
res.sendStatus = {};
|
||||
req.secure = false;
|
||||
@ -201,4 +201,26 @@ describe('checkSSL', function () {
|
||||
next.called.should.be.false();
|
||||
done();
|
||||
});
|
||||
|
||||
it('should redirect to correct path with force admin SSL (admin as subapp)', function (done) {
|
||||
req.url = '/';
|
||||
req.originalUrl = '/ghost/';
|
||||
res.isAdmin = true;
|
||||
res.redirect = {};
|
||||
req.secure = false;
|
||||
configUtils.set({
|
||||
url: 'http://default.com:2368/',
|
||||
urlSSL: '',
|
||||
forceAdminSSL: true
|
||||
});
|
||||
sandbox.stub(res, 'redirect', function (statusCode, url) {
|
||||
statusCode.should.eql(301);
|
||||
url.should.not.be.empty();
|
||||
url.should.eql('https://default.com:2368/ghost/');
|
||||
return;
|
||||
});
|
||||
checkSSL(req, res, next);
|
||||
next.called.should.be.false();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -1,60 +0,0 @@
|
||||
var should = require('should'),
|
||||
sinon = require('sinon'),
|
||||
decideIsAdmin = require('../../../server/middleware/decide-is-admin');
|
||||
|
||||
// To stop jshint complaining
|
||||
should.equal(true, true);
|
||||
|
||||
describe('Middleware: decideIsAdmin', function () {
|
||||
var sandbox,
|
||||
res,
|
||||
req,
|
||||
next;
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
next = sinon.spy();
|
||||
res = sinon.spy();
|
||||
req = {};
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('sets the isAdmin flag if the url contains /ghost/.', function (done) {
|
||||
var trueUrls = [
|
||||
'/ghost/',
|
||||
'/ghost/foo?bar=foo'
|
||||
],
|
||||
falseUrls = [
|
||||
'/ghost',
|
||||
'ghost/',
|
||||
'/foobar/ghost',
|
||||
'/things/ghost/foo'
|
||||
];
|
||||
|
||||
trueUrls.forEach(function (url) {
|
||||
res = sinon.spy();
|
||||
next = sinon.spy();
|
||||
req.url = url;
|
||||
|
||||
decideIsAdmin(req, res, next);
|
||||
res.isAdmin.should.be.exactly(true);
|
||||
next.calledOnce.should.be.exactly(true);
|
||||
});
|
||||
|
||||
falseUrls.forEach(function (url) {
|
||||
res = sinon.spy();
|
||||
next = sinon.spy();
|
||||
req.url = url;
|
||||
|
||||
decideIsAdmin(req, res, next);
|
||||
res.isAdmin.should.be.exactly(false);
|
||||
next.calledOnce.should.be.exactly(true);
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
19
core/test/utils/assertions.js
Normal file
19
core/test/utils/assertions.js
Normal file
@ -0,0 +1,19 @@
|
||||
var should = require('should'),
|
||||
errorProps = ['message', 'errorType'];
|
||||
|
||||
should.Assertion.add('JSONErrorObject', function () {
|
||||
this.params = {operator: 'to be a valid JSON Error Object'};
|
||||
this.obj.should.be.an.Object();
|
||||
this.obj.should.have.properties(errorProps);
|
||||
});
|
||||
|
||||
should.Assertion.add('JSONErrorResponse', function () {
|
||||
this.params = {operator: 'to be a valid JSON Error Response'};
|
||||
|
||||
this.obj.should.have.property('errors').which.is.an.Array();
|
||||
this.obj.errors.length.should.be.above(0);
|
||||
|
||||
this.obj.errors.forEach(function (err) {
|
||||
err.should.be.a.JSONErrorObject();
|
||||
});
|
||||
});
|
@ -39,6 +39,9 @@ var Promise = require('bluebird'),
|
||||
initData,
|
||||
clearData;
|
||||
|
||||
// Require additional assertions which help us keep our tests small and clear
|
||||
require('./assertions');
|
||||
|
||||
/** TEST FIXTURES **/
|
||||
fixtures = {
|
||||
insertPosts: function insertPosts(posts) {
|
||||
|
Loading…
Reference in New Issue
Block a user