From 142656222bc2c23bf4ab5d51e256909688745070 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 6 Sep 2023 17:53:20 +0530 Subject: [PATCH] feat: add support for CSP nonce --- README.md | 12 ++++++++++++ src/Youch.js | 37 +++++++++++++++++++++++-------------- src/error.compiled.mustache | 10 ++++++++++ static/error.mustache | 10 ++++++++++ test/youch.spec.js | 25 +++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 4eafc55..49e46bc 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,18 @@ await youch .toHTML() ``` +## Adding CSP nonce +Youch HTML output outputs inline `style` and `script` tags and therefore you will have add `nonce` attribute to them when you have enabled CSP on your website. + +You can pass the `cspNonce` property to the `toHTML` method at the time of rendering the error to an HTML output. + +```js +const youch = new Youch(error, req) +const html = await youch.toHTML({ + cspNonce: 'nonce-value' +}) +``` + ## Get stack as JSON You can also the error stack frames as JSON by calling the `.toJSON` method. diff --git a/src/Youch.js b/src/Youch.js index 21ea07a..6085289 100644 --- a/src/Youch.js +++ b/src/Youch.js @@ -259,27 +259,32 @@ class Youch { */ _serializeRequest () { const headers = [] + const cookies = [] - Object.keys(this.request.headers).forEach((key) => { - if (this._filterHeaders.indexOf(key) > -1) { - return - } - headers.push({ - key: key.toUpperCase(), - value: this.request.headers[key] + if (this.request.headers) { + Object.keys(this.request.headers).forEach((key) => { + if (this._filterHeaders.indexOf(key) > -1) { + return + } + headers.push({ + key: key.toUpperCase(), + value: this.request.headers[key] + }) }) - }) - const parsedCookies = cookie.parse(this.request.headers.cookie || '') - const cookies = Object.keys(parsedCookies).map((key) => { - return { key, value: parsedCookies[key] } - }) + if (this.request.headers.cookie) { + const parsedCookies = cookie.parse(this.request.headers.cookie || '') + Object.keys(parsedCookies).forEach((key) => { + cookies.push({ key, value: parsedCookies[key] }) + }) + } + } return { url: this.request.url, httpVersion: this.request.httpVersion, method: this.request.method, - connection: this.request.headers.connection, + connection: this.request.headers ? this.request.headers.connection : null, headers, cookies } @@ -335,7 +340,7 @@ class Youch { * * @return {Promise} */ - toHTML () { + toHTML (templateState) { return new Promise((resolve, reject) => { this._parseError() .then((stack) => { @@ -348,6 +353,10 @@ class Youch { return serializedFrame }) + if (templateState) { + Object.assign(data, templateState) + } + if (this.request) { data.request = this._serializeRequest() } diff --git a/src/error.compiled.mustache b/src/error.compiled.mustache index 70b01d7..952db8b 100644 --- a/src/error.compiled.mustache +++ b/src/error.compiled.mustache @@ -9,7 +9,12 @@ {{/loadFA}} + {{#cspNonce}} + @@ -119,7 +124,12 @@ {{/request}} + {{#cspNonce}} + diff --git a/test/youch.spec.js b/test/youch.spec.js index 2c70bc5..27a130c 100644 --- a/test/youch.spec.js +++ b/test/youch.spec.js @@ -241,4 +241,29 @@ test.group('Youch', () => { done() }).catch(done) }) + + test('convert error to HTML', (assert, done) => { + const error = new Error('foo') + const youch = new Youch(error, {}) + youch + .toHTML() + .then((html) => { + assert.isTrue(html.startsWith('')) + assert.isFalse(html.includes('