From 4f77ffa1a9fb23bbe9e92097f4b988a519768dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Thu, 18 Jan 2024 16:32:35 +0000 Subject: [PATCH] fix: Handle CORS for map docs URLs --- .../modules/ordnanceSurvey/controller.ts | 25 +------ .../ordnanceSurvey/ordnanceSurvey.test.ts | 73 ------------------- api.planx.uk/server.ts | 12 ++- 3 files changed, 11 insertions(+), 99 deletions(-) diff --git a/api.planx.uk/modules/ordnanceSurvey/controller.ts b/api.planx.uk/modules/ordnanceSurvey/controller.ts index d950eb36ab..41508db02b 100644 --- a/api.planx.uk/modules/ordnanceSurvey/controller.ts +++ b/api.planx.uk/modules/ordnanceSurvey/controller.ts @@ -4,37 +4,16 @@ import { IncomingMessage } from "http"; export const OS_DOMAIN = "https://api.os.uk"; -const MAP_ALLOWLIST: RegExp[] = [ - // Local development - /^http:\/\/(127\.0\.0\.1|localhost):(3000|5173|6006|7007)\/$/i, - // Documentation - /^https:\/\/.*\.netlify\.app\/$/i, - // PlanX - /^https:\/\/.*planx\.(pizza|dev|uk)\/$/i, - // Custom domains - /^https:\/\/.*(\.gov\.uk\/)$/i, -]; - export const useOrdnanceSurveyProxy = async ( req: Request, res: Response, next: NextFunction, -) => { - if (!isValid(req)) - return next({ - status: 401, - message: "Unauthorised", - }); - - return useProxy({ +) => + useProxy({ target: OS_DOMAIN, onProxyRes: (proxyRes) => setCORPHeaders(proxyRes), pathRewrite: (fullPath, req) => appendAPIKey(fullPath, req), })(req, res, next); -}; - -const isValid = (req: Request): boolean => - MAP_ALLOWLIST.some((re) => re.test(req.headers?.referer as string)); const setCORPHeaders = (proxyRes: IncomingMessage): void => { proxyRes.headers["Cross-Origin-Resource-Policy"] = "cross-origin"; diff --git a/api.planx.uk/modules/ordnanceSurvey/ordnanceSurvey.test.ts b/api.planx.uk/modules/ordnanceSurvey/ordnanceSurvey.test.ts index eebd817e33..56e38d548c 100644 --- a/api.planx.uk/modules/ordnanceSurvey/ordnanceSurvey.test.ts +++ b/api.planx.uk/modules/ordnanceSurvey/ordnanceSurvey.test.ts @@ -17,7 +17,6 @@ describe("Ordnance Survey proxy endpoint", () => { .reply(200, { test: "returned tile" }); await get(ENDPOINT + TILE_PATH) - .set({ referer: "https://123.planx.pizza/" }) .expect(200) .then((response) => { expect(response.body).toEqual({ @@ -33,7 +32,6 @@ describe("Ordnance Survey proxy endpoint", () => { .reply(200, { test: "returned tile" }); await get(ENDPOINT + TILE_PATH + "?srs=3857") - .set({ referer: "https://www.planx.dev/" }) .expect(200) .then((response) => { expect(response.body).toEqual({ @@ -49,7 +47,6 @@ describe("Ordnance Survey proxy endpoint", () => { .reply(401, { test: "failed request" }); await get(ENDPOINT + TILE_PATH) - .set({ referer: "https://www.planx.uk/" }) .expect(401) .then((response) => { expect(response.body).toEqual({ @@ -57,76 +54,6 @@ describe("Ordnance Survey proxy endpoint", () => { }); }); }); - - describe("CORS functionality", () => { - it("blocks requests which are not from a valid referrer", async () => { - await get(ENDPOINT + TILE_PATH) - .set({ referer: "https://www.invalid-site.com/" }) - .expect(401) - .then((response) => { - expect(response.body).toEqual({ - error: "Unauthorised", - }); - }); - }); - - it("allows requests from allow-listed URLs", async () => { - nock(OS_DOMAIN) - .get(TILE_PATH) - .query({ key: process.env.ORDNANCE_SURVEY_API_KEY }) - .reply(200, { test: "returned tile" }); - - await get(ENDPOINT + TILE_PATH) - .set({ referer: "https://oslmap.netlify.app/" }) - .expect(200) - .then((response) => { - expect(response.body).toEqual({ - test: "returned tile", - }); - expect(response.headers["cross-origin-resource-policy"]).toEqual( - "cross-origin", - ); - }); - }); - - it("allows requests from PlanX", async () => { - nock(OS_DOMAIN) - .get(TILE_PATH) - .query({ key: process.env.ORDNANCE_SURVEY_API_KEY }) - .reply(200, { test: "returned tile" }); - - await get(ENDPOINT + TILE_PATH) - .set({ referer: "https://www.planx.dev/" }) - .expect(200) - .then((response) => { - expect(response.body).toEqual({ - test: "returned tile", - }); - expect(response.headers["cross-origin-resource-policy"]).toEqual( - "cross-origin", - ); - }); - }); - - it("allows requests from custom domains", async () => { - nock(OS_DOMAIN) - .get(TILE_PATH) - .query({ key: process.env.ORDNANCE_SURVEY_API_KEY }) - .reply(200, { test: "returned tile" }); - - await get(ENDPOINT + TILE_PATH) - .set({ referer: "https://planningservices.buckinghamshire.gov.uk/" }) - .expect(200) - .then((response) => { - expect(response.body).toEqual({ - test: "returned tile", - }); - expect(response.headers["cross-origin-resource-policy"]).toEqual( - "cross-origin", - ); - }); - }); - }); }); describe("appendAPIKey helper function", () => { diff --git a/api.planx.uk/server.ts b/api.planx.uk/server.ts index 8c95c29f04..78af294e10 100644 --- a/api.planx.uk/server.ts +++ b/api.planx.uk/server.ts @@ -39,12 +39,18 @@ useSwaggerDocs(app); app.set("trust proxy", 1); const checkAllowedOrigins: CorsOptions["origin"] = (origin, callback) => { + if (!origin) return callback(null, true); + const isTest = process.env.NODE_ENV === "test"; - const isDevelopment = process.env.APP_ENVIRONMENT === "development"; + const localDevEnvs = + /^http:\/\/(127\.0\.0\.1|localhost):(3000|5173|6006|7007)$/; + const isDevelopment = + process.env.APP_ENVIRONMENT === "development" || localDevEnvs.test(origin); const allowList = process.env.CORS_ALLOWLIST?.split(", ") || []; - const isAllowed = Boolean(origin && allowList.includes(origin)); + const isAllowed = Boolean(allowList.includes(origin)); + const isMapDocs = Boolean(origin.endsWith("oslmap.netlify.app")); - !origin || isTest || isDevelopment || isAllowed + isTest || isDevelopment || isAllowed || isMapDocs ? callback(null, true) : callback(new Error("Not allowed by CORS")); };