mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-06-05 22:44:17 +00:00
Add support for the anchor redirects
This commit is contained in:
+34
-6
@@ -286,13 +286,41 @@ func (t *TraefikOidc) handleCallback(rw http.ResponseWriter, req *http.Request,
|
||||
http.Error(rw, "Failed to save session", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// Redirect to original path or root
|
||||
redirectPath := "/"
|
||||
if incomingPath := session.GetIncomingPath(); incomingPath != "" && incomingPath != t.redirURLPath {
|
||||
redirectPath = incomingPath
|
||||
}
|
||||
|
||||
// Redirect to original path or root
|
||||
redirectPath := "/"
|
||||
if incomingPath := session.GetIncomingPath(); incomingPath != "" && incomingPath != t.redirURLPath {
|
||||
redirectPath = incomingPath
|
||||
}
|
||||
|
||||
// For redirecting, we need to ensure URL fragments are preserved
|
||||
// To do this, we'll use a small JavaScript snippet that preserves any URL fragments
|
||||
// This is necessary because URL fragments are not sent to the server
|
||||
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(rw, `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Authentication Complete</title>
|
||||
<script>
|
||||
// Preserve URL fragments by combining the redirectPath with any fragment in the current URL
|
||||
(function() {
|
||||
var redirectPath = %q;
|
||||
var redirectUrl = new URL(redirectPath, window.location.href);
|
||||
|
||||
// If we have a hash in the current URL, and the redirect path doesn't already have one,
|
||||
// append the hash to the redirect URL to preserve anchors
|
||||
if (window.location.hash && !redirectPath.includes('#')) {
|
||||
redirectUrl.hash = window.location.hash;
|
||||
}
|
||||
|
||||
window.location.replace(redirectUrl.toString());
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<p>Authentication successful. Redirecting...</p>
|
||||
</body>
|
||||
</html>`, redirectPath)
|
||||
http.Redirect(rw, req, redirectPath, http.StatusFound)
|
||||
}
|
||||
|
||||
|
||||
+87
-1
@@ -513,7 +513,7 @@ func TestHandleCallback(t *testing.T) {
|
||||
session.SetCSRF("test-csrf-token")
|
||||
session.SetNonce("test-nonce")
|
||||
},
|
||||
expectedStatus: http.StatusFound,
|
||||
expectedStatus: http.StatusOK, // Changed from StatusFound since we now return HTML instead of redirect
|
||||
},
|
||||
{
|
||||
name: "Missing Code",
|
||||
@@ -2056,6 +2056,92 @@ func TestExchangeCodeForToken(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleCallback_PreservesURLFragments tests that URL fragments (anchors) are preserved during the authentication callback process.
|
||||
func TestHandleCallback_PreservesURLFragments(t *testing.T) {
|
||||
ts := &TestSuite{t: t}
|
||||
ts.Setup()
|
||||
|
||||
// Create a new instance for this specific test
|
||||
logger := NewLogger("info")
|
||||
sessionManager, _ := NewSessionManager("test-secret-key-that-is-at-least-32-bytes", false, logger)
|
||||
|
||||
tOidc := &TraefikOidc{
|
||||
allowedUserDomains: map[string]struct{}{"example.com": {}},
|
||||
logger: logger,
|
||||
tokenVerifier: ts.tOidc.tokenVerifier,
|
||||
jwtVerifier: ts.tOidc.jwtVerifier,
|
||||
sessionManager: sessionManager,
|
||||
redirURLPath: "/callback",
|
||||
extractClaimsFunc: func(tokenString string) (map[string]interface{}, error) {
|
||||
return map[string]interface{}{
|
||||
"email": "user@example.com",
|
||||
"nonce": "test-nonce",
|
||||
}, nil
|
||||
},
|
||||
exchangeCodeForTokenFunc: func(code string, redirectURL string, codeVerifier string) (*TokenResponse, error) {
|
||||
return &TokenResponse{
|
||||
IDToken: ts.token,
|
||||
RefreshToken: "test-refresh-token",
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
// Create a request with the callback URL
|
||||
req := httptest.NewRequest("GET", "/callback?code=test-code&state=test-csrf-token", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// Create session with an incoming path that contains a URL fragment
|
||||
session, err := sessionManager.GetSession(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get session: %v", err)
|
||||
}
|
||||
|
||||
// Set up the session with necessary values and an incoming path with a fragment
|
||||
session.SetCSRF("test-csrf-token")
|
||||
session.SetNonce("test-nonce")
|
||||
session.SetIncomingPath("/dashboard?param=value") // The fragment will be client-side only
|
||||
|
||||
if err := session.Save(req, rr); err != nil {
|
||||
t.Fatalf("Failed to save session: %v", err)
|
||||
}
|
||||
|
||||
// Copy cookies to the request
|
||||
for _, cookie := range rr.Result().Cookies() {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
|
||||
// Reset response recorder
|
||||
rr = httptest.NewRecorder()
|
||||
|
||||
// Call handleCallback
|
||||
tOidc.handleCallback(rr, req, "http://example.com/callback")
|
||||
|
||||
// The response should be OK (200) since we're returning HTML, not a redirect
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %d", rr.Code)
|
||||
}
|
||||
|
||||
// Verify that the response is HTML and contains our JavaScript for preserving fragments
|
||||
contentType := rr.Header().Get("Content-Type")
|
||||
if !strings.Contains(contentType, "text/html") {
|
||||
t.Errorf("Expected Content-Type to contain 'text/html', got %s", contentType)
|
||||
}
|
||||
|
||||
// Verify the response contains the redirect path and JavaScript for preserving fragments
|
||||
body := rr.Body.String()
|
||||
if !strings.Contains(body, "/dashboard?param=value") {
|
||||
t.Errorf("Response body doesn't contain the original redirect path")
|
||||
}
|
||||
|
||||
if !strings.Contains(body, "window.location.hash") {
|
||||
t.Errorf("Response doesn't contain JavaScript logic to preserve URL fragments")
|
||||
}
|
||||
|
||||
if !strings.Contains(body, "redirectUrl.hash = window.location.hash") {
|
||||
t.Errorf("Response doesn't contain logic to copy the fragment from current URL")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDefaultInitiateAuthentication_PreservesQueryParameters tests that defaultInitiateAuthentication preserves query parameters in the incoming path.
|
||||
func TestDefaultInitiateAuthentication_PreservesQueryParameters(t *testing.T) {
|
||||
ts := &TestSuite{t: t}
|
||||
|
||||
Reference in New Issue
Block a user