mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-03 15:06:36 +08:00
fix(frontend): sanitize redirect targets to prevent XSS/open redirect
Sanitize and validate redirect targets from callbackUrl parameter and sessionStorage in login, registration, and OAuth callback flows. Introduced safeRedirectTarget helper which rejects protocol-relative URLs, non-relative schemes, control characters, backslashes, and encoding bypasses.
This commit is contained in:
@@ -18,6 +18,7 @@ import {AuthHeading} from "@/components/auth/auth-shell"
|
||||
import {OTPForm} from "./otp-form"
|
||||
import services from "@/lib/services"
|
||||
import type {LoginRequest} from "@/lib/services/auth/types"
|
||||
import {safeRedirectTarget} from "@/lib/utils"
|
||||
|
||||
function getRedirectTarget(searchParams: ReturnType<typeof useSearchParams>) {
|
||||
const callbackUrl = searchParams.get("callbackUrl")
|
||||
@@ -31,16 +32,17 @@ function getRedirectTarget(searchParams: ReturnType<typeof useSearchParams>) {
|
||||
sessionStorage.removeItem("redirect_after_login")
|
||||
}
|
||||
|
||||
return target
|
||||
return safeRedirectTarget(target)
|
||||
}
|
||||
|
||||
function persistRedirectTarget(searchParams: ReturnType<typeof useSearchParams>) {
|
||||
const callbackUrl = searchParams.get("callbackUrl")
|
||||
if (callbackUrl && typeof window !== "undefined") {
|
||||
sessionStorage.setItem("redirect_after_login", callbackUrl)
|
||||
sessionStorage.setItem("redirect_after_login", safeRedirectTarget(callbackUrl))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function configBool(value: string | undefined, fallback: boolean) {
|
||||
if (value === undefined) return fallback
|
||||
return value === "true"
|
||||
|
||||
@@ -11,6 +11,7 @@ import {Check} from "lucide-react"
|
||||
|
||||
import services from "@/lib/services"
|
||||
import {useAuth} from "@/components/providers/auth-provider"
|
||||
import {safeRedirectTarget} from "@/lib/utils"
|
||||
|
||||
|
||||
/**
|
||||
@@ -48,9 +49,10 @@ export function LoginPage() {
|
||||
sessionStorage.removeItem('redirect_after_login')
|
||||
}
|
||||
|
||||
return target
|
||||
return safeRedirectTarget(target)
|
||||
}, [searchParams])
|
||||
|
||||
|
||||
/* 登录页兜底:已登录用户直接跳转 */
|
||||
useEffect(() => {
|
||||
const state = searchParams.get('state')
|
||||
|
||||
@@ -14,6 +14,7 @@ import {Field, FieldGroup, FieldLabel} from "@/components/ui/field"
|
||||
import {AuthHeading} from "@/components/auth/auth-shell"
|
||||
import services from "@/lib/services"
|
||||
import type {RegisterRequest} from "@/lib/services/auth/types"
|
||||
import {safeRedirectTarget} from "@/lib/utils"
|
||||
|
||||
function getRedirectTarget(searchParams: ReturnType<typeof useSearchParams>) {
|
||||
const callbackUrl = searchParams.get("callbackUrl")
|
||||
@@ -21,9 +22,10 @@ function getRedirectTarget(searchParams: ReturnType<typeof useSearchParams>) {
|
||||
typeof window === "undefined"
|
||||
? null
|
||||
: sessionStorage.getItem("redirect_after_login")
|
||||
return callbackUrl || storedRedirect || "/home"
|
||||
return safeRedirectTarget(callbackUrl || storedRedirect || "/home")
|
||||
}
|
||||
|
||||
|
||||
function configBool(value: string | undefined, fallback: boolean) {
|
||||
if (value === undefined) return fallback
|
||||
return value === "true"
|
||||
|
||||
Reference in New Issue
Block a user