// Open-redirect guard for the `?next=` parameter. // // After sign-in we send the browser to wherever it was headed — but a `next=` // an attacker controls is a phishing vector (`?next=https://evil.example/login` // that looks like us). So we accept ONLY same-origin, absolute-PATH targets: // a value must start with a single `/` and NOT with `//` (a protocol-relative // URL like `//evil.example` that browsers treat as cross-origin). Anything else // falls back to `home`. /** The safelisted redirect target, or `home` when `next` is missing/unsafe. */ export const safeNext = (next: string | null | undefined, home: string): string => { if (!next) return home // Must be an absolute path, and not protocol-relative (`//host`) or a // backslash-smuggled variant (`/\evil`). if (!next.startsWith('/') || next.startsWith('//') || next.startsWith('/\\')) return home return next } /** Read + safelist `?next=` from the current URL (browser only). */ export const nextFromLocation = (home: string): string => { if (typeof window === 'undefined') return home return safeNext(new URLSearchParams(window.location.search).get('next'), home) } /** Read a `?token=` from the current URL (browser only). */ export const tokenFromLocation = (): string | null => { if (typeof window === 'undefined') return null return new URLSearchParams(window.location.search).get('token') }