diff --git a/apps/edr-passenger-web/portal/.env.example b/apps/edr-passenger-web/portal/.env.example index 25ffe6909..6560533f2 100644 --- a/apps/edr-passenger-web/portal/.env.example +++ b/apps/edr-passenger-web/portal/.env.example @@ -1,5 +1,9 @@ # API Configuration NEXT_PUBLIC_API_URL=https://your-api-domain.com +# Allowlisted destination hosts for the /go D-Money redirect bounce page (comma-separated). +# The /go?url=... page only forwards to https hosts that match one of these (host or subdomain). +NEXT_PUBLIC_DMONEY_ALLOWED_HOSTS=d-money.dj + # GitHub Packages Token GITHUB_PACKAGE_TOKEN=$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 \ No newline at end of file diff --git a/apps/edr-passenger-web/portal/src/app/go/page.tsx b/apps/edr-passenger-web/portal/src/app/go/page.tsx new file mode 100644 index 000000000..255b3980a --- /dev/null +++ b/apps/edr-passenger-web/portal/src/app/go/page.tsx @@ -0,0 +1,100 @@ +"use client"; + +import { Suspense, useEffect, useMemo } from "react"; +import { useSearchParams } from "next/navigation"; +import { Loader2, ShieldAlert, ExternalLink } from "lucide-react"; + + + +const ALLOWED_HOSTS = ( + process.env.NEXT_PUBLIC_DMONEY_ALLOWED_HOSTS ?? "d-money.dj" +) + .split(",") + .map((h) => h.trim().toLowerCase()) + .filter(Boolean); + +/** True only for https URLs whose host is (or is a subdomain of) an allowlisted host. */ +function isTrustedDMoneyUrl(raw: string | null): raw is string { + if (!raw) return false; + let parsed: URL; + try { + parsed = new URL(raw); + } catch { + return false; + } + if (parsed.protocol !== "https:") return false; + const host = parsed.hostname.toLowerCase(); + return ALLOWED_HOSTS.some( + (allowed) => host === allowed || host.endsWith(`.${allowed}`), + ); +} + +const REDIRECT_DELAY_MS = 1000; + +function RedirectView() { + const searchParams = useSearchParams(); + const raw = searchParams.get("url"); + const target = useMemo(() => (isTrustedDMoneyUrl(raw) ? raw : null), [raw]); + + useEffect(() => { + if (!target) return; + const timer = setTimeout(() => { + + window.location.replace(target); + }, REDIRECT_DELAY_MS); + return () => clearTimeout(timer); + }, [target]); + + if (!target) { + return ( +
+
+ +
+

+ Can't continue +

+

+ This link is missing a valid D-Money checkout address or points to an + untrusted destination. Please start the payment again from the app. +

+
+ ); + } + + return ( +
+
+ +
+

+ Redirecting to D-Money +

+

+ Taking you to the secure D-Money checkout to complete your payment… +

+ + + Continue to D-Money + + +
+ ); +} + +export default function GoPage() { + return ( +
+ + } + > + + +
+ ); +}