mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 08:33:53 +00:00
Adding support for Content-Security-Policy script-src safe inline, for SSR state transfer
This commit is contained in:
parent
c316be20ef
commit
6e9be715b2
@ -34,6 +34,9 @@ Options.from = function (_options) {
|
|||||||
if (typeof options.extensions === 'string') {
|
if (typeof options.extensions === 'string') {
|
||||||
options.extensions = [ options.extensions ]
|
options.extensions = [ options.extensions ]
|
||||||
}
|
}
|
||||||
|
if (options.render.csp === true) {
|
||||||
|
options.render.csp = { hashAlgorithm: 'sha256' }
|
||||||
|
}
|
||||||
|
|
||||||
const hasValue = v => typeof v === 'string' && v
|
const hasValue = v => typeof v === 'string' && v
|
||||||
options.rootDir = hasValue(options.rootDir) ? options.rootDir : process.cwd()
|
options.rootDir = hasValue(options.rootDir) ? options.rootDir : process.cwd()
|
||||||
@ -278,7 +281,8 @@ Options.defaults = {
|
|||||||
},
|
},
|
||||||
etag: {
|
etag: {
|
||||||
weak: false
|
weak: false
|
||||||
}
|
},
|
||||||
|
csp: undefined
|
||||||
},
|
},
|
||||||
watchers: {
|
watchers: {
|
||||||
webpack: {
|
webpack: {
|
||||||
|
@ -11,7 +11,7 @@ module.exports = async function nuxtMiddleware(req, res, next) {
|
|||||||
try {
|
try {
|
||||||
const result = await this.renderRoute(req.url, context)
|
const result = await this.renderRoute(req.url, context)
|
||||||
await this.nuxt.callHook('render:route', req.url, result)
|
await this.nuxt.callHook('render:route', req.url, result)
|
||||||
const { html, error, redirected, getPreloadFiles } = result
|
const { html, cspScriptSrcHashes, error, redirected, getPreloadFiles } = result
|
||||||
|
|
||||||
if (redirected) {
|
if (redirected) {
|
||||||
return html
|
return html
|
||||||
@ -61,6 +61,10 @@ module.exports = async function nuxtMiddleware(req, res, next) {
|
|||||||
res.setHeader('Link', pushAssets.join(','))
|
res.setHeader('Link', pushAssets.join(','))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.options.render.csp) {
|
||||||
|
res.setHeader('Content-Security-Policy', `script-src 'self' ${(cspScriptSrcHashes || []).join(' ')}`)
|
||||||
|
}
|
||||||
|
|
||||||
// Send response
|
// Send response
|
||||||
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
||||||
res.setHeader('Content-Length', Buffer.byteLength(html))
|
res.setHeader('Content-Length', Buffer.byteLength(html))
|
||||||
|
@ -9,6 +9,7 @@ const { createBundleRenderer } = require('vue-server-renderer')
|
|||||||
const Debug = require('debug')
|
const Debug = require('debug')
|
||||||
const connect = require('connect')
|
const connect = require('connect')
|
||||||
const launchMiddleware = require('launch-editor-middleware')
|
const launchMiddleware = require('launch-editor-middleware')
|
||||||
|
const crypto = require('crypto')
|
||||||
|
|
||||||
const { setAnsiColors, isUrl, waitFor } = require('../common/utils')
|
const { setAnsiColors, isUrl, waitFor } = require('../common/utils')
|
||||||
const { Options } = require('../common')
|
const { Options } = require('../common')
|
||||||
@ -315,7 +316,15 @@ module.exports = class Renderer {
|
|||||||
HEAD += context.renderResourceHints()
|
HEAD += context.renderResourceHints()
|
||||||
}
|
}
|
||||||
|
|
||||||
APP += `<script type="text/javascript">window.__NUXT__=${serialize(context.nuxt, { isJSON: true })};</script>`
|
let serializedSession = `window.__NUXT__=${serialize(context.nuxt, { isJSON: true })};`
|
||||||
|
let cspScriptSrcHashes = []
|
||||||
|
if (this.options.render.csp) {
|
||||||
|
let hash = crypto.createHash(this.options.render.csp.hashAlgorithm)
|
||||||
|
hash.update(serializedSession)
|
||||||
|
cspScriptSrcHashes.push(`'${this.options.render.csp.hashAlgorithm}-${hash.digest('base64')}'`)
|
||||||
|
}
|
||||||
|
|
||||||
|
APP += `<script type="text/javascript">${serializedSession}</script>`
|
||||||
APP += context.renderScripts()
|
APP += context.renderScripts()
|
||||||
APP += m.script.text({ body: true })
|
APP += m.script.text({ body: true })
|
||||||
|
|
||||||
@ -331,6 +340,7 @@ module.exports = class Renderer {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
html,
|
html,
|
||||||
|
cspScriptSrcHashes,
|
||||||
getPreloadFiles: context.getPreloadFiles,
|
getPreloadFiles: context.getPreloadFiles,
|
||||||
error: context.nuxt.error,
|
error: context.nuxt.error,
|
||||||
redirected: context.redirected
|
redirected: context.redirected
|
||||||
|
@ -22,6 +22,9 @@ test.serial('Init Nuxt.js', async t => {
|
|||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
stats: false
|
stats: false
|
||||||
|
},
|
||||||
|
render: {
|
||||||
|
csp: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,6 +231,12 @@ test('ETag Header', async t => {
|
|||||||
t.is(error.statusCode, 304)
|
t.is(error.statusCode, 304)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Content-Security-Policy Header', async t => {
|
||||||
|
const { headers } = await rp(url('/stateless'), { resolveWithFullResponse: true })
|
||||||
|
// Verify functionality
|
||||||
|
t.is(headers['content-security-policy'], "script-src 'self' 'sha256-BBvfKxDOoRM/gnFwke9u60HBZX3HUss/0lSI1sBRvOU='")
|
||||||
|
})
|
||||||
|
|
||||||
test('/_nuxt/server-bundle.json should return 404', async t => {
|
test('/_nuxt/server-bundle.json should return 404', async t => {
|
||||||
const err = await t.throws(rp(url('/_nuxt/server-bundle.json'), { resolveWithFullResponse: true }))
|
const err = await t.throws(rp(url('/_nuxt/server-bundle.json'), { resolveWithFullResponse: true }))
|
||||||
t.is(err.statusCode, 404)
|
t.is(err.statusCode, 404)
|
||||||
|
Loading…
Reference in New Issue
Block a user