mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-12 04:04:26 +00:00
The SSE and WebSocket bypass does not check the token expiry. A session stays usable for streaming until the session cookie expires, also after the IdP disables the user. The bypass now forwards the configured headers (#162), so this gap also gives the proxy secret to such a session. Add the streamingRefresh option. The default is false. When it is true and the access token has expired, the bypass refreshes the session. It uses the ID token expiry if the access token is opaque. If the IdP rejects the refresh with invalid_grant, the request gets 401. If the IdP cannot be reached, the request is forwarded as before. The bypass waits for the full coordinated refresh. A shorter wait can drop a rotated refresh token, and the next refresh then logs the user out. applyBypassUserHeaders now takes the ResponseWriter, so a refresh can write the new session cookie. Add a yaegi check for the invalid_grant path through the real token endpoint and RefreshCoordinator. Claude-Session: https://claude.ai/code/session_01G1d1n1BrHHHw8dBUkbiBPC
93 lines
3.0 KiB
Go
93 lines
3.0 KiB
Go
package traefikoidc
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
// TestApplyBypassUserHeaders_SanitizesIdentifier is a regression test for the
|
|
// auth-bypass header path (applyBypassUserHeaders). The claim-derived user
|
|
// identifier is injected into X-Forwarded-User / X-Auth-Request-User without
|
|
// sanitization, unlike forwardAuthorized which uses sanitizeHeaderClaimValue.
|
|
// An IdP-controlled identifier containing CRLF (or a delimiter) would
|
|
// otherwise inject or confuse downstream header parsing. Now the bypass path
|
|
// sanitizes the identifier and drops the header on failure.
|
|
func TestApplyBypassUserHeaders_SanitizesIdentifier(t *testing.T) {
|
|
sm := createTestSessionManager(t)
|
|
|
|
base := httptest.NewRequest(http.MethodGet, "/protected", nil)
|
|
baseRec := httptest.NewRecorder()
|
|
|
|
// Build an authenticated session whose user identifier contains CRLF.
|
|
session, err := sm.GetSession(base)
|
|
if err != nil {
|
|
t.Fatalf("GetSession: %v", err)
|
|
}
|
|
if err := session.SetAuthenticated(true); err != nil {
|
|
t.Fatalf("SetAuthenticated: %v", err)
|
|
}
|
|
session.SetUserIdentifier("user\nX-Injected: yes")
|
|
if err := session.Save(base, baseRec); err != nil {
|
|
t.Fatalf("Save: %v", err)
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/protected", nil)
|
|
for _, c := range baseRec.Result().Cookies() {
|
|
req.AddCookie(c)
|
|
}
|
|
|
|
oidc := &TraefikOidc{
|
|
sessionManager: sm,
|
|
logger: NewLogger("debug"),
|
|
}
|
|
|
|
if ok, _ := oidc.applyBypassUserHeaders(httptest.NewRecorder(), req, "test"); !ok {
|
|
t.Fatalf("applyBypassUserHeaders returned false; bypass should be honored")
|
|
}
|
|
if v := req.Header.Get("X-Forwarded-User"); v != "" {
|
|
t.Fatalf("X-Forwarded-User = %q, want empty (unsafe identifier dropped)", v)
|
|
}
|
|
if v := req.Header.Get("X-Auth-Request-User"); v != "" {
|
|
t.Fatalf("X-Auth-Request-User = %q, want empty (unsafe identifier dropped)", v)
|
|
}
|
|
}
|
|
|
|
// TestApplyBypassUserHeaders_KeepsCleanIdentifier ensures a normal identifier
|
|
// still flows through, and is trimmed/sanitized.
|
|
func TestApplyBypassUserHeaders_KeepsCleanIdentifier(t *testing.T) {
|
|
sm := createTestSessionManager(t)
|
|
|
|
base := httptest.NewRequest(http.MethodGet, "/protected", nil)
|
|
baseRec := httptest.NewRecorder()
|
|
|
|
session, err := sm.GetSession(base)
|
|
if err != nil {
|
|
t.Fatalf("GetSession: %v", err)
|
|
}
|
|
if err := session.SetAuthenticated(true); err != nil {
|
|
t.Fatalf("SetAuthenticated: %v", err)
|
|
}
|
|
session.SetUserIdentifier(" [email protected] ")
|
|
if err := session.Save(base, baseRec); err != nil {
|
|
t.Fatalf("Save: %v", err)
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/protected", nil)
|
|
for _, c := range baseRec.Result().Cookies() {
|
|
req.AddCookie(c)
|
|
}
|
|
|
|
oidc := &TraefikOidc{
|
|
sessionManager: sm,
|
|
logger: NewLogger("debug"),
|
|
}
|
|
|
|
if ok, _ := oidc.applyBypassUserHeaders(httptest.NewRecorder(), req, "test"); !ok {
|
|
t.Fatalf("applyBypassUserHeaders returned false; bypass should be honored")
|
|
}
|
|
if v := req.Header.Get("X-Forwarded-User"); v != "[email protected]" {
|
|
t.Fatalf("X-Forwarded-User = %q, want %q (trimmed)", v, "[email protected]")
|
|
}
|
|
}
|