Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent management pages using "plugin" GOV.UK Frontend views #2355

Merged
merged 8 commits into from
Oct 11, 2023
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Fixes

- [#2355: Prevent management pages using "plugin" GOV.UK Frontend views](https://github.com/alphagov/govuk-prototype-kit/pull/2355)

## 13.13.4

### Fixes
Expand Down
2 changes: 1 addition & 1 deletion lib/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ function sassKitFrontendDependency () {
const govukFrontendInternal = govukFrontendPaths([packageDir, projectDir])

// Get GOV.UK Frontend (internal) stylesheets
const govukFrontendSass = (govukFrontendInternal.config?.sass || [])
const govukFrontendSass = [govukFrontendInternal.config.sass].flat()
.map(sassPath => path.join(govukFrontendInternal.baseDir, sassPath))

const fileContents = sassVariables('/manage-prototype/dependencies') +
Expand Down
12 changes: 4 additions & 8 deletions lib/errorServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,17 +102,13 @@ function runErrorServer (error) {
res.setHeader('Content-Type', 'text/html')
res.writeHead(500)

// Get GOV.UK Frontend (internal) views
const govukFrontendNunjucksPaths = (govukFrontendInternal.config?.nunjucksPaths || [])
.map(nunjucksPath => path.join(govukFrontendInternal.baseDir, nunjucksPath))

const fileContentsParts = []

try {
const nunjucksAppEnv = getNunjucksAppEnv([
path.join(__dirname, 'nunjucks'),
...govukFrontendNunjucksPaths
])
const nunjucksAppEnv = getNunjucksAppEnv(
[path.join(__dirname, 'nunjucks')],
govukFrontendInternal // Add GOV.UK Frontend paths to Nunjucks views
)
res.end(nunjucksAppEnv.render('views/error-handling/server-error', {
govukFrontendInternal, // Add GOV.UK Frontend paths to Nunjucks context
...getErrorModel(error)
Expand Down
19 changes: 15 additions & 4 deletions lib/govukFrontendPaths.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const fse = require('fs-extra')
* Find GOV.UK Frontend via search paths
*
* @param {string[]} searchPaths - Search paths for `require.resolve()`
* @returns {{ baseDir: string, includePath: string, assetPath: string, config: { [key: string]: unknown } }}
* @returns {GOVUKFrontendPaths}
*/
function govukFrontendPaths (searchPaths = []) {
/**
Expand All @@ -29,12 +29,23 @@ function govukFrontendPaths (searchPaths = []) {
assetPath: `/${path.relative(baseDir, path.join(includeDir, 'assets'))}`,

// GOV.UK Frontend plugin config
config: fse.readJsonSync(path.join(baseDir, 'govuk-prototype-kit.config.json'), {
throws: false
})
config: fse.readJsonSync(path.join(baseDir, 'govuk-prototype-kit.config.json'), { throws: false }) ?? {
nunjucksPaths: [],
sass: []
}
}
}

module.exports = {
govukFrontendPaths
}

/**
* GOV.UK Frontend paths object
*
* @typedef {object} GOVUKFrontendPaths
* @property {string} baseDir - GOV.UK Frontend directory path
* @property {URL["pathname"]} includePath - URL path to GOV.UK Frontend includes
* @property {URL["pathname"]} assetPath - URL path to GOV.UK Frontend assets
* @property {{ [key: string]: unknown }} config - GOV.UK Frontend plugin config
*/
44 changes: 30 additions & 14 deletions lib/manage-prototype-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const { doubleCsrf } = require('csrf-csrf')
const config = require('./config')
const plugins = require('./plugins/plugins')
const { exec } = require('./exec')
const { govukFrontendPaths } = require('./govukFrontendPaths')
const { prototypeAppScripts } = require('./utils')
const { projectDir, packageDir, appViewsDir } = require('./utils/paths')
const nunjucksConfiguration = require('./nunjucks/nunjucksConfiguration')
Expand All @@ -31,6 +32,13 @@ const appViews = plugins.getAppViews([
path.join(packageDir, 'lib/final-backup-nunjucks')
])

// Nunjucks environment for management pages skips `getAppViews()` to
// avoid plugins but adds GOV.UK Frontend views via internal package
const nunjucksManagementEnv = nunjucksConfiguration.getNunjucksAppEnv(
[path.join(__dirname, 'nunjucks')],
govukFrontendPaths([packageDir, projectDir])
)

let kitRestarted = false

const {
Expand Down Expand Up @@ -83,19 +91,19 @@ function getCsrfTokenHandler (req, res) {

// Clear all data in session
function getClearDataHandler (req, res) {
res.render(getManagementView('clear-data.njk'))
res.send(nunjucksManagementEnv.render(getManagementView('clear-data.njk'), req.app.locals))
}

function postClearDataHandler (req, res) {
req.session.data = {}
res.render(getManagementView('clear-data-success.njk'))
res.send(nunjucksManagementEnv.render(getManagementView('clear-data-success.njk')))
}

// Render password page with a returnURL to redirect people to where they came from
function getPasswordHandler (req, res) {
const returnURL = req.query.returnURL || '/'
const error = req.query.error
res.render(getManagementView('password.njk'), { returnURL, error })
res.send(nunjucksManagementEnv.render(getManagementView('password.njk'), { ...req.app.locals, returnURL, error }))
}

// Check authentication password
Expand Down Expand Up @@ -125,7 +133,7 @@ function developmentOnlyMiddleware (req, res, next) {
if (config.getConfig().isDevelopment || req.url.startsWith('/dependencies/govuk-frontend')) {
next()
} else {
res.render(getManagementView('manage-prototype-not-available.njk'))
res.send(nunjucksManagementEnv.render(getManagementView('manage-prototype-not-available.njk'), req.app.locals))
}
}

Expand Down Expand Up @@ -173,6 +181,7 @@ async function getHomeHandler (req, res) {
const kitPackage = await lookupPackageInfo('govuk-prototype-kit')

const viewData = {
...req.app.locals,
currentUrl: req.originalUrl,
currentSection: pageName,
links: managementLinks,
Expand All @@ -189,7 +198,7 @@ async function getHomeHandler (req, res) {
]
}

res.render(getManagementView('index.njk'), viewData)
res.send(nunjucksManagementEnv.render(getManagementView('index.njk'), viewData))
}

function exampleTemplateConfig (packageName, { name, path }) {
Expand Down Expand Up @@ -242,12 +251,13 @@ async function getTemplatesHandler (req, res) {
}
}

res.render(getManagementView('templates.njk'), {
res.send(nunjucksManagementEnv.render(getManagementView('templates.njk'), {
...req.app.locals,
currentSection: pageName,
links: managementLinks,
availableTemplates,
commonTemplatesDetails
})
}))
}

function locateTemplateConfig (req) {
Expand Down Expand Up @@ -279,6 +289,8 @@ function getTemplatesViewHandler (req, res) {
}
const templateConfig = locateTemplateConfig(req)

// Nunjucks environment for template previews uses `getAppViews()` to
// add plugins including GOV.UK Frontend views via project package
const nunjucksAppEnv = nunjucksConfiguration.getNunjucksAppEnv(appViews)

if (templateConfig) {
Expand All @@ -292,7 +304,8 @@ function getTemplatesInstallHandler (req, res) {
const templateConfig = locateTemplateConfig(req)

if (templateConfig) {
res.render(getManagementView('template-install.njk'), {
res.send(nunjucksManagementEnv.render(getManagementView('template-install.njk'), {
...req.app.locals,
currentSection: 'Templates',
pageName: `Create new ${templateConfig.name}`,
currentUrl: req.originalUrl,
Expand All @@ -307,7 +320,7 @@ function getTemplatesInstallHandler (req, res) {
invalid: 'Path must not include !$&\'()*+,;=:?#[]@.% or space'
})[req.query.errorType],
chosenUrl: req.query['chosen-url']
})
}))
} else {
res.status(404).send('Template not found.')
}
Expand Down Expand Up @@ -381,13 +394,14 @@ function getTemplatesPostInstallHandler (req, res) {
const pageName = 'Page created'
const chosenUrl = req.query['chosen-url']

res.render(getManagementView('template-post-install.njk'), {
res.send(nunjucksManagementEnv.render(getManagementView('template-post-install.njk'), {
...req.app.locals,
currentSection: 'Templates',
pageName,
links: managementLinks,
url: chosenUrl,
filePath: path.join('app', 'views', `${chosenUrl}.${getFileExtensionForNunjucksFiles()}`)
})
}))
}

function buildPluginData (pluginData) {
Expand Down Expand Up @@ -510,6 +524,7 @@ async function getPluginsHandler (req, res) {
const foundMessage = found === 1 ? found + ' Plugin found' : found + ' Plugins found'
const updatesMessage = updates ? updates === 1 ? updates + ' UPDATE AVAILABLE' : updates + ' UPDATES AVAILABLE' : ''
const model = {
...req.app.locals,
currentSection: pageName,
links: managementLinks,
isInstalledPage,
Expand All @@ -520,7 +535,7 @@ async function getPluginsHandler (req, res) {
foundMessage,
status
}
res.render(getManagementView('plugins.njk'), model)
res.send(nunjucksManagementEnv.render(getManagementView('plugins.njk'), model))
}

async function postPluginsHandler (req, res) {
Expand Down Expand Up @@ -615,7 +630,8 @@ async function getPluginsModeHandler (req, res) {
dependencyHeading = `${fullPluginName} needs other plugins`
}

res.render(getManagementView('plugin-install-or-uninstall.njk'), {
res.send(nunjucksManagementEnv.render(getManagementView('plugin-install-or-uninstall.njk'), {
...req.app.locals,
currentSection: 'Plugins',
pageName,
currentUrl: req.originalUrl,
Expand All @@ -627,7 +643,7 @@ async function getPluginsModeHandler (req, res) {
verb,
isSameOrigin,
returnLink
})
}))
}

function setKitRestarted (state) {
Expand Down
Loading