From ee9121d771a158d4c51ca22e327637b84c545dda Mon Sep 17 00:00:00 2001 From: wyh0626 <44987669+wyh0626@users.noreply.github.com> Date: Tue, 31 Mar 2026 11:54:24 +0800 Subject: [PATCH] fix: respect X-Forwarded-Prefix in RequestURL --- x/http.go | 34 ++++++++++++++++++++++++++++++++++ x/http_test.go | 18 ++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/x/http.go b/x/http.go index 281b36c9e3b8..1a1b1e101e35 100644 --- a/x/http.go +++ b/x/http.go @@ -8,6 +8,7 @@ import ( "context" "net/http" "net/url" + "strings" "github.com/golang/gddo/httputil" @@ -61,6 +62,15 @@ func RequestURL(r *http.Request) *url.URL { source.Scheme = proto } + if prefix := r.Header.Get("X-Forwarded-Prefix"); len(prefix) > 0 { + if !hasPathPrefix(source.Path, prefix) { + source.Path = joinPathPrefix(prefix, source.Path) + } + if source.RawPath != "" && !hasPathPrefix(source.RawPath, prefix) { + source.RawPath = joinPathPrefix(prefix, source.RawPath) + } + } + if source.Scheme == "" { source.Scheme = "https" if r.TLS == nil { @@ -71,6 +81,30 @@ func RequestURL(r *http.Request) *url.URL { return &source } +func hasPathPrefix(path, prefix string) bool { + prefix = strings.TrimSuffix(prefix, "/") + if prefix == "" { + return true + } + + return path == prefix || strings.HasPrefix(path, prefix+"/") +} + +func joinPathPrefix(prefix, path string) string { + switch { + case prefix == "" || prefix == "/": + return path + case path == "": + return prefix + case strings.HasSuffix(prefix, "/") && strings.HasPrefix(path, "/"): + return prefix + strings.TrimPrefix(path, "/") + case !strings.HasSuffix(prefix, "/") && !strings.HasPrefix(path, "/"): + return prefix + "/" + path + default: + return prefix + path + } +} + // SendFlowCompletedAsRedirectOrJSON should be used when a login, registration, ... flow has been completed successfully. // It will redirect the user to the provided URL if the request accepts HTML, or return a JSON response if the request is // an SPA request diff --git a/x/http_test.go b/x/http_test.go index d8514194e536..28423bb6512b 100644 --- a/x/http_test.go +++ b/x/http_test.go @@ -39,6 +39,24 @@ func TestRequestURL(t *testing.T) { assert.EqualValues(t, RequestURL(&http.Request{ URL: urlx.ParseOrPanic("/foo"), Host: "foobar", Header: http.Header{"X-Forwarded-Host": []string{"notfoobar"}, "X-Forwarded-Proto": {"https"}}, }).String(), "https://notfoobar/foo") + assert.EqualValues(t, RequestURL(&http.Request{ + URL: urlx.ParseOrPanic("/self-service/login/browser?flow=123"), + Host: "foobar", + Header: http.Header{ + "X-Forwarded-Host": []string{"notfoobar"}, + "X-Forwarded-Proto": []string{"https"}, + "X-Forwarded-Prefix": []string{"/.ory/kratos"}, + }, + }).String(), "https://notfoobar/.ory/kratos/self-service/login/browser?flow=123") + assert.EqualValues(t, RequestURL(&http.Request{ + URL: urlx.ParseOrPanic("/.ory/kratos/self-service/login/browser?flow=123"), + Host: "foobar", + Header: http.Header{ + "X-Forwarded-Host": []string{"notfoobar"}, + "X-Forwarded-Proto": []string{"https"}, + "X-Forwarded-Prefix": []string{"/.ory/kratos"}, + }, + }).String(), "https://notfoobar/.ory/kratos/self-service/login/browser?flow=123") } func TestAcceptToRedirectOrJSON(t *testing.T) {