fix: increase cache, use csp checksums over nonce (#988)
attempt to address #985
This commit is contained in:
parent
752715becb
commit
d947f819ab
|
@ -97,7 +97,6 @@
|
||||||
"terser-webpack-plugin": "^1.2.2",
|
"terser-webpack-plugin": "^1.2.2",
|
||||||
"text-encoding": "^0.7.0",
|
"text-encoding": "^0.7.0",
|
||||||
"tiny-queue": "^0.2.1",
|
"tiny-queue": "^0.2.1",
|
||||||
"uuid": "^3.3.2",
|
|
||||||
"web-animations-js": "^2.3.1",
|
"web-animations-js": "^2.3.1",
|
||||||
"webpack": "^4.29.3",
|
"webpack": "^4.29.3",
|
||||||
"webpack-bundle-analyzer": "^3.0.4"
|
"webpack-bundle-analyzer": "^3.0.4"
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
import * as sapper from '../__sapper__/server.js'
|
import * as sapper from '../__sapper__/server.js'
|
||||||
const express = require('express')
|
import express from 'express'
|
||||||
const compression = require('compression')
|
import compression from 'compression'
|
||||||
const serveStatic = require('serve-static')
|
import serveStatic from 'serve-static'
|
||||||
const app = express()
|
import helmet from 'helmet'
|
||||||
const helmet = require('helmet')
|
import fetch from 'node-fetch'
|
||||||
const uuidv4 = require('uuid/v4')
|
import inlineScriptChecksum from './inline-script/checksum'
|
||||||
|
import { sapperInlineScriptChecksums } from './server/sapperInlineScriptChecksums'
|
||||||
const headScriptChecksum = require('./inline-script/checksum')
|
|
||||||
|
|
||||||
const { PORT = 4002 } = process.env
|
const { PORT = 4002 } = process.env
|
||||||
|
const app = express()
|
||||||
|
|
||||||
// this allows us to do e.g. `fetch('/_api/blog')` on the server
|
// this allows us to do e.g. `fetch('/_api/blog')` on the server
|
||||||
const fetch = require('node-fetch')
|
|
||||||
global.fetch = (url, opts) => {
|
global.fetch = (url, opts) => {
|
||||||
if (url[0] === '/') {
|
if (url[0] === '/') {
|
||||||
url = `http://localhost:${PORT}${url}`
|
url = `http://localhost:${PORT}${url}`
|
||||||
|
@ -19,32 +18,23 @@ global.fetch = (url, opts) => {
|
||||||
return fetch(url, opts)
|
return fetch(url, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
const debugPaths = ['/report.html', '/stats.json']
|
|
||||||
|
|
||||||
const debugOnly = (fn) => (req, res, next) => (
|
|
||||||
!~debugPaths.indexOf(req.path) ? next() : fn(req, res, next)
|
|
||||||
)
|
|
||||||
|
|
||||||
const nonDebugOnly = (fn) => (req, res, next) => (
|
|
||||||
~debugPaths.indexOf(req.path) ? next() : fn(req, res, next)
|
|
||||||
)
|
|
||||||
|
|
||||||
app.use(compression({ threshold: 0 }))
|
app.use(compression({ threshold: 0 }))
|
||||||
|
|
||||||
app.use((req, res, next) => {
|
// CSP only needs to apply to core HTML files, not debug files
|
||||||
res.locals.nonce = uuidv4()
|
// like report.html or the JS/CSS/JSON/image files
|
||||||
next()
|
const coreHtmlFilesOnly = (fn) => (req, res, next) => {
|
||||||
})
|
let coreHtml = !/\.(js|css|json|png|svg|jpe?g|map)$/.test(req.path) &&
|
||||||
|
!(/\/report.html/.test(req.path))
|
||||||
|
return coreHtml ? fn(req, res, next) : next()
|
||||||
|
}
|
||||||
|
|
||||||
// report.html needs to have CSP disable because it has inline scripts
|
app.use(coreHtmlFilesOnly(helmet({
|
||||||
app.use(debugOnly(helmet()))
|
|
||||||
app.use(nonDebugOnly(helmet({
|
|
||||||
contentSecurityPolicy: {
|
contentSecurityPolicy: {
|
||||||
directives: {
|
directives: {
|
||||||
scriptSrc: [
|
scriptSrc: [
|
||||||
`'self'`,
|
`'self'`,
|
||||||
`'sha256-${headScriptChecksum}'`,
|
`'sha256-${inlineScriptChecksum}'`,
|
||||||
(req, res) => `'nonce-${res.locals.nonce}'`
|
...sapperInlineScriptChecksums.map(_ => `'sha256-${_}'`)
|
||||||
],
|
],
|
||||||
workerSrc: [`'self'`],
|
workerSrc: [`'self'`],
|
||||||
styleSrc: [`'self'`, `'unsafe-inline'`],
|
styleSrc: [`'self'`, `'unsafe-inline'`],
|
||||||
|
@ -60,13 +50,12 @@ app.use(nonDebugOnly(helmet({
|
||||||
|
|
||||||
app.use(serveStatic('static', {
|
app.use(serveStatic('static', {
|
||||||
setHeaders: (res) => {
|
setHeaders: (res) => {
|
||||||
res.setHeader('Cache-Control', 'public,max-age=600')
|
res.setHeader('Cache-Control', 'public,max-age=3600')
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
debugPaths.forEach(debugPath => {
|
app.use(express.static('__sapper__/build/client/report.html'))
|
||||||
app.use(debugPath, express.static(`__sapper__/build/client${debugPath}`))
|
app.use(express.static('__sapper__/build/client/stats.json'))
|
||||||
})
|
|
||||||
|
|
||||||
app.use(sapper.middleware())
|
app.use(sapper.middleware())
|
||||||
|
|
||||||
|
|
21
src/server/sapperInlineScriptChecksums.js
Normal file
21
src/server/sapperInlineScriptChecksums.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// TODO: this is a hacky solution to generating checksums for Sapper's inline scripts.
|
||||||
|
// I determined this by running `sapper export` and then checking all unique inline scripts.
|
||||||
|
// Really, this ought to be built into Sapper somehow.
|
||||||
|
|
||||||
|
import crypto from 'crypto'
|
||||||
|
|
||||||
|
let scripts = [
|
||||||
|
`__SAPPER__={baseUrl:"",preloaded:[{},{}]};`,
|
||||||
|
`__SAPPER__={baseUrl:"",preloaded:[{}]};`,
|
||||||
|
`__SAPPER__={baseUrl:"",preloaded:[{},null,null,{}]};`,
|
||||||
|
`__SAPPER__={baseUrl:"",preloaded:[{},null,{}]};`
|
||||||
|
]
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
// sapper adds service worker only in production
|
||||||
|
scripts = scripts.map(script => `${script}if('serviceWorker' in navigator)navigator.serviceWorker.register('/service-worker.js');`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sapperInlineScriptChecksums = scripts.map(script => {
|
||||||
|
return crypto.createHash('sha256').update(script).digest('base64')
|
||||||
|
})
|
Loading…
Reference in a new issue