Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

koa-better-error-handler

Package Overview
Dependencies
Maintainers
1
Versions
46
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

koa-better-error-handler - npm Package Compare versions

Comparing version 5.0.0 to 6.0.0

291

index.js
const fs = require('fs');
const path = require('path');
const fastSafeStringify = require('fast-safe-stringify');
const Boom = require('@hapi/boom');
const Debug = require('debug');
const camelCase = require('camelcase');

@@ -32,4 +32,2 @@ const capitalize = require('capitalize');

const debug = new Debug('koa-better-error-handler');
const passportLocalMongooseErrorNames = [

@@ -55,175 +53,180 @@ 'AuthenticationError',

// eslint-disable-next-line complexity
async function errorHandler(err) {
if (!err) return;
function errorHandler(
cookiesKey = false,
_logger = console,
useCtxLogger = true, // useful if you have ctx.logger (e.g. you're using Cabin's middleware)
stringify = fastSafeStringify // you could alternatively use JSON.stringify
) {
// eslint-disable-next-line complexity
return async function (err) {
if (!err) return;
if (!_isError(err)) err = new Error(err);
const logger = useCtxLogger && this.logger ? this.logger : _logger;
const type = this.accepts(['text', 'json', 'html']);
if (!_isError(err)) err = new Error(err);
if (!type) {
debug('invalid type, sending 406 error');
err.status = 406;
err.message = Boom.notAcceptable().output.payload;
}
const type = this.accepts(['text', 'json', 'html']);
// parse mongoose validation errors
err = parseValidationError(this, err);
if (!type) {
logger.warn('invalid type, sending 406 error');
err.status = 406;
err.message = Boom.notAcceptable().output.payload;
}
// check if we threw just a status code in order to keep it simple
const val = parseInt(err.message, 10);
if (_isNumber(val) && val >= 400)
err = Boom[camelCase(toIdentifier(statuses.message[val]))]();
// parse mongoose validation errors
err = parseValidationError(this, err);
// check if we have a boom error that specified
// a status code already for us (and then use it)
if (_isObject(err.output) && _isNumber(err.output.statusCode))
err.status = err.output.statusCode;
// check if we threw just a status code in order to keep it simple
const val = parseInt(err.message, 10);
if (_isNumber(val) && val >= 400)
err = Boom[camelCase(toIdentifier(statuses.message[val]))]();
if (!_isNumber(err.status)) err.status = 500;
// check if we have a boom error that specified
// a status code already for us (and then use it)
if (_isObject(err.output) && _isNumber(err.output.statusCode))
err.status = err.output.statusCode;
// check if there is flash messaging
const hasFlash = _isFunction(this.flash);
debug('hasFlash', hasFlash);
if (!_isNumber(err.status)) err.status = 500;
// check if there is a view rendering engine binding `this.render`
const hasRender = _isFunction(this.render);
debug('hasRender', hasRender);
// check if there is flash messaging
const hasFlash = _isFunction(this.flash);
// check if we're about to go into a possible endless redirect loop
const noReferrer = this.get('Referrer') === '';
// check if there is a view rendering engine binding `this.render`
const hasRender = _isFunction(this.render);
// nothing we can do here other
// than delegate to the app-level
// handler and log.
if (this.headerSent || !this.writable) {
debug('headers were already sent, returning early');
err.headerSent = true;
return;
}
// check if we're about to go into a possible endless redirect loop
const noReferrer = this.get('Referrer') === '';
// populate the status and body with `boom` error message payload
// (e.g. you can do `ctx.throw(404)` and it will output a beautiful err obj)
err.status = err.status || 500;
err.statusCode = err.status;
this.statusCode = err.statusCode;
this.status = this.statusCode;
// nothing we can do here other
// than delegate to the app-level
// handler and log.
if (this.headerSent || !this.writable) {
logger.error(new Error('Headers were already sent, returning early'));
err.headerSent = true;
return;
}
const friendlyAPIMessage = makeAPIFriendly(this, err.message);
// populate the status and body with `boom` error message payload
// (e.g. you can do `ctx.throw(404)` and it will output a beautiful err obj)
err.status = err.status || 500;
err.statusCode = err.status;
this.statusCode = err.statusCode;
this.status = this.statusCode;
this.body = new Boom.Boom(friendlyAPIMessage, {
statusCode: err.status
}).output.payload;
const friendlyAPIMessage = makeAPIFriendly(this, err.message);
// set any additional error headers specified
// (e.g. for BasicAuth we use `basic-auth` which specifies WWW-Authenticate)
if (_isObject(err.headers) && Object.keys(err.headers).length > 0)
this.set(err.headers);
this.body = new Boom.Boom(friendlyAPIMessage, {
statusCode: err.status
}).output.payload;
debug('status code was %d', this.status);
// set any additional error headers specified
// (e.g. for BasicAuth we use `basic-auth` which specifies WWW-Authenticate)
if (_isObject(err.headers) && Object.keys(err.headers).length > 0)
this.set(err.headers);
this.app.emit('error', err, this);
this.app.emit('error', err, this);
// fix page title and description
if (!this.api) {
this.state.meta = this.state.meta || {};
this.state.meta.title = this.body.error;
this.state.meta.description = err.message;
debug('set `this.state.meta.title` to %s', this.state.meta.title);
debug('set `this.state.meta.desc` to %s', this.state.meta.description);
}
// fix page title and description
if (!this.api) {
this.state.meta = this.state.meta || {};
this.state.meta.title = this.body.error;
this.state.meta.description = err.message;
}
debug('type was %s', type);
switch (type) {
case 'html':
this.type = 'html';
switch (type) {
case 'html':
this.type = 'html';
if (this.status === 404) {
// render the 404 page
// https://github.com/koajs/koa/issues/646
if (hasRender) {
try {
debug('rendering 404 page');
await this.render('404');
} catch (err_) {
debug('could not find 404 page, using built-in 404 html', err_);
if (this.status === 404) {
// render the 404 page
// https://github.com/koajs/koa/issues/646
if (hasRender) {
try {
await this.render('404');
} catch (err_) {
logger.error(err_);
this.body = _404;
}
} else {
this.body = _404;
}
} else {
this.body = _404;
}
} else if (noReferrer || this.status === 500) {
// this prevents a redirect loop by detecting an empty Referrer
// ...otherwise it would reach the next conditional block which
// would endlessly rediret the user with `this.redirect('back')`
if (noReferrer) debug('prevented endless redirect loop!');
} else if (noReferrer || this.status === 500) {
// this prevents a redirect loop by detecting an empty Referrer
// ...otherwise it would reach the next conditional block which
// would endlessly rediret the user with `this.redirect('back')`
// flash an error message
if (hasFlash) this.flash('error', err.message);
// flash an error message
if (hasFlash) this.flash('error', err.message);
// render the 500 page
if (hasRender) {
try {
debug('rendering 500 page');
await this.render('500');
} catch (err_) {
debug('could not find 500 page, using built-in 500 html', err_);
// render the 500 page
if (hasRender) {
try {
await this.render('500');
} catch (err_) {
logger.error(err_);
this.body = _500;
}
} else {
this.body = _500;
}
} else {
this.body = _500;
}
} else {
// flash an error message
if (hasFlash) this.flash('error', err.message);
// flash an error message
if (hasFlash) this.flash('error', err.message);
// TODO: until the issue is resolved, we need to add this here
// <https://github.com/koajs/generic-session/pull/95#issuecomment-246308544>
if (
this.sessionStore &&
this.sessionId &&
this.session &&
this.state.cookiesKey
) {
await co
.wrap(this.sessionStore.set)
.call(this.sessionStore, this.sessionId, this.session);
this.cookies.set(
this.state.cookiesKey,
this.sessionId,
this.session.cookie
// TODO: until the issue is resolved, we need to add this here
// <https://github.com/koajs/generic-session/pull/95#issuecomment-246308544>
if (
this.sessionStore &&
this.sessionId &&
this.session &&
cookiesKey
) {
try {
await co
.wrap(this.sessionStore.set)
.call(this.sessionStore, this.sessionId, this.session);
this.cookies.set(cookiesKey, this.sessionId, this.session.cookie);
} catch (err) {
logger.error(err);
// eslint-disable-next-line max-depth
if (err.code === 'ERR_HTTP_HEADERS_SENT') return;
}
}
/*
// TODO: we need to add support for `koa-session-store` here
// <https://github.com/koajs/generic-session/pull/95#issuecomment-246308544>
//
// these comments may no longer be valid and need reconsidered:
//
// if we're using `koa-session-store` we need to add
// `this._session = new Session()`, and then run this:
await co.wrap(this._session._store.save).call(
this._session._store,
this._session._sid,
stringify(this.session)
);
this.cookies.set(this._session._name, stringify({
_sid: this._session._sid
}), this._session._cookieOpts);
*/
// redirect the user to the page they were just on
this.redirect('back');
}
/*
// if we're using `koa-session-store` we need to add
// `this._session = new Session()`, and then run this:
await co.wrap(this._session._store.save).call(
this._session._store,
this._session._sid,
JSON.stringify(this.session)
);
this.cookies.set(this._session._name, JSON.stringify({
_sid: this._session._sid
}), this._session._cookieOpts);
*/
break;
case 'json':
this.type = 'json';
this.body = stringify(this.body, null, 2);
break;
default:
this.type = this.api ? 'json' : 'text';
this.body = stringify(this.body, null, 2);
break;
}
// redirect the user to the page they were just on
this.redirect('back');
}
break;
case 'json':
this.type = 'json';
this.body = JSON.stringify(this.body, null, 2);
break;
default:
this.type = this.api ? 'json' : 'text';
this.body = JSON.stringify(this.body, null, 2);
break;
}
this.length = Buffer.byteLength(this.body);
this.res.end(this.body);
this.length = Buffer.byteLength(this.body);
this.res.end(this.body);
};
}

@@ -230,0 +233,0 @@

{
"name": "koa-better-error-handler",
"description": "A better error-handler for Lad and Koa. Makes `ctx.throw` awesome (best used with koa-404-handler)",
"version": "5.0.0",
"version": "6.0.0",
"author": {

@@ -43,3 +43,3 @@ "name": "Nick Baugh",

"co": "^4.6.0",
"debug": "^4.1.1",
"fast-safe-stringify": "^2.0.7",
"html-to-text": "^5.1.1",

@@ -58,7 +58,7 @@ "humanize-string": "^2.1.0",

"devDependencies": {
"@commitlint/cli": "^9.0.1",
"@commitlint/config-conventional": "^9.0.1",
"@koa/router": "^9.3.1",
"ava": "3.8.2",
"codecov": "^3.7.0",
"@commitlint/cli": "^9.1.2",
"@commitlint/config-conventional": "^9.1.2",
"@koa/router": "^9.4.0",
"ava": "3.11.1",
"codecov": "^3.7.2",
"cross-env": "^7.0.2",

@@ -76,12 +76,13 @@ "eslint-config-xo-lass": "^1.0.3",

"lint-staged": "^10.2.11",
"lodash": "^4.17.15",
"lodash": "^4.17.20",
"nyc": "^15.1.0",
"redis": "^3.0.2",
"remark-cli": "^8.0.0",
"remark-preset-github": "^1.0.1",
"remark-cli": "^8.0.1",
"remark-preset-github": "^3.0.0",
"rimraf": "^3.0.2",
"supertest": "^4.0.2",
"xo": "^0.32.1"
"xo": "^0.33.0"
},
"engines": {
"node": ">= 12"
"node": ">= 10.14"
},

@@ -146,8 +147,10 @@ "homepage": "https://github.com/ladjs/koa-better-error-handler",

"scripts": {
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
"coverage": "nyc report --reporter=text-lcov > coverage.lcov",
"lint": "npm run lint:js",
"lint:js": "xo",
"lint:md": "remark . -qfo",
"postcoverage": "codecov",
"precommit": "lint-staged && npm tes-ci",
"pretest-ci": "npm run lint",
"pretest-cov": "rimraf .nyc_output",
"test": "ava",

@@ -154,0 +157,0 @@ "test-ci": "npm run test-cov",

@@ -49,2 +49,15 @@ # koa-better-error-handler

The package exports a function which accepts four arguments (in order):
* `cookiesKey` - defaults to `false`
* `logger` - defaults to `console`
* `useCtxLogger` - defaults to `true`
* `stringify` - defaults to `fast-safe-stringify` (you can also use `JSON.stringify` or another option here if preferred)
If you pass a `cookiesKey` then support for sessions will be added. You should always set this argument's value if you are using cookies and sessions (e.g. web server).
We recommend to use [Cabin][] for your `logger` and also you should use its middleware too, as it will auto-populate `ctx.logger` for you to make context-based logs easy.
Note that this package only supports `koa-generic-session`, and does not yet support `koa-session-store` (see the code in [index.js](index.js) for more insight, pull requests are welcome).
### API

@@ -64,3 +77,3 @@

// override koa's undocumented error handler
app.context.onerror = errorHandler;
app.context.onerror = errorHandler();

@@ -120,5 +133,7 @@ // specify that this is our api

// add sessions to our app
const cookiesKey = 'lad.sid';
app.use(
convert(
session({
key: cookiesKey,
store: redisStore

@@ -133,3 +148,3 @@ })

// override koa's undocumented error handler
app.context.onerror = errorHandler;
app.context.onerror = errorHandler({ cookiesKey });

@@ -251,1 +266,3 @@ // use koa-404-handler

[koa-404-handler]: https://github.com/ladjs/koa-404-handler
[cabin]: https://cabinjs.com

@@ -13,6 +13,6 @@ const http = require('http');

const statusCodes = _.keys(http.STATUS_CODES)
.map(code => {
.map((code) => {
return parseInt(code, 10);
})
.filter(code => code >= 400);
.filter((code) => code >= 400);

@@ -24,3 +24,3 @@ // this doesn't ensure 100% code coverage, but ensures that

test.beforeEach(t => {
test.beforeEach((t) => {
// initialize our app

@@ -30,3 +30,3 @@ t.context.app = new Koa();

// override koa's undocumented error handler
t.context.app.context.onerror = errorHandler;
t.context.app.context.onerror = errorHandler();

@@ -37,11 +37,11 @@ // set up some routes

// throw an error anywhere you want!
_.each(statusCodes, code => {
router.get(`/${code}`, ctx => ctx.throw(code));
_.each(statusCodes, (code) => {
router.get(`/${code}`, (ctx) => ctx.throw(code));
});
router.get('/basic-auth', auth({ name: 'tj', pass: 'tobi' }), ctx => {
router.get('/basic-auth', auth({ name: 'tj', pass: 'tobi' }), (ctx) => {
ctx.body = 'Hello World';
});
router.get('/break-headers-sent', ctx => {
router.get('/break-headers-sent', (ctx) => {
ctx.type = 'text/html';

@@ -61,5 +61,5 @@ ctx.body = 'foo';

// check for response types
_.each(['text/html', 'application/json', 'text/plain'], type => {
_.each(statusCodes, code => {
test.cb(`responds with ${type} for ${code} request`, t => {
_.each(['text/html', 'application/json', 'text/plain'], (type) => {
_.each(statusCodes, (code) => {
test.cb(`responds with ${type} for ${code} request`, (t) => {
request(t.context.app.listen())

@@ -75,3 +75,3 @@ .get(`/${code}`)

test.cb("Won't throw after sending headers", t => {
test.cb("Won't throw after sending headers", (t) => {
request(t.context.app.listen())

@@ -84,3 +84,3 @@ .get('/break-headers-sent')

test.cb('Throws with WWW-Authenticate header on basic auth fail', t => {
test.cb('Throws with WWW-Authenticate header on basic auth fail', (t) => {
request(t.context.app.listen())

@@ -87,0 +87,0 @@ .get('/basic-auth')

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc