diff --git a/api.planx.uk/server.ts b/api.planx.uk/server.ts index 0016cd8659..8c95c29f04 100644 --- a/api.planx.uk/server.ts +++ b/api.planx.uk/server.ts @@ -3,7 +3,7 @@ import { json, urlencoded } from "body-parser"; import assert from "assert"; import cookieParser from "cookie-parser"; import cookieSession from "cookie-session"; -import cors from "cors"; +import cors, { CorsOptions } from "cors"; import express, { ErrorRequestHandler } from "express"; import noir from "pino-noir"; import pinoLogger from "express-pino-logger"; @@ -38,11 +38,22 @@ useSwaggerDocs(app); app.set("trust proxy", 1); +const checkAllowedOrigins: CorsOptions["origin"] = (origin, callback) => { + const isTest = process.env.NODE_ENV === "test"; + const isDevelopment = process.env.APP_ENVIRONMENT === "development"; + const allowList = process.env.CORS_ALLOWLIST?.split(", ") || []; + const isAllowed = Boolean(origin && allowList.includes(origin)); + + !origin || isTest || isDevelopment || isAllowed + ? callback(null, true) + : callback(new Error("Not allowed by CORS")); +}; + app.use( cors({ credentials: true, methods: "*", - origin: process.env.EDITOR_URL_EXT, + origin: checkAllowedOrigins, allowedHeaders: [ "Accept", "Authorization", diff --git a/docker-compose.yml b/docker-compose.yml index 25fc9bccee..f9e380b099 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -141,6 +141,7 @@ services: SLACK_WEBHOOK_URL: ${SLACK_WEBHOOK_URL} ORDNANCE_SURVEY_API_KEY: ${ORDNANCE_SURVEY_API_KEY} MINIO_PORT: ${MINIO_PORT} + CORS_ALLOWLIST: ${EDITOR_URL_EXT} # Local authority config # Lambeth GOV_UK_PAY_TOKEN_LAMBETH: ${GOV_UK_PAY_TOKEN_LAMBETH} @@ -153,7 +154,7 @@ services: UNIFORM_CLIENT_AYLESBURY_VALE: ${UNIFORM_CLIENT_AYLESBURY_VALE} UNIFORM_CLIENT_CHILTERN: ${UNIFORM_CLIENT_CHILTERN} UNIFORM_CLIENT_WYCOMBE: ${UNIFORM_CLIENT_WYCOMBE} - #Medway + # Medway GOV_UK_PAY_TOKEN_MEDWAY: ${GOV_UK_PAY_TOKEN_MEDWAY} sharedb: diff --git a/infrastructure/application/index.ts b/infrastructure/application/index.ts index ca0ffa66e4..799cf57539 100644 --- a/infrastructure/application/index.ts +++ b/infrastructure/application/index.ts @@ -10,9 +10,9 @@ import * as mime from "mime"; import * as tldjs from "tldjs"; import * as url from "url"; -import { generateTeamSecrets } from "./utils/generateTeamSecrets"; +import { generateTeamSecrets, generateCORSAllowList, addRedirectToCloudFlareListenerRule } from "./utils"; import { createHasuraService } from "./services/hasura"; -import { addRedirectToCloudFlareListenerRule } from "./utils/addListenerRule"; +import { CustomDomains } from "../common/teams"; const config = new pulumi.Config(); @@ -25,7 +25,7 @@ const data = new pulumi.StackReference(`planx/data/${env}`); // You can generate tokens here: https://dash.cloudflare.com/profile/api-tokens new pulumi.Config("cloudflare").requireSecret("apiToken"); -const CUSTOM_DOMAINS = +const CUSTOM_DOMAINS: CustomDomains = env === "production" ? [ { @@ -377,6 +377,7 @@ export = async () => { name: "ORDNANCE_SURVEY_API_KEY", value: config.requireSecret("ordnance-survey-api-key"), }, + generateCORSAllowList(CUSTOM_DOMAINS, DOMAIN), ...generateTeamSecrets(config, env), ], }, diff --git a/infrastructure/application/utils/generateCORSAllowList.ts b/infrastructure/application/utils/generateCORSAllowList.ts new file mode 100644 index 0000000000..d49aebb77f --- /dev/null +++ b/infrastructure/application/utils/generateCORSAllowList.ts @@ -0,0 +1,16 @@ +import * as awsx from "@pulumi/awsx"; + +import { CustomDomains } from "../../common/teams"; + +export const generateCORSAllowList = (customDomains: CustomDomains, domain: string): awsx.ecs.KeyValuePair => { + const customDomainURLs = customDomains.map(team => `https://${team.domain}`); + const editorURL = `https://${domain}`; + const corsAllowList = [...customDomainURLs, editorURL]; + + const secret: awsx.ecs.KeyValuePair = { + name: "CORS_ALLOWLIST", + value: corsAllowList.join(", "), + }; + + return secret; +}; \ No newline at end of file diff --git a/infrastructure/application/utils/index.ts b/infrastructure/application/utils/index.ts new file mode 100644 index 0000000000..6d3e3d95d8 --- /dev/null +++ b/infrastructure/application/utils/index.ts @@ -0,0 +1,3 @@ +export * from "./addListenerRule"; +export * from "./generateCORSAllowList"; +export * from "./generateTeamSecrets"; \ No newline at end of file diff --git a/infrastructure/common/teams.ts b/infrastructure/common/teams.ts index 3a921288ac..c677e1d692 100644 --- a/infrastructure/common/teams.ts +++ b/infrastructure/common/teams.ts @@ -31,4 +31,9 @@ export const teams: Team[] = [ }, ]; +export type CustomDomains = Array<{ + name: string, + domain: string +}>; + export default { teams } \ No newline at end of file