2026-02-27 23:28:52 +01:00
|
|
|
/**
|
|
|
|
|
* Validates a redirect URL to prevent open redirect vulnerabilities.
|
|
|
|
|
*
|
|
|
|
|
* Only allows same-origin redirects (paths starting with `/` but not `//`).
|
|
|
|
|
* External URLs, protocol-relative URLs, and invalid input are rejected.
|
|
|
|
|
*
|
|
|
|
|
* @param redirect - The redirect URL to validate (typically from query parameters)
|
|
|
|
|
* @param fallback - The fallback path to use if validation fails (default: '/dashboard')
|
|
|
|
|
* @returns A validated same-origin path or the fallback path
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
* ```typescript
|
|
|
|
|
* // Valid same-origin paths
|
|
|
|
|
* validateRedirect('/dashboard') // returns '/dashboard'
|
|
|
|
|
* validateRedirect('/projects/123') // returns '/projects/123'
|
|
|
|
|
*
|
|
|
|
|
* // Rejected external URLs (returns fallback)
|
|
|
|
|
* validateRedirect('https://evil.com') // returns '/dashboard'
|
|
|
|
|
* validateRedirect('//evil.com') // returns '/dashboard'
|
|
|
|
|
*
|
|
|
|
|
* // Invalid input (returns fallback)
|
|
|
|
|
* validateRedirect(null) // returns '/dashboard'
|
|
|
|
|
* validateRedirect(undefined) // returns '/dashboard'
|
|
|
|
|
*
|
|
|
|
|
* // Custom fallback
|
|
|
|
|
* validateRedirect('https://evil.com', '/login') // returns '/login'
|
|
|
|
|
* ```
|
|
|
|
|
*/
|
2025-12-12 13:57:44 +01:00
|
|
|
export const validateRedirect = (redirect: string | unknown, fallback = '/dashboard'): string => {
|
|
|
|
|
if (typeof redirect !== 'string') {
|
|
|
|
|
return fallback;
|
|
|
|
|
}
|
|
|
|
|
if (redirect.startsWith('/') && !redirect.startsWith('//')) {
|
|
|
|
|
return redirect;
|
|
|
|
|
}
|
|
|
|
|
return fallback;
|
|
|
|
|
}
|