fix: increase cache, use csp checksums over nonce (#988)

attempt to address #985
This commit is contained in:
Nolan Lawson 2019-02-14 19:39:24 -08:00 committed by GitHub
parent 752715becb
commit d947f819ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 42 additions and 33 deletions

View file

@ -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"

View file

@ -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())

View 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')
})