diff --git a/src/config/index.ts b/src/config/index.ts index 30740a137..305b57d90 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -55,7 +55,7 @@ export const config = convict({ }, enforceCsrf: { format: Boolean, - default: isProduction, + default: true, env: 'ENFORCE_CSRF' }, diff --git a/src/server/plugins/engine/helpers.test.ts b/src/server/plugins/engine/helpers.test.ts index 264c74ca9..b75d0f537 100644 --- a/src/server/plugins/engine/helpers.test.ts +++ b/src/server/plugins/engine/helpers.test.ts @@ -18,7 +18,6 @@ import { getExponentialBackoffDelay, getPageHref, proceed, - safeGenerateCrumb, setPageTitles, type GlobalScope } from '~/src/server/plugins/engine/helpers.js' @@ -36,7 +35,6 @@ import { import { FormAction, FormStatus, - type FormRequest, type FormResponseToolkit } from '~/src/server/routes/types.js' import definition from '~/test/form/definitions/basic.js' @@ -493,78 +491,6 @@ describe('Helpers', () => { }) }) - describe('safeGenerateCrumb', () => { - it('should return undefined when request.state is missing (malformed request)', () => { - const malformedRequest = { - server: { - plugins: { - crumb: { - generate: jest.fn() - } - } - }, - plugins: {}, - route: { settings: { plugins: {} } }, - path: '/test', - url: { search: '' } - // state intentionally omitted - } as unknown as FormRequest - - const crumbToken = safeGenerateCrumb(malformedRequest) - expect(crumbToken).toBeUndefined() - expect( - malformedRequest.server.plugins.crumb.generate - ).not.toHaveBeenCalled() - }) - - it('should return undefined if crumb is disabled in route settings', () => { - const requestWithDisabledCrumb = { - server: { - plugins: { - crumb: { - generate: jest.fn().mockReturnValue('test-token') - } - } - }, - plugins: {}, - route: { settings: { plugins: { crumb: false } } }, - path: '/test', - url: { search: '' }, - state: {} - } as unknown as FormRequest - - const crumbToken = safeGenerateCrumb(requestWithDisabledCrumb) - expect(crumbToken).toBeUndefined() - expect( - requestWithDisabledCrumb.server.plugins.crumb.generate - ).not.toHaveBeenCalled() - }) - - it('should generate crumb when state exists and crumb plugin is available', () => { - const mockCrumb = 'generated-crumb-value' - const validRequest = { - server: { - plugins: { - crumb: { - generate: jest.fn().mockReturnValue(mockCrumb) - } - } - }, - plugins: {}, - route: { settings: { plugins: {} } }, - path: '/test', - url: { search: '' }, - state: {} - } as unknown as FormRequest - - const crumbToken = safeGenerateCrumb(validRequest) - expect(crumbToken).toBe(mockCrumb) - expect(validRequest.server.plugins.crumb.generate).toHaveBeenCalledWith( - validRequest - ) - }) - }) - describe('getExponentialBackoffDelay', () => { it.each([ { depth: 1, expected: 2000 }, diff --git a/src/server/plugins/engine/helpers.ts b/src/server/plugins/engine/helpers.ts index 2935fcd60..14c060adf 100644 --- a/src/server/plugins/engine/helpers.ts +++ b/src/server/plugins/engine/helpers.ts @@ -25,7 +25,6 @@ import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js' import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js' import { stripParam } from '~/src/server/plugins/engine/pageControllers/helpers/state.js' import { - type AnyFormRequest, type FormContext, type FormContextRequest, type FormSubmissionError @@ -336,32 +335,6 @@ export function createError(componentName: string, message: string) { } } -/** - * A small helper to safely generate a crumb token. - * Checks that the crumb plugin is available, that crumb - * is not disabled on the current route, and that cookies/state are present. - */ -export function safeGenerateCrumb( - request: AnyFormRequest | null -): string | undefined { - // no request or no .state - if (!request?.state) { - return undefined - } - - // crumb plugin or its generate method doesn't exist - if (!request.server.plugins.crumb.generate) { - return undefined - } - - // crumb is explicitly disabled for this route - if (request.route.settings.plugins?.crumb === false) { - return undefined - } - - return request.server.plugins.crumb.generate(request) -} - /** * Calculates an exponential backoff delay (in milliseconds) based on the current retry depth, * using a base delay of 2000ms (2 seconds) and doubling for each additional depth, while capping the delay at 25,000ms (25 seconds). diff --git a/src/server/plugins/nunjucks/context.js b/src/server/plugins/nunjucks/context.js index ef5b9dfca..9634e6720 100644 --- a/src/server/plugins/nunjucks/context.js +++ b/src/server/plugins/nunjucks/context.js @@ -8,8 +8,7 @@ import { config } from '~/src/config/index.js' import { createLogger } from '~/src/server/common/helpers/logging/logger.js' import { checkFormStatus, - encodeUrl, - safeGenerateCrumb + encodeUrl } from '~/src/server/plugins/engine/helpers.js' const logger = createLogger() @@ -51,7 +50,6 @@ export async function context(request) { // take consumers props first so we can override it ...consumerViewContext, baseLayoutPath: pluginStorage.baseLayoutPath, - crumb: safeGenerateCrumb(request), currentPath: `${request.path}${request.url.search}`, previewMode: isPreviewMode ? formState : undefined, slug: isResponseOK ? params?.slug : undefined diff --git a/src/server/plugins/nunjucks/context.test.js b/src/server/plugins/nunjucks/context.test.js index 330e18fbc..f199c2263 100644 --- a/src/server/plugins/nunjucks/context.test.js +++ b/src/server/plugins/nunjucks/context.test.js @@ -101,43 +101,6 @@ describe('Nunjucks context', () => { malformedRequest.server.plugins.crumb.generate ).not.toHaveBeenCalled() }) - - it('should generate crumb when state exists', async () => { - const mockCrumb = 'generated-crumb-value' - const validRequest = /** @type {FormRequest} */ ( - /** @type {unknown} */ ({ - server: { - plugins: { - crumb: { - generate: jest.fn().mockReturnValue(mockCrumb) - }, - 'forms-engine-plugin': { - baseLayoutPath: 'randomValue' - } - } - }, - plugins: {}, - route: { - settings: { - plugins: {} - } - }, - path: '/test', - url: { search: '' }, - state: {}, - yar: { - flash: jest.fn().mockReturnValue([]), - commit: jest.fn() - } - }) - ) - - const { crumb } = await context(validRequest) - expect(crumb).toBe(mockCrumb) - expect(validRequest.server.plugins.crumb.generate).toHaveBeenCalledWith( - validRequest - ) - }) }) }) diff --git a/src/server/routes/dummy-api.test.ts b/src/server/routes/dummy-api.test.ts index 58c727da0..4c84ca534 100644 --- a/src/server/routes/dummy-api.test.ts +++ b/src/server/routes/dummy-api.test.ts @@ -21,7 +21,9 @@ describe('Dummy API', () => { beforeAll(async () => { MockDate.set('2025-01-01T00:00:00Z') - server = await createServer() + server = await createServer({ + enforceCsrf: false + }) await server.initialize() }) diff --git a/test/condition/checkboxes.test.js b/test/condition/checkboxes.test.js index 4e3f943f3..32acd362c 100644 --- a/test/condition/checkboxes.test.js +++ b/test/condition/checkboxes.test.js @@ -21,7 +21,8 @@ describe('Checkboxes based conditions', () => { beforeAll(async () => { server = await createServer({ formFileName: 'checkboxes.json', - formFilePath: resolve(import.meta.dirname, '../form/definitions') + formFilePath: resolve(import.meta.dirname, '../form/definitions'), + enforceCsrf: false }) await server.initialize() diff --git a/test/condition/radios.test.js b/test/condition/radios.test.js index 4290eab42..2db41cd59 100644 --- a/test/condition/radios.test.js +++ b/test/condition/radios.test.js @@ -21,7 +21,8 @@ describe('Radio based conditions', () => { beforeAll(async () => { server = await createServer({ formFileName: 'radios.json', - formFilePath: resolve(import.meta.dirname, '../form/definitions') + formFilePath: resolve(import.meta.dirname, '../form/definitions'), + enforceCsrf: false }) await server.initialize() diff --git a/test/condition/text.test.js b/test/condition/text.test.js index 994990b1a..32a8cafd1 100644 --- a/test/condition/text.test.js +++ b/test/condition/text.test.js @@ -21,7 +21,8 @@ describe('TextField based conditions', () => { beforeAll(async () => { server = await createServer({ formFileName: 'text.json', - formFilePath: resolve(import.meta.dirname, '../form/definitions') + formFilePath: resolve(import.meta.dirname, '../form/definitions'), + enforceCsrf: false }) await server.initialize() diff --git a/test/form/component-state-errors.test.js b/test/form/component-state-errors.test.js index d80c1dbec..cbcc8b633 100644 --- a/test/form/component-state-errors.test.js +++ b/test/form/component-state-errors.test.js @@ -53,7 +53,8 @@ describe('Component State Error Tests - File Upload', () => { beforeAll(async () => { server = await createServer({ formFileName: 'file-upload-basic.js', - formFilePath: join(import.meta.dirname, 'definitions') + formFilePath: join(import.meta.dirname, 'definitions'), + enforceCsrf: false }) await server.initialize() diff --git a/test/form/file-upload.test.js b/test/form/file-upload.test.js index 7d84a7288..6b85bcffd 100644 --- a/test/form/file-upload.test.js +++ b/test/form/file-upload.test.js @@ -174,7 +174,8 @@ describe('File upload POST tests', () => { beforeAll(async () => { server = await createServer({ formFileName: 'file-upload.js', - formFilePath: resolve(import.meta.dirname, '../form/definitions') + formFilePath: resolve(import.meta.dirname, '../form/definitions'), + enforceCsrf: false }) await server.initialize() diff --git a/test/form/govuk-notify.test.js b/test/form/govuk-notify.test.js index 22efc6aa1..8b83b2db1 100644 --- a/test/form/govuk-notify.test.js +++ b/test/form/govuk-notify.test.js @@ -74,7 +74,8 @@ describe('Submission journey test', () => { beforeAll(async () => { server = await createServer({ formFileName: 'components.json', - formFilePath: join(import.meta.dirname, 'definitions') + formFilePath: join(import.meta.dirname, 'definitions'), + enforceCsrf: false }) await server.initialize() diff --git a/test/form/joined-conditions.test.js b/test/form/joined-conditions.test.js index fe136a4c8..b4c96acea 100644 --- a/test/form/joined-conditions.test.js +++ b/test/form/joined-conditions.test.js @@ -20,7 +20,8 @@ describe('Joined conditions functional tests', () => { beforeAll(async () => { server = await createServer({ formFileName: 'joined-conditions-simple-v2.js', - formFilePath: resolve(import.meta.dirname, 'definitions') + formFilePath: resolve(import.meta.dirname, 'definitions'), + enforceCsrf: false }) await server.initialize() }) @@ -270,7 +271,8 @@ describe('Joined conditions functional tests', () => { beforeAll(async () => { server = await createServer({ formFileName: 'joined-conditions-complex-v2.js', - formFilePath: resolve(import.meta.dirname, 'definitions') + formFilePath: resolve(import.meta.dirname, 'definitions'), + enforceCsrf: false }) await server.initialize() }) diff --git a/test/form/persist-files.test.js b/test/form/persist-files.test.js index 37f893820..1128df34a 100644 --- a/test/form/persist-files.test.js +++ b/test/form/persist-files.test.js @@ -75,7 +75,8 @@ describe('Submission journey test', () => { beforeAll(async () => { server = await createServer({ formFileName: 'file-upload-basic.js', - formFilePath: join(import.meta.dirname, 'definitions') + formFilePath: join(import.meta.dirname, 'definitions'), + enforceCsrf: false }) await server.initialize() diff --git a/test/form/repeat.test.js b/test/form/repeat.test.js index 77602d37f..0d3573048 100644 --- a/test/form/repeat.test.js +++ b/test/form/repeat.test.js @@ -90,7 +90,8 @@ describe('Repeat GET tests', () => { beforeAll(async () => { server = await createServer({ formFileName: 'repeat.js', - formFilePath: resolve(import.meta.dirname, '../form/definitions') + formFilePath: resolve(import.meta.dirname, '../form/definitions'), + enforceCsrf: false }) const model = server.app.model @@ -363,7 +364,8 @@ describe('Repeat POST tests', () => { server = await createServer({ formFileName: 'repeat.js', formFilePath: resolve(import.meta.dirname, '../form/definitions'), - saveAndExit: (request, h, _context) => h.redirect('/my-save-and-exit') + saveAndExit: (request, h, _context) => h.redirect('/my-save-and-exit'), + enforceCsrf: false }) const model = server.app.model