1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00

Use nonce attribute for all scripts and preloads if provided (#4539)

When implementing a strict CSP with nonces and `strict-dynamic`, every script and preload requires a nonce.

https://csp.withgoogle.com/docs/strict-csp.html
This commit is contained in:
Thomas Hermann 2018-06-28 14:16:30 -04:00 committed by Tim Neutkens
parent 17e410a1d0
commit 1c817d2bbf
3 changed files with 26 additions and 8 deletions

View file

@ -39,6 +39,10 @@ export class Head extends Component {
_documentProps: PropTypes.any _documentProps: PropTypes.any
} }
static propTypes = {
nonce: PropTypes.string
}
getChunkPreloadLink (filename) { getChunkPreloadLink (filename) {
const { __NEXT_DATA__, buildManifest } = this.context._documentProps const { __NEXT_DATA__, buildManifest } = this.context._documentProps
let { assetPrefix, buildId } = __NEXT_DATA__ let { assetPrefix, buildId } = __NEXT_DATA__
@ -48,6 +52,7 @@ export class Head extends Component {
return files.map(file => { return files.map(file => {
return <link return <link
key={filename} key={filename}
nonce={this.props.nonce}
rel='preload' rel='preload'
href={`${assetPrefix}/_next/${file}`} href={`${assetPrefix}/_next/${file}`}
as='script' as='script'
@ -79,6 +84,7 @@ export class Head extends Component {
rel='preload' rel='preload'
href={`${assetPrefix}/_next/webpack/chunks/${chunk}`} href={`${assetPrefix}/_next/webpack/chunks/${chunk}`}
as='script' as='script'
nonce={this.props.nonce}
/> />
)) ))
} }
@ -90,9 +96,9 @@ export class Head extends Component {
return <head {...this.props}> return <head {...this.props}>
{(head || []).map((h, i) => React.cloneElement(h, { key: h.key || i }))} {(head || []).map((h, i) => React.cloneElement(h, { key: h.key || i }))}
{page !== '/_error' && <link rel='preload' href={`${assetPrefix}/_next/${buildId}/page${pagePathname}`} as='script' />} {page !== '/_error' && <link rel='preload' href={`${assetPrefix}/_next/${buildId}/page${pagePathname}`} as='script' nonce={this.props.nonce} />}
<link rel='preload' href={`${assetPrefix}/_next/${buildId}/page/_app.js`} as='script' /> <link rel='preload' href={`${assetPrefix}/_next/${buildId}/page/_app.js`} as='script' nonce={this.props.nonce} />
<link rel='preload' href={`${assetPrefix}/_next/${buildId}/page/_error.js`} as='script' /> <link rel='preload' href={`${assetPrefix}/_next/${buildId}/page/_error.js`} as='script' nonce={this.props.nonce} />
{this.getPreloadDynamicChunks()} {this.getPreloadDynamicChunks()}
{this.getPreloadMainLinks()} {this.getPreloadMainLinks()}
{styles || null} {styles || null}
@ -136,6 +142,7 @@ export class NextScript extends Component {
<script <script
key={filename} key={filename}
src={`${assetPrefix}/_next/${file}`} src={`${assetPrefix}/_next/${file}`}
nonce={this.props.nonce}
{...additionalProps} {...additionalProps}
/> />
)) ))
@ -165,6 +172,7 @@ export class NextScript extends Component {
async async
key={chunk} key={chunk}
src={`${assetPrefix}/_next/webpack/chunks/${chunk}`} src={`${assetPrefix}/_next/webpack/chunks/${chunk}`}
nonce={this.props.nonce}
/> />
))} ))}
</Fragment> </Fragment>
@ -204,9 +212,9 @@ export class NextScript extends Component {
`} `}
` `
}} />} }} />}
{page !== '/_error' && <script async id={`__NEXT_PAGE__${pathname}`} src={`${assetPrefix}/_next/${buildId}/page${pagePathname}`} />} {page !== '/_error' && <script async id={`__NEXT_PAGE__${pathname}`} src={`${assetPrefix}/_next/${buildId}/page${pagePathname}`} nonce={this.props.nonce} />}
<script async id={`__NEXT_PAGE__/_app`} src={`${assetPrefix}/_next/${buildId}/page/_app.js`} /> <script async id={`__NEXT_PAGE__/_app`} src={`${assetPrefix}/_next/${buildId}/page/_app.js`} nonce={this.props.nonce} />
<script async id={`__NEXT_PAGE__/_error`} src={`${assetPrefix}/_next/${buildId}/page/_error.js`} /> <script async id={`__NEXT_PAGE__/_error`} src={`${assetPrefix}/_next/${buildId}/page/_error.js`} nonce={this.props.nonce} />
{staticMarkup ? null : this.getDynamicChunks()} {staticMarkup ? null : this.getDynamicChunks()}
{staticMarkup ? null : this.getScripts()} {staticMarkup ? null : this.getScripts()}
</Fragment> </Fragment>

View file

@ -9,14 +9,14 @@ export default class MyDocument extends Document {
render () { render () {
return ( return (
<html> <html>
<Head> <Head nonce='test-nonce'>
<style>{`body { margin: 0 } /* custom! */`}</style> <style>{`body { margin: 0 } /* custom! */`}</style>
</Head> </Head>
<body className='custom_class'> <body className='custom_class'>
<p id='custom-property'>{this.props.customProperty}</p> <p id='custom-property'>{this.props.customProperty}</p>
<p id='document-hmr'>Hello Document HMR</p> <p id='document-hmr'>Hello Document HMR</p>
<Main /> <Main />
<NextScript /> <NextScript nonce='test-nonce' />
</body> </body>
</html> </html>
) )

View file

@ -24,6 +24,16 @@ export default function ({ app }, suiteName, render, fetch) {
const $ = await get$('/') const $ = await get$('/')
expect($('#custom-property').text() === 'Hello Document') expect($('#custom-property').text() === 'Hello Document')
}) })
test('It adds nonces to all scripts and preload links', async () => {
const $ = await get$('/')
const nonce = 'test-nonce'
let noncesAdded = true
$('script, link[rel=preload]').each((index, element) => {
if ($(element).attr('nonce') !== nonce) noncesAdded = false
})
expect(noncesAdded).toBe(true)
})
}) })
describe('_app', () => { describe('_app', () => {