From 9231bc8be909dfe80585aff342e3d698e227076a Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Wed, 13 Nov 2024 22:55:43 +0000 Subject: [PATCH 01/19] Set Strict-Transport-Security header in production This will ensure that browsers automatically load the prototype over https, even if the `http://` prefix is typed into the URL bar. It does require `NODE_ENV` to be set to `production`, however many platforms such as Heroku will [set this by default](https://devcenter.heroku.com/changelog-items/688) for Node.js apps. Once this is set you can also submit the domain to https://hstspreload.org which will ensure the browsers at it to their list of domains to always use https on. --- app.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app.js b/app.js index f438feaf2..ab0327b3b 100755 --- a/app.js +++ b/app.js @@ -215,6 +215,15 @@ if (useDocumentation || onlyDocumentation === 'true') { app.use('/prototype-admin', prototypeAdminRoutes); +if (process.env.NODE_ENV === 'production') { + app.use((req, res, next) => { + // Set Strict-Transport-Security header to + // ensure that browsers only use HTTPS + res.setHeader('Strict-Transport-Security', 'max-age=31536000; preload'); + next(); + }); +} + // Redirect all POSTs to GETs - this allows users to use POST for autoStoreData app.post(/^\/([^.]+)$/, (req, res) => { res.redirect(`/${req.params[0]}`); From 5311296f5d37978b809fc1f4f5c2ecbd2672126a Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Wed, 13 Nov 2024 22:55:54 +0000 Subject: [PATCH 02/19] Remove older commented-out https code --- lib/utils.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index e2f73a8e1..25a79ff99 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -110,21 +110,6 @@ exports.findAvailablePort = function (app, callback) { } */ -/* -// Redirect HTTP requests to HTTPS -exports.forceHttps = function (req, res, next) { - if (req.headers['x-forwarded-proto'] !== 'https') { - console.log('Redirecting request to https') - // 302 temporary - this is a feature that can be disabled - return res.redirect(302, 'https://' + req.get('Host') + req.url) - } - - // Mark proxy as secure (allows secure cookies) - req.connection.proxySecure = true - next() -} -*/ - /* // Synchronously get the URL for the latest release on GitHub and cache it exports.getLatestRelease = function () { From 6c0a9cc2ab52bc35fb482da4b2ecf566005e48dc Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Wed, 13 Nov 2024 23:04:17 +0000 Subject: [PATCH 03/19] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4144c103..4919e0d61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # NHS.UK prototype kit Changelog +## Unreleased + +- Use strict https in production mode - ([PR 410](https://github.com/nhsuk/nhsuk-prototype-kit/pull/410)) + ## 5.1.0 - 12 November 2024 - Remove guidance and tutorials - these can now be found online on the [NHS Prototype Kit website](https://prototype-kit.service-manual.nhs.uk) - ([PR 385](https://github.com/nhsuk/nhsuk-prototype-kit/pull/385)) From e9cb08eb11ec1d76c271368b1bada8a47a3f8fdf Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Sun, 26 Jan 2025 17:01:03 +0000 Subject: [PATCH 04/19] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8d1df896..af3f30fc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,10 +18,10 @@ - Update default index page ([PR 423](https://github.com/nhsuk/nhsuk-prototype-kit/pull/423)) - Import task list component ([PR 437](https://github.com/nhsuk/nhsuk-prototype-kit/pull/437)) - The example page templates have moved from the `docs` folder to `lib/example-templates` - ([PR 409](https://github.com/nhsuk/nhsuk-prototype-kit/pull/409)) -- Added a devcontainer.json file to configure Github Codespaces for use of the kit ([PR 428])(https://github.com/nhsuk/nhsuk-prototype-kit/pull/428)) - The middleware folder has been moved into the `lib` folder - ([PR 440](https://github.com/nhsuk/nhsuk-prototype-kit/pull/440)) - Added a devcontainer.json file to configure Github Codespaces for use of the kit ([PR 428](https://github.com/nhsuk/nhsuk-prototype-kit/pull/428)) + ## 5.1.0 - 12 November 2024 - Remove guidance and tutorials - these can now be found online on the [NHS Prototype Kit website](https://prototype-kit.service-manual.nhs.uk) - ([PR 385](https://github.com/nhsuk/nhsuk-prototype-kit/pull/385)) From 93471364a1dbc40f0307765c7ae51be2a6fdb1d5 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 10 Feb 2025 14:07:50 +0000 Subject: [PATCH 05/19] Include subdomains in HST header Co-authored-by: Colin Rotherham --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index 02722f6ab..883e8eae0 100755 --- a/app.js +++ b/app.js @@ -193,7 +193,7 @@ if (process.env.NODE_ENV === 'production') { app.use((req, res, next) => { // Set Strict-Transport-Security header to // ensure that browsers only use HTTPS - res.setHeader('Strict-Transport-Security', 'max-age=31536000; preload'); + res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); next(); }); } From daedfa00be70b665c4196caec0ada3763162f0f4 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 10 Feb 2025 14:41:26 +0000 Subject: [PATCH 06/19] Add Content-Security-Policy --- app.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app.js b/app.js index 883e8eae0..195f74d5b 100755 --- a/app.js +++ b/app.js @@ -194,6 +194,10 @@ if (process.env.NODE_ENV === 'production') { // Set Strict-Transport-Security header to // ensure that browsers only use HTTPS res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); + + // Set content security policy to upgrade + // all HTTP requests to HTTPS + res.setHeader('Content-Security-Policy', 'upgrade-insecure-requests'); next(); }); } From e79ddf69404099d79b8afee89760c2ef6202e53a Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 10 Feb 2025 15:14:31 +0000 Subject: [PATCH 07/19] Move the production headers middleware up --- app.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app.js b/app.js index 195f74d5b..bf3729ebd 100755 --- a/app.js +++ b/app.js @@ -77,6 +77,19 @@ const sessionOptions = { }, }; +if (process.env.NODE_ENV === 'production') { + app.use((req, res, next) => { + // Set Strict-Transport-Security header to + // ensure that browsers only use HTTPS + res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); + + // Set content security policy to upgrade + // all HTTP requests to HTTPS + res.setHeader('Content-Security-Policy', 'upgrade-insecure-requests'); + next(); + }); +} + // Authentication app.use(authentication); @@ -189,19 +202,6 @@ exampleTemplatesApp.get(/^([^.]+)$/, (req, res, next) => { app.use('/prototype-admin', prototypeAdminRoutes); -if (process.env.NODE_ENV === 'production') { - app.use((req, res, next) => { - // Set Strict-Transport-Security header to - // ensure that browsers only use HTTPS - res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); - - // Set content security policy to upgrade - // all HTTP requests to HTTPS - res.setHeader('Content-Security-Policy', 'upgrade-insecure-requests'); - next(); - }); -} - // Redirect all POSTs to GETs - this allows users to use POST for autoStoreData app.post(/^\/([^.]+)$/, (req, res) => { res.redirect(`/${req.params[0]}`); From ddace4028ecefd7c828b549a6e1b4469091ed658 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 10 Feb 2025 15:22:33 +0000 Subject: [PATCH 08/19] Refactor to move production headers into lib/ --- app.js | 15 ++------------- lib/middleware/authentication.js | 5 +---- lib/middleware/production.js | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 17 deletions(-) create mode 100644 lib/middleware/production.js diff --git a/app.js b/app.js index bf3729ebd..241a91074 100755 --- a/app.js +++ b/app.js @@ -78,21 +78,10 @@ const sessionOptions = { }; if (process.env.NODE_ENV === 'production') { - app.use((req, res, next) => { - // Set Strict-Transport-Security header to - // ensure that browsers only use HTTPS - res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); - - // Set content security policy to upgrade - // all HTTP requests to HTTPS - res.setHeader('Content-Security-Policy', 'upgrade-insecure-requests'); - next(); - }); + app.use(production); + app.use(authentication); } -// Authentication -app.use(authentication); - // Support session data in cookie or memory if (useCookieSessionStore === 'true') { app.use(sessionInCookie(Object.assign(sessionOptions, { diff --git a/lib/middleware/authentication.js b/lib/middleware/authentication.js index 1412526fd..d1619baf5 100644 --- a/lib/middleware/authentication.js +++ b/lib/middleware/authentication.js @@ -10,7 +10,6 @@ const allowedPathsWhenUnauthenticated = [ ]; const encryptedPassword = encryptPassword(process.env.PROTOTYPE_PASSWORD); -const nodeEnv = process.env.NODE_ENV || 'development'; // Redirect the user to the password page, with // the current page path set as the returnURL in a query @@ -34,9 +33,7 @@ function showNoPasswordError(res) { } function authentication(req, res, next) { - if (nodeEnv !== 'production') { - next(); - } else if (!process.env.PROTOTYPE_PASSWORD) { + if (!process.env.PROTOTYPE_PASSWORD) { showNoPasswordError(res); } else if (allowedPathsWhenUnauthenticated.includes(req.path)) { next(); diff --git a/lib/middleware/production.js b/lib/middleware/production.js new file mode 100644 index 000000000..0fbd7da53 --- /dev/null +++ b/lib/middleware/production.js @@ -0,0 +1,16 @@ +// This Express middleware function sets some +// HTTP headers which should only be set in deployed +// environments + +function production(req, res, next) { + // Set Strict-Transport-Security header to + // ensure that browsers only use HTTPS + res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); + + // Set content security policy to upgrade + // all HTTP requests to HTTPS + res.setHeader('Content-Security-Policy', 'upgrade-insecure-requests'); + next(); +} + +module.exports = authentication; From b40000f8bf7041f4b631d06040ae17b4904c2151 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 10 Feb 2025 15:23:17 +0000 Subject: [PATCH 09/19] Bugfix --- lib/middleware/production.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/middleware/production.js b/lib/middleware/production.js index 0fbd7da53..6b95222a8 100644 --- a/lib/middleware/production.js +++ b/lib/middleware/production.js @@ -13,4 +13,4 @@ function production(req, res, next) { next(); } -module.exports = authentication; +module.exports = production; From ddc51084b0476ee286996a6fcafb54f0f745a1fe Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 10 Feb 2025 15:55:59 +0000 Subject: [PATCH 10/19] Require the production middleware --- app.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app.js b/app.js index 241a91074..db914be74 100755 --- a/app.js +++ b/app.js @@ -17,6 +17,7 @@ dotenv.config(); // Local dependencies const packageInfo = require('./package.json'); const authentication = require('./lib/middleware/authentication'); +const production = require('./lib/middleware/production'); const automaticRouting = require('./lib/middleware/auto-routing'); const config = require('./app/config'); const locals = require('./app/locals'); From 9d375e55a436ecc3ff09803b13c536c6b69717a9 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 14 Feb 2025 09:48:00 +0000 Subject: [PATCH 11/19] Add redirect to https if served over http --- lib/middleware/production.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/middleware/production.js b/lib/middleware/production.js index 6b95222a8..18ec05ee3 100644 --- a/lib/middleware/production.js +++ b/lib/middleware/production.js @@ -10,7 +10,14 @@ function production(req, res, next) { // Set content security policy to upgrade // all HTTP requests to HTTPS res.setHeader('Content-Security-Policy', 'upgrade-insecure-requests'); - next(); + + // Redirect to HTTPS if page requested over HTTP + if (req.headers['X-Forwarded-Proto'] === 'http') { + return res.redirect(302, 'https://' + req.get('Host') + req.url) + } else { + next() + } + } module.exports = production; From 88d8db35911a644ae98e1577dcdf9408e7a20812 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 14 Feb 2025 09:48:09 +0000 Subject: [PATCH 12/19] Add temporary debug --- lib/middleware/production.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/middleware/production.js b/lib/middleware/production.js index 18ec05ee3..65a904a3f 100644 --- a/lib/middleware/production.js +++ b/lib/middleware/production.js @@ -11,6 +11,8 @@ function production(req, res, next) { // all HTTP requests to HTTPS res.setHeader('Content-Security-Policy', 'upgrade-insecure-requests'); + console.log(req.heaaders) + // Redirect to HTTPS if page requested over HTTP if (req.headers['X-Forwarded-Proto'] === 'http') { return res.redirect(302, 'https://' + req.get('Host') + req.url) From 16ede58a06f82c93cbf95c6fa94f748f54b8ea98 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 14 Feb 2025 09:56:10 +0000 Subject: [PATCH 13/19] Fix stupid typo --- lib/middleware/production.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/middleware/production.js b/lib/middleware/production.js index 65a904a3f..527cc5512 100644 --- a/lib/middleware/production.js +++ b/lib/middleware/production.js @@ -11,7 +11,7 @@ function production(req, res, next) { // all HTTP requests to HTTPS res.setHeader('Content-Security-Policy', 'upgrade-insecure-requests'); - console.log(req.heaaders) + console.log(req.headers) // Redirect to HTTPS if page requested over HTTP if (req.headers['X-Forwarded-Proto'] === 'http') { From 307c9774adc7466cb85c7f5a8ceca99dcf5243e8 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 14 Feb 2025 09:58:06 +0000 Subject: [PATCH 14/19] HTTP headers are lowercase within req.headers --- lib/middleware/production.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/middleware/production.js b/lib/middleware/production.js index 527cc5512..b74f68334 100644 --- a/lib/middleware/production.js +++ b/lib/middleware/production.js @@ -14,7 +14,7 @@ function production(req, res, next) { console.log(req.headers) // Redirect to HTTPS if page requested over HTTP - if (req.headers['X-Forwarded-Proto'] === 'http') { + if (req.headers['x-forwarded-proto'] === 'http') { return res.redirect(302, 'https://' + req.get('Host') + req.url) } else { next() From f4106a15aacf37e60bfcad1a3dbb2f4132828198 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 14 Feb 2025 10:04:26 +0000 Subject: [PATCH 15/19] Add comment --- lib/middleware/production.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/middleware/production.js b/lib/middleware/production.js index b74f68334..a32f34546 100644 --- a/lib/middleware/production.js +++ b/lib/middleware/production.js @@ -14,6 +14,10 @@ function production(req, res, next) { console.log(req.headers) // Redirect to HTTPS if page requested over HTTP + // This relies on the X-Forwarded-Proto HTTP header as hosts + // like Heroku place apps behind a load balance which uses HTTPS + // internally. The X-Forwarded-Proto reveals the protocol of the + // original request. if (req.headers['x-forwarded-proto'] === 'http') { return res.redirect(302, 'https://' + req.get('Host') + req.url) } else { From fca98e52d0c3d9cf2d7513dee2d84be51d98ad10 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 14 Feb 2025 10:08:35 +0000 Subject: [PATCH 16/19] Code style fixes --- lib/middleware/production.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/middleware/production.js b/lib/middleware/production.js index a32f34546..6014a90d6 100644 --- a/lib/middleware/production.js +++ b/lib/middleware/production.js @@ -11,19 +11,16 @@ function production(req, res, next) { // all HTTP requests to HTTPS res.setHeader('Content-Security-Policy', 'upgrade-insecure-requests'); - console.log(req.headers) - // Redirect to HTTPS if page requested over HTTP // This relies on the X-Forwarded-Proto HTTP header as hosts // like Heroku place apps behind a load balance which uses HTTPS // internally. The X-Forwarded-Proto reveals the protocol of the // original request. if (req.headers['x-forwarded-proto'] === 'http') { - return res.redirect(302, 'https://' + req.get('Host') + req.url) + res.redirect(302, `https://${req.get('Host')}${req.url}`); } else { - next() + next(); } - } module.exports = production; From 3c94d5566033f6ed21047a3cacc7acb7893b888f Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 14 Feb 2025 10:29:58 +0000 Subject: [PATCH 17/19] Use the trust proxy setting --- app.js | 6 ++++++ lib/middleware/production.js | 8 ++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app.js b/app.js index db914be74..af4245f95 100755 --- a/app.js +++ b/app.js @@ -151,6 +151,12 @@ app.use(locals(config)); app.set('view engine', 'html'); exampleTemplatesApp.set('view engine', 'html'); +// This setting trusts the X-Forwarded headers set by +// a proxy and uses them to set the standard header in +// req. This is needed for hosts like Heroku. +// See https://expressjs.com/en/guide/behind-proxies.html +app.set('trust proxy', 1); + // Middleware to serve static assets app.use(express.static(path.join(__dirname, 'public'))); app.use('/nhsuk-frontend', express.static(path.join(__dirname, 'node_modules/nhsuk-frontend/packages'))); diff --git a/lib/middleware/production.js b/lib/middleware/production.js index 6014a90d6..3af7f7753 100644 --- a/lib/middleware/production.js +++ b/lib/middleware/production.js @@ -11,12 +11,8 @@ function production(req, res, next) { // all HTTP requests to HTTPS res.setHeader('Content-Security-Policy', 'upgrade-insecure-requests'); - // Redirect to HTTPS if page requested over HTTP - // This relies on the X-Forwarded-Proto HTTP header as hosts - // like Heroku place apps behind a load balance which uses HTTPS - // internally. The X-Forwarded-Proto reveals the protocol of the - // original request. - if (req.headers['x-forwarded-proto'] === 'http') { + // Redirect to HTTP requests to HTTPS + if (req.protocol !== 'https') { res.redirect(302, `https://${req.get('Host')}${req.url}`); } else { next(); From 3e0b6eb751a962731e43eb0b1081bd36f2a53298 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 14 Feb 2025 11:40:06 +0000 Subject: [PATCH 18/19] Fix typo --- lib/middleware/production.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/middleware/production.js b/lib/middleware/production.js index 3af7f7753..5748ad062 100644 --- a/lib/middleware/production.js +++ b/lib/middleware/production.js @@ -11,7 +11,7 @@ function production(req, res, next) { // all HTTP requests to HTTPS res.setHeader('Content-Security-Policy', 'upgrade-insecure-requests'); - // Redirect to HTTP requests to HTTPS + // Redirect HTTP requests to HTTPS if (req.protocol !== 'https') { res.redirect(302, `https://${req.get('Host')}${req.url}`); } else { From 61e44413dbb303d58a0667767bc6b88237604670 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 14 Feb 2025 12:22:14 +0000 Subject: [PATCH 19/19] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af3f30fc7..10c1dbbe6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## [Unreleased] -- Use strict https in production mode - ([PR 410](https://github.com/nhsuk/nhsuk-prototype-kit/pull/410)) +- Use strict https and automatic http to https redirects in production mode - ([PR 410](https://github.com/nhsuk/nhsuk-prototype-kit/pull/410)) ### Updated