mirror of
https://github.com/rustfs/console.git
synced 2026-08-30 17:14:47 +08:00
fix: prevent open redirect in OIDC callback flow (#61)
This commit is contained in:
@@ -5,6 +5,7 @@ import { useRouter } from "next/navigation"
|
||||
import { useAuth } from "@/contexts/auth-context"
|
||||
import { useMessage } from "@/lib/feedback/message"
|
||||
import { parseOidcCallback } from "@/lib/oidc"
|
||||
import { isSafeRedirectPath } from "@/lib/routes"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
export default function OidcCallbackPage() {
|
||||
@@ -30,7 +31,7 @@ export default function OidcCallbackPage() {
|
||||
return
|
||||
}
|
||||
|
||||
redirectPath.current = credentials.redirect || "/browser"
|
||||
redirectPath.current = isSafeRedirectPath(credentials.redirect, "/browser")
|
||||
|
||||
loginWithStsCredentials({
|
||||
AccessKeyId: credentials.accessKey,
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
import type { OidcProvider } from "@/types/config"
|
||||
import { isSafeRedirectPath } from "@/lib/routes"
|
||||
|
||||
/**
|
||||
* Fetch configured OIDC providers from the server.
|
||||
@@ -52,6 +53,6 @@ export function parseOidcCallback(hash: string): {
|
||||
secretKey,
|
||||
sessionToken,
|
||||
expiration: params.get("expiration") ?? "",
|
||||
redirect: params.get("redirect") ?? "/",
|
||||
redirect: isSafeRedirectPath(params.get("redirect") ?? "", "/"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,3 +26,34 @@ export function buildRoute(path: string): string {
|
||||
export function getLoginRoute(): string {
|
||||
return buildRoute("/auth/login")
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a redirect path is safe (relative, no protocol, no external domain).
|
||||
* Returns the path if safe, or the fallback otherwise.
|
||||
*/
|
||||
export function isSafeRedirectPath(path: string, fallback = "/"): string {
|
||||
if (!path || typeof path !== "string") return fallback
|
||||
|
||||
const trimmed = path.trim()
|
||||
|
||||
// Block absolute URLs with protocol (e.g. https://evil.com)
|
||||
if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed)) return fallback
|
||||
|
||||
// Block protocol-relative URLs and backslash variants (e.g. //evil.com, \/evil.com, \\evil.com)
|
||||
// Some browsers treat backslashes as forward slashes in URLs
|
||||
if (/^[\\/]{2,}/.test(trimmed)) return fallback
|
||||
|
||||
// Block single leading backslash (e.g. \evil.com) which some browsers interpret as //
|
||||
if (trimmed.startsWith("\\")) return fallback
|
||||
|
||||
// Block data: and javascript: URIs (case-insensitive, with optional whitespace)
|
||||
if (/^\s*(javascript|data)\s*:/i.test(trimmed)) return fallback
|
||||
|
||||
// Ensure it starts with / (relative path)
|
||||
if (!trimmed.startsWith("/")) return fallback
|
||||
|
||||
// Block directory traversal sequences (e.g. /../evil.com, /../../etc/passwd)
|
||||
if (/(?:^|\/)\.\.(?:\/|$)/.test(trimmed)) return fallback
|
||||
|
||||
return trimmed
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user