Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@aws-sdk/client-sns": "^3.997.0",
"@defra/forms-engine-plugin": "^4.11.3",
"@defra/forms-engine-plugin": "^4.12.0",
"@defra/forms-model": "^3.0.665",
"@defra/hapi-tracing": "^1.30.0",
"@elastic/ecs-pino-format": "^1.5.0",
Expand Down
9 changes: 2 additions & 7 deletions src/server/plugins/error-preview/error-preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ import { FormStatus } from '@defra/forms-engine-plugin/types'
import Boom from '@hapi/boom'

import { createErrorPreviewModel } from '~/src/server/plugins/error-preview/error-preview-helper.js'
import {
getFormDefinition,
getFormMetadata
} from '~/src/server/services/formsService.js'
import { getFormMetadata } from '~/src/server/services/formMetadataGuards.js'
import { getFormDefinition } from '~/src/server/services/formsService.js'

/**
* @param {FormRequest} request
Expand All @@ -15,10 +13,7 @@ export async function getErrorPreviewHandler(request, h) {
const { params } = request
const { slug, path, itemId } = params

// Get the form metadata using the `slug` param
const metadata = await getFormMetadata(slug)

// Get the form definition using the `id` from the metadata
const definition = await getFormDefinition(metadata.id, FormStatus.Draft)
if (!definition) {
throw Boom.notFound(
Expand Down
16 changes: 10 additions & 6 deletions src/server/plugins/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,8 @@ import {
publicRoutes,
saveAndExitRoutes
} from '~/src/server/routes/index.js'
import {
getFormDefinition,
getFormMetadata
} from '~/src/server/services/formsService.js'
import { getFormMetadata } from '~/src/server/services/formMetadataGuards.js'
import { getFormDefinition } from '~/src/server/services/formsService.js'
import { getFeedbackFormLink } from '~/src/server/utils/utils.js'

const routes: ServerRoute[] = [...publicRoutes, healthRoute]
Expand Down Expand Up @@ -170,10 +168,13 @@ export default {
options
})

server.route({
server.route<{ Params: { slug: string } }>({
method: 'get',
path: '/help/cookies/{slug}',
async handler(request, h) {
const { slug } = request.params
await getFormMetadata(slug)

const sessionTimeout = config.get('sessionTimeout')

const sessionDurationPretty = humanizeDuration(sessionTimeout)
Expand Down Expand Up @@ -307,7 +308,10 @@ export default {
server.route<{ Params: { slug: string } }>({
method: 'get',
path: '/help/accessibility-statement/{slug}',
handler(_request, h) {
async handler(request, h) {
const { slug } = request.params
await getFormMetadata(slug)

return h.view('help/accessibility-statement')
},
options
Expand Down
3 changes: 3 additions & 0 deletions src/server/routes/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ describe('Routes', () => {
})

test('cookies page is served with 24 hour duration and GA info', async () => {
jest.mocked(getFormMetadata).mockResolvedValue(fixtures.form.metadata)
config.set('sessionTimeout', 86400000)
config.set('googleTagManagerContainerId', 'GTM-XXXXXXXX')
config.set('googleAnalyticsContainerId', 'YYYYYYYYYY')
Expand Down Expand Up @@ -62,6 +63,7 @@ describe('Routes', () => {
})

test('cookies page is served without GA info', async () => {
jest.mocked(getFormMetadata).mockResolvedValue(fixtures.form.metadata)
config.set('sessionTimeout', 86400000)
config.set('googleTagManagerContainerId', '')
config.set('googleAnalyticsContainerId', '')
Expand Down Expand Up @@ -97,6 +99,7 @@ describe('Routes', () => {
})

test('accessibility statement page is served', async () => {
jest.mocked(getFormMetadata).mockResolvedValue(fixtures.form.metadata)
const options = {
method: 'GET',
url: '/help/accessibility-statement/slug'
Expand Down
69 changes: 56 additions & 13 deletions src/server/routes/save-and-exit.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ import {
import {
getFormMetadata,
getFormMetadataById,
isOfflineBoom
} from '~/src/server/services/formMetadataGuards.js'
import {
getSaveAndExitDetails,
validateSaveAndExitCredentials
} from '~/src/server/services/formsService.js'
Expand Down Expand Up @@ -148,7 +151,10 @@ export default [
const { params, payload } = request
const { slug, state: status } = params
const { email, securityQuestion, securityAnswer } = payload
// Throws the offline marker BEFORE publishSaveAndExitEvent so we never
// emit a magic-link email for a form the user can no longer reach.
const metadata = await getFormMetadata(slug)

const cacheService = getCacheService(request.server)

// Publish topic message
Expand Down Expand Up @@ -201,6 +207,7 @@ export default [
const { params, payload } = request
const { slug, state: status } = params
const metadata = await getFormMetadata(slug)

const model = detailsViewModel(
metadata,
status,
Expand Down Expand Up @@ -255,11 +262,15 @@ export default [
const { params } = request
const { formId, magicLinkId } = params

// Check form id
// Asserts the form is online BEFORE looking up the magic link, so we
// don't reveal link validity timing for offline forms.
let form
try {
form = await getFormMetadataById(formId)
} catch (err) {
if (isOfflineBoom(err)) {
throw err
}
logger.error(
err,
`Invalid formId ${formId} in magic link id ${magicLinkId}`
Expand Down Expand Up @@ -338,24 +349,29 @@ export default [
async handler(request, h) {
const { params } = request
const { formId, magicLinkId } = params
const resumeDetails = await getSaveAndExitDetails(magicLinkId)

if (!resumeDetails) {
return h.redirect(ERROR_BASE_URL)
}

// Check form id
// Assert the form is online BEFORE looking up save-and-exit details so
// we don't leak magic-link validity timing for offline forms.
let form
try {
form = await getFormMetadataById(resumeDetails.form.id)
form = await getFormMetadataById(formId)
} catch (err) {
if (isOfflineBoom(err)) {
throw err
}
logger.error(
err,
`Invalid formId ${formId} in magic link id ${magicLinkId}`
)
return h.redirect(ERROR_BASE_URL)
}

const resumeDetails = await getSaveAndExitDetails(magicLinkId)

if (!resumeDetails) {
return h.redirect(ERROR_BASE_URL)
}

const model = passwordViewModel(
form,
resumeDetails.question,
Expand All @@ -376,9 +392,25 @@ export default [
({
method: 'GET',
path: '/resume-form-error/{slug?}',
handler(request, h) {
async handler(request, h) {
const { params } = request
const { slug } = params

if (slug) {
try {
await getFormMetadata(slug)
} catch (err) {
if (isOfflineBoom(err)) {
throw err
}
// Fall through to the existing error view if metadata can't be fetched.
logger.info(
{ err },
`Could not load metadata for resume-form-error slug ${slug}; rendering generic error view`
)
}
}

const model = resumeErrorViewModel({ slug })

return h.view(RESUME_ERROR, model)
Expand All @@ -404,15 +436,25 @@ export default [
const { formId, magicLinkId } = params
const { securityAnswer } = payload

// Validate the security answer
let form
try {
form = await getFormMetadataById(formId)
} catch (err) {
if (isOfflineBoom(err)) {
throw err
}
logger.error(
err,
`Invalid formId ${formId} in magic link id ${magicLinkId}`
)
return h.redirect(ERROR_BASE_URL)
}

const validatedLink = await validateSaveAndExitCredentials(
magicLinkId,
securityAnswer
)

// Reload form title in case it has changed
const form = await getFormMetadataById(formId)

if (validatedLink.validPassword) {
// Restore state
const cacheService = getCacheService(request.server)
Expand Down Expand Up @@ -499,6 +541,7 @@ export default [
const { params } = request
const { slug, state } = params
const form = await getFormMetadata(slug)

const model = resumeSuccessViewModel(
form,
/** @type {FormStatus | undefined} */ (state)
Expand Down
Loading
Loading