diff --git a/contact-api/.env b/contact-api/.env index 3d2bb6a..b230968 100644 --- a/contact-api/.env +++ b/contact-api/.env @@ -1,6 +1,6 @@ NODE_ENV=production PORT=8787 -CONTACT_ALLOWED_ORIGIN=https://jodyholt.com +CONTACT_ALLOWED_ORIGIN=https://www.jodyholt.com TURNSTILE_SECRET_KEY=0x4AAAAAACfQyRwRzwsEMIfVtCSkjz7__Yc TURNSTILE_EXPECTED_HOSTNAME=jodyholt.com TURNSTILE_EXPECTED_ACTION=contact_form @@ -18,4 +18,3 @@ RATE_LIMIT_WINDOW_MS=600000 RATE_LIMIT_MAX=5 HONEYPOT_FIELD=website MIN_SUBMIT_TIME_MS=3000 -CONTACT_ALLOWED_ORIGIN=https://www.jodyholt.com \ No newline at end of file diff --git a/contact-api/src/index.ts b/contact-api/src/index.ts index 84c1cee..907f287 100644 --- a/contact-api/src/index.ts +++ b/contact-api/src/index.ts @@ -16,6 +16,18 @@ type ApiErrorResponse = { const app = express(); app.set("trust proxy", 1); +const normalizeOrigin = (value: string): string => + value + .trim() + .replace(/^['"]|['"]$/g, "") + .replace(/\/+$/g, "") + .toLowerCase(); + +const allowedOrigins = config.CONTACT_ALLOWED_ORIGIN + .split(",") + .map(normalizeOrigin) + .filter((value, index, all) => value.length > 0 && all.indexOf(value) === index); + app.use( pinoHttp({ level: config.NODE_ENV === "production" ? "info" : "debug", @@ -30,7 +42,13 @@ app.use(helmet()); app.use( cors({ origin(origin, callback) { - if (!origin || origin === config.CONTACT_ALLOWED_ORIGIN) { + if (!origin) { + callback(null, true); + return; + } + + const normalizedOrigin = normalizeOrigin(origin); + if (allowedOrigins.includes(normalizedOrigin)) { callback(null, true); return; } diff --git a/contact-api/src/turnstile.ts b/contact-api/src/turnstile.ts index 0ba9e65..bd976af 100644 --- a/contact-api/src/turnstile.ts +++ b/contact-api/src/turnstile.ts @@ -7,6 +7,18 @@ type TurnstileVerifyResponse = { "error-codes"?: string[]; }; +const normalizeHostname = (value: string): string => + value + .trim() + .replace(/^['"]|['"]$/g, "") + .replace(/\.+$/g, "") + .toLowerCase(); + +const expectedHostnames = config.TURNSTILE_EXPECTED_HOSTNAME + .split(",") + .map(normalizeHostname) + .filter((value, index, all) => value.length > 0 && all.indexOf(value) === index); + export async function verifyTurnstileToken( token: string, remoteIp?: string, @@ -36,7 +48,7 @@ export async function verifyTurnstileToken( return { ok: false, reason: codes }; } - if (result.hostname !== config.TURNSTILE_EXPECTED_HOSTNAME) { + if (!result.hostname || !expectedHostnames.includes(normalizeHostname(result.hostname))) { return { ok: false, reason: "hostname_mismatch" }; }