diff --git a/api.planx.uk/modules/flows/findReplace/controller.ts b/api.planx.uk/modules/flows/findReplace/controller.ts
index 73d7802a42..c64387dc64 100644
--- a/api.planx.uk/modules/flows/findReplace/controller.ts
+++ b/api.planx.uk/modules/flows/findReplace/controller.ts
@@ -4,12 +4,6 @@ import { z } from "zod";
import { ServerError } from "../../../errors";
import { findAndReplaceInFlow } from "./service";
import { FlowGraph } from "@opensystemslab/planx-core/types";
-import { JSDOM } from "jsdom";
-import createDOMPurify from "dompurify";
-
-// Setup JSDOM and DOMPurify
-const window = new JSDOM("").window;
-const DOMPurify = createDOMPurify(window);
interface FindAndReplaceResponse {
message: string;
@@ -23,12 +17,7 @@ export const findAndReplaceSchema = z.object({
}),
query: z.object({
find: z.string(),
- replace: z
- .string()
- .optional()
- .transform(
- (val) => val && DOMPurify.sanitize(val, { ADD_ATTR: ["target"] }),
- ),
+ replace: z.string().optional(),
}),
});
diff --git a/api.planx.uk/modules/flows/findReplace/findReplace.test.ts b/api.planx.uk/modules/flows/findReplace/findReplace.test.ts
index 1a3ee89927..962fc88744 100644
--- a/api.planx.uk/modules/flows/findReplace/findReplace.test.ts
+++ b/api.planx.uk/modules/flows/findReplace/findReplace.test.ts
@@ -265,279 +265,3 @@ describe("string replacement", () => {
});
});
});
-
-describe("HTML replacement", () => {
- const originalHTML = ``;
- const mockFlowData: Flow["data"] = {
- _root: {
- edges: ["RRQwM2zAgy", "vcTgmVQAre", "QsEdip17H5"],
- },
- "6dwuQp5xjA": {
- data: {
- text: "No",
- },
- type: 200,
- },
- "8AWcYxZgBw": {
- data: {
- fn: "property.constraints.planning",
- text: "Is it monument inside a portal?",
- },
- type: 100,
- edges: ["AJnWX6O1xt", "6dwuQp5xjA"],
- },
- AJnWX6O1xt: {
- data: {
- val: "designated.monument",
- text: "Yes",
- description: originalHTML,
- },
- type: 200,
- },
- Hfh8KuSzUq: {
- data: {
- val: "designated.monument",
- text: "Yes",
- description: originalHTML,
- },
- type: 200,
- },
- RRQwM2zAgy: {
- data: {
- fn: "property.constraints.planning",
- text: "Is it a monument",
- },
- type: 100,
- edges: ["Hfh8KuSzUq", "ft26KlH7Oy"],
- },
- ft26KlH7Oy: {
- data: {
- text: "No",
- },
- type: 200,
- },
- vcTgmVQAre: {
- data: {
- text: "internal-portal-test",
- },
- type: 300,
- edges: ["8AWcYxZgBw"],
- },
- QsEdip17H5: {
- type: 310,
- data: {
- flowId: "f54b6505-c352-4fbc-aca3-7c4be99b49d4",
- },
- },
- };
-
- beforeEach(() => {
- queryMock.mockQuery({
- name: "GetFlowData",
- matchOnVariables: false,
- data: {
- flow: {
- data: mockFlowData,
- slug: "test",
- },
- },
- });
- });
-
- describe("Replacing unsafe HTML", () => {
- const unsafeHTML = "";
- const safeHTML = '';
-
- const replacedFlowData: Flow["data"] = {
- _root: {
- edges: ["RRQwM2zAgy", "vcTgmVQAre", "QsEdip17H5"],
- },
- "6dwuQp5xjA": {
- data: {
- text: "No",
- },
- type: 200,
- },
- "8AWcYxZgBw": {
- data: {
- fn: "property.constraints.planning",
- text: "Is it monument inside a portal?",
- },
- type: 100,
- edges: ["AJnWX6O1xt", "6dwuQp5xjA"],
- },
- AJnWX6O1xt: {
- data: {
- val: "monument",
- text: "Yes",
- description: safeHTML,
- },
- type: 200,
- },
- Hfh8KuSzUq: {
- data: {
- val: "monument",
- text: "Yes",
- description: safeHTML,
- },
- type: 200,
- },
- RRQwM2zAgy: {
- data: {
- fn: "property.constraints.planning",
- text: "Is it a monument",
- },
- type: 100,
- edges: ["Hfh8KuSzUq", "ft26KlH7Oy"],
- },
- ft26KlH7Oy: {
- data: {
- text: "No",
- },
- type: 200,
- },
- vcTgmVQAre: {
- data: {
- text: "internal-portal-test",
- },
- type: 300,
- edges: ["8AWcYxZgBw"],
- },
- QsEdip17H5: {
- type: 310,
- data: {
- flowId: "f54b6505-c352-4fbc-aca3-7c4be99b49d4",
- },
- },
- };
-
- beforeEach(() => {
- queryMock.mockQuery({
- name: "UpdateFlow",
- matchOnVariables: false,
- data: {
- flow: {
- data: replacedFlowData,
- slug: "test",
- },
- },
- });
- });
-
- it("sanitises unsafe replace values", async () => {
- await supertest(app)
- .post(`/flows/2/search?find=${originalHTML}&replace=${unsafeHTML}`)
- .set(auth)
- .expect(200)
- .then((res) => {
- expect(res.body).toEqual({
- message:
- 'Found 2 matches of "" and replaced with ""',
- matches: {
- AJnWX6O1xt: {
- data: {
- description: '',
- },
- },
- Hfh8KuSzUq: {
- data: {
- description: '',
- },
- },
- },
- updatedFlow: replacedFlowData,
- });
- });
- });
- });
-
- describe("Replacing safe HTML", () => {
- const replacementHTML = ``;
- const replacedFlowData: Flow["data"] = {
- _root: {
- edges: ["RRQwM2zAgy", "vcTgmVQAre", "QsEdip17H5"],
- },
- "6dwuQp5xjA": {
- data: {
- text: "No",
- },
- type: 200,
- },
- "8AWcYxZgBw": {
- data: {
- fn: "property.constraints.planning",
- text: "Is it monument inside a portal?",
- },
- type: 100,
- edges: ["AJnWX6O1xt", "6dwuQp5xjA"],
- },
- AJnWX6O1xt: {
- data: {
- val: "monument",
- text: "Yes",
- description: replacementHTML,
- },
- type: 200,
- },
- Hfh8KuSzUq: {
- data: {
- val: "monument",
- text: "Yes",
- description: replacementHTML,
- },
- type: 200,
- },
- RRQwM2zAgy: {
- data: {
- fn: "property.constraints.planning",
- text: "Is it a monument",
- },
- type: 100,
- edges: ["Hfh8KuSzUq", "ft26KlH7Oy"],
- },
- ft26KlH7Oy: {
- data: {
- text: "No",
- },
- type: 200,
- },
- vcTgmVQAre: {
- data: {
- text: "internal-portal-test",
- },
- type: 300,
- edges: ["8AWcYxZgBw"],
- },
- QsEdip17H5: {
- type: 310,
- data: {
- flowId: "f54b6505-c352-4fbc-aca3-7c4be99b49d4",
- },
- },
- };
-
- beforeEach(() => {
- queryMock.mockQuery({
- name: "UpdateFlow",
- matchOnVariables: false,
- data: {
- flow: {
- data: replacedFlowData,
- slug: "test",
- },
- },
- });
- });
-
- it("does not remove the 'target' attribute from anchors", async () => {
- await supertest(app)
- .post(`/flows/2/search?find=${originalHTML}&replace=${replacementHTML}`)
- .set(auth)
- .expect(200)
- .then((res) => {
- // Checking substring as DOMPurify rearranges attribute order in a non-deterministic manner
- expect(res.body.message).toMatch(/target=["]\\?_blank\\?["]/);
- });
- });
- });
-});