diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e1e3ec39..10c1dbbe6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## [Unreleased] +- Use strict https and automatic http to https redirects in production mode - ([PR 410](https://github.com/nhsuk/nhsuk-prototype-kit/pull/410)) + ### Updated - Updated .devcontainer to remove the image attribute ([PR 451](https://github.com/nhsuk/nhsuk-prototype-kit/pull/451)) diff --git a/app.js b/app.js index 6c9f59dc8..af4245f95 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'); @@ -77,8 +78,10 @@ const sessionOptions = { }, }; -// Authentication -app.use(authentication); +if (process.env.NODE_ENV === 'production') { + app.use(production); + app.use(authentication); +} // Support session data in cookie or memory if (useCookieSessionStore === 'true') { @@ -148,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/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..5748ad062 --- /dev/null +++ b/lib/middleware/production.js @@ -0,0 +1,22 @@ +// 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'); + + // Redirect HTTP requests to HTTPS + if (req.protocol !== 'https') { + res.redirect(302, `https://${req.get('Host')}${req.url}`); + } else { + next(); + } +} + +module.exports = production; 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 () {