From 633b73bf10af029b8366574b93ee41e0f9500747 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Fri, 3 Jul 2026 01:27:08 +0530 Subject: [PATCH 1/2] fix(mobile): validate external redirect URLs --- .../(event-types)/event-type-detail.tsx | 8 +++++ .../utils/redirectUrlValidation.test.js | 35 +++++++++++++++++++ apps/mobile/utils/redirectUrlValidation.ts | 22 ++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 apps/mobile/utils/redirectUrlValidation.test.js create mode 100644 apps/mobile/utils/redirectUrlValidation.ts diff --git a/apps/mobile/app/(tabs)/(event-types)/event-type-detail.tsx b/apps/mobile/app/(tabs)/(event-types)/event-type-detail.tsx index 193607f..ecb2057 100644 --- a/apps/mobile/app/(tabs)/(event-types)/event-type-detail.tsx +++ b/apps/mobile/app/(tabs)/(event-types)/event-type-detail.tsx @@ -53,6 +53,7 @@ import { mapItemToApiLocation, validateLocationItem, } from "@/utils/locationHelpers"; +import { validateExternalRedirectUrl } from "@/utils/redirectUrlValidation"; import { getCalAppUrl } from "@/utils/region"; import { safeLogError } from "@/utils/safeLogger"; @@ -1204,6 +1205,12 @@ export default function EventTypeDetail() { } } + const redirectValidation = validateExternalRedirectUrl(successRedirectUrl); + if (!redirectValidation.valid) { + showErrorAlert("Error", redirectValidation.error || "Invalid redirect URL"); + return; + } + // Detect create vs update mode const isCreateMode = id === "new"; @@ -1278,6 +1285,7 @@ export default function EventTypeDetail() { currentFormState, eventTypeData, updateEventType, + successRedirectUrl, ]); const headerTitle = id === "new" ? "Create Event Type" : truncateTitle(title); diff --git a/apps/mobile/utils/redirectUrlValidation.test.js b/apps/mobile/utils/redirectUrlValidation.test.js new file mode 100644 index 0000000..7324a9f --- /dev/null +++ b/apps/mobile/utils/redirectUrlValidation.test.js @@ -0,0 +1,35 @@ +import { describe, expect, test } from "@jest/globals"; +import { validateExternalRedirectUrl } from "./redirectUrlValidation"; + +describe("validateExternalRedirectUrl", () => { + test("allows an empty redirect URL", () => { + expect(validateExternalRedirectUrl("")).toEqual({ valid: true }); + expect(validateExternalRedirectUrl(" ")).toEqual({ valid: true }); + }); + + test("allows https redirect URLs", () => { + expect(validateExternalRedirectUrl("https://example.com/thanks?booking=123")).toEqual({ + valid: true, + }); + }); + + test("allows http redirect URLs to match the web app schema", () => { + expect(validateExternalRedirectUrl("http://localhost:3000/thanks")).toEqual({ valid: true }); + expect(validateExternalRedirectUrl("http://127.0.0.1:3000/thanks")).toEqual({ valid: true }); + expect(validateExternalRedirectUrl("http://example.com/thanks")).toEqual({ valid: true }); + }); + + test("rejects javascript and data URLs", () => { + expect(validateExternalRedirectUrl("javascript:alert(1)").valid).toBe(false); + expect(validateExternalRedirectUrl("data:text/html,").valid).toBe( + false + ); + }); + + test("rejects non-http redirect URLs", () => { + const result = validateExternalRedirectUrl("ftp://example.com/file"); + + expect(result.valid).toBe(false); + expect(result.error).toContain("HTTP"); + }); +}); diff --git a/apps/mobile/utils/redirectUrlValidation.ts b/apps/mobile/utils/redirectUrlValidation.ts new file mode 100644 index 0000000..476912b --- /dev/null +++ b/apps/mobile/utils/redirectUrlValidation.ts @@ -0,0 +1,22 @@ +export interface RedirectUrlValidationResult { + valid: boolean; + error?: string; +} + +export function validateExternalRedirectUrl(value: string): RedirectUrlValidationResult { + const trimmed = value.trim(); + if (!trimmed) return { valid: true }; + + let parsed: URL; + try { + parsed = new URL(trimmed); + } catch { + return { valid: false, error: "Redirect URL must be a valid URL." }; + } + + if (parsed.protocol === "http:" || parsed.protocol === "https:") { + return { valid: true }; + } + + return { valid: false, error: "Redirect URL must use HTTP or HTTPS." }; +} From 38aa768a687acac0ef4353471b610cb0b3d2ac60 Mon Sep 17 00:00:00 2001 From: Dhairyashil Date: Fri, 3 Jul 2026 03:10:07 +0530 Subject: [PATCH 2/2] fix(mobile): require redirect URL prefix --- apps/mobile/utils/redirectUrlValidation.test.js | 9 ++++++++- apps/mobile/utils/redirectUrlValidation.ts | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/mobile/utils/redirectUrlValidation.test.js b/apps/mobile/utils/redirectUrlValidation.test.js index 7324a9f..0c6b21a 100644 --- a/apps/mobile/utils/redirectUrlValidation.test.js +++ b/apps/mobile/utils/redirectUrlValidation.test.js @@ -30,6 +30,13 @@ describe("validateExternalRedirectUrl", () => { const result = validateExternalRedirectUrl("ftp://example.com/file"); expect(result.valid).toBe(false); - expect(result.error).toContain("HTTP"); + expect(result.error).toContain("http://"); + }); + + test("rejects http URLs without an explicit double-slash prefix", () => { + expect(validateExternalRedirectUrl("https:example.com").valid).toBe(false); + expect(validateExternalRedirectUrl("http:example.com").valid).toBe(false); + expect(validateExternalRedirectUrl("https:/example.com").valid).toBe(false); + expect(validateExternalRedirectUrl("http:/example.com").valid).toBe(false); }); }); diff --git a/apps/mobile/utils/redirectUrlValidation.ts b/apps/mobile/utils/redirectUrlValidation.ts index 476912b..a4e1675 100644 --- a/apps/mobile/utils/redirectUrlValidation.ts +++ b/apps/mobile/utils/redirectUrlValidation.ts @@ -7,6 +7,10 @@ export function validateExternalRedirectUrl(value: string): RedirectUrlValidatio const trimmed = value.trim(); if (!trimmed) return { valid: true }; + if (!/^https?:\/\//.test(trimmed)) { + return { valid: false, error: "Redirect URL must start with http:// or https://." }; + } + let parsed: URL; try { parsed = new URL(trimmed);