From 6844b3a818c9ad34944533f365a31e840d905212 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 4 Mar 2020 12:08:58 -0600 Subject: [PATCH 1/2] Make sure to error when setting too large of preview data --- packages/next/next-server/server/api-utils.ts | 11 ++++++++++- .../getserversideprops-preview/pages/api/preview.js | 11 ++++++++++- .../getserversideprops-preview/test/index.test.js | 6 ++++++ .../prerender-preview/pages/api/preview.js | 11 ++++++++++- test/integration/prerender-preview/test/index.test.js | 6 ++++++ 5 files changed, 42 insertions(+), 3 deletions(-) diff --git a/packages/next/next-server/server/api-utils.ts b/packages/next/next-server/server/api-utils.ts index 3ccfba8773dea..fda0e5bc3050b 100644 --- a/packages/next/next-server/server/api-utils.ts +++ b/packages/next/next-server/server/api-utils.ts @@ -361,12 +361,21 @@ function setPreviewData( throw new Error('invariant: invalid previewModeSigningKey') } + const stringifiedData = JSON.stringify(data) + + // preview mode cookie can't exceed 4KB or else the browser will drop it + if (stringifiedData.length > 4000) { + throw new Error( + `Preview data can not exceed 4KB as this exceeds the cookie limit` + ) + } + const jsonwebtoken = require('jsonwebtoken') as typeof import('jsonwebtoken') const payload = jsonwebtoken.sign( encryptWithSecret( Buffer.from(options.previewModeEncryptionKey), - JSON.stringify(data) + stringifiedData ), options.previewModeSigningKey, { diff --git a/test/integration/getserversideprops-preview/pages/api/preview.js b/test/integration/getserversideprops-preview/pages/api/preview.js index b201b0e02b90a..be10d3cc6159c 100644 --- a/test/integration/getserversideprops-preview/pages/api/preview.js +++ b/test/integration/getserversideprops-preview/pages/api/preview.js @@ -1,4 +1,13 @@ export default (req, res) => { - res.setPreviewData(req.query) + if (req.query.tooBig) { + try { + res.setPreviewData(new Array(4000).fill('a')) + } catch (err) { + return res.status(500).end('too big') + } + } else { + res.setPreviewData(req.query) + } + res.status(200).end() } diff --git a/test/integration/getserversideprops-preview/test/index.test.js b/test/integration/getserversideprops-preview/test/index.test.js index 6e94de5f0bb4d..d210e6611ed7e 100644 --- a/test/integration/getserversideprops-preview/test/index.test.js +++ b/test/integration/getserversideprops-preview/test/index.test.js @@ -156,6 +156,12 @@ function runTests(startServer = nextStart) { expect(cookies[1]).not.toHaveProperty('Max-Age') }) + it('should throw error when setting too large of preview data', async () => { + const res = await fetchViaHTTP(appPort, '/api/preview?tooBig=true') + expect(res.status).toBe(500) + expect(await res.text()).toBe('too big') + }) + /** @type import('next-webdriver').Chain */ let browser it('should start the client-side browser', async () => { diff --git a/test/integration/prerender-preview/pages/api/preview.js b/test/integration/prerender-preview/pages/api/preview.js index b201b0e02b90a..be10d3cc6159c 100644 --- a/test/integration/prerender-preview/pages/api/preview.js +++ b/test/integration/prerender-preview/pages/api/preview.js @@ -1,4 +1,13 @@ export default (req, res) => { - res.setPreviewData(req.query) + if (req.query.tooBig) { + try { + res.setPreviewData(new Array(4000).fill('a')) + } catch (err) { + return res.status(500).end('too big') + } + } else { + res.setPreviewData(req.query) + } + res.status(200).end() } diff --git a/test/integration/prerender-preview/test/index.test.js b/test/integration/prerender-preview/test/index.test.js index 52f5757e36d02..9c40640c79212 100644 --- a/test/integration/prerender-preview/test/index.test.js +++ b/test/integration/prerender-preview/test/index.test.js @@ -63,6 +63,12 @@ function runTests(startServer = nextStart) { expect(pre).toBe('undefined and undefined') }) + it('should throw error when setting too large of preview data', async () => { + const res = await fetchViaHTTP(appPort, '/api/preview?tooBig=true') + expect(res.status).toBe(500) + expect(await res.text()).toBe('too big') + }) + let previewCookieString it('should enable preview mode', async () => { const res = await fetchViaHTTP(appPort, '/api/preview', { lets: 'goooo' }) From 03eec110ddfe81c933bb499835f17c0f54752b9d Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 4 Mar 2020 12:41:24 -0600 Subject: [PATCH 2/2] Update to check size after signing and limit to 2KB --- packages/next/next-server/server/api-utils.ts | 19 +++++++++---------- .../pages/api/preview.js | 2 +- .../prerender-preview/pages/api/preview.js | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/next/next-server/server/api-utils.ts b/packages/next/next-server/server/api-utils.ts index fda0e5bc3050b..a6bbb234d67c7 100644 --- a/packages/next/next-server/server/api-utils.ts +++ b/packages/next/next-server/server/api-utils.ts @@ -361,21 +361,12 @@ function setPreviewData( throw new Error('invariant: invalid previewModeSigningKey') } - const stringifiedData = JSON.stringify(data) - - // preview mode cookie can't exceed 4KB or else the browser will drop it - if (stringifiedData.length > 4000) { - throw new Error( - `Preview data can not exceed 4KB as this exceeds the cookie limit` - ) - } - const jsonwebtoken = require('jsonwebtoken') as typeof import('jsonwebtoken') const payload = jsonwebtoken.sign( encryptWithSecret( Buffer.from(options.previewModeEncryptionKey), - stringifiedData + JSON.stringify(data) ), options.previewModeSigningKey, { @@ -386,6 +377,14 @@ function setPreviewData( } ) + // limit preview mode cookie to 2KB since we shouldn't store too much + // data here and browsers drop cookies over 4KB + if (payload.length > 2048) { + throw new Error( + `Preview data is limited to 2KB currently, reduce how much data you are storing as preview data to continue` + ) + } + const { serialize } = require('cookie') as typeof import('cookie') const previous = res.getHeader('Set-Cookie') res.setHeader(`Set-Cookie`, [ diff --git a/test/integration/getserversideprops-preview/pages/api/preview.js b/test/integration/getserversideprops-preview/pages/api/preview.js index be10d3cc6159c..6ab2fb0d11dc1 100644 --- a/test/integration/getserversideprops-preview/pages/api/preview.js +++ b/test/integration/getserversideprops-preview/pages/api/preview.js @@ -1,7 +1,7 @@ export default (req, res) => { if (req.query.tooBig) { try { - res.setPreviewData(new Array(4000).fill('a')) + res.setPreviewData(new Array(2000).fill('a').join('')) } catch (err) { return res.status(500).end('too big') } diff --git a/test/integration/prerender-preview/pages/api/preview.js b/test/integration/prerender-preview/pages/api/preview.js index be10d3cc6159c..6ab2fb0d11dc1 100644 --- a/test/integration/prerender-preview/pages/api/preview.js +++ b/test/integration/prerender-preview/pages/api/preview.js @@ -1,7 +1,7 @@ export default (req, res) => { if (req.query.tooBig) { try { - res.setPreviewData(new Array(4000).fill('a')) + res.setPreviewData(new Array(2000).fill('a').join('')) } catch (err) { return res.status(500).end('too big') }