package handler import ( "baron-sso-backend/internal/service" "bytes" "encoding/json" "io" "net/http" "net/http/httptest" "testing" "github.com/gofiber/fiber/v2" ) func newOidcLoginTestApp(h *AuthHandler) *fiber.App { app := fiber.New() app.Post("/api/v1/auth/oidc/login/accept", h.AcceptOidcLoginRequest) return app } func TestAcceptOidcLoginRequest_CookieOnly(t *testing.T) { var gotSubject string var gotChallenge string transport := roundTripFunc(func(r *http.Request) (*http.Response, error) { switch r.URL.Host { case "kratos.test": if r.URL.Path != "/sessions/whoami" { return httpResponse(r, http.StatusNotFound, "not found"), nil } if r.Header.Get("X-Session-Token") != "" { return httpResponse(r, http.StatusUnauthorized, "invalid token"), nil } if r.Header.Get("Cookie") == "" { return httpResponse(r, http.StatusUnauthorized, "missing cookie"), nil } return httpJSONAny(r, http.StatusOK, map[string]any{ "identity": map[string]any{ "id": "kratos-123", "traits": map[string]any{}, }, }), nil case "hydra.test": if r.URL.Path != "/oauth2/auth/requests/login/accept" { return httpResponse(r, http.StatusNotFound, "not found"), nil } gotChallenge = r.URL.Query().Get("login_challenge") body, _ := io.ReadAll(r.Body) var payload map[string]any _ = json.Unmarshal(body, &payload) if subject, ok := payload["subject"].(string); ok { gotSubject = subject } return httpResponse(r, http.StatusOK, `{"redirect_to":"http://rp/cb"}`), nil default: return httpResponse(r, http.StatusNotFound, "not found"), nil } }) client := &http.Client{Transport: transport} origDefault := http.DefaultClient http.DefaultClient = client defer func() { http.DefaultClient = origDefault }() t.Setenv("KRATOS_PUBLIC_URL", "http://kratos.test") h := &AuthHandler{ Hydra: &service.HydraAdminService{ AdminURL: "http://hydra.test", HTTPClient: client, }, } app := newOidcLoginTestApp(h) body, _ := json.Marshal(map[string]string{ "login_challenge": "challenge-123", }) req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/oidc/login/accept", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Cookie", "ory_kratos_session=abc123") resp, err := app.Test(req) if err != nil { t.Fatalf("request failed: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("expected 200, got %d", resp.StatusCode) } var got map[string]string if err := json.NewDecoder(resp.Body).Decode(&got); err != nil { t.Fatalf("failed to decode response: %v", err) } if got["redirectTo"] != "http://rp/cb" { t.Fatalf("unexpected redirectTo: %v", got["redirectTo"]) } if gotSubject != "kratos-123" { t.Fatalf("unexpected subject: %v", gotSubject) } if gotChallenge != "challenge-123" { t.Fatalf("unexpected login_challenge: %v", gotChallenge) } } func TestAcceptOidcLoginRequest_TokenFallbackToCookie(t *testing.T) { var gotSubject string transport := roundTripFunc(func(r *http.Request) (*http.Response, error) { switch r.URL.Host { case "kratos.test": if r.URL.Path != "/sessions/whoami" { return httpResponse(r, http.StatusNotFound, "not found"), nil } if r.Header.Get("X-Session-Token") != "" { return httpResponse(r, http.StatusUnauthorized, "invalid token"), nil } if r.Header.Get("Cookie") == "" { return httpResponse(r, http.StatusUnauthorized, "missing cookie"), nil } return httpJSONAny(r, http.StatusOK, map[string]any{ "identity": map[string]any{ "id": "kratos-456", "traits": map[string]any{}, }, }), nil case "hydra.test": if r.URL.Path != "/oauth2/auth/requests/login/accept" { return httpResponse(r, http.StatusNotFound, "not found"), nil } body, _ := io.ReadAll(r.Body) var payload map[string]any _ = json.Unmarshal(body, &payload) if subject, ok := payload["subject"].(string); ok { gotSubject = subject } return httpResponse(r, http.StatusOK, `{"redirect_to":"http://rp/cb"}`), nil default: return httpResponse(r, http.StatusNotFound, "not found"), nil } }) client := &http.Client{Transport: transport} origDefault := http.DefaultClient http.DefaultClient = client defer func() { http.DefaultClient = origDefault }() t.Setenv("KRATOS_PUBLIC_URL", "http://kratos.test") h := &AuthHandler{ Hydra: &service.HydraAdminService{ AdminURL: "http://hydra.test", HTTPClient: client, }, } app := newOidcLoginTestApp(h) body, _ := json.Marshal(map[string]string{ "login_challenge": "challenge-456", }) req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/oidc/login/accept", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer invalid-token") req.Header.Set("Cookie", "ory_kratos_session=def456") resp, err := app.Test(req) if err != nil { t.Fatalf("request failed: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("expected 200, got %d", resp.StatusCode) } if gotSubject != "kratos-456" { t.Fatalf("unexpected subject: %v", gotSubject) } }