(path: string | null | undefined)
| 15 | |
| 16 | /** Parse a same-origin landing path, or return null for absent or unsafe input. */ |
| 17 | export const safeReturnTo = (path: string | null | undefined): string | null => { |
| 18 | if (!path || !path.startsWith("/") || path.startsWith("//")) return null; |
| 19 | // Browsers treat backslashes as path separators and strip some control |
| 20 | // characters. Reject those spellings before interpreting the destination. |
| 21 | for (const character of path) { |
| 22 | if (character === "\\" || character <= " " || character === "\u007f") return null; |
| 23 | } |
| 24 | |
| 25 | // The fixed origin and single leading slash guarantee a parseable URL. |
| 26 | // Check the normalized pathname so dot segments cannot bypass the API gate. |
| 27 | const destination = new URL(path, RETURN_TO_ORIGIN); |
| 28 | if (destination.origin !== RETURN_TO_ORIGIN) return null; |
| 29 | if (/^\/api(\/|$)/.test(destination.pathname) && destination.pathname !== "/api/oauth/callback") { |
| 30 | return null; |
| 31 | } |
| 32 | return `${destination.pathname}${destination.search}${destination.hash}`; |
| 33 | }; |
| 34 | |
| 35 | /** Whether a value parses as a same-origin landing path. */ |
| 36 | export const isSafeReturnTo = (path: string): boolean => safeReturnTo(path) !== null; |
no outgoing calls
no test coverage detected