🎨 configurable logging with bunyan (#7431)

- 🛠  add bunyan and prettyjson, remove morgan

-   add logging module
  - GhostLogger class that handles setup of bunyan
  - PrettyStream for stdout

-   config for logging
  - @TODO: testing level fatal?

-   log each request via GhostLogger (express middleware)
  - @TODO: add errors to output

- 🔥  remove errors.updateActiveTheme
  - we can read the value from config

- 🔥  remove 15 helper functions in core/server/errors/index.js
  - all these functions get replaced by modules:
    1. logging
    2. error middleware handling for html/json
    3. error creation (which will be part of PR #7477)

-   add express error handler for html/json
  - one true error handler for express responses
  - contains still some TODO's, but they are not high priority for first implementation/integration
  - this middleware only takes responsibility of either rendering html responses or return json error responses

- 🎨  use new express error handler in middleware/index
  - 404 and 500 handling

- 🎨  return error instead of error message in permissions/index.js
  - the rule for error handling should be: if you call a unit, this unit should return a custom Ghost error

- 🎨  wrap serve static module
  - rule: if you call a module/unit, you should always wrap this error
  - it's always the same rule
  - so the caller never has to worry about what comes back
  - it's always a clear error instance
  - in this case: we return our notfounderror if serve static does not find the resource
  - this avoid having checks everywhere

- 🎨  replace usages of errors/index.js functions and adapt tests
  - use logging.error, logging.warn
  - make tests green
  - remove some usages of logging and throwing api errors -> because when a request is involved, logging happens automatically

- 🐛  return errorDetails to Ghost-Admin
  - errorDetails is used for Theme error handling

- 🎨  use 500er error for theme is missing error in theme-handler

- 🎨  extend file rotation to 1w
This commit is contained in:
Katharina Irrgang 2016-10-04 17:33:43 +02:00 committed by Hannah Wolfe
parent dea984565d
commit 1882278b5b
90 changed files with 962 additions and 1390 deletions

View File

@ -8,6 +8,7 @@ var _ = require('lodash'),
globalUtils = require('../utils'),
utils = require('./utils'),
errors = require('../errors'),
logging = require('../logging'),
models = require('../models'),
events = require('../events'),
config = require('../config'),
@ -488,14 +489,10 @@ authentication = {
}]
};
apiMail.send(payload, {context: {internal: true}}).catch(function (error) {
errors.logError(
error.message,
i18n.t(
'errors.api.authentication.unableToSendWelcomeEmail'
),
i18n.t('errors.api.authentication.checkEmailConfigInstructions', {url: 'http://support.ghost.org/mail/'})
);
apiMail.send(payload, {context: {internal: true}}).catch(function (err) {
err.context = i18n.t('errors.api.authentication.unableToSendWelcomeEmail');
err.help = i18n.t('errors.api.authentication.checkEmailConfigInstructions', {url: 'http://support.ghost.org/mail/'});
logging.error(err);
});
})
.return(setupUser);

View File

@ -8,6 +8,7 @@ var _ = require('lodash'),
globalUtils = require('../utils'),
utils = require('./utils'),
errors = require('../errors'),
logging = require('../logging'),
config = require('../config'),
i18n = require('../i18n'),
docName = 'invites',
@ -144,8 +145,7 @@ invites = {
if (error && error.errorType === 'EmailError') {
error.message = i18n.t('errors.api.invites.errorSendingEmail.error', {message: error.message}) + ' ' +
i18n.t('errors.api.invites.errorSendingEmail.help');
errors.logWarn(error.message);
logging.warn(error.message);
}
return Promise.reject(error);

View File

@ -6,6 +6,7 @@ var _ = require('lodash'),
config = require('../config'),
canThis = require('../permissions').canThis,
errors = require('../errors'),
logging = require('../logging'),
utils = require('./utils'),
i18n = require('../i18n'),
@ -36,17 +37,16 @@ var _ = require('lodash'),
* @private
*/
updateConfigCache = function () {
var errorMessages = [
i18n.t('errors.api.settings.invalidJsonInLabs'),
i18n.t('errors.api.settings.labsColumnCouldNotBeParsed'),
i18n.t('errors.api.settings.tryUpdatingLabs')
], labsValue = {};
var labsValue = {};
if (settingsCache.labs && settingsCache.labs.value) {
try {
labsValue = JSON.parse(settingsCache.labs.value);
} catch (e) {
errors.logError.apply(this, errorMessages);
} catch (err) {
err.message = i18n.t('errors.api.settings.invalidJsonInLabs');
err.context = i18n.t('errors.api.settings.labsColumnCouldNotBeParsed');
err.help = i18n.t('errors.api.settings.tryUpdatingLabs');
logging.error(err);
}
}

View File

@ -7,6 +7,7 @@ var Promise = require('bluebird'),
config = require('../config'),
errors = require('../errors'),
events = require('../events'),
logging = require('../logging'),
storage = require('../storage'),
settings = require('./settings'),
apiUtils = require('./utils'),
@ -103,7 +104,7 @@ themes = {
// happens in background
Promise.promisify(fs.removeSync)(zip.path)
.catch(function (err) {
errors.logError(err);
logging.error(err);
});
// remove extracted dir from gscan
@ -111,7 +112,7 @@ themes = {
if (theme) {
Promise.promisify(fs.removeSync)(theme.path)
.catch(function (err) {
errors.logError(err);
logging.error(err);
});
}
});

View File

@ -164,8 +164,8 @@ users = {
return options;
});
});
}).catch(function handleError(error) {
return errors.formatAndRejectAPIError(error, i18n.t('errors.api.users.noPermissionToEditUser'));
}).catch(function handleError(err) {
return Promise.reject(new errors.NoPermissionError(err.message, i18n.t('errors.api.users.noPermissionToEditUser')));
});
}
@ -214,8 +214,8 @@ users = {
return canThis(options.context).destroy.user(options.id).then(function permissionGranted() {
options.status = 'all';
return options;
}).catch(function handleError(error) {
return errors.formatAndRejectAPIError(error, i18n.t('errors.api.users.noPermissionToDestroyUser'));
}).catch(function handleError(err) {
return Promise.reject(new errors.NoPermissionError(err.message, i18n.t('errors.api.users.noPermissionToDestroyUser')));
});
}
@ -235,8 +235,8 @@ users = {
]).then(function () {
return dataProvider.User.destroy(options);
}).return(null);
}).catch(function (error) {
return errors.formatAndRejectAPIError(error);
}).catch(function (err) {
return Promise.reject(new errors.NoPermissionError(err.message));
});
}
@ -270,8 +270,8 @@ users = {
function handlePermissions(options) {
return canThis(options.context).edit.user(options.data.password[0].user_id).then(function permissionGranted() {
return options;
}).catch(function (error) {
return errors.formatAndRejectAPIError(error, i18n.t('errors.api.users.noPermissionToChangeUsersPwd'));
}).catch(function (err) {
return Promise.reject(new errors.NoPermissionError(err.message, i18n.t('errors.api.users.noPermissionToChangeUsersPwd')));
});
}
@ -322,8 +322,6 @@ users = {
return canThis(options.context).assign.role(ownerRole);
}).then(function () {
return options;
}).catch(function (error) {
return errors.formatAndRejectAPIError(error);
});
}
@ -348,8 +346,6 @@ users = {
// Pipeline calls each task passing the result of one to be the arguments for the next
return pipeline(tasks, object, options).then(function formatResult(result) {
return Promise.resolve({users: result});
}).catch(function (error) {
return errors.formatAndRejectAPIError(error);
});
}
};

View File

@ -91,7 +91,7 @@ utils = {
}
// For now, we can only handle showing the first validation error
return errors.logAndRejectError(validationErrors[0]);
return Promise.reject(validationErrors[0]);
}
// If we got an object, check that too
@ -187,8 +187,6 @@ utils = {
return permsPromise.then(function permissionGranted() {
return options;
}).catch(function handleError(error) {
return errors.formatAndRejectAPIError(error);
});
};
},
@ -218,8 +216,6 @@ utils = {
error.message = i18n.t('errors.api.utils.noPermissionToCall', {method: method, docName: docName});
// forward error to next catch()
return Promise.reject(error);
}).catch(function handleError(error) {
return errors.formatAndRejectAPIError(error);
});
};
},
@ -275,7 +271,7 @@ utils = {
*/
checkObject: function (object, docName, editId) {
if (_.isEmpty(object) || _.isEmpty(object[docName]) || _.isEmpty(object[docName][0])) {
return errors.logAndRejectError(new errors.BadRequestError(i18n.t('errors.api.utils.noRootKeyProvided', {docName: docName})));
return Promise.reject(new errors.BadRequestError(i18n.t('errors.api.utils.noRootKeyProvided', {docName: docName})));
}
// convert author property to author_id to match the name in the database
@ -296,7 +292,7 @@ utils = {
});
if (editId && object[docName][0].id && parseInt(editId, 10) !== parseInt(object[docName][0].id, 10)) {
return errors.logAndRejectError(new errors.BadRequestError(i18n.t('errors.api.utils.invalidIdProvided')));
return Promise.reject(new errors.BadRequestError(i18n.t('errors.api.utils.invalidIdProvided')));
}
return Promise.resolve(object);

View File

@ -12,7 +12,7 @@ var hbs = require('express-hbs'),
moment = require('moment'),
sanitizeHtml = require('sanitize-html'),
config = require('../../../../config'),
errors = require('../../../../errors'),
logging = require('../../../../logging'),
makeAbsoluteUrl = require('../../../../utils/make-absolute-urls'),
cheerio = require('cheerio'),
amperize = new Amperize(),
@ -126,9 +126,10 @@ function getAmperizeHTML(html, post) {
amperize.parse(html, function (err, res) {
if (err) {
if (err.src) {
errors.logError(err.message, 'AMP HTML couldn\'t get parsed: ' + err.src);
err.context = 'AMP HTML couldn\'t get parsed: ' + err.src;
logging.error(err);
} else {
errors.logError(err);
logging.error(err);
}
// save it in cache to prevent multiple calls to Amperize until

View File

@ -1,7 +1,7 @@
var _ = require('lodash'),
Promise = require('bluebird'),
errors = require('../errors'),
logging = require('../logging'),
api = require('../api'),
loader = require('./loader'),
i18n = require('../i18n'),
@ -46,12 +46,11 @@ module.exports = {
appsToLoad = appsToLoad.concat(config.get('internalApps'));
});
} catch (e) {
errors.logError(
i18n.t('errors.apps.failedToParseActiveAppsSettings.error', {message: e.message}),
i18n.t('errors.apps.failedToParseActiveAppsSettings.context'),
i18n.t('errors.apps.failedToParseActiveAppsSettings.help')
);
} catch (err) {
err.message = i18n.t('errors.apps.failedToParseActiveAppsSettings.error', {message: err.message});
err.help = i18n.t('errors.apps.failedToParseActiveAppsSettings.context');
err.context = i18n.t('errors.apps.failedToParseActiveAppsSettings.help');
logging.error(err);
return Promise.resolve();
}
@ -88,11 +87,9 @@ module.exports = {
// Extend the loadedApps onto the available apps
_.extend(availableApps, loadedApps);
}).catch(function (err) {
errors.logError(
err.message || err,
i18n.t('errors.apps.appWillNotBeLoaded.error'),
i18n.t('errors.apps.appWillNotBeLoaded.help')
);
err.context = i18n.t('errors.apps.appWillNotBeLoaded.error');
err.help = i18n.t('errors.apps.appWillNotBeLoaded.help');
logging.error(err);
});
});
},

View File

@ -1,21 +1,26 @@
var config = require('../../config'),
utils = require('../../utils'),
errors = require('../../errors'),
logging = require('../../logging'),
i18n = require('../../i18n'),
middleware = require('./lib/middleware'),
router = require('./lib/router');
module.exports = {
activate: function activate() {
var err, paths;
if (utils.url.getSubdir()) {
var paths = utils.url.getSubdir().split('/');
paths = utils.url.getSubdir().split('/');
if (paths.pop() === config.get('routeKeywords').private) {
errors.logErrorAndExit(
new Error(i18n.t('errors.config.urlCannotContainPrivateSubdir.error')),
i18n.t('errors.config.urlCannotContainPrivateSubdir.description'),
i18n.t('errors.config.urlCannotContainPrivateSubdir.help')
);
err = new Error();
err.message = i18n.t('errors.config.urlCannotContainPrivateSubdir.error');
err.context = i18n.t('errors.config.urlCannotContainPrivateSubdir.description');
err.help = i18n.t('errors.config.urlCannotContainPrivateSubdir.help');
logging.error(err);
// @TODO: why?
process.exit(0);
}
}
},

View File

@ -1,12 +1,13 @@
var _ = require('lodash'),
fs = require('fs'),
config = require('../../../config'),
session = require('cookie-session'),
crypto = require('crypto'),
path = require('path'),
api = require('../../../api'),
Promise = require('bluebird'),
config = require('../../../config'),
api = require('../../../api'),
errors = require('../../../errors'),
session = require('cookie-session'),
logging = require('../../../logging'),
utils = require('../../../utils'),
i18n = require('../../../i18n'),
privateRoute = '/' + config.get('routeKeywords').private + '/',
@ -55,7 +56,7 @@ privateBlogging = {
if (req.path.lastIndexOf('/rss/', 0) === 0 ||
req.path.lastIndexOf('/rss/') === req.url.length - 5 ||
(req.path.lastIndexOf('/sitemap', 0) === 0 && req.path.lastIndexOf('.xml') === req.path.length - 4)) {
return errors.error404(req, res, next);
return next(new errors.NotFoundError(i18n.t('errors.errors.pageNotFound')));
} else if (req.url.lastIndexOf('/robots.txt', 0) === 0) {
fs.readFile(path.resolve(__dirname, '../', 'robots.txt'), function readFile(err, buf) {
if (err) {
@ -145,7 +146,8 @@ privateBlogging = {
ipCount = '',
message = i18n.t('errors.middleware.spamprevention.tooManyAttempts'),
deniedRateLimit = '',
password = req.body.password;
password = req.body.password,
err;
if (password) {
protectedSecurity.push({ip: remoteAddress, time: currentTime});
@ -165,15 +167,19 @@ privateBlogging = {
deniedRateLimit = (ipCount[remoteAddress] > rateProtectedAttempts);
if (deniedRateLimit) {
errors.logError(
i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error', {rfa: rateProtectedAttempts, rfp: rateProtectedPeriod}),
i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.context')
);
err = new Error();
err.message = i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error', {rfa: rateProtectedAttempts, rfp: rateProtectedPeriod});
err.context = i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.context');
logging.error(err);
message += rateProtectedPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater');
// @TODO: why?
res.error = {
message: message
};
}
return next();
}
};

View File

@ -92,11 +92,8 @@ describe('Private Blogging', function () {
});
describe('private', function () {
var errorSpy;
beforeEach(function () {
res.isPrivateBlog = true;
errorSpy = sandbox.spy(errors, 'error404');
res = {
status: function () {
return this;
@ -121,39 +118,62 @@ describe('Private Blogging', function () {
it('filterPrivateRoutes should throw 404 if url is sitemap', function () {
req.path = req.url = '/sitemap.xml';
var next = function next(err) {
(err instanceof errors.NotFoundError).should.eql(true);
};
privateBlogging.filterPrivateRoutes(req, res, next);
errorSpy.called.should.be.true();
});
it('filterPrivateRoutes should throw 404 if url is sitemap with param', function () {
req.url = '/sitemap.xml?weird=param';
req.path = '/sitemap.xml';
var next = function next(err) {
(err instanceof errors.NotFoundError).should.eql(true);
};
privateBlogging.filterPrivateRoutes(req, res, next);
errorSpy.called.should.be.true();
});
it('filterPrivateRoutes should throw 404 if url is rss', function () {
req.path = req.url = '/rss/';
var next = function next(err) {
(err instanceof errors.NotFoundError).should.eql(true);
};
privateBlogging.filterPrivateRoutes(req, res, next);
errorSpy.called.should.be.true();
});
it('filterPrivateRoutes should throw 404 if url is author rss', function () {
req.path = req.url = '/author/halfdan/rss/';
var next = function next(err) {
(err instanceof errors.NotFoundError).should.eql(true);
};
privateBlogging.filterPrivateRoutes(req, res, next);
errorSpy.called.should.be.true();
});
it('filterPrivateRoutes should throw 404 if url is tag rss', function () {
req.path = req.url = '/tag/slimer/rss/';
var next = function next(err) {
(err instanceof errors.NotFoundError).should.eql(true);
};
privateBlogging.filterPrivateRoutes(req, res, next);
errorSpy.called.should.be.true();
});
it('filterPrivateRoutes should throw 404 if url is rss plus something', function () {
req.path = req.url = '/rss/sometag';
var next = function next(err) {
(err instanceof errors.NotFoundError).should.eql(true);
};
privateBlogging.filterPrivateRoutes(req, res, next);
errorSpy.called.should.be.true();
});
it('filterPrivateRoutes should render custom robots.txt', function () {

View File

@ -5,7 +5,7 @@ var _ = require('lodash'),
// Dirty requires
config = require('../../config'),
errors = require('../../errors'),
logging = require('../../logging'),
i18n = require('../../i18n'),
labs = require('../../utils/labs'),
template = require('../../helpers/template'),
@ -53,11 +53,7 @@ function subscribeFormHelper(options) {
module.exports = {
activate: function activate(ghost) {
var errorMessages = [
i18n.t('warnings.helpers.helperNotAvailable', {helperName: 'subscribe_form'}),
i18n.t('warnings.helpers.apiMustBeEnabled', {helperName: 'subscribe_form', flagName: 'subscribers'}),
i18n.t('warnings.helpers.seeLink', {url: 'http://support.ghost.org/subscribers-beta/'})
];
var err;
// Correct way to register a helper from an app
ghost.helpers.register('subscribe_form', function labsEnabledHelper() {
@ -65,8 +61,13 @@ module.exports = {
return subscribeFormHelper.apply(this, arguments);
}
errors.logError.apply(this, errorMessages);
return new hbs.handlebars.SafeString('<script>console.error("' + errorMessages.join(' ') + '");</script>');
err = new Error();
err.message = i18n.t('warnings.helpers.helperNotAvailable', {helperName: 'subscribe_form'});
err.context = i18n.t('warnings.helpers.apiMustBeEnabled', {helperName: 'subscribe_form', flagName: 'subscribers'});
err.help = i18n.t('warnings.helpers.seeLink', {url: 'http://support.ghost.org/subscribers-beta/'});
logging.error(err);
return new hbs.handlebars.SafeString('<script>console.error(' + JSON.stringify(err) + ');</script>');
});
},

View File

@ -44,12 +44,11 @@ authenticate = {
}
if (!req.body.client_id || !req.body.client_secret) {
errors.logError(
i18n.t('errors.middleware.auth.clientAuthenticationFailed'),
return next(new errors.UnauthorizedError(
i18n.t('errors.middleware.auth.accessDenied')),
i18n.t('errors.middleware.auth.clientCredentialsNotProvided'),
i18n.t('errors.middleware.auth.forInformationRead', {url: 'http://api.ghost.org/docs/client-authentication'})
);
return errors.handleAPIError(new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDenied')), req, res, next);
}
return passport.authenticate(['oauth2-client-password'], {session: false, failWithError: false},
@ -63,12 +62,11 @@ authenticate = {
delete req.body.client_secret;
if (!client) {
errors.logError(
i18n.t('errors.middleware.auth.clientAuthenticationFailed'),
return next(new errors.UnauthorizedError(
i18n.t('errors.middleware.auth.accessDenied')),
i18n.t('errors.middleware.auth.clientCredentialsNotValid'),
i18n.t('errors.middleware.auth.forInformationRead', {url: 'http://api.ghost.org/docs/client-authentication'})
);
return errors.handleAPIError(new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDenied')), req, res, next);
}
req.client = client;
@ -94,13 +92,13 @@ authenticate = {
events.emit('user.authenticated', user);
return next(null, user, info);
} else if (isBearerAutorizationHeader(req)) {
return errors.handleAPIError(new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDenied')), req, res, next);
return next(new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDenied')));
} else if (req.client) {
req.user = {id: 0};
return next();
}
return errors.handleAPIError(new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDenied')), req, res, next);
return next(new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDenied')));
}
)(req, res, next);
},
@ -110,7 +108,7 @@ authenticate = {
req.query.code = req.body.authorizationCode;
if (!req.query.code) {
return errors.handleAPIError(new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDenied')), req, res, next);
return next(new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDenied')));
}
passport.authenticate('ghost', {session: false, failWithError: false}, function authenticate(err, user, info) {
@ -119,7 +117,7 @@ authenticate = {
}
if (!user) {
return errors.handleAPIError(new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDenied')), req, res, next);
return next(new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDenied')));
}
req.authInfo = info;

View File

@ -10,7 +10,7 @@ authorize = {
if (req.user && req.user.id) {
return next();
} else {
return errors.handleAPIError(new errors.NoPermissionError(i18n.t('errors.middleware.auth.pleaseSignIn')), req, res, next);
return next(new errors.NoPermissionError(i18n.t('errors.middleware.auth.pleaseSignIn')));
}
},
@ -22,7 +22,7 @@ authorize = {
if (req.user && req.user.id) {
return next();
} else {
return errors.handleAPIError(new errors.NoPermissionError(i18n.t('errors.middleware.auth.pleaseSignIn')), req, res, next);
return next(new errors.NoPermissionError(i18n.t('errors.middleware.auth.pleaseSignIn')));
}
}
}

View File

@ -28,5 +28,10 @@
"auth": {
"type": "ghost",
"url": "http://devauth.ghost.org:8080"
},
"logging": {
"level": "info",
"rotation": false,
"transports": ["stdout"]
}
}

View File

@ -8,5 +8,10 @@
},
"paths": {
"contentPath": "content/"
},
"logging": {
"level": "info",
"rotation": true,
"transports": ["file"]
}
}

View File

@ -15,5 +15,7 @@
"auth": {
"type": "password"
},
"logging": false
"logging": {
"level": "fatal"
}
}

View File

@ -12,5 +12,7 @@
"auth": {
"type": "password"
},
"logging": false
"logging": {
"level": "fatal"
}
}

View File

@ -3,7 +3,7 @@ var debug = require('debug')('ghost:admin:controller'),
Promise = require('bluebird'),
api = require('../api'),
config = require('../config'),
errors = require('../errors'),
logging = require('../logging'),
updateCheck = require('../update-check'),
i18n = require('../i18n'),
adminControllers;
@ -67,7 +67,7 @@ adminControllers = {
});
}).finally(function noMatterWhat() {
renderIndex();
}).catch(errors.logError);
}).catch(logging.error);
}
};

View File

@ -2,9 +2,9 @@ var express = require('express'),
_ = require('lodash'),
config = require('../../config'),
errors = require('../../errors'),
i18n = require('../../i18n'),
rss = require('../../data/xml/rss'),
utils = require('../../utils'),
channelConfig = require('./channel-config'),
renderChannel = require('./render-channel'),
@ -26,7 +26,7 @@ function handlePageParam(req, res, next, page) {
}
} else if (page < 1 || isNaN(page)) {
// Nothing less than 1 is a valid page number, go straight to a 404
return next(new errors.NotFoundError());
return next(new errors.NotFoundError(i18n.t('errors.errors.pageNotFound')));
} else {
// Set req.params.page to the already parsed number, and continue
req.params.page = page;

View File

@ -1,6 +1,7 @@
var debug = require('debug')('ghost:channels:render'),
_ = require('lodash'),
errors = require('../../errors'),
i18n = require('../../i18n'),
filters = require('../../filters'),
safeString = require('../../utils/index').safeString,
labs = require('../../utils/labs'),
@ -37,7 +38,7 @@ function renderChannel(req, res, next) {
return fetchData(channelOpts).then(function handleResult(result) {
// If page is greater than number of pages we have, go straight to 404
if (pageParam > result.meta.pagination.pages) {
return next(new errors.NotFoundError());
return next(new errors.NotFoundError(i18n.t('errors.errors.pageNotFound')));
}
// @TODO: figure out if this can be removed, it's supposed to ensure that absolutely URLs get generated

View File

@ -5,6 +5,7 @@ var _ = require('lodash'),
versioning = require('../schema').versioning,
serverUtils = require('../../utils'),
errors = require('../../errors'),
logging = require('../../logging'),
settings = require('../../api/settings'),
i18n = require('../../i18n'),
@ -29,7 +30,7 @@ exportFileName = function exportFileName() {
}
return title + 'ghost.' + datetime + '.json';
}).catch(function (err) {
errors.logError(err);
logging.error(err);
return 'ghost.' + datetime + '.json';
});
};
@ -74,7 +75,7 @@ doExport = function doExport() {
return exportData;
}).catch(function (err) {
errors.logAndThrowError(err, i18n.t('errors.data.export.errorExportingData'), '');
return Promise.reject(new errors.InternalServerError(err.message, i18n.t('errors.data.export.errorExportingData')));
});
};

View File

@ -31,10 +31,12 @@ JSONHandler = {
}
return importData;
} catch (e) {
errors.logError(e, i18n.t('errors.data.importer.handlers.json.apiDbImportContent'),
i18n.t('errors.data.importer.handlers.json.checkImportJsonIsValid'));
return Promise.reject(new errors.BadRequestError(i18n.t('errors.data.importer.handlers.json.failedToParseImportJson')));
} catch (err) {
return Promise.reject(new errors.BadRequestError(
i18n.t('errors.data.importer.handlers.json.failedToParseImportJson'),
i18n.t('errors.data.importer.handlers.json.apiDbImportContent'),
i18n.t('errors.data.importer.handlers.json.checkImportJsonIsValid')
));
}
});
}

View File

@ -9,6 +9,7 @@ var _ = require('lodash'),
uuid = require('node-uuid'),
extract = require('extract-zip-fork'),
errors = require('../../errors'),
logging = require('../../logging'),
ImageHandler = require('./handlers/image'),
JSONHandler = require('./handlers/json'),
MarkdownHandler = require('./handlers/markdown'),
@ -108,8 +109,9 @@ _.extend(ImportManager.prototype, {
_.each(filesToDelete, function (fileToDelete) {
fs.remove(fileToDelete, function (err) {
if (err) {
errors.logError(err, i18n.t('errors.data.importer.index.couldNotCleanUpFile.error'),
i18n.t('errors.data.importer.index.couldNotCleanUpFile.context'));
err.context = i18n.t('errors.data.importer.index.couldNotCleanUpFile.error');
err.help = i18n.t('errors.data.importer.index.couldNotCleanUpFile.context');
logging.error(err);
}
});
});

View File

@ -4,19 +4,19 @@ var Promise = require('bluebird'),
_ = require('lodash'),
commands = require('../schema').commands,
fixtures = require('./fixtures'),
errors = require('../../errors'),
db = require('../../data/db'),
logging = require('../../logging'),
errors = require('../../errors'),
schema = require('../schema').tables,
schemaTables = Object.keys(schema),
populate, logger;
// @TODO: remove me asap!
logger = {
info: function info(message) {
errors.logComponentInfo('Migrations', message);
logging.info('Migrations:' + message);
},
warn: function warn(message) {
errors.logComponentWarn('Skipping Migrations', message);
logging.warn('Skipping Migrations:' + message);
}
};

View File

@ -5,6 +5,7 @@ var Promise = require('bluebird'),
backup = require('./backup'),
fixtures = require('./fixtures'),
errors = require('../../errors'),
logging = require('../../logging'),
i18n = require('../../i18n'),
db = require('../../data/db'),
versioning = require('../schema').versioning,
@ -22,13 +23,12 @@ var Promise = require('bluebird'),
migrateToDatabaseVersion,
execute, logger, isDatabaseOutOfDate;
// @TODO: remove me asap!
logger = {
info: function info(message) {
errors.logComponentInfo('Migrations', message);
logging.info('Migrations:' + message);
},
warn: function warn(message) {
errors.logComponentWarn('Skipping Migrations', message);
logging.warn('Skipping Migrations:' + message);
}
};

View File

@ -1,9 +1,9 @@
var https = require('https'),
errors = require('../../errors'),
url = require('url'),
Promise = require('bluebird'),
utils = require('../../utils'),
events = require('../../events'),
logging = require('../../logging'),
api = require('../../api/settings'),
i18n = require('../../i18n'),
schema = require('../schema').checks,
@ -31,12 +31,10 @@ function makeRequest(reqOptions, reqPayload) {
reqPayload = JSON.stringify(reqPayload);
req.write(reqPayload);
req.on('error', function (error) {
errors.logError(
error,
i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.error'),
i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.help', {url: 'http://support.ghost.org'})
);
req.on('error', function (err) {
err.context = i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.error');
err.help = i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.help', {url: 'http://support.ghost.org'});
logging.error(err);
});
req.end();

View File

@ -4,6 +4,7 @@ var crypto = require('crypto'),
config = require('../../../config'),
utils = require('../../../utils'),
errors = require('../../../errors'),
i18n = require('../../../i18n'),
filters = require('../../../filters'),
processUrls = require('../../../utils/make-absolute-urls'),
labs = require('../../../utils/labs'),
@ -173,7 +174,7 @@ generate = function generate(req, res, next) {
// If page is greater than number of pages we have, redirect to last page
if (pageParam > maxPage) {
return next(new errors.NotFoundError());
return next(new errors.NotFoundError(i18n.t('errors.errors.pageNotFound')));
}
data.version = res.locals.safeVersion;

View File

@ -3,7 +3,7 @@ var _ = require('lodash'),
xml = require('xml'),
config = require('../../config'),
utils = require('../../utils'),
errors = require('../../errors'),
logging = require('../../logging'),
events = require('../../events'),
i18n = require('../../i18n'),
pingList;
@ -67,14 +67,11 @@ function ping(post) {
req = http.request(options);
req.write(pingXML);
req.on('error', function handleError(error) {
errors.logError(
error,
i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.error'),
i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.help', {url: 'http://support.ghost.org'})
);
}
);
req.on('error', function handleError(err) {
err.context = i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.error');
err.help = i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.help', {url: 'http://support.ghost.org'});
logging.error(err);
});
req.end();
});
}

View File

@ -1,11 +1,13 @@
// # Bad request error
// Custom error class with status code and type prefilled.
function BadRequestError(message) {
function BadRequestError(message, context, help) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 400;
this.errorType = this.name;
this.context = context;
this.help = help;
}
BadRequestError.prototype = Object.create(Error.prototype);

View File

@ -1,11 +1,13 @@
// # Email error
// Custom error class with status code and type prefilled.
function EmailError(message) {
function EmailError(message, context, help) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 500;
this.errorType = this.name;
this.context = context;
this.help = help;
}
EmailError.prototype = Object.create(Error.prototype);

View File

@ -1,465 +1,41 @@
// # Errors
/*jslint regexp: true */
var _ = require('lodash'),
chalk = require('chalk'),
path = require('path'),
Promise = require('bluebird'),
hbs = require('express-hbs'),
NotFoundError = require('./not-found-error'),
BadRequestError = require('./bad-request-error'),
InternalServerError = require('./internal-server-error'),
NoPermissionError = require('./no-permission-error'),
MethodNotAllowedError = require('./method-not-allowed-error'),
var NotFoundError = require('./not-found-error'),
BadRequestError = require('./bad-request-error'),
InternalServerError = require('./internal-server-error'),
NoPermissionError = require('./no-permission-error'),
MethodNotAllowedError = require('./method-not-allowed-error'),
RequestEntityTooLargeError = require('./request-too-large-error'),
UnauthorizedError = require('./unauthorized-error'),
ValidationError = require('./validation-error'),
ThemeValidationError = require('./theme-validation-error'),
UnsupportedMediaTypeError = require('./unsupported-media-type-error'),
EmailError = require('./email-error'),
DataImportError = require('./data-import-error'),
TooManyRequestsError = require('./too-many-requests-error'),
TokenRevocationError = require('./token-revocation-error'),
VersionMismatchError = require('./version-mismatch-error'),
IncorrectUsage = require('./incorrect-usage'),
Maintenance = require('./maintenance'),
DatabaseNotPopulated = require('./database-not-populated'),
DatabaseVersion = require('./database-version'),
i18n = require('../i18n'),
config = require('../config'),
errors,
// Paths for views
userErrorTemplateExists = false;
function isValidErrorStatus(status) {
return _.isNumber(status) && status >= 400 && status < 600;
}
function getStatusCode(error) {
if (error.statusCode) {
return error.statusCode;
}
if (error.status && isValidErrorStatus(error.status)) {
error.statusCode = error.status;
return error.statusCode;
}
if (error.code && isValidErrorStatus(error.code)) {
error.statusCode = error.code;
return error.statusCode;
}
error.statusCode = 500;
return error.statusCode;
}
/**
* Basic error handling helpers
*/
errors = {
updateActiveTheme: function (activeTheme) {
userErrorTemplateExists = config.get('paths').availableThemes[activeTheme].hasOwnProperty('error.hbs');
},
throwError: function (err) {
if (!err) {
err = new Error(i18n.t('errors.errors.anErrorOccurred'));
}
if (_.isString(err)) {
throw new Error(err);
}
throw err;
},
// ## Reject Error
// Used to pass through promise errors when we want to handle them at a later time
rejectError: function (err) {
return Promise.reject(err);
},
logComponentInfo: function (component, info) {
if (process.env.NODE_LEVEL === 'DEBUG' ||
process.env.NODE_ENV === 'development' ||
process.env.NODE_ENV === 'staging' ||
process.env.NODE_ENV === 'production') {
console.info(chalk.cyan(component + ':', info));
}
},
logComponentWarn: function (component, warning) {
if (process.env.NODE_LEVEL === 'DEBUG' ||
process.env.NODE_ENV === 'development' ||
process.env.NODE_ENV === 'staging' ||
process.env.NODE_ENV === 'production') {
console.info(chalk.yellow(component + ':', warning));
}
},
logWarn: function (warn, context, help) {
if (process.env.NODE_LEVEL === 'DEBUG' ||
process.env.NODE_ENV === 'development' ||
process.env.NODE_ENV === 'staging' ||
process.env.NODE_ENV === 'production') {
warn = warn || i18n.t('errors.errors.noMessageSupplied');
var msgs = [chalk.yellow(i18n.t('errors.errors.warning'), warn), '\n'];
if (context) {
msgs.push(chalk.white(context), '\n');
}
if (help) {
msgs.push(chalk.green(help));
}
// add a new line
msgs.push('\n');
console.log.apply(console, msgs);
}
},
logError: function (err, context, help) {
var self = this,
origArgs = _.toArray(arguments).slice(1),
stack,
msgs,
hideStack = false;
// DatabaseVersion errors are usually fatal, we output a nice message
// And the stack is not at all useful in this case
if (err instanceof DatabaseVersion) {
hideStack = true;
}
if (_.isArray(err)) {
_.each(err, function (e) {
var newArgs = [e].concat(origArgs);
errors.logError.apply(self, newArgs);
});
return;
}
stack = err ? err.stack : null;
if (!_.isString(err)) {
if (_.isObject(err) && _.isString(err.message)) {
err = err.message;
} else {
err = i18n.t('errors.errors.unknownErrorOccurred');
}
}
// Overwrite error to provide information that this is probably a permission problem
// TODO: https://github.com/TryGhost/Ghost/issues/3687
if (err.indexOf('SQLITE_READONLY') !== -1) {
context = i18n.t('errors.errors.databaseIsReadOnly');
help = i18n.t('errors.errors.checkDatabase');
}
// TODO: Logging framework hookup
// Eventually we'll have better logging which will know about envs
// you can use DEBUG=true when running tests and need error stdout
if ((process.env.NODE_LEVEL === 'DEBUG' ||
process.env.NODE_ENV === 'development' ||
process.env.NODE_ENV === 'staging' ||
process.env.NODE_ENV === 'production')) {
msgs = [chalk.red(i18n.t('errors.errors.error'), err), '\n'];
if (context) {
msgs.push(chalk.white(context), '\n');
}
if (help) {
msgs.push(chalk.green(help));
}
// add a new line
msgs.push('\n');
if (stack && !hideStack) {
msgs.push(stack, '\n');
}
console.error.apply(console, msgs);
}
},
logErrorAndExit: function (err, context, help) {
this.logError(err, context, help);
// Exit with 0 to prevent npm errors as we have our own
process.exit(0);
},
logAndThrowError: function (err, context, help) {
this.logError(err, context, help);
this.throwError(err, context, help);
},
logAndRejectError: function (err, context, help) {
this.logError(err, context, help);
return this.rejectError(err, context, help);
},
logErrorWithRedirect: function (msg, context, help, redirectTo, req, res) {
/*jshint unused:false*/
var self = this;
return function () {
self.logError(msg, context, help);
if (_.isFunction(res.redirect)) {
res.redirect(redirectTo);
}
};
},
/**
* ### Format HTTP Errors
* Converts the error response from the API into a format which can be returned over HTTP
*
* @private
* @param {Array} error
* @return {{errors: Array, statusCode: number}}
*/
formatHttpErrors: function formatHttpErrors(error) {
var statusCode = 500,
errors = [];
if (!_.isArray(error)) {
error = [].concat(error);
}
_.each(error, function each(errorItem) {
var errorContent = {};
// TODO: add logic to set the correct status code
statusCode = getStatusCode(errorItem);
errorContent.message = _.isString(errorItem) ? errorItem :
(_.isObject(errorItem) ? errorItem.message : i18n.t('errors.errors.unknownApiError'));
errorContent.errorType = errorItem.errorType || 'InternalServerError';
if (errorItem.errorType === 'ThemeValidationError' && errorItem.errorDetails) {
errorContent.errorDetails = errorItem.errorDetails;
}
errors.push(errorContent);
});
return {errors: errors, statusCode: statusCode};
},
formatAndRejectAPIError: function (error, permsMessage) {
if (!error) {
return this.rejectError(
new this.NoPermissionError(permsMessage || i18n.t('errors.errors.notEnoughPermission'))
);
}
if (_.isString(error)) {
return this.rejectError(new this.NoPermissionError(error));
}
if (error.errorType) {
return this.rejectError(error);
}
// handle database errors
if (error.code && (error.errno || error.detail)) {
error.db_error_code = error.code;
error.errorType = 'DatabaseError';
error.statusCode = 500;
return this.rejectError(error);
}
return this.rejectError(new this.InternalServerError(error));
},
handleAPIError: function errorHandler(err, req, res, next) {
/*jshint unused:false */
var httpErrors = this.formatHttpErrors(err);
this.logError(err);
// Send a properly formatted HTTP response containing the errors
res.status(httpErrors.statusCode).json({errors: httpErrors.errors});
},
renderErrorPage: function (statusCode, err, req, res, next) {
/*jshint unused:false*/
var self = this,
defaultErrorTemplatePath = path.resolve(config.get('paths').adminViews, 'user-error.hbs');
function parseStack(stack) {
if (!_.isString(stack)) {
return stack;
}
// TODO: split out line numbers
var stackRegex = /\s*at\s*(\w+)?\s*\(([^\)]+)\)\s*/i;
return (
stack
.split(/[\r\n]+/)
.slice(1)
.map(function (line) {
var parts = line.match(stackRegex);
if (!parts) {
return null;
}
return {
function: parts[1],
at: parts[2]
};
})
.filter(function (line) {
return !!line;
})
);
}
// Render the error!
function renderErrorInt(errorView) {
var stack = null;
// Not Found and Maintenance Errors don't need a stack trace
if (statusCode !== 404 && statusCode !== 503 && process.env.NODE_ENV !== 'production' && err.stack) {
stack = parseStack(err.stack);
}
res.status(statusCode).render((errorView || 'error'), {
message: err.message || err,
// We have to use code here, as it's the variable passed to the template
// And error templates can be customised... therefore this constitutes API
// In future I recommend we make this be used for a combo-version of statusCode & errorCode
code: statusCode,
// Adding this as being distinctly, the status code, as opposed to any other code see #6526
statusCode: statusCode,
stack: stack
}, function (templateErr, html) {
if (!templateErr) {
return res.status(statusCode).send(html);
}
// There was an error trying to render the error page, output the error
self.logError(templateErr, i18n.t('errors.errors.errorWhilstRenderingError'), i18n.t('errors.errors.errorTemplateHasError'));
// 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(templateErr.message || templateErr) + '</pre>' +
'<br ><p>' + i18n.t('errors.errors.whilstTryingToRender') + '</p>' +
statusCode + ' ' + '<pre>' + hbs.handlebars.Utils.escapeExpression(err.message || err) + '</pre>'
);
});
}
if (statusCode >= 500) {
this.logError(err, i18n.t('errors.errors.renderingErrorPage'), i18n.t('errors.errors.caughtProcessingError'));
}
// Are we admin? If so, don't worry about the user template
if ((res.isAdmin && req.user && req.user.id) || userErrorTemplateExists === true) {
return renderErrorInt();
}
// We're not admin and the template doesn't exist. Render the default.
return renderErrorInt(defaultErrorTemplatePath);
},
error404: function (req, res, next) {
var message = i18n.t('errors.errors.pageNotFound');
// do not cache 404 error
res.set({'Cache-Control': 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'});
if (req.method === 'GET') {
this.renderErrorPage(404, message, req, res, next);
} else {
res.status(404).send(message);
}
},
error500: function (err, req, res, next) {
var statusCode = getStatusCode(err),
returnErrors = [];
// 500 errors should never be cached
res.set({'Cache-Control': 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'});
if (statusCode === 404) {
return this.error404(req, res, next);
}
if (req.method === 'GET') {
if (!err || !(err instanceof Error)) {
next();
}
errors.renderErrorPage(statusCode, err, req, res, next);
} else {
if (!_.isArray(err)) {
err = [].concat(err);
}
_.each(err, function (errorItem) {
var errorContent = {};
errorContent.message = _.isString(errorItem) ? errorItem :
(_.isObject(errorItem) ? errorItem.message : i18n.t('errors.errors.unknownError'));
errorContent.errorType = errorItem.errorType || 'InternalServerError';
returnErrors.push(errorContent);
});
res.status(statusCode).json({errors: returnErrors});
}
}
};
// Ensure our 'this' context for methods and preserve method arity by
// using Function#bind for expressjs
_.each([
'logWarn',
'logComponentInfo',
'logComponentWarn',
'rejectError',
'throwError',
'logError',
'logAndThrowError',
'logAndRejectError',
'logErrorAndExit',
'logErrorWithRedirect',
'handleAPIError',
'formatAndRejectAPIError',
'formatHttpErrors',
'renderErrorPage',
'error404',
'error500'
], function (funcName) {
errors[funcName] = errors[funcName].bind(errors);
});
module.exports = errors;
module.exports.NotFoundError = NotFoundError;
module.exports.BadRequestError = BadRequestError;
module.exports.InternalServerError = InternalServerError;
module.exports.NoPermissionError = NoPermissionError;
module.exports.UnauthorizedError = UnauthorizedError;
module.exports.ValidationError = ValidationError;
module.exports.ThemeValidationError = ThemeValidationError;
UnauthorizedError = require('./unauthorized-error'),
ValidationError = require('./validation-error'),
ThemeValidationError = require('./theme-validation-error'),
UnsupportedMediaTypeError = require('./unsupported-media-type-error'),
EmailError = require('./email-error'),
DataImportError = require('./data-import-error'),
TooManyRequestsError = require('./too-many-requests-error'),
TokenRevocationError = require('./token-revocation-error'),
VersionMismatchError = require('./version-mismatch-error'),
IncorrectUsage = require('./incorrect-usage'),
Maintenance = require('./maintenance'),
DatabaseNotPopulated = require('./database-not-populated'),
DatabaseVersion = require('./database-version');
module.exports.NotFoundError = NotFoundError;
module.exports.BadRequestError = BadRequestError;
module.exports.InternalServerError = InternalServerError;
module.exports.NoPermissionError = NoPermissionError;
module.exports.UnauthorizedError = UnauthorizedError;
module.exports.ValidationError = ValidationError;
module.exports.ThemeValidationError = ThemeValidationError;
module.exports.RequestEntityTooLargeError = RequestEntityTooLargeError;
module.exports.UnsupportedMediaTypeError = UnsupportedMediaTypeError;
module.exports.EmailError = EmailError;
module.exports.DataImportError = DataImportError;
module.exports.MethodNotAllowedError = MethodNotAllowedError;
module.exports.TooManyRequestsError = TooManyRequestsError;
module.exports.TokenRevocationError = TokenRevocationError;
module.exports.VersionMismatchError = VersionMismatchError;
module.exports.IncorrectUsage = IncorrectUsage;
module.exports.Maintenance = Maintenance;
module.exports.DatabaseNotPopulated = DatabaseNotPopulated;
module.exports.DatabaseVersion = DatabaseVersion;
module.exports.UnsupportedMediaTypeError = UnsupportedMediaTypeError;
module.exports.EmailError = EmailError;
module.exports.DataImportError = DataImportError;
module.exports.MethodNotAllowedError = MethodNotAllowedError;
module.exports.TooManyRequestsError = TooManyRequestsError;
module.exports.TokenRevocationError = TokenRevocationError;
module.exports.VersionMismatchError = VersionMismatchError;
module.exports.IncorrectUsage = IncorrectUsage;
module.exports.Maintenance = Maintenance;
module.exports.DatabaseNotPopulated = DatabaseNotPopulated;
module.exports.DatabaseVersion = DatabaseVersion;

View File

@ -1,11 +1,13 @@
// # Internal Server Error
// Custom error class with status code and type prefilled.
function InternalServerError(message) {
function InternalServerError(message, context, help) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 500;
this.errorType = this.name;
this.context = context;
this.help = help;
}
InternalServerError.prototype = Object.create(Error.prototype);

View File

@ -1,11 +1,13 @@
// # Too Many Requests Error
// Custom error class with status code and type prefilled.
function TooManyRequestsError(message) {
function TooManyRequestsError(message, context, help) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 429;
this.errorType = this.name;
this.context = context;
this.help = help;
}
TooManyRequestsError.prototype = Object.create(Error.prototype);

View File

@ -1,11 +1,13 @@
// # Unauthorized error
// Custom error class with status code and type prefilled.
function UnauthorizedError(message) {
function UnauthorizedError(message, context, help) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 401;
this.errorType = this.name;
this.context = context;
this.help = help;
}
UnauthorizedError.prototype = Object.create(Error.prototype);

View File

@ -1,7 +1,7 @@
// # Validation Error
// Custom error class with status code and type prefilled.
function ValidationError(message, offendingProperty) {
function ValidationError(message, offendingProperty, context, help) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 422;
@ -9,6 +9,8 @@ function ValidationError(message, offendingProperty) {
this.property = offendingProperty;
}
this.errorType = this.name;
this.context = context;
this.help = help;
}
ValidationError.prototype = Object.create(Error.prototype);

View File

@ -7,6 +7,7 @@ var debug = require('debug')('ghost:server'),
path = require('path'),
_ = require('lodash'),
errors = require('./errors'),
logging = require('./logging'),
config = require('./config'),
i18n = require('./i18n'),
moment = require('moment');
@ -75,18 +76,19 @@ GhostServer.prototype.start = function (externalApp) {
self.httpServer.on('error', function (error) {
if (error.errno === 'EADDRINUSE') {
errors.logError(
logging.error(new errors.InternalServerError(
i18n.t('errors.httpServer.addressInUse.error'),
i18n.t('errors.httpServer.addressInUse.context', {port: config.get('server').port}),
i18n.t('errors.httpServer.addressInUse.help')
);
));
} else {
errors.logError(
logging.error(new errors.InternalServerError(
i18n.t('errors.httpServer.otherError.error', {errorNumber: error.errno}),
i18n.t('errors.httpServer.otherError.context'),
i18n.t('errors.httpServer.otherError.help')
);
));
}
process.exit(-1);
});
self.httpServer.on('connection', self.connection.bind(self));

View File

@ -4,7 +4,7 @@
// Block helper designed for looping through posts
var hbs = require('express-hbs'),
_ = require('lodash'),
errors = require('../errors'),
logging = require('../logging'),
i18n = require('../i18n'),
labs = require('../utils/labs'),
utils = require('./utils'),
@ -33,7 +33,7 @@ function filterItemsByVisibility(items, options) {
foreach = function (items, options) {
if (!options) {
errors.logWarn(i18n.t('warnings.helpers.foreach.iteratorNeeded'));
logging.warn(i18n.t('warnings.helpers.foreach.iteratorNeeded'));
}
if (hbsUtils.isFunction(items)) {

View File

@ -4,7 +4,7 @@
var _ = require('lodash'),
hbs = require('express-hbs'),
Promise = require('bluebird'),
errors = require('../errors'),
logging = require('../logging'),
api = require('../api'),
jsonpath = require('jsonpath'),
labs = require('../utils/labs'),
@ -101,13 +101,13 @@ get = function get(resource, options) {
if (!options.fn) {
data.error = i18n.t('warnings.helpers.get.mustBeCalledAsBlock');
errors.logWarn(data.error);
logging.warn(data.error);
return Promise.resolve();
}
if (!_.includes(resources, resource)) {
data.error = i18n.t('warnings.helpers.get.invalidResource');
errors.logWarn(data.error);
logging.warn(data.error);
return Promise.resolve(options.inverse(self, {data: data}));
}
@ -145,19 +145,20 @@ get = function get(resource, options) {
module.exports = function getWithLabs(resource, options) {
var self = this,
errorMessages = [
i18n.t('warnings.helpers.get.helperNotAvailable'),
i18n.t('warnings.helpers.get.apiMustBeEnabled'),
i18n.t('warnings.helpers.get.seeLink', {url: 'http://support.ghost.org/public-api-beta'})
];
err;
if (labs.isSet('publicAPI') === true) {
// get helper is active
return get.call(self, resource, options);
} else {
errors.logError.apply(this, errorMessages);
err = new Error();
err.message = i18n.t('warnings.helpers.get.helperNotAvailable');
err.context = i18n.t('warnings.helpers.get.apiMustBeEnabled');
err.help = i18n.t('warnings.helpers.get.seeLink', {url: 'http://support.ghost.org/public-api-beta'});
logging.error(err);
return Promise.resolve(function noGetHelper() {
return '<script>console.error("' + errorMessages.join(' ') + '");</script>';
return '<script>console.error(' + JSON.stringify(err) + ');</script>';
});
}
};

View File

@ -4,7 +4,7 @@
// Checks if a post has a particular property
var _ = require('lodash'),
errors = require('../errors'),
logging = require('../logging'),
i18n = require('../i18n'),
has;
@ -41,7 +41,7 @@ has = function (options) {
}
if (!tagList && !authorList) {
errors.logWarn(i18n.t('warnings.helpers.has.invalidAttribute'));
logging.warn(i18n.t('warnings.helpers.has.invalidAttribute'));
return;
}

View File

@ -1,6 +1,7 @@
var hbs = require('express-hbs'),
Promise = require('bluebird'),
errors = require('../errors'),
logging = require('../logging'),
utils = require('./utils'),
i18n = require('../i18n'),
coreHelpers = {},
@ -48,7 +49,8 @@ coreHelpers.helperMissing = function (arg) {
if (arguments.length === 2) {
return undefined;
}
errors.logError(i18n.t('warnings.helpers.index.missingHelper', {arg: arg}));
logging.error(new errors.InternalServerError(i18n.t('warnings.helpers.index.missingHelper', {arg: arg})));
};
// Register an async handlebars helper for a given handlebars instance
@ -64,7 +66,8 @@ function registerAsyncHelper(hbs, name, fn) {
Promise.resolve(fn.call(this, context, options)).then(function (result) {
cb(result);
}).catch(function (err) {
errors.logAndThrowError(err, 'registerAsyncThemeHelper: ' + name);
logging.warn('registerAsyncThemeHelper: ' + name);
throw err;
});
});
}

View File

@ -2,7 +2,7 @@
// Usage: `{{#is "paged"}}`, `{{#is "index, paged"}}`
// Checks whether we're in a given context.
var _ = require('lodash'),
errors = require('../errors'),
logging = require('../logging'),
i18n = require('../i18n'),
is;
@ -12,7 +12,7 @@ is = function (context, options) {
var currentContext = options.data.root.context;
if (!_.isString(context)) {
errors.logWarn(i18n.t('warnings.helpers.is.invalidAttribute'));
logging.warn(i18n.t('warnings.helpers.is.invalidAttribute'));
return;
}

View File

@ -5,7 +5,6 @@
var _ = require('lodash'),
hbs = require('express-hbs'),
i18n = require('../i18n'),
errors = require('../errors'),
template = require('./template'),
navigation;
@ -19,13 +18,13 @@ navigation = function (options) {
data;
if (!_.isObject(navigationData) || _.isFunction(navigationData)) {
return errors.logAndThrowError(i18n.t('warnings.helpers.navigation.invalidData'));
throw new errors.IncorrectUsage(i18n.t('warnings.helpers.navigation.invalidData'));
}
if (navigationData.filter(function (e) {
return (_.isUndefined(e.label) || _.isUndefined(e.url));
}).length > 0) {
return errors.logAndThrowError(i18n.t('warnings.helpers.navigation.valuesMustBeDefined'));
throw new errors.IncorrectUsage(i18n.t('warnings.helpers.navigation.valuesMustBeDefined'));
}
// check for non-null string values
@ -33,7 +32,7 @@ navigation = function (options) {
return ((!_.isNull(e.label) && !_.isString(e.label)) ||
(!_.isNull(e.url) && !_.isString(e.url)));
}).length > 0) {
return errors.logAndThrowError(i18n.t('warnings.helpers.navigation.valuesMustBeString'));
throw new errors.IncorrectUsage(i18n.t('warnings.helpers.navigation.valuesMustBeString'));
}
function _slugify(label) {

View File

@ -8,7 +8,7 @@
//
// We use the name page_url to match the helper for consistency:
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
var errors = require('../errors'),
var logging = require('../logging'),
i18n = require('../i18n'),
getPaginatedUrl = require('../data/meta/paginated_url'),
page_url,
@ -31,7 +31,7 @@ page_url = function (page, options) {
// context. This helper is deprecated and will be removed in future versions.
//
pageUrl = function (pageNum, options) {
errors.logWarn(i18n.t('warnings.helpers.page_url.isDeprecated'));
logging.warn(i18n.t('warnings.helpers.page_url.isDeprecated'));
/*jshint unused:false*/
var self = this;

View File

@ -11,22 +11,22 @@ var _ = require('lodash'),
pagination = function (options) {
/*jshint unused:false*/
if (!_.isObject(this.pagination) || _.isFunction(this.pagination)) {
return errors.logAndThrowError(i18n.t('warnings.helpers.pagination.invalidData'));
throw new errors.IncorrectUsage(i18n.t('warnings.helpers.pagination.invalidData'));
}
if (_.isUndefined(this.pagination.page) || _.isUndefined(this.pagination.pages) ||
_.isUndefined(this.pagination.total) || _.isUndefined(this.pagination.limit)) {
return errors.logAndThrowError(i18n.t('warnings.helpers.pagination.valuesMustBeDefined'));
throw new errors.IncorrectUsage(i18n.t('warnings.helpers.pagination.valuesMustBeDefined'));
}
if ((!_.isNull(this.pagination.next) && !_.isNumber(this.pagination.next)) ||
(!_.isNull(this.pagination.prev) && !_.isNumber(this.pagination.prev))) {
return errors.logAndThrowError(i18n.t('warnings.helpers.pagination.nextPrevValuesMustBeNumeric'));
throw new errors.IncorrectUsage(i18n.t('warnings.helpers.pagination.nextPrevValuesMustBeNumeric'));
}
if (!_.isNumber(this.pagination.page) || !_.isNumber(this.pagination.pages) ||
!_.isNumber(this.pagination.total) || !_.isNumber(this.pagination.limit)) {
return errors.logAndThrowError(i18n.t('warnings.helpers.pagination.valuesMustBeNumeric'));
throw new errors.IncorrectUsage(i18n.t('warnings.helpers.pagination.valuesMustBeNumeric'));
}
var data = _.merge({}, this.pagination);

View File

@ -17,7 +17,7 @@ var hbs = require('express-hbs'),
plural = function (number, options) {
if (_.isUndefined(options.hash) || _.isUndefined(options.hash.empty) ||
_.isUndefined(options.hash.singular) || _.isUndefined(options.hash.plural)) {
return errors.logAndThrowError(i18n.t('warnings.helpers.plural.valuesMustBeDefined'));
throw new errors.IncorrectUsage(i18n.t('warnings.helpers.plural.valuesMustBeDefined'));
}
if (number === 0) {

View File

@ -11,8 +11,7 @@ templates.execute = function (name, context, options) {
var partial = hbs.handlebars.partials[name];
if (partial === undefined) {
errors.logAndThrowError(i18n.t('warnings.helpers.template.templateNotFound', {name: name}));
return;
throw new errors.IncorrectUsage(i18n.t('warnings.helpers.template.templateNotFound', {name: name}));
}
// If the partial view is not compiled, it compiles and saves in handlebars

View File

@ -20,7 +20,7 @@ var debug = require('debug')('ghost:boot:init'),
i18n = require('./i18n'),
api = require('./api'),
config = require('./config'),
errors = require('./errors'),
logging = require('./logging'),
middleware = require('./middleware'),
db = require('./data/schema'),
models = require('./models'),
@ -121,11 +121,11 @@ function init(options) {
.catch(function (result) {
// TODO: change `result` to something better
result.errors.forEach(function (err) {
errors.logError(err.message, err.context, err.help);
logging.error(err);
});
result.warnings.forEach(function (warn) {
errors.logWarn(warn.message, warn.context, warn.help);
logging.warn(warn.message);
});
});

View File

@ -0,0 +1,210 @@
var bunyan = require('bunyan'),
_ = require('lodash'),
GhostPrettyStream = require('./PrettyStream');
function GhostLogger(options) {
this.env = options.env;
this.transports = options.transports || ['stdout'];
this.level = options.level || 'info';
this.mode = options.mode || 'short';
this.path = options.path || 'ghost.log';
this.rotation = options.rotation || false;
this.loggers = {};
this.setSerializers();
this.setLoggers();
this.setStreams();
}
// @TODO: add correlation identifier
// @TODO: res.on('finish') has no access to the response body
GhostLogger.prototype.setSerializers = function setSerializers() {
var self = this;
this.serializers = {
req: function (req) {
return {
url: req.url,
method: req.method,
originalUrl: req.originalUrl,
params: req.params,
headers: self.removeSensitiveData(req.headers),
body: self.removeSensitiveData(req.body),
query: self.removeSensitiveData(req.query)
};
},
res: function (res) {
return {
_headers: self.removeSensitiveData(res._headers),
statusCode: res.statusCode
};
},
err: function (err) {
return {
name: err.errorType,
statusCode: err.statusCode,
level: err.level,
message: err.message,
context: err.context,
help: err.help,
stack: err.stack,
hideStack: err.hideStack
};
}
};
};
GhostLogger.prototype.setLoggers = function setLoggers() {
var self = this;
this.log = {
info: function (options) {
var req = options.req,
res = options.res;
_.each(self.loggers, function (logger) {
logger.log.info({
req: req,
res: res
});
});
},
debug: function (options) {
var req = options.req,
res = options.res;
_.each(self.loggers, function (logger) {
logger.log.debug({
req: req,
res: res
});
});
},
error: function (options) {
var req = options.req,
res = options.res,
err = options.err;
_.each(self.loggers, function (logger) {
logger.log.error({
req: req,
res: res,
err: err
});
});
}
};
};
GhostLogger.prototype.setStreams = function setStreams() {
var self = this,
streams = [],
prettyStdOut;
_.each(self.transports, function (transport) {
if (transport === 'file') {
streams.push({
name: 'file',
stream: {
path: self.path,
level: self.level
}
});
}
if (transport === 'stdout') {
prettyStdOut = new GhostPrettyStream();
prettyStdOut.pipe(process.stdout);
streams.push({
name: 'stdout',
stream: {
type: 'raw',
stream: prettyStdOut,
level: self.level
}
});
}
});
if (self.rotation) {
streams.push({
name: 'rotation',
stream: {
type: 'rotating-file',
path: self.path,
period: '1w',
count: 3,
level: self.level
}
});
}
// the env defines which streams are available
_.each(streams, function (stream) {
self.loggers[stream.name] = {
name: stream.name,
log: bunyan.createLogger({
name: 'Log',
streams: [stream.stream],
serializers: self.serializers
})
};
});
};
GhostLogger.prototype.removeSensitiveData = function removeSensitiveData(obj) {
var newObj = {};
_.each(obj, function (value, key) {
if (!key.match(/pin|password|authorization|cookie/gi)) {
newObj[key] = value;
}
});
return newObj;
};
GhostLogger.prototype.info = function info() {
var print = '';
_.each(arguments, function (value) {
print += value;
print += ' ';
});
this.loggers.stdout.log.info(print);
};
GhostLogger.prototype.warn = function warn() {
var print = '';
_.each(arguments, function (value) {
print += value;
print += ' ';
});
this.loggers.stdout.log.warn(print);
};
GhostLogger.prototype.debug = function debug(options) {
this.loggers.stdout.log.debug(options);
};
GhostLogger.prototype.error = function error(err) {
this.log.error({err: err});
};
GhostLogger.prototype.request = function request(options) {
var req = options.req,
res = options.res,
err = options.err;
if (err) {
this.log.error({req: req, res: res, err: err});
} else {
this.log.info({req: req, res: res});
}
};
module.exports = GhostLogger;

View File

@ -0,0 +1,154 @@
// jscs:disable
var _ = require('lodash'),
moment = require('moment'),
Stream = require('stream').Stream,
util = require('util'),
format = util.format,
prettyjson = require('prettyjson'),
__private__ = {
levelFromName: {
10: 'trace',
20: 'debug',
30: 'info',
40: 'warn',
50: 'error',
60: 'fatal'
},
colorForLevel: {
10: 'grey',
20: 'grey',
30: 'cyan',
40: 'magenta',
50: 'red',
60: 'inverse'
},
colors: {
'bold': [1, 22],
'italic': [3, 23],
'underline': [4, 24],
'inverse': [7, 27],
'white': [37, 39],
'grey': [90, 39],
'black': [30, 39],
'blue': [34, 39],
'cyan': [36, 39],
'green': [32, 39],
'magenta': [35, 39],
'red': [31, 39],
'yellow': [33, 39]
}
};
function PrettyStream() {
}
util.inherits(PrettyStream, Stream);
function colorize(color, value) {
return '\x1B[' + __private__.colors[color][0] + 'm' + value + '\x1B[' + __private__.colors[color][1] + 'm';
}
PrettyStream.prototype.write = function write(data) {
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (err) {
this.emit('data', err);
}
}
var body = {},
time = moment(data.time).format('YYYY-MM-DD HH:mm:ss'),
logLevel = __private__.levelFromName[data.level].toUpperCase(),
codes = __private__.colors[__private__.colorForLevel[data.level]],
bodyPretty = '';
logLevel = '\x1B[' + codes[0] + 'm' + logLevel + '\x1B[' + codes[1] + 'm';
if (data.msg) {
body.msg = data.msg;
}
if (data.req && data.res) {
_.each(data.req, function (value, key) {
if (['headers', 'query', 'body'].indexOf(key) !== -1 && !_.isEmpty(value)) {
bodyPretty += colorize('yellow', key.toUpperCase()) + '\n';
bodyPretty += prettyjson.render(value, {}) + '\n';
}
});
bodyPretty += '\n';
if (data.err) {
if (data.err.level) {
bodyPretty += colorize('yellow', 'ERROR (' + data.err.level + ')') + '\n';
} else {
bodyPretty += colorize('yellow', 'ERROR\n');
}
_.each(data.err, function (value, key) {
if (['message', 'context', 'help', 'stack'].indexOf(key) !== -1 && !_.isEmpty(value)) {
bodyPretty += value + '\n';
}
});
}
} else if (data.err) {
_.each(data.err, function (value, key) {
if (_.isEmpty(value)) {
return;
}
if (key === 'level') {
bodyPretty += colorize('underline', key + ':' + value) + '\n\n';
}
else if (key === 'message') {
bodyPretty += colorize('red', value) + '\n';
}
else if (key === 'context') {
bodyPretty += colorize('white', value) + '\n';
}
else if (key === 'help') {
bodyPretty += colorize('yellow', value) + '\n';
}
else if (key === 'stack' && !data.err['hideStack']) {
bodyPretty += colorize('white', value) + '\n';
}
});
} else {
// print string
bodyPretty += data.msg;
}
try {
if (data.req && data.res) {
this.emit('data', format('[%s] %s --> %s %s (%s) \n%s\n\n',
time,
logLevel,
data.req.method,
data.req.url,
data.res.statusCode,
colorize('grey', bodyPretty)
));
} else if (data.err) {
this.emit('data', format('[%s] %s \n%s\n\n',
time,
logLevel,
colorize('grey', bodyPretty)
));
} else {
this.emit('data', format('[%s] %s %s\n',
time,
logLevel,
colorize('grey', bodyPretty)
));
}
} catch (err) {
this.emit('data', err);
}
return true;
};
module.exports = PrettyStream;

View File

@ -0,0 +1,12 @@
var config = require('../config'),
GhostLogger = require('./GhostLogger'),
adapter = new GhostLogger({
env: config.get('env'),
mode: process.env.NODE_MODE,
level: process.env.NODE_LEVEL || config.get('logging:level'),
transports: config.get('logging:transports'),
rotation: config.get('logging:rotation'),
path: config.get('paths:appRoot') + '/ghost.log'
});
module.exports = adapter;

View File

@ -0,0 +1,110 @@
var _ = require('lodash'),
path = require('path'),
hbs = require('express-hbs'),
config = require('../config'),
i18n = require('../i18n'),
_private = {};
_private.parseStack = function (stack) {
if (!_.isString(stack)) {
return stack;
}
// TODO: split out line numbers
var stackRegex = /\s*at\s*(\w+)?\s*\(([^\)]+)\)\s*/i;
return (
stack
.split(/[\r\n]+/)
.slice(1)
.map(function (line) {
var parts = line.match(stackRegex);
if (!parts) {
return null;
}
return {
function: parts[1],
at: parts[2]
};
})
.filter(function (line) {
return !!line;
})
);
};
_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!)
* @TODO: support multiple errors
* @TODO: decouple req.err
*/
module.exports = function errorHandler(err, req, res, next) {
if (_.isArray(err)) {
err = err[0];
}
req.err = err;
res.statusCode = err.statusCode;
// never cache errors
res.set({
'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';
}
// 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)
});
};

View File

@ -5,12 +5,13 @@ var debug = require('debug')('ghost:middleware'),
errors = require('../errors'),
express = require('express'),
hbs = require('express-hbs'),
logger = require('morgan'),
path = require('path'),
routes = require('../routes'),
serveStatic = require('express').static,
slashes = require('connect-slashes'),
storage = require('../storage'),
logging = require('../logging'),
i18n = require('../i18n'),
utils = require('../utils'),
sitemapHandler = require('../data/xml/sitemap/handler'),
multer = require('multer'),
@ -24,7 +25,8 @@ var debug = require('debug')('ghost:middleware'),
staticTheme = require('./static-theme'),
themeHandler = require('./theme-handler'),
uncapitalise = require('./uncapitalise'),
maintenance = require('./maintenance'),
maintenance = require('./maintenance'),
errorHandler = require('./error-handler'),
versionMatch = require('./api/version-match'),
cors = require('./cors'),
validation = require('./validation'),
@ -40,7 +42,7 @@ middleware = {
cacheControl: cacheControl,
spamPrevention: spamPrevention,
api: {
errorHandler: errors.handleAPIError,
errorHandler: errorHandler,
cors: cors,
labs: labs,
versionMatch: versionMatch,
@ -50,8 +52,7 @@ middleware = {
setupMiddleware = function setupMiddleware(blogApp) {
debug('Middleware start');
var logging = config.get('logging'),
corePath = config.get('paths').corePath,
var corePath = config.get('paths').corePath,
adminApp = express(),
adminHbs = hbs.create();
@ -79,14 +80,16 @@ setupMiddleware = function setupMiddleware(blogApp) {
// (X-Forwarded-Proto header will be checked, if present)
blogApp.enable('trust proxy');
// Logging configuration
if (logging !== false) {
if (blogApp.get('env') !== 'development') {
blogApp.use(logger('combined', logging));
} else {
blogApp.use(logger('dev', logging));
}
}
/**
* 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
@ -210,12 +213,12 @@ setupMiddleware = function setupMiddleware(blogApp) {
// Set up Frontend routes (including private blogging routes)
blogApp.use(routes.frontend());
// ### Error handling
// 404 Handler
blogApp.use(errors.error404);
// ### Error handlers
blogApp.use(function pageNotFound(req, res, next) {
next(new errors.NotFoundError(i18n.t('errors.errors.pageNotFound')));
});
// 500 Handler
blogApp.use(errors.error500);
blogApp.use(errorHandler);
debug('Middleware end');
};

View File

@ -7,7 +7,7 @@ labs = {
if (labsUtil.isSet('subscribers') === true) {
return next();
} else {
return errors.handleAPIError(new errors.NotFoundError(), req, res, next);
return next(new errors.NotFoundError());
}
}
};

View File

@ -22,7 +22,6 @@ spamPrevention = {
remoteAddress = req.connection.remoteAddress,
deniedRateLimit = '',
ipCount = '',
message = i18n.t('errors.middleware.spamprevention.tooManyAttempts'),
rateSigninPeriod = config.rateSigninPeriod || 3600,
rateSigninAttempts = config.rateSigninAttempts || 10;
@ -44,12 +43,11 @@ spamPrevention = {
deniedRateLimit = (ipCount[remoteAddress] > rateSigninAttempts);
if (deniedRateLimit) {
errors.logError(
return next(new errors.TooManyRequestsError(
i18n.t('errors.middleware.spamprevention.tooManyAttempts') + rateSigninPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater'),
i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.error', {rateSigninAttempts: rateSigninAttempts, rateSigninPeriod: rateSigninPeriod}),
i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.context')
);
message += rateSigninPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater');
return next(new errors.TooManyRequestsError(message));
));
}
next();
},
@ -65,7 +63,6 @@ spamPrevention = {
ipCount = '',
deniedRateLimit = '',
deniedEmailRateLimit = '',
message = i18n.t('errors.middleware.spamprevention.tooManyAttempts'),
index = _.findIndex(forgottenSecurity, function findIndex(logTime) {
return (logTime.ip === remoteAddress && logTime.email === email);
});
@ -94,22 +91,19 @@ spamPrevention = {
}
if (deniedEmailRateLimit) {
errors.logError(
return next(new errors.TooManyRequestsError(
i18n.t('errors.middleware.spamprevention.tooManyAttempts') + rateForgottenPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater'),
i18n.t('errors.middleware.spamprevention.forgottenPasswordEmail.error', {rfa: rateForgottenAttempts, rfp: rateForgottenPeriod}),
i18n.t('errors.middleware.spamprevention.forgottenPasswordEmail.context')
);
));
}
if (deniedRateLimit) {
errors.logError(
return next(new errors.TooManyRequestsError(
i18n.t('errors.middleware.spamprevention.tooManyAttempts') + rateForgottenPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater'),
i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error', {rfa: rateForgottenAttempts, rfp: rateForgottenPeriod}),
i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.context')
);
}
if (deniedEmailRateLimit || deniedRateLimit) {
message += rateForgottenPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater');
return next(new errors.TooManyRequestsError(message));
));
}
next();

View File

@ -4,6 +4,7 @@ var _ = require('lodash'),
hbs = require('express-hbs'),
api = require('../api'),
config = require('../config'),
logging = require('../logging'),
errors = require('../errors'),
i18n = require('../i18n'),
themeHandler;
@ -80,9 +81,6 @@ themeHandler = {
blogApp.engine('hbs', hbs.express3(hbsOptions));
// Update user error template
errors.updateActiveTheme(activeTheme);
// Set active theme variable on the express server
blogApp.set('activeTheme', activeTheme);
},
@ -102,15 +100,14 @@ themeHandler = {
// Change theme
if (!config.get('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(i18n.t('errors.middleware.themehandler.missingTheme', {theme: activeTheme.value}));
return next(new errors.InternalServerError(i18n.t('errors.middleware.themehandler.missingTheme', {theme: activeTheme.value})));
} else {
// At this point the activated theme is not present and the current
// request is for the admin client. In order to allow the user access
// to the admin client we set an hbs instance on the app so that middleware
// processing can continue.
blogApp.engine('hbs', hbs.express3());
errors.logWarn(i18n.t('errors.middleware.themehandler.missingTheme', {theme: activeTheme.value}));
logging.warn(i18n.t('errors.middleware.themehandler.missingTheme', {theme: activeTheme.value}));
return next();
}

View File

@ -173,7 +173,7 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
} else if (options.context && options.context.external) {
return 0;
} else {
errors.logAndThrowError(new Error(i18n.t('errors.models.base.index.missingContext')));
throw new errors.IncorrectUsage(i18n.t('errors.models.base.index.missingContext'));
}
},

View File

@ -1,7 +1,7 @@
var config = require('../../config'),
events = require(config.get('paths:corePath') + '/server/events'),
models = require(config.get('paths:corePath') + '/server/models'),
errors = require(config.get('paths:corePath') + '/server/errors'),
logging = require(config.get('paths:corePath') + '/server/logging'),
sequence = require(config.get('paths:corePath') + '/server/utils/sequence'),
moment = require('moment-timezone');
@ -11,7 +11,7 @@ var config = require('../../config'),
events.on('token.added', function (tokenModel) {
models.User.edit({last_login: moment().toDate()}, {id: tokenModel.get('user_id')})
.catch(function (err) {
errors.logError(err);
logging.error(err);
});
});
@ -61,11 +61,11 @@ events.on('settings.activeTimezone.edited', function (settingModel) {
};
})).each(function (result) {
if (!result.isFulfilled()) {
errors.logError(result.reason());
logging.error(result.reason());
}
});
})
.catch(function (err) {
errors.logError(err);
logging.error(err);
});
});

View File

@ -25,8 +25,9 @@ filterUtils = {
return _.isString(arg) ? gql.parse(arg) : arg;
});
} catch (error) {
errors.logAndThrowError(
new errors.ValidationError(error.message, 'filter'),
throw new errors.ValidationError(
error.message,
'filter',
i18n.t('errors.models.plugins.filter.errorParsing'),
i18n.t('errors.models.plugins.filter.forInformationRead', {url: 'http://api.ghost.org/docs/filter'})
);

View File

@ -5,6 +5,7 @@ var _ = require('lodash'),
Promise = require('bluebird'),
sequence = require('../utils/sequence'),
errors = require('../errors'),
logging = require('../logging'),
Showdown = require('showdown-ghost'),
legacyConverter = new Showdown.converter({extensions: ['ghostgfm', 'footnotes', 'highlight']}),
Mobiledoc = require('mobiledoc-html-renderer').default,
@ -376,11 +377,12 @@ Post = ghostBookshelf.Model.extend({
}).then(function () {
// Don't do anything, the transaction processed ok
}).catch(function failure(error) {
errors.logError(
error,
logging.error(new errors.InternalServerError(
error.message,
i18n.t('errors.models.post.tagUpdates.error'),
i18n.t('errors.models.post.tagUpdates.help')
);
));
return Promise.reject(new errors.InternalServerError(
i18n.t('errors.models.post.tagUpdates.error') + ' ' + i18n.t('errors.models.post.tagUpdates.help') + error
));
@ -700,7 +702,7 @@ Post = ghostBookshelf.Model.extend({
var newArgs = [foundPostModel].concat(origArgs);
return self.permissible.apply(self, newArgs);
}, errors.logAndThrowError);
});
}
if (postModel) {

View File

@ -51,13 +51,14 @@ Role = ghostBookshelf.Model.extend({
if (_.isNumber(roleModelOrId) || _.isString(roleModelOrId)) {
// Grab the original args without the first one
origArgs = _.toArray(arguments).slice(1);
// Get the actual role model
return this.findOne({id: roleModelOrId, status: 'all'}).then(function then(foundRoleModel) {
// Build up the original args but substitute with actual model
var newArgs = [foundRoleModel].concat(origArgs);
return self.permissible.apply(self, newArgs);
}, errors.logAndThrowError);
});
}
if (action === 'assign' && loadedPermissions.user) {

View File

@ -139,7 +139,7 @@ Settings = ghostBookshelf.Model.extend({
}
return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.settings.unableToFindSetting', {key: item.key})));
}, errors.logAndThrowError);
});
});
},

View File

@ -9,6 +9,7 @@ var _ = require('lodash'),
validator = require('validator'),
validation = require('../data/validation'),
events = require('../events'),
logging = require('../logging'),
i18n = require('../i18n'),
bcryptGenSalt = Promise.promisify(bcrypt.genSalt),
@ -121,7 +122,7 @@ User = ghostBookshelf.Model.extend({
} else if (this.get('id')) {
return this.get('id');
} else {
errors.logAndThrowError(new errors.NotFoundError(i18n.t('errors.models.user.missingContext')));
throw new errors.IncorrectUsage(i18n.t('errors.models.user.missingContext'));
}
},
@ -463,7 +464,7 @@ User = ghostBookshelf.Model.extend({
var newArgs = [foundUserModel].concat(origArgs);
return self.permissible.apply(self, newArgs);
}, errors.logAndThrowError);
});
}
if (action === 'edit') {
@ -554,30 +555,28 @@ User = ghostBookshelf.Model.extend({
return Promise.reject(new errors.UnauthorizedError(i18n.t('errors.models.user.incorrectPasswordAttempts', {remaining: remaining, s: s})));
// Use comma structure, not .catch, because we don't want to catch incorrect passwords
}, function handleError(error) {
}, function handleError(err) {
// If we get a validation or other error during this save, catch it and log it, but don't
// cause a login error because of it. The user validation is not important here.
errors.logError(
error,
i18n.t('errors.models.user.userUpdateError.context'),
i18n.t('errors.models.user.userUpdateError.help')
);
err.context = i18n.t('errors.models.user.userUpdateError.context');
err.help = i18n.t('errors.models.user.userUpdateError.help');
logging.error(err);
return Promise.reject(new errors.UnauthorizedError(i18n.t('errors.models.user.incorrectPassword')));
});
}
return Promise.resolve(user.set({status: 'active', last_login: new Date()}).save({validate: false}))
.catch(function handleError(error) {
.catch(function handleError(err) {
// If we get a validation or other error during this save, catch it and log it, but don't
// cause a login error because of it. The user validation is not important here.
errors.logError(
error,
i18n.t('errors.models.user.userUpdateError.context'),
i18n.t('errors.models.user.userUpdateError.help')
);
err.context = i18n.t('errors.models.user.userUpdateError.context');
err.help = i18n.t('errors.models.user.userUpdateError.help');
logging.error(err);
return user;
});
}, errors.logAndThrowError);
});
}
return Promise.reject(new errors.NoPermissionError(
i18n.t('errors.models.user.accountLocked')));

View File

@ -38,7 +38,7 @@ effective = {
});
return {permissions: allPerms, roles: user.roles};
}, errors.logAndThrowError);
});
},
app: function (appName) {
@ -49,7 +49,7 @@ effective = {
}
return {permissions: foundApp.related('permissions').models};
}, errors.logAndThrowError);
});
}
};

View File

@ -56,14 +56,14 @@ function parseContext(context) {
}
function applyStatusRules(docName, method, opts) {
var errorMsg = i18n.t('errors.permissions.applyStatusRules.error', {docName: docName});
var err = new errors.NoPermissionError(i18n.t('errors.permissions.applyStatusRules.error', {docName: docName}));
// Enforce status 'active' for users
if (docName === 'users') {
if (!opts.status) {
return 'active';
} else if (opts.status !== 'active') {
throw errorMsg;
throw err;
}
}
@ -80,7 +80,7 @@ function applyStatusRules(docName, method, opts) {
return opts.status;
} else if (opts.status !== 'published') {
// any other parameter would make this a permissions error
throw errorMsg;
throw err;
}
}

View File

@ -2,7 +2,7 @@ var util = require('util'),
moment = require('moment'),
request = require('superagent'),
SchedulingBase = require(__dirname + '/SchedulingBase'),
errors = require(__dirname + '/../errors');
logging = require(__dirname + '/../logging');
/**
* allJobs is a sorted list by time attribute
@ -212,7 +212,7 @@ SchedulingDefault.prototype._pingUrl = function (object) {
}, self.retryTimeoutInMs);
}
errors.logError(err);
logging.error(err);
}
});
};

View File

@ -7,8 +7,9 @@ var serveStatic = require('express').static,
path = require('path'),
util = require('util'),
Promise = require('bluebird'),
errors = require('../errors'),
config = require('../config'),
errors = require('../errors'),
i18n = require('../i18n'),
utils = require('../utils'),
BaseStore = require('./base'),
remove = Promise.promisify(fs.remove);
@ -44,7 +45,6 @@ LocalFileStore.prototype.save = function (image, targetDir) {
return fullUrl;
}).catch(function (e) {
errors.logError(e);
return Promise.reject(e);
});
};
@ -100,7 +100,20 @@ LocalFileStore.prototype.serve = function (options) {
// CASE: serve images
// For some reason send divides the max age number by 1000
// Fallthrough: false ensures that if an image isn't found, it automatically 404s
return serveStatic(config.getContentPath('images'), {maxAge: utils.ONE_YEAR_MS, fallthrough: false});
// Wrap server static errors
return function serveStaticContent(req, res, next) {
return serveStatic(config.getContentPath('images'), {maxAge: utils.ONE_YEAR_MS, fallthrough: false})(req, res, function (err) {
if (err) {
if (err.statusCode === 404) {
return next(new errors.NotFoundError(i18n.t('errors.errors.pageNotFound')));
}
return next(err);
}
next();
});
};
}
};

View File

@ -28,10 +28,9 @@ var crypto = require('crypto'),
Promise = require('bluebird'),
_ = require('lodash'),
url = require('url'),
api = require('./api'),
config = require('./config'),
errors = require('./errors'),
logging = require('./logging'),
i18n = require('./i18n'),
internal = {context: {internal: true}},
allowedCheckEnvironments = ['development', 'production'],
@ -44,11 +43,9 @@ function updateCheckError(error) {
internal
);
errors.logError(
error,
i18n.t('errors.update-check.checkingForUpdatesFailed.error'),
i18n.t('errors.update-check.checkingForUpdatesFailed.help', {url: 'http://support.ghost.org'})
);
error.context = i18n.t('errors.update-check.checkingForUpdatesFailed.error');
error.help = i18n.t('errors.update-check.checkingForUpdatesFailed.help', {url: 'http://support.ghost.org'});
logging.error(error);
}
/**

View File

@ -58,6 +58,7 @@ describe('Authentication API', function () {
it('can\'t authenticate unknown user', function (done) {
request.post(testUtils.API.getApiQuery('authentication/token'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.send({
grant_type: 'password',
username: 'invalid@email.com',
@ -81,6 +82,7 @@ describe('Authentication API', function () {
it('can\'t authenticate invalid password user', function (done) {
request.post(testUtils.API.getApiQuery('authentication/token'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.send({
grant_type: 'password',
username: user.email,
@ -145,6 +147,7 @@ describe('Authentication API', function () {
it('can\'t request new access token with invalid refresh token', function (done) {
request.post(testUtils.API.getApiQuery('authentication/token'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.send({
grant_type: 'refresh_token',
refresh_token: 'invalid',

View File

@ -66,6 +66,7 @@ describe('DB API', function () {
it('import should fail without file', function (done) {
request.post(testUtils.API.getApiQuery('db/'))
.set('Authorization', 'Bearer ' + accesstoken)
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(403)
.end(function (err) {

View File

@ -30,6 +30,7 @@ describe('Unauthorized', function () {
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) {

View File

@ -325,6 +325,7 @@ describe('Post API', function () {
it('can\'t retrieve non existent post', function (done) {
request.get(testUtils.API.getApiQuery('posts/99/'))
.set('Authorization', 'Bearer ' + accesstoken)
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)
@ -345,6 +346,7 @@ describe('Post API', function () {
it('can\'t retrieve a draft post', function (done) {
request.get(testUtils.API.getApiQuery('posts/5/'))
.set('Authorization', 'Bearer ' + accesstoken)
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)
@ -365,6 +367,7 @@ describe('Post API', function () {
it('can\'t retrieve a draft page', function (done) {
request.get(testUtils.API.getApiQuery('posts/8/'))
.set('Authorization', 'Bearer ' + accesstoken)
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)
@ -929,6 +932,7 @@ describe('Post API', function () {
it('can\'t delete a non existent post', function (done) {
request.del(testUtils.API.getApiQuery('posts/99/'))
.set('Authorization', 'Bearer ' + accesstoken)
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)

View File

@ -160,6 +160,7 @@ describe('Public API', function () {
it('denies access with invalid client_secret', function (done) {
request.get(testUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=invalid_secret'))
.set('Origin', testUtils.API.getURL())
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(401)
@ -180,6 +181,7 @@ describe('Public API', function () {
it('denies access with invalid client_id', function (done) {
request.get(testUtils.API.getApiQuery('posts/?client_id=invalid-id&client_secret=not_available'))
.set('Origin', testUtils.API.getURL())
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(401)
@ -200,6 +202,7 @@ describe('Public API', function () {
it('does not send CORS headers on an invalid origin', function (done) {
request.get(testUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available'))
.set('Origin', 'http://invalid-origin')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
@ -218,6 +221,7 @@ describe('Public API', function () {
it('denies access to settings endpoint', function (done) {
request.get(testUtils.API.getApiQuery('settings/?client_id=ghost-admin&client_secret=not_available'))
.set('Origin', testUtils.API.getURL())
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403)
@ -238,6 +242,7 @@ describe('Public API', function () {
it('throws version mismatch error when request includes a version', function (done) {
request.get(testUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available'))
.set('Origin', testUtils.API.getURL())
.set('Accept', 'application/json')
.set('X-Ghost-Version', '0.3')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)

View File

@ -76,6 +76,7 @@ describe('Settings API', function () {
it('can\'t retrieve non existent setting', function (done) {
request.get(testUtils.API.getApiQuery('settings/testsetting/'))
.set('Authorization', 'Bearer ' + accesstoken)
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)
@ -138,6 +139,7 @@ describe('Settings API', function () {
it('can\'t edit settings with invalid accesstoken', function (done) {
request.get(testUtils.API.getApiQuery('settings/'))
.set('Authorization', 'Bearer ' + accesstoken)
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.end(function (err, res) {
@ -168,6 +170,7 @@ describe('Settings API', function () {
it('can\'t edit non existent setting', function (done) {
request.get(testUtils.API.getApiQuery('settings/'))
.set('Authorization', 'Bearer ' + accesstoken)
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.end(function (err, res) {

View File

@ -123,6 +123,7 @@ describe('Slug API', function () {
it('should not be able to get a slug for an unknown type', function (done) {
request.get(testUtils.API.getApiQuery('slugs/unknown/who knows/'))
.set('Authorization', 'Bearer ' + accesstoken)
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(400)

View File

@ -89,6 +89,7 @@ describe('Upload API', function () {
it('import should fail without file', function (done) {
request.post(testUtils.API.getApiQuery('uploads'))
.set('Authorization', 'Bearer ' + accesstoken)
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(403)
.end(function (err) {

View File

@ -276,6 +276,7 @@ describe('User API', function () {
it('can\'t retrieve non existent user by id', function (done) {
request.get(testUtils.API.getApiQuery('users/99/'))
.set('Authorization', 'Bearer ' + ownerAccessToken)
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)
@ -296,6 +297,7 @@ describe('User API', function () {
it('can\'t retrieve non existent user by slug', function (done) {
request.get(testUtils.API.getApiQuery('users/slug/blargh/'))
.set('Authorization', 'Bearer ' + ownerAccessToken)
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)

View File

@ -139,6 +139,7 @@ describe('Frontend Routing', function () {
it('should 404 for unknown frontend route', function (done) {
request.get('/spectacular/marvellous/')
.set('Accept', 'application/json')
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)
.expect(/Page not found/)

View File

@ -1,8 +1,6 @@
var testUtils = require('../../utils'),
should = require('should'),
_ = require('lodash'),
// Stuff we are testing
RoleAPI = require('../../../server/api/roles'),
context = testUtils.context;

View File

@ -2,10 +2,9 @@ var should = require('should'),
sinon = require('sinon'),
_ = require('lodash'),
Promise = require('bluebird'),
permissions = require('../../server/permissions'),
errors = require('../../server/errors'),
apiUtils = require('../../server/api/utils'),
sandbox = sinon.sandbox.create();
describe('API Utils', function () {
@ -485,7 +484,7 @@ describe('API Utils', function () {
it('should throw a permissions error if permission is not granted', function (done) {
var cTMethodStub = {
test: {
test: sandbox.stub().returns(Promise.reject())
test: sandbox.stub().returns(Promise.reject(new errors.NoPermissionError()))
}
},
cTStub = sandbox.stub(permissions, 'canThis').returns(cTMethodStub);
@ -497,7 +496,7 @@ describe('API Utils', function () {
cTMethodStub.test.test.calledOnce.should.eql(true);
err.errorType.should.eql('NoPermissionError');
done();
}).catch(done);
});
});
});
});

View File

@ -4,6 +4,7 @@ var sinon = require('sinon'),
rewire = require('rewire'),
errors = require('../../../server/errors'),
auth = rewire('../../../server/auth'),
logging = require('../../../server/logging'),
BearerStrategy = require('passport-http-bearer').Strategy,
ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy,
user = {id: 1},
@ -84,13 +85,13 @@ function registerFaultyClientPasswordStrategy() {
}
describe('Auth', function () {
var res, req, next, errorStub;
var res, req, next, loggingStub;
beforeEach(function () {
req = {};
res = {};
next = sandbox.spy();
errorStub = sandbox.stub(errors, 'logError');
loggingStub = sandbox.stub(logging, 'error');
});
afterEach(function () {
@ -110,18 +111,13 @@ describe('Auth', function () {
req.user = false;
res.status = {};
sandbox.stub(res, 'status', function (statusCode) {
statusCode.should.eql(403);
return {
json: function (err) {
err.errors[0].errorType.should.eql('NoPermissionError');
}
};
});
var next = function next(err) {
err.statusCode.should.eql(403);
(err instanceof errors.NoPermissionError).should.eql(true);
done();
};
auth.authorize.requiresAuthorizedUser(req, res, next);
next.called.should.be.false();
done();
});
describe('User Authentication', function () {
@ -154,40 +150,28 @@ describe('Auth', function () {
req.headers.authorization = 'Bearer ' + token;
res.status = {};
sandbox.stub(res, 'status', function (statusCode) {
statusCode.should.eql(401);
return {
json: function (err) {
err.errors[0].errorType.should.eql('UnauthorizedError');
}
};
});
var next = function next(err) {
err.statusCode.should.eql(401);
(err instanceof errors.UnauthorizedError).should.eql(true);
done();
};
registerUnsuccessfulBearerStrategy();
auth.authenticate.authenticateUser(req, res, next);
next.called.should.be.false();
done();
});
it('shouldn\'t authenticate without bearer token', function (done) {
req.headers = {};
res.status = {};
sandbox.stub(res, 'status', function (statusCode) {
statusCode.should.eql(401);
return {
json: function (err) {
err.errors[0].errorType.should.eql('UnauthorizedError');
}
};
});
var next = function next(err) {
err.statusCode.should.eql(401);
(err instanceof errors.UnauthorizedError).should.eql(true);
done();
};
registerUnsuccessfulBearerStrategy();
auth.authenticate.authenticateUser(req, res, next);
next.called.should.be.false();
done();
});
it('shouldn\'t authenticate with bearer token and client', function (done) {
@ -196,20 +180,14 @@ describe('Auth', function () {
req.client = {id: 1};
res.status = {};
sandbox.stub(res, 'status', function (statusCode) {
statusCode.should.eql(401);
return {
json: function (err) {
err.errors[0].errorType.should.eql('UnauthorizedError');
}
};
});
var next = function next(err) {
err.statusCode.should.eql(401);
(err instanceof errors.UnauthorizedError).should.eql(true);
done();
};
registerUnsuccessfulBearerStrategy();
auth.authenticate.authenticateUser(req, res, next);
next.called.should.be.false();
done();
});
it('shouldn\'t authenticate when error', function (done) {
@ -242,36 +220,26 @@ describe('Auth', function () {
req.headers.authorization = 'Bearer';
res.status = {};
sandbox.stub(res, 'status', function (statusCode) {
statusCode.should.eql(401);
return {
json: function (err) {
err.errors[0].errorType.should.eql('UnauthorizedError');
}
};
});
var next = function next(err) {
err.statusCode.should.eql(401);
(err instanceof errors.UnauthorizedError).should.eql(true);
done();
};
auth.authenticate.authenticateClient(req, res, next);
next.called.should.be.false();
done();
});
it('shouldn\'t authenticate client without client_id/client_secret', function (done) {
req.body = {};
res.status = {};
sandbox.stub(res, 'status', function (statusCode) {
statusCode.should.eql(401);
return {
json: function (err) {
err.errors[0].errorType.should.eql('UnauthorizedError');
}
};
});
var next = function next(err) {
err.statusCode.should.eql(401);
(err instanceof errors.UnauthorizedError).should.eql(true);
done();
};
auth.authenticate.authenticateClient(req, res, next);
next.called.should.be.false();
done();
});
it('shouldn\'t authenticate client without client_id', function (done) {
@ -279,18 +247,13 @@ describe('Auth', function () {
req.body.client_secret = testSecret;
res.status = {};
sandbox.stub(res, 'status', function (statusCode) {
statusCode.should.eql(401);
return {
json: function (err) {
err.errors[0].errorType.should.eql('UnauthorizedError');
}
};
});
var next = function next(err) {
err.statusCode.should.eql(401);
(err instanceof errors.UnauthorizedError).should.eql(true);
done();
};
auth.authenticate.authenticateClient(req, res, next);
next.called.should.be.false();
done();
});
it('shouldn\'t authenticate client without client_secret', function (done) {
@ -298,18 +261,13 @@ describe('Auth', function () {
req.body.client_id = testClient;
res.status = {};
sandbox.stub(res, 'status', function (statusCode) {
statusCode.should.eql(401);
return {
json: function (err) {
err.errors[0].errorType.should.eql('UnauthorizedError');
}
};
});
var next = function next(err) {
err.statusCode.should.eql(401);
(err instanceof errors.UnauthorizedError).should.eql(true);
done();
};
auth.authenticate.authenticateClient(req, res, next);
next.called.should.be.false();
done();
});
it('shouldn\'t authenticate without full client credentials', function (done) {
@ -317,22 +275,14 @@ describe('Auth', function () {
req.body.client_id = testClient;
res.status = {};
sandbox.stub(res, 'status', function (statusCode) {
statusCode.should.eql(401);
return {
json: function (err) {
err.errors[0].errorType.should.eql('UnauthorizedError');
}
};
});
var next = function next(err) {
err.statusCode.should.eql(401);
(err instanceof errors.UnauthorizedError).should.eql(true);
done();
};
registerUnsuccessfulClientPasswordStrategy();
auth.authenticate.authenticateClient(req, res, next);
next.called.should.be.false();
errorStub.calledTwice.should.be.true();
errorStub.getCall(0).args[1].should.eql('Client credentials were not provided');
done();
});
it('shouldn\'t authenticate invalid/unknown client', function (done) {
@ -341,22 +291,14 @@ describe('Auth', function () {
req.body.client_secret = testSecret;
res.status = {};
sandbox.stub(res, 'status', function (statusCode) {
statusCode.should.eql(401);
return {
json: function (err) {
err.errors[0].errorType.should.eql('UnauthorizedError');
}
};
});
var next = function next(err) {
err.statusCode.should.eql(401);
(err instanceof errors.UnauthorizedError).should.eql(true);
done();
};
registerUnsuccessfulClientPasswordStrategy();
auth.authenticate.authenticateClient(req, res, next);
next.called.should.be.false();
errorStub.calledTwice.should.be.true();
errorStub.getCall(0).args[1].should.eql('Client credentials were not valid');
done();
});
it('should authenticate valid/known client', function (done) {

View File

@ -1,499 +0,0 @@
var should = require('should'),
Promise = require('bluebird'),
sinon = require('sinon'),
express = require('express'),
rewire = require('rewire'),
// Stuff we are testing
chalk = require('chalk'),
errors = rewire('../../server/errors'),
configUtils = require('../utils/configUtils'),
// storing current environment
currentEnv = process.env.NODE_ENV;
describe('Error handling', function () {
// Just getting rid of jslint unused error
should.exist(errors);
describe('Throwing', function () {
it('throws error objects', function () {
var toThrow = new Error('test1'),
runThrowError = function () {
errors.throwError(toThrow);
};
runThrowError.should.throw('test1');
});
it('throws error strings', function () {
var toThrow = 'test2',
runThrowError = function () {
errors.throwError(toThrow);
};
runThrowError.should.throw('test2');
});
it('throws error even if nothing passed', function () {
var runThrowError = function () {
errors.throwError();
};
runThrowError.should.throw('An error occurred');
});
});
describe('Warn Logging', function () {
var logStub,
// Can't use afterEach here, because mocha uses console.log to output the checkboxes
// which we've just stubbed, so we need to restore it before the test ends to see ticks.
resetEnvironment = function () {
logStub.restore();
process.env.NODE_ENV = currentEnv;
};
beforeEach(function () {
logStub = sinon.stub(console, 'log');
process.env.NODE_ENV = 'development';
});
afterEach(function () {
logStub.restore();
});
it('logs default warn with no message supplied', function () {
errors.logWarn();
logStub.calledOnce.should.be.true();
logStub.calledWith(
chalk.yellow('\nWarning: no message supplied'), '\n');
// Future tests: This is important here!
resetEnvironment();
});
it('logs warn with only message', function () {
var errorText = 'Error1';
errors.logWarn(errorText);
logStub.calledOnce.should.be.true();
logStub.calledWith(chalk.yellow('\nWarning: ' + errorText), '\n');
// Future tests: This is important here!
resetEnvironment();
});
it('logs warn with message and context', function () {
var errorText = 'Error1',
contextText = 'Context1';
errors.logWarn(errorText, contextText);
logStub.calledOnce.should.be.true();
logStub.calledWith(
chalk.yellow('\nWarning: ' + errorText), '\n', chalk.white(contextText), '\n'
);
// Future tests: This is important here!
resetEnvironment();
});
it('logs warn with message and context and help', function () {
var errorText = 'Error1',
contextText = 'Context1',
helpText = 'Help1';
errors.logWarn(errorText, contextText, helpText);
logStub.calledOnce.should.be.true();
logStub.calledWith(
chalk.yellow('\nWarning: ' + errorText), '\n', chalk.white(contextText), '\n', chalk.green(helpText), '\n'
);
// Future tests: This is important here!
resetEnvironment();
});
});
describe('Error Logging', function () {
var logStub;
beforeEach(function () {
logStub = sinon.stub(console, 'error');
// give environment a value that will console log
process.env.NODE_ENV = 'development';
});
afterEach(function () {
logStub.restore();
// reset the environment
process.env.NODE_ENV = currentEnv;
});
it('logs errors from error objects', function () {
var err = new Error('test1');
errors.logError(err);
// Calls log with message on Error objects
logStub.calledOnce.should.be.true();
logStub.calledWith(chalk.red('\nERROR:', err.message), '\n', '\n', err.stack, '\n').should.be.true();
});
it('logs errors from strings', function () {
var err = 'test2';
errors.logError(err);
// Calls log with string on strings
logStub.calledOnce.should.be.true();
logStub.calledWith(chalk.red('\nERROR:', err), '\n').should.be.true();
});
it('logs errors from an error object and two string arguments', function () {
var err = new Error('test1'),
message = 'Testing';
errors.logError(err, message, message);
// Calls log with message on Error objects
logStub.calledOnce.should.be.true();
logStub.calledWith(
chalk.red('\nERROR:', err.message), '\n', chalk.white(message), '\n', chalk.green(message), '\n', err.stack, '\n'
);
});
it('logs errors from three string arguments', function () {
var message = 'Testing';
errors.logError(message, message, message);
// Calls log with message on Error objects
logStub.calledOnce.should.be.true();
logStub.calledWith(
chalk.red('\nERROR:', message), '\n', chalk.white(message), '\n', chalk.green(message), '\n'
).should.be.true();
});
it('logs errors from an undefined error argument', function () {
var message = 'Testing';
errors.logError(undefined, message, message);
// Calls log with message on Error objects
logStub.calledOnce.should.be.true();
logStub.calledWith(
chalk.red('\nERROR:', 'An unknown error occurred.'), '\n', chalk.white(message), '\n', chalk.green(message), '\n'
).should.be.true();
});
it('logs errors from an undefined context argument', function () {
var message = 'Testing';
errors.logError(message, undefined, message);
// Calls log with message on Error objects
logStub.calledOnce.should.be.true();
logStub.calledWith(chalk.red('\nERROR:', message), '\n', chalk.green(message), '\n').should.be.true();
});
it('logs errors from an undefined help argument', function () {
var message = 'Testing';
errors.logError(message, message, undefined);
// Calls log with message on Error objects
logStub.calledOnce.should.be.true();
logStub.calledWith(chalk.red('\nERROR:', message), '\n', chalk.white(message), '\n').should.be.true();
});
it('logs errors from a null error argument', function () {
var message = 'Testing';
errors.logError(null, message, message);
// Calls log with message on Error objects
logStub.calledOnce.should.be.true();
logStub.calledWith(
chalk.red('\nERROR:', 'An unknown error occurred.'), '\n', chalk.white(message), '\n', chalk.green(message), '\n'
).should.be.true();
});
it('logs errors from a null context argument', function () {
var message = 'Testing';
errors.logError(message, null, message);
// Calls log with message on Error objects
logStub.calledOnce.should.be.true();
logStub.firstCall.calledWith(chalk.red('\nERROR:', message), '\n', chalk.green(message), '\n').should.be.true();
});
it('logs errors from a null help argument', function () {
var message = 'Testing';
errors.logError(message, message, null);
// Calls log with message on Error objects
logStub.calledOnce.should.be.true();
logStub.firstCall.calledWith(chalk.red('\nERROR:', message), '\n', chalk.white(message), '\n').should.be.true();
});
it('logs promise errors and redirects', function (done) {
var req = null,
res = {
redirect: function () {
return;
}
},
redirectStub = sinon.stub(res, 'redirect');
// give environment a value that will console log
Promise.reject().then(function () {
throw new Error('Ran success handler');
}, errors.logErrorWithRedirect('test1', null, null, '/testurl', req, res));
Promise.reject().catch(function () {
logStub.calledWith(chalk.red('\nERROR:', 'test1')).should.equal(true);
logStub.restore();
redirectStub.calledWith('/testurl').should.equal(true);
redirectStub.restore();
done();
});
});
});
describe('API Error Handlers', function () {
var sandbox, req, res, next;
beforeEach(function () {
sandbox = sinon.sandbox.create();
req = {};
res = {};
res.json = sandbox.spy();
res.status = sandbox.stub().returns(res);
next = sandbox.spy();
});
afterEach(function () {
sandbox.restore();
});
it('handleAPIError: sends a JSON error response', function () {
errors.logError = sandbox.spy(errors, 'logError');
errors.formatHttpErrors = sandbox.spy(errors, 'formatHttpErrors');
var msg = 'Something got lost',
err = new errors.NotFoundError(msg);
errors.handleAPIError(err, req, res, next);
next.called.should.be.false();
errors.logError.calledOnce.should.be.true();
errors.formatHttpErrors.calledOnce.should.be.true();
res.status.calledWith(404).should.be.true();
res.json.calledOnce.should.be.true();
res.json.firstCall.args[0].errors[0].message.should.eql(msg);
res.json.firstCall.args[0].errors[0].errorType.should.eql('NotFoundError');
});
});
describe('Rendering', function () {
var app,
sandbox;
before(function () {
configUtils.set({
paths: {
themePath: '/content/themes',
availableThemes: {
casper: {
assets: null,
'default.hbs': '/content/themes/casper/default.hbs',
'index.hbs': '/content/themes/casper/index.hbs',
'page.hbs': '/content/themes/casper/page.hbs',
'tag.hbs': '/content/themes/casper/tag.hbs'
},
'theme-with-error': {
'error.hbs': ''
}
}
}
});
errors.updateActiveTheme('casper');
});
after(function () {
configUtils.restore();
});
beforeEach(function () {
app = express();
sandbox = sinon.sandbox.create();
});
afterEach(function () {
sandbox.restore();
});
it('Renders end-of-middleware 404 errors correctly', function (done) {
var req = {method: 'GET'},
res = app.response;
sandbox.stub(res, 'render', function (view, options/*, fn*/) {
view.should.match(/user-error\.hbs/);
// Test that the message is correct
options.message.should.equal('Page not found');
// Template variable
options.code.should.equal(404);
this.statusCode.should.equal(404);
done();
});
sandbox.stub(res, 'status', function (status) {
this.statusCode = status;
return this;
});
sandbox.stub(res, 'set', function (value) {
// Test that the headers are correct
value['Cache-Control'].should.eql('no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0');
return this;
});
errors.error404(req, res, done);
});
it('Renders thrown 404 errors correctly', function (done) {
var err = new Error('A thing was not found'),
req = {method: 'GET'},
res = app.response;
sandbox.stub(res, 'render', function (view, options/*, fn*/) {
view.should.match(/user-error\.hbs/);
// Test that the message is correct
options.message.should.equal('Page not found');
// Template variable
options.code.should.equal(404);
this.statusCode.should.equal(404);
done();
});
sandbox.stub(res, 'status', function (status) {
this.statusCode = status;
return this;
});
sandbox.stub(res, 'set', function (value) {
// Test that the headers are correct
value['Cache-Control'].should.eql('no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0');
return this;
});
err.status = 404;
errors.error500(err, req, res, null);
});
it('Renders thrown errors correctly', function (done) {
var err = new Error('I am a big bad error'),
req = {method: 'GET'},
res = app.response;
sandbox.stub(res, 'render', function (view, options/*, fn*/) {
view.should.match(/user-error\.hbs/);
// Test that the message is correct
options.message.should.equal('I am a big bad error');
// Template variable
options.code.should.equal(500);
this.statusCode.should.equal(500);
done();
});
sandbox.stub(res, 'set', function (value) {
// Test that the headers are correct
value['Cache-Control'].should.eql('no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0');
return this;
});
sandbox.stub(res, 'status', function (status) {
this.statusCode = status;
return this;
});
errors.error500(err, req, res, null);
});
it('Renders 500 errors correctly', function (done) {
var err = new Error('I am a big bad error'),
req = {method: 'GET'},
res = app.response;
sandbox.stub(res, 'render', function (view, options/*, fn*/) {
view.should.match(/user-error\.hbs/);
// Test that the message is correct
options.message.should.equal('I am a big bad error');
// Template variable
options.code.should.equal(500);
this.statusCode.should.equal(500);
done();
});
sandbox.stub(res, 'status', function (status) {
this.statusCode = status;
return this;
});
sandbox.stub(res, 'set', function (value) {
// Test that the headers are correct
value['Cache-Control'].should.eql('no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0');
return this;
});
err.statusCode = 500;
errors.error500(err, req, res, null);
});
it('Renders custom error template if one exists', function (done) {
var statusCode = 404,
error = {message: 'Custom view test'},
req = {
session: null
},
res = {
status: function (statusCode) {
/*jshint unused:false*/
return this;
},
render: function (view, model, fn) {
/*jshint unused:false*/
view.should.eql('error');
errors.updateActiveTheme('casper');
done();
}
},
next = null;
errors.updateActiveTheme('theme-with-error');
errors.renderErrorPage(statusCode, error, req, res, next);
});
});
});

View File

@ -82,15 +82,15 @@ describe('Exporter', function () {
it('should catch and log any errors', function (done) {
// Setup for failure
var errorStub = sandbox.stub(errors, 'logAndThrowError');
queryMock.select.returns(new Promise.reject({}));
// Execute
exporter.doExport().then(function (exportData) {
should.not.exist(exportData);
errorStub.calledOnce.should.be.true();
exporter.doExport().then(function () {
done(new Error('expected error on export data'));
}).catch(function (err) {
(err instanceof errors.InternalServerError).should.eql(true);
done();
}).catch(done);
});
});
});

View File

@ -2,14 +2,11 @@ var sinon = require('sinon'),
should = require('should'),
express = require('express'),
Promise = require('bluebird'),
// Stuff we test
fs = require('fs'),
hbs = require('express-hbs'),
themeHandler = require('../../../server/middleware/theme-handler'),
errors = require('../../../server/errors'),
logging = require('../../../server/logging'),
api = require('../../../server/api'),
configUtils = require('../../utils/configUtils'),
sandbox = sinon.sandbox.create();
@ -45,15 +42,13 @@ describe('Theme Handler', function () {
describe('activateTheme', function () {
it('should activate new theme with partials', function () {
var errorStub = sandbox.stub(errors, 'updateActiveTheme'),
fsStub = sandbox.stub(fs, 'stat', function (path, cb) {
var fsStub = sandbox.stub(fs, 'stat', function (path, cb) {
cb(null, {isDirectory: function () { return true; }});
}),
hbsStub = sandbox.spy(hbs, 'express3');
themeHandler.activateTheme(blogApp, 'casper');
errorStub.calledWith('casper').should.be.true();
fsStub.calledOnce.should.be.true();
hbsStub.calledOnce.should.be.true();
hbsStub.firstCall.args[0].should.be.an.Object().and.have.property('partialsDir');
@ -62,15 +57,13 @@ describe('Theme Handler', function () {
});
it('should activate new theme without partials', function () {
var errorStub = sandbox.stub(errors, 'updateActiveTheme'),
fsStub = sandbox.stub(fs, 'stat', function (path, cb) {
var fsStub = sandbox.stub(fs, 'stat', function (path, cb) {
cb(null, null);
}),
hbsStub = sandbox.spy(hbs, 'express3');
themeHandler.activateTheme(blogApp, 'casper');
errorStub.calledWith('casper').should.be.true();
fsStub.calledOnce.should.be.true();
hbsStub.calledOnce.should.be.true();
hbsStub.firstCall.args[0].should.be.an.Object().and.have.property('partialsDir');
@ -162,8 +155,7 @@ describe('Theme Handler', function () {
});
it('throws error if theme is missing', function (done) {
var errorSpy = sandbox.spy(errors, 'throwError'),
activateThemeSpy = sandbox.spy(themeHandler, 'activateTheme');
var activateThemeSpy = sandbox.spy(themeHandler, 'activateTheme');
sandbox.stub(api.settings, 'read').withArgs(sandbox.match.has('key', 'activeTheme')).returns(Promise.resolve({
settings: [{
@ -171,12 +163,12 @@ describe('Theme Handler', function () {
value: 'rasper'
}]
}));
blogApp.set('activeTheme', 'not-casper');
configUtils.set({paths: {availableThemes: {casper: {}}}});
themeHandler.updateActiveTheme(req, res, function (err) {
should.exist(err);
errorSpy.called.should.be.true();
activateThemeSpy.called.should.be.false();
err.message.should.eql('The currently active theme "rasper" is missing.');
done();
@ -184,8 +176,7 @@ describe('Theme Handler', function () {
});
it('throws only warns if theme is missing for admin req', function (done) {
var errorSpy = sandbox.spy(errors, 'throwError'),
warnSpy = sandbox.spy(errors, 'logWarn'),
var warnSpy = sandbox.spy(logging, 'warn'),
activateThemeSpy = sandbox.spy(themeHandler, 'activateTheme');
sandbox.stub(api.settings, 'read').withArgs(sandbox.match.has('key', 'activeTheme')).returns(Promise.resolve({
@ -199,7 +190,6 @@ describe('Theme Handler', function () {
configUtils.set({paths: {availableThemes: {casper: {}}}});
themeHandler.updateActiveTheme(req, res, function () {
errorSpy.called.should.be.false();
activateThemeSpy.called.should.be.false();
warnSpy.called.should.be.true();
warnSpy.calledWith('The currently active theme "rasper" is missing.').should.be.true();

View File

@ -3,13 +3,9 @@ var testUtils = require('../utils'),
sinon = require('sinon'),
Promise = require('bluebird'),
_ = require('lodash'),
// Stuff we are testing
Models = require('../../server/models'),
errors = require('../../server/errors'),
permissions = require('../../server/permissions'),
// effectivePerms = require('../../server/permissions/effective'),
// context = testUtils.context.owner,
sandbox = sinon.sandbox.create();
describe('Permissions', function () {
@ -174,7 +170,7 @@ describe('Permissions', function () {
permissions.applyPublicRules('posts', 'read', _.cloneDeep(draft)).then(function () {
done('Did not throw an error for draft');
}).catch(function (err) {
err.should.be.a.String();
(err instanceof errors.NoPermissionError).should.eql(true);
done();
});
});
@ -185,7 +181,7 @@ describe('Permissions', function () {
permissions.applyPublicRules('posts', 'browse', _.cloneDeep(draft)).then(function () {
done('Did not throw an error for draft');
}).catch(function (err) {
err.should.be.a.String();
(err instanceof errors.NoPermissionError).should.eql(true);
done();
});
});
@ -214,7 +210,7 @@ describe('Permissions', function () {
permissions.applyPublicRules('posts', 'browse', _.cloneDeep(draft)).then(function () {
done('Did not throw an error for draft');
}).catch(function (err) {
err.should.be.a.String();
(err instanceof errors.NoPermissionError).should.eql(true);
done();
});
});
@ -225,7 +221,7 @@ describe('Permissions', function () {
permissions.applyPublicRules('posts', 'browse', _.cloneDeep(draft)).then(function () {
done('Did not throw an error for draft');
}).catch(function (err) {
err.should.be.a.String();
(err instanceof errors.NoPermissionError).should.eql(true);
done();
});
});
@ -236,13 +232,14 @@ describe('Permissions', function () {
permissions.applyPublicRules('posts', 'read', _.cloneDeep(draft)).then(function () {
done('Did not throw an error for draft');
}).catch(function (err) {
err.should.be.a.String();
(err instanceof errors.NoPermissionError).should.eql(true);
draft = {context: {}, data: {status: 'draft', uuid: '1234-abcd', slug: 'abcd'}};
return permissions.applyPublicRules('posts', 'read', _.cloneDeep(draft)).then(function () {
done('Did not throw an error for draft');
}).catch(function (err) {
err.should.be.a.String();
(err instanceof errors.NoPermissionError).should.eql(true);
done();
});
});
@ -275,7 +272,7 @@ describe('Permissions', function () {
permissions.applyPublicRules('users', 'browse', _.cloneDeep(inactive)).then(function () {
done('Did not throw an error for inactive');
}).catch(function (err) {
err.should.be.a.String();
(err instanceof errors.NoPermissionError).should.eql(true);
done();
});
});

View File

@ -2,11 +2,9 @@ var should = require('should'),
sinon = require('sinon'),
hbs = require('express-hbs'),
utils = require('./utils'),
// Stuff we are testing
handlebars = hbs.handlebars,
helpers = require('../../../server/helpers'),
errors = require('../../../server/errors');
logging = require('../../../server/logging');
describe('{{#is}} helper', function () {
before(function () {
@ -63,7 +61,7 @@ describe('{{#is}} helper', function () {
it('should log warning with no args', function () {
var fn = sinon.spy(),
inverse = sinon.spy(),
logWarn = sinon.stub(errors, 'logWarn');
logWarn = sinon.stub(logging, 'warn');
helpers.is.call(
{},

View File

@ -108,12 +108,12 @@ describe('XMLRPC', function () {
var ping1 = nock('http://blogsearch.google.com').post('/ping/RPC2').reply(200),
ping2 = nock('http://rpc.pingomatic.com').post('/').replyWithError('ping site is down'),
testPost = _.clone(testUtils.DataGenerator.Content.posts[2]),
errorMock, resetXmlRpc;
loggingMock, resetXmlRpc;
errorMock = {
logError: function logError(error) {
should.exist(error);
error.message.should.eql('ping site is down');
loggingMock = {
error: function onError(err) {
should.exist(err);
err.message.should.eql('ping site is down');
// Reset xmlrpc handleError method and exit test
resetXmlRpc();
@ -121,7 +121,7 @@ describe('XMLRPC', function () {
}
};
resetXmlRpc = xmlrpc.__set__('errors', errorMock);
resetXmlRpc = xmlrpc.__set__('logging', loggingMock);
ping(testPost);

View File

@ -55,7 +55,11 @@ function forkGhost(newConfig, envName) {
newConfig.url = url.format(_.extend({}, url.parse(config.get('url')), {port: newConfig.server.port, host: null}));
}
newConfig.logging = false;
newConfig.logging = {
level: 'fatal',
transports: ['stdout'],
rotation: false
};
var newConfigFile = path.join(config.get('paths').appRoot, 'config.test.' + envName + '.json');

View File

@ -3,7 +3,7 @@
var ghost = require('./core'),
debug = require('debug')('ghost:boot:index'),
express = require('express'),
errors = require('./core/server/errors'),
logging = require('./core/server/logging'),
utils = require('./core/server/utils'),
parentApp = express();
@ -16,5 +16,6 @@ ghost().then(function (ghostServer) {
// Let Ghost handle starting our server instance.
ghostServer.start(parentApp);
}).catch(function (err) {
errors.logErrorAndExit(err, err.context, err.help);
logging.error(err);
process.exit(0);
});

View File

@ -31,6 +31,7 @@
"bluebird": "3.4.6",
"body-parser": "1.15.2",
"bookshelf": "0.10.1",
"bunyan": "1.8.1",
"chalk": "1.1.3",
"cheerio": "0.22.0",
"compression": "1.6.2",
@ -57,7 +58,6 @@
"mobiledoc-html-renderer": "0.3.0",
"moment": "2.15.1",
"moment-timezone": "0.5.5",
"morgan": "1.7.0",
"multer": "1.2.0",
"nconf": "0.8.4",
"netjet": "1.1.3",
@ -69,6 +69,7 @@
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"path-match": "1.2.4",
"prettyjson": "1.1.3",
"rss": "1.2.1",
"sanitize-html": "1.13.0",
"semver": "5.3.0",