Skip to content

Commit

Permalink
feat: add support for CSP nonce
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Sep 6, 2023
1 parent 84f6eed commit 1426562
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 14 deletions.
12 changes: 12 additions & 0 deletions README.md
Expand Up @@ -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.

Expand Down
37 changes: 23 additions & 14 deletions src/Youch.js
Expand Up @@ -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
}
Expand Down Expand Up @@ -335,7 +340,7 @@ class Youch {
*
* @return {Promise}
*/
toHTML () {
toHTML (templateState) {
return new Promise((resolve, reject) => {
this._parseError()
.then((stack) => {
Expand All @@ -348,6 +353,10 @@ class Youch {
return serializedFrame
})

if (templateState) {
Object.assign(data, templateState)
}

if (this.request) {
data.request = this._serializeRequest()
}
Expand Down
10 changes: 10 additions & 0 deletions src/error.compiled.mustache

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions static/error.mustache
Expand Up @@ -9,7 +9,12 @@
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/fontawesome.css" integrity="sha384-1rquJLNOM3ijoueaaeS5m+McXPJCGdr5HcA03/VHXxcp2kX2sUrQDmFc3jR5i/C7" crossorigin="anonymous">
{{/loadFA}}

{{#cspNonce}}
<style type="text/css" nonce="{{ cspNonce }}">
{{/cspNonce}}
{{^cspNonce}}
<style type="text/css">
{{/cspNonce}}
[[__css__]]
</style>
Expand Down Expand Up @@ -119,7 +124,12 @@
</section>
{{/request}}
</section>
{{#cspNonce}}
<script type="text/javascript" nonce="{{ cspNonce }}">
{{/cspNonce}}
{{^cspNonce}}
<script type="text/javascript">
{{/cspNonce}}
[[__js__]]
</script>
</body>
Expand Down
25 changes: 25 additions & 0 deletions test/youch.spec.js
Expand Up @@ -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('<!DOCTYPE html>'))
assert.isFalse(html.includes('<script type="text/javascript" nonce="foo">'))
assert.isFalse(html.includes('<style type="text/css" nonce="foo">'))
done()
}).catch(done)
})

test('pass csp nonce to script and style tags', (assert, done) => {
const error = new Error('foo')
const youch = new Youch(error, {})
youch
.toHTML({ cspNonce: 'foo' })
.then((html) => {
assert.isTrue(html.includes('<script type="text/javascript" nonce="foo">'))
assert.isTrue(html.includes('<style type="text/css" nonce="foo">'))
done()
}).catch(done)
})
})

0 comments on commit 1426562

Please sign in to comment.