mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
Merge pull request #2238 from gimelfarb/fix-1838-admin-ssl
Support for urlSSL config option and forceAdminSSL 403 response
This commit is contained in:
commit
3ffa552cfc
8 changed files with 312 additions and 12 deletions
|
@ -26,17 +26,19 @@ function setConfig(config) {
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// - urlPath - string which must start and end with a slash
|
// - urlPath - string which must start and end with a slash
|
||||||
// - absolute (optional, default:false) - boolean whether or not the url should be absolute
|
// - absolute (optional, default:false) - boolean whether or not the url should be absolute
|
||||||
|
// - secure (optional, default:false) - boolean whether or not to use urlSSL or url config
|
||||||
// Returns:
|
// Returns:
|
||||||
// - a URL which always ends with a slash
|
// - a URL which always ends with a slash
|
||||||
function createUrl(urlPath, absolute) {
|
function createUrl(urlPath, absolute, secure) {
|
||||||
urlPath = urlPath || '/';
|
urlPath = urlPath || '/';
|
||||||
absolute = absolute || false;
|
absolute = absolute || false;
|
||||||
|
|
||||||
var output = '';
|
var output = '', baseUrl;
|
||||||
|
|
||||||
// create base of url, always ends without a slash
|
// create base of url, always ends without a slash
|
||||||
if (absolute) {
|
if (absolute) {
|
||||||
output += ghostConfig.url.replace(/\/$/, '');
|
baseUrl = (secure && ghostConfig.urlSSL) ? ghostConfig.urlSSL : ghostConfig.url;
|
||||||
|
output += baseUrl.replace(/\/$/, '');
|
||||||
} else {
|
} else {
|
||||||
output += ghostConfig.paths.subdir;
|
output += ghostConfig.paths.subdir;
|
||||||
}
|
}
|
||||||
|
@ -99,6 +101,7 @@ function urlPathForPost(post, permalinks) {
|
||||||
// This is probably not the right place for this, but it's the best place for now
|
// This is probably not the right place for this, but it's the best place for now
|
||||||
function urlFor(context, data, absolute) {
|
function urlFor(context, data, absolute) {
|
||||||
var urlPath = '/',
|
var urlPath = '/',
|
||||||
|
secure,
|
||||||
knownObjects = ['post', 'tag', 'user'],
|
knownObjects = ['post', 'tag', 'user'],
|
||||||
knownPaths = {'home': '/', 'rss': '/rss/'}; // this will become really big
|
knownPaths = {'home': '/', 'rss': '/rss/'}; // this will become really big
|
||||||
|
|
||||||
|
@ -108,14 +111,19 @@ function urlFor(context, data, absolute) {
|
||||||
data = null;
|
data = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Can pass 'secure' flag in either context or data arg
|
||||||
|
secure = (context && context.secure) || (data && data.secure);
|
||||||
|
|
||||||
if (_.isObject(context) && context.relativeUrl) {
|
if (_.isObject(context) && context.relativeUrl) {
|
||||||
urlPath = context.relativeUrl;
|
urlPath = context.relativeUrl;
|
||||||
} else if (_.isString(context) && _.indexOf(knownObjects, context) !== -1) {
|
} else if (_.isString(context) && _.indexOf(knownObjects, context) !== -1) {
|
||||||
// trying to create a url for an object
|
// trying to create a url for an object
|
||||||
if (context === 'post' && data.post && data.permalinks) {
|
if (context === 'post' && data.post && data.permalinks) {
|
||||||
urlPath = urlPathForPost(data.post, data.permalinks);
|
urlPath = urlPathForPost(data.post, data.permalinks);
|
||||||
|
secure = data.post.secure;
|
||||||
} else if (context === 'tag' && data.tag) {
|
} else if (context === 'tag' && data.tag) {
|
||||||
urlPath = '/tag/' + data.tag.slug + '/';
|
urlPath = '/tag/' + data.tag.slug + '/';
|
||||||
|
secure = data.tag.secure;
|
||||||
}
|
}
|
||||||
// other objects are recognised but not yet supported
|
// other objects are recognised but not yet supported
|
||||||
} else if (_.isString(context) && _.indexOf(_.keys(knownPaths), context) !== -1) {
|
} else if (_.isString(context) && _.indexOf(_.keys(knownPaths), context) !== -1) {
|
||||||
|
@ -123,7 +131,7 @@ function urlFor(context, data, absolute) {
|
||||||
urlPath = knownPaths[context] || '/';
|
urlPath = knownPaths[context] || '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
return createUrl(urlPath, absolute);
|
return createUrl(urlPath, absolute, secure);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ## urlForPost
|
// ## urlForPost
|
||||||
|
|
|
@ -301,8 +301,9 @@ adminControllers = {
|
||||||
var email = req.body.email;
|
var email = req.body.email;
|
||||||
|
|
||||||
api.users.generateResetToken(email).then(function (token) {
|
api.users.generateResetToken(email).then(function (token) {
|
||||||
var siteLink = '<a href="' + config().url + '">' + config().url + '</a>',
|
var baseUrl = config().forceAdminSSL ? (config().urlSSL || config().url) : config().url,
|
||||||
resetUrl = config().url.replace(/\/$/, '') + '/ghost/reset/' + token + '/',
|
siteLink = '<a href="' + baseUrl + '">' + baseUrl + '</a>',
|
||||||
|
resetUrl = baseUrl.replace(/\/$/, '') + '/ghost/reset/' + token + '/',
|
||||||
resetLink = '<a href="' + resetUrl + '">' + resetUrl + '</a>',
|
resetLink = '<a href="' + resetUrl + '">' + resetUrl + '</a>',
|
||||||
message = {
|
message = {
|
||||||
to: email,
|
to: email,
|
||||||
|
|
|
@ -56,6 +56,14 @@ function handleError(next) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add Request context parameter to the data object
|
||||||
|
// to be passed down to the templates
|
||||||
|
function setReqCtx(req, data) {
|
||||||
|
(Array.isArray(data) ? data : [data]).forEach(function (d) {
|
||||||
|
d.secure = req.secure;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
frontendControllers = {
|
frontendControllers = {
|
||||||
'homepage': function (req, res, next) {
|
'homepage': function (req, res, next) {
|
||||||
// Parse the page number
|
// Parse the page number
|
||||||
|
@ -76,6 +84,8 @@ frontendControllers = {
|
||||||
return res.redirect(page.meta.pagination.pages === 1 ? config().paths.subdir + '/' : (config().paths.subdir + '/page/' + page.meta.pagination.pages + '/'));
|
return res.redirect(page.meta.pagination.pages === 1 ? config().paths.subdir + '/' : (config().paths.subdir + '/page/' + page.meta.pagination.pages + '/'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setReqCtx(req, page.posts);
|
||||||
|
|
||||||
// Render the page of posts
|
// Render the page of posts
|
||||||
filters.doFilter('prePostsRender', page.posts).then(function (posts) {
|
filters.doFilter('prePostsRender', page.posts).then(function (posts) {
|
||||||
res.render('index', formatPageResponse(posts, page));
|
res.render('index', formatPageResponse(posts, page));
|
||||||
|
@ -113,6 +123,9 @@ frontendControllers = {
|
||||||
return res.redirect(tagUrl(options.tag, page.meta.pagination.pages));
|
return res.redirect(tagUrl(options.tag, page.meta.pagination.pages));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setReqCtx(req, page.posts);
|
||||||
|
setReqCtx(req, page.aspect.tag);
|
||||||
|
|
||||||
// Render the page of posts
|
// Render the page of posts
|
||||||
filters.doFilter('prePostsRender', page.posts).then(function (posts) {
|
filters.doFilter('prePostsRender', page.posts).then(function (posts) {
|
||||||
api.settings.read('activeTheme').then(function (activeTheme) {
|
api.settings.read('activeTheme').then(function (activeTheme) {
|
||||||
|
@ -184,6 +197,9 @@ frontendControllers = {
|
||||||
// Use throw 'no match' to show 404.
|
// Use throw 'no match' to show 404.
|
||||||
throw new Error('no match');
|
throw new Error('no match');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setReqCtx(req, post);
|
||||||
|
|
||||||
filters.doFilter('prePostsRender', post).then(function (post) {
|
filters.doFilter('prePostsRender', post).then(function (post) {
|
||||||
api.settings.read('activeTheme').then(function (activeTheme) {
|
api.settings.read('activeTheme').then(function (activeTheme) {
|
||||||
var paths = config().paths.availableThemes[activeTheme.value],
|
var paths = config().paths.availableThemes[activeTheme.value],
|
||||||
|
@ -279,8 +295,8 @@ frontendControllers = {
|
||||||
var title = result[0].value.value,
|
var title = result[0].value.value,
|
||||||
description = result[1].value.value,
|
description = result[1].value.value,
|
||||||
permalinks = result[2].value,
|
permalinks = result[2].value,
|
||||||
siteUrl = config.urlFor('home', null, true),
|
siteUrl = config.urlFor('home', {secure: req.secure}, true),
|
||||||
feedUrl = config.urlFor('rss', null, true),
|
feedUrl = config.urlFor('rss', {secure: req.secure}, true),
|
||||||
maxPage = page.meta.pagination.pages,
|
maxPage = page.meta.pagination.pages,
|
||||||
feedItems = [],
|
feedItems = [],
|
||||||
feed;
|
feed;
|
||||||
|
@ -315,6 +331,8 @@ frontendControllers = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setReqCtx(req, page.posts);
|
||||||
|
|
||||||
filters.doFilter('prePostsRender', page.posts).then(function (posts) {
|
filters.doFilter('prePostsRender', page.posts).then(function (posts) {
|
||||||
posts.forEach(function (post) {
|
posts.forEach(function (post) {
|
||||||
var deferred = when.defer(),
|
var deferred = when.defer(),
|
||||||
|
|
|
@ -70,13 +70,24 @@ function ghostLocals(req, res, next) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initThemeData(secure) {
|
||||||
|
var themeConfig = config.theme();
|
||||||
|
if (secure && config().urlSSL) {
|
||||||
|
// For secure requests override .url property with the SSL version
|
||||||
|
themeConfig = _.clone(themeConfig);
|
||||||
|
themeConfig.url = config().urlSSL.replace(/\/$/, '');
|
||||||
|
}
|
||||||
|
return themeConfig;
|
||||||
|
}
|
||||||
|
|
||||||
// ### InitViews Middleware
|
// ### InitViews Middleware
|
||||||
// Initialise Theme or Admin Views
|
// Initialise Theme or Admin Views
|
||||||
function initViews(req, res, next) {
|
function initViews(req, res, next) {
|
||||||
/*jslint unparam:true*/
|
/*jslint unparam:true*/
|
||||||
|
|
||||||
if (!res.isAdmin) {
|
if (!res.isAdmin) {
|
||||||
hbs.updateTemplateOptions({ data: {blog: config.theme()} });
|
var themeData = initThemeData(req.secure);
|
||||||
|
hbs.updateTemplateOptions({ data: {blog: themeData} });
|
||||||
expressServer.engine('hbs', expressServer.get('theme view engine'));
|
expressServer.engine('hbs', expressServer.get('theme view engine'));
|
||||||
expressServer.set('views', path.join(config().paths.themePath, expressServer.get('activeTheme')));
|
expressServer.set('views', path.join(config().paths.themePath, expressServer.get('activeTheme')));
|
||||||
} else {
|
} else {
|
||||||
|
@ -84,6 +95,10 @@ function initViews(req, res, next) {
|
||||||
expressServer.set('views', config().paths.adminViews);
|
expressServer.set('views', config().paths.adminViews);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pass 'secure' flag to the view engine
|
||||||
|
// so that templates can choose 'url' vs 'urlSSL'
|
||||||
|
res.locals.secure = req.secure;
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,9 +199,20 @@ function isSSLrequired(isAdmin) {
|
||||||
function checkSSL(req, res, next) {
|
function checkSSL(req, res, next) {
|
||||||
if (isSSLrequired(res.isAdmin)) {
|
if (isSSLrequired(res.isAdmin)) {
|
||||||
if (!req.secure) {
|
if (!req.secure) {
|
||||||
|
var forceAdminSSL = config().forceAdminSSL,
|
||||||
|
redirectUrl;
|
||||||
|
|
||||||
|
// Check if forceAdminSSL: { redirect: false } is set, which means
|
||||||
|
// we should just deny non-SSL access rather than redirect
|
||||||
|
if (forceAdminSSL && forceAdminSSL.redirect !== undefined && !forceAdminSSL.redirect) {
|
||||||
|
return res.send(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectUrl = url.parse(config().urlSSL || config().url);
|
||||||
return res.redirect(301, url.format({
|
return res.redirect(301, url.format({
|
||||||
protocol: 'https:',
|
protocol: 'https:',
|
||||||
hostname: url.parse(config().url).hostname,
|
hostname: redirectUrl.hostname,
|
||||||
|
port: redirectUrl.port,
|
||||||
pathname: req.path,
|
pathname: req.path,
|
||||||
query: req.query
|
query: req.query
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -111,6 +111,80 @@ describe('Admin Routing', function () {
|
||||||
.end(doEndNoAuth(done));
|
.end(doEndNoAuth(done));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// we'll use X-Forwarded-Proto: https to simulate an 'https://' request behind a proxy
|
||||||
|
describe('Require HTTPS - no redirect', function() {
|
||||||
|
var forkedGhost, request;
|
||||||
|
before(function (done) {
|
||||||
|
var configTestHttps = testUtils.fork.config();
|
||||||
|
configTestHttps.forceAdminSSL = {redirect: false};
|
||||||
|
configTestHttps.urlSSL = 'https://localhost/';
|
||||||
|
|
||||||
|
testUtils.fork.ghost(configTestHttps, 'testhttps')
|
||||||
|
.then(function(child) {
|
||||||
|
forkedGhost = child;
|
||||||
|
request = require('supertest');
|
||||||
|
request = request(configTestHttps.url.replace(/\/$/, ''));
|
||||||
|
}, done)
|
||||||
|
.then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function (done) {
|
||||||
|
if (forkedGhost) {
|
||||||
|
forkedGhost.kill(done);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should block admin access over non-HTTPS', function(done) {
|
||||||
|
request.get('/ghost/')
|
||||||
|
.expect(403)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow admin access over HTTPS', function(done) {
|
||||||
|
request.get('/ghost/signup/')
|
||||||
|
.set('X-Forwarded-Proto', 'https')
|
||||||
|
.expect(200)
|
||||||
|
.end(doEnd(done));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Require HTTPS - redirect', function() {
|
||||||
|
var forkedGhost, request;
|
||||||
|
before(function (done) {
|
||||||
|
var configTestHttps = testUtils.fork.config();
|
||||||
|
configTestHttps.forceAdminSSL = {redirect: true};
|
||||||
|
configTestHttps.urlSSL = 'https://localhost/';
|
||||||
|
|
||||||
|
testUtils.fork.ghost(configTestHttps, 'testhttps')
|
||||||
|
.then(function(child) {
|
||||||
|
forkedGhost = child;
|
||||||
|
request = require('supertest');
|
||||||
|
request = request(configTestHttps.url.replace(/\/$/, ''));
|
||||||
|
}, done)
|
||||||
|
.then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function (done) {
|
||||||
|
if (forkedGhost) {
|
||||||
|
forkedGhost.kill(done);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should redirect admin access over non-HTTPS', function(done) {
|
||||||
|
request.get('/ghost/')
|
||||||
|
.expect('Location', /^https:\/\/localhost\/ghost\//)
|
||||||
|
.expect(301)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow admin access over HTTPS', function(done) {
|
||||||
|
request.get('/ghost/signup/')
|
||||||
|
.set('X-Forwarded-Proto', 'https')
|
||||||
|
.expect(200)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Ghost Admin Signup', function () {
|
describe('Ghost Admin Signup', function () {
|
||||||
it('should have a session cookie which expires in 12 hours', function (done) {
|
it('should have a session cookie which expires in 12 hours', function (done) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ var request = require('supertest'),
|
||||||
express = require('express'),
|
express = require('express'),
|
||||||
should = require('should'),
|
should = require('should'),
|
||||||
moment = require('moment'),
|
moment = require('moment'),
|
||||||
|
path = require('path'),
|
||||||
|
|
||||||
testUtils = require('../../utils'),
|
testUtils = require('../../utils'),
|
||||||
ghost = require('../../../../core'),
|
ghost = require('../../../../core'),
|
||||||
|
@ -142,6 +143,47 @@ describe('Frontend Routing', function () {
|
||||||
.end(doEnd(done));
|
.end(doEnd(done));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// we'll use X-Forwarded-Proto: https to simulate an 'https://' request behind a proxy
|
||||||
|
describe('HTTPS', function() {
|
||||||
|
var forkedGhost, request;
|
||||||
|
before(function (done) {
|
||||||
|
var configTestHttps = testUtils.fork.config();
|
||||||
|
configTestHttps.forceAdminSSL = {redirect: false};
|
||||||
|
configTestHttps.urlSSL = 'https://localhost/';
|
||||||
|
|
||||||
|
testUtils.fork.ghost(configTestHttps, 'testhttps')
|
||||||
|
.then(function(child) {
|
||||||
|
forkedGhost = child;
|
||||||
|
request = require('supertest');
|
||||||
|
request = request(configTestHttps.url.replace(/\/$/, ''));
|
||||||
|
}, done)
|
||||||
|
.then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function (done) {
|
||||||
|
if (forkedGhost) {
|
||||||
|
forkedGhost.kill(done);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set links to url over non-HTTPS', function(done) {
|
||||||
|
request.get('/')
|
||||||
|
.expect(200)
|
||||||
|
.expect(/\<link rel="canonical" href="http:\/\/127.0.0.1:2370\/" \/\>/)
|
||||||
|
.expect(/copyright \<a href="http:\/\/127.0.0.1:2370\/">Ghost\<\/a\>/)
|
||||||
|
.end(doEnd(done));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set links to urlSSL over HTTPS', function(done) {
|
||||||
|
request.get('/')
|
||||||
|
.set('X-Forwarded-Proto', 'https')
|
||||||
|
.expect(200)
|
||||||
|
.expect(/\<link rel="canonical" href="https:\/\/localhost\/" \/\>/)
|
||||||
|
.expect(/copyright \<a href="https:\/\/localhost\/">Ghost\<\/a\>/)
|
||||||
|
.end(doEnd(done));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('RSS', function () {
|
describe('RSS', function () {
|
||||||
it('should redirect without slash', function (done) {
|
it('should redirect without slash', function (done) {
|
||||||
|
|
128
core/test/utils/fork.js
Normal file
128
core/test/utils/fork.js
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
var cp = require('child_process'),
|
||||||
|
_ = require('lodash'),
|
||||||
|
fs = require('fs'),
|
||||||
|
url = require('url'),
|
||||||
|
net = require('net'),
|
||||||
|
when = require('when'),
|
||||||
|
path = require('path'),
|
||||||
|
config = require('../../server/config');
|
||||||
|
|
||||||
|
function findFreePort(port) {
|
||||||
|
var deferred = when.defer();
|
||||||
|
|
||||||
|
if (typeof port === 'string') port = parseInt(port);
|
||||||
|
if (typeof port !== 'number') port = 2368;
|
||||||
|
port = port + 1;
|
||||||
|
|
||||||
|
var server = net.createServer();
|
||||||
|
server.on('error', function(e) {
|
||||||
|
if (e.code === 'EADDRINUSE') {
|
||||||
|
when.chain(findFreePort(port), deferred);
|
||||||
|
} else {
|
||||||
|
deferred.reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server.listen(port, function() {
|
||||||
|
var listenPort = server.address().port;
|
||||||
|
server.close(function() {
|
||||||
|
deferred.resolve(listenPort);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a copy of current config object from file, to be modified before
|
||||||
|
// passing to forkGhost() method
|
||||||
|
function forkConfig() {
|
||||||
|
// require caches values, and we want to read it fresh from the file
|
||||||
|
delete require.cache[config().paths.config];
|
||||||
|
return _.cloneDeep(require(config().paths.config)[process.env.NODE_ENV]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new fork of Ghost process with a given config
|
||||||
|
// Useful for tests that want to verify certain config options
|
||||||
|
function forkGhost(newConfig, envName) {
|
||||||
|
var deferred = when.defer();
|
||||||
|
envName = envName || 'forked';
|
||||||
|
findFreePort(newConfig.server ? newConfig.server.port : undefined)
|
||||||
|
.then(function(port) {
|
||||||
|
newConfig.server = newConfig.server || {};
|
||||||
|
newConfig.server.port = port;
|
||||||
|
newConfig.url = url.format(_.extend(url.parse(newConfig.url), {port: port, host: null}));
|
||||||
|
|
||||||
|
var newConfigFile = path.join(config().paths.appRoot, 'config.test' + port + '.js');
|
||||||
|
fs.writeFile(newConfigFile, 'module.exports = {' + envName + ': ' + JSON.stringify(newConfig) + '}', function(err) {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
// setup process environment for the forked Ghost to use the new config file
|
||||||
|
var env = _.clone(process.env);
|
||||||
|
env['GHOST_CONFIG'] = newConfigFile;
|
||||||
|
env['NODE_ENV'] = envName;
|
||||||
|
var child = cp.fork(path.join(config().paths.appRoot, 'index.js'), {env: env});
|
||||||
|
|
||||||
|
var pingTries = 0;
|
||||||
|
var pingCheck;
|
||||||
|
var pingStop = function() {
|
||||||
|
if (pingCheck) {
|
||||||
|
clearInterval(pingCheck);
|
||||||
|
pingCheck = undefined;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
// periodic check until forked Ghost is running and is listening on the port
|
||||||
|
pingCheck = setInterval(function() {
|
||||||
|
var socket = net.connect(port);
|
||||||
|
socket.on('connect', function() {
|
||||||
|
socket.end();
|
||||||
|
if (pingStop()) {
|
||||||
|
deferred.resolve(child);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
socket.on('error', function(err) {
|
||||||
|
// continue checking
|
||||||
|
if (++pingTries >= 20 && pingStop()) {
|
||||||
|
deferred.reject(new Error("Timed out waiting for child process"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
child.on('exit', function(code, signal) {
|
||||||
|
child.exited = true;
|
||||||
|
if (pingStop()) {
|
||||||
|
deferred.reject(new Error("Child process exit code: " + code));
|
||||||
|
}
|
||||||
|
// cleanup the temporary config file
|
||||||
|
fs.unlink(newConfigFile);
|
||||||
|
});
|
||||||
|
|
||||||
|
// override kill() to have an async callback
|
||||||
|
var baseKill = child.kill;
|
||||||
|
child.kill = function(signal, cb) {
|
||||||
|
if (typeof signal === 'function') {
|
||||||
|
cb = signal;
|
||||||
|
signal = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cb) {
|
||||||
|
child.on('exit', function() {
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child.exited) {
|
||||||
|
process.nextTick(cb);
|
||||||
|
} else {
|
||||||
|
baseKill.apply(child, [signal]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.otherwise(deferred.reject);
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.ghost = forkGhost;
|
||||||
|
module.exports.config = forkConfig;
|
|
@ -8,7 +8,8 @@ var knex = require('../../server/models/base').knex,
|
||||||
migration = require("../../server/data/migration/"),
|
migration = require("../../server/data/migration/"),
|
||||||
Settings = require('../../server/models/settings').Settings,
|
Settings = require('../../server/models/settings').Settings,
|
||||||
DataGenerator = require('./fixtures/data-generator'),
|
DataGenerator = require('./fixtures/data-generator'),
|
||||||
API = require('./api');
|
API = require('./api'),
|
||||||
|
fork = require('./fork');
|
||||||
|
|
||||||
function initData() {
|
function initData() {
|
||||||
return migration.init();
|
return migration.init();
|
||||||
|
@ -228,5 +229,7 @@ module.exports = {
|
||||||
loadExportFixture: loadExportFixture,
|
loadExportFixture: loadExportFixture,
|
||||||
|
|
||||||
DataGenerator: DataGenerator,
|
DataGenerator: DataGenerator,
|
||||||
API: API
|
API: API,
|
||||||
|
|
||||||
|
fork: fork
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue