Skip to content

Commit bfab636

Browse files
committed
Add CSP support
1 parent 9eda85e commit bfab636

File tree

7 files changed

+53
-11
lines changed

7 files changed

+53
-11
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ with custom values to setup a completely independent project:
9191
9292
aws ssm put-parameter --type "String" \
9393
--name "/app/aws-lambda-edge/experimental/appConfig" \
94-
--value '{"title":"Lambda@Edge Immutable Web App (Experimental)"}'
94+
--value '{"title":"Lambda@Edge Immutable Web App (Experimental)","reportUri":"https://httpbin.org/post"}'
9595
```
9696
5. Build and deploy the initial version with
9797
```
@@ -118,7 +118,7 @@ with custom values to setup a completely independent project:
118118
119119
aws ssm put-parameter --type "String" \
120120
--name "/app/aws-lambda-edge/live/appConfig" \
121-
--value '{"title":"Lambda@Edge Immutable Web App"}'
121+
--value '{"title":"Lambda@Edge Immutable Web App","reportUri":"https://httpbin.org/post"}'
122122
123123
npm run build
124124
npm run deploy:assets

lib/content.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ const getIndexContent = (options, callback) => {
1414
getIndexTemplate(options, (err, template) => {
1515
try {
1616
if (err) return callback(err)
17-
const content = createContent(template, options)
18-
callback(null, content)
17+
const data = createContent(template, options)
18+
callback(null, data)
1919
} catch (error) {
2020
callback(error)
2121
}
@@ -65,11 +65,16 @@ const createContent = (template, {
6565
version
6666
}) => {
6767
const encodedConfig = encodeURIComponent(config)
68-
return render(template, {
68+
const configScript = `window.config = JSON.parse(decodeURIComponent('${encodedConfig}'))`
69+
const content = render(template, {
6970
root,
7071
version,
71-
config: encodedConfig
72+
configScript
7273
})
74+
return {
75+
content,
76+
scripts: [configScript]
77+
}
7378
}
7479

7580
module.exports = getIndexContent

lib/csp.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const { createHash } = require('crypto')
2+
3+
const createCspHeader = (options, {
4+
scripts = []
5+
}) => {
6+
const { origin, config } = options
7+
const { reportUri, api } = JSON.parse(config)
8+
const scriptHashes = scripts.map(cspHash)
9+
const directives = [
10+
['default-src', origin],
11+
['script-src', "'self'", origin, ...scriptHashes],
12+
['img-src', "'self'", origin],
13+
['connect-src', api],
14+
['report-uri', reportUri]
15+
]
16+
return directives.map(d => d.join(' ')).join('; ')
17+
}
18+
19+
const cspHash = script => {
20+
const hash = createHash('sha256')
21+
hash.update(script)
22+
const digest = hash.digest('base64')
23+
return `'sha256-${digest}'`
24+
}
25+
26+
module.exports = createCspHeader

lib/options.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
'use strict'
2+
13
const appVersionCookieName = 'appVersion'
24

35
const getOptions = (req, options) => {

lib/response.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const { gzip } = require('zlib')
44

55
const getIndexContent = require('./content')
6+
const createCspHeader = require('./csp')
67
const getOptions = require('./options')
78

89
const getResponse = (req, options, callback) => {
@@ -21,12 +22,13 @@ const getResponse = (req, options, callback) => {
2122
}
2223

2324
const getIndexResponse = (options, callback) => {
24-
getIndexContent(options, (err, content) => {
25+
getIndexContent(options, (err, { content, scripts }) => {
2526
if (err) return callback(err)
2627
getBody(content, (err, body) => {
2728
try {
2829
if (err) return callback(err)
29-
const response = createIndexResponse(body)
30+
const csp = createCspHeader(options, { scripts })
31+
const response = createIndexResponse(body, csp)
3032
callback(null, response)
3133
} catch (error) {
3234
callback(error)
@@ -47,8 +49,12 @@ const getBody = (content, callback) => {
4749
})
4850
}
4951

50-
const createIndexResponse = body => ({
52+
const createIndexResponse = (body, csp) => ({
5153
headers: {
54+
'content-security-policy': [{
55+
key: 'Content-Security-Policy',
56+
value: csp
57+
}],
5258
'cache-control': [{
5359
key: 'Cache-Control',
5460
value: 'no-cache, no-store, must-revalidate'

public/index.html.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</head>
1111
<body>
1212
<div id="root"></div>
13-
<script>window.config = JSON.parse(decodeURIComponent('{{ config }}'))</script>
13+
<script>{{{ configScript }}}</script>
1414
<script src="{{ root }}/index.js"></script>
1515
</body>
1616
</html>

server.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ const assetPort = 8081
1212
const host = 'localhost'
1313

1414
const config = {
15-
title: 'Lambda@Edge Immutable Web App'
15+
title: 'Lambda@Edge Immutable Web App',
16+
reportUri: 'https://httpbin.org/post',
17+
api: 'https://httpbin.org'
1618
}
19+
1720
const options = {
1821
config: JSON.stringify(config),
1922
origin: `http://${host}:${assetPort}`,

0 commit comments

Comments
 (0)