refactor(oidcgate): simplify cloneAndRewrite, flip ?rd precedence, assert XFU passthrough

This commit is contained in:
2026-05-19 14:07:44 +01:00
parent a46be72be5
commit 46679c82eb
2 changed files with 26 additions and 7 deletions
+9 -7
View File
@@ -28,7 +28,10 @@ func newAuthHandler(next http.Handler) http.Handler {
func newStartHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
r2 := cloneAndRewrite(req, sentinelPath)
if rd := req.URL.Query().Get("rd"); rd != "" && r2.Header.Get("X-Forwarded-Uri") == "" {
// Precedence: explicit ?rd= wins over an ambient upstream
// X-Forwarded-Uri so /oauth2/start?rd=/dashboard does not get
// silently overridden by the proxy's current-URL forwarding.
if rd := req.URL.Query().Get("rd"); rd != "" {
r2.Header.Set("X-Forwarded-Uri", rd)
}
next.ServeHTTP(rw, r2)
@@ -55,13 +58,12 @@ func newLogoutHandler(next http.Handler, logoutURL string) http.Handler {
})
}
// cloneAndRewrite returns a shallow clone of req with URL.Path set to newPath.
// The query string is preserved verbatim — middleware logic for code/state
// extraction reads URL.Query() which still works.
// cloneAndRewrite returns a clone of req with URL.Path set to newPath.
// req.Clone deep-copies URL via net/http's cloneURL, so mutating
// r2.URL.Path does not affect the original req. RawQuery, Host,
// Fragment, RawPath are preserved unchanged.
func cloneAndRewrite(req *http.Request, newPath string) *http.Request {
r2 := req.Clone(req.Context())
u := *req.URL
u.Path = newPath
r2.URL = &u
r2.URL.Path = newPath
return r2
}
+17
View File
@@ -49,6 +49,9 @@ func TestAuth_RewritesToSentinel_AndConverts302To401(t *testing.T) {
if rec.Header().Get("X-Auth-Redirect") == "" {
t.Error("X-Auth-Redirect should carry Location")
}
if got := stub.calls[0].header.Get("X-Forwarded-Uri"); got != "/protected/page" {
t.Errorf("X-Forwarded-Uri must pass through to middleware: want /protected/page, got %q", got)
}
}
func TestAuth_AuthenticatedReturnsHeadersAnd200(t *testing.T) {
@@ -107,6 +110,20 @@ func TestStart_ForwardsRdAsXForwardedURI(t *testing.T) {
}
}
func TestStart_RdQueryWinsOverUpstreamHeader(t *testing.T) {
stub := &stubMiddleware{
fn: func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusFound) },
}
h := newStartHandler(stub)
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/oauth2/start?rd=/explicit", nil)
req.Header.Set("X-Forwarded-Uri", "/ambient")
h.ServeHTTP(rec, req)
if got := stub.calls[0].header.Get("X-Forwarded-Uri"); got != "/explicit" {
t.Fatalf("?rd= must win over upstream X-Forwarded-Uri: want /explicit, got %q", got)
}
}
func TestCallback_RewritesToConfiguredCallbackURL(t *testing.T) {
var seenPath, seenQuery string
stub := &stubMiddleware{