package connect import ( "net/http" "net/http/httptest" "net/url" "strings" "testing" ) func oauthTarget() string { return "https://keymail.example/oauth/authorize?" + url.Values{"client_id": {configOrigin}, "redirect_uri": {configOrigin + "/auth/callback"}, "scope": {"identify"}, "code_challenge_method": {"S256"}, "state": {"private-state"}, "code_challenge": {"challenge"}}.Encode() } func TestOAuthContinuationPreservesPolicyCookiesAndLocalResponses(t *testing.T) { for _, destination := range []string{oauthTarget(), "/signin?sent=1"} { w := httptest.NewRecorder() w.Header().Set("Content-Security-Policy", "form-action 'self'") rendered := "" h := OAuthContinuation(configOrigin, func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Set-Cookie", "pending=private; Path=/; Secure; HttpOnly") http.Redirect(w, r, destination, 303) }, func(w http.ResponseWriter, r *http.Request, target string) { rendered = target; w.WriteHeader(200) }) h(w, httptest.NewRequest("POST", configOrigin+"/signin", nil)) if w.Header().Get("Content-Security-Policy") != "form-action 'self'" || len(w.Result().Cookies()) != 1 { t.Fatal("policy or pending cookie changed") } if strings.HasPrefix(destination, "https:") { if w.Code != 200 || rendered != destination || w.Header().Get("Location") != "" || w.Header().Get("Cache-Control") != "no-store" || w.Header().Get("Referrer-Policy") != "no-referrer" { t.Fatal("OAuth POST did not terminate", w.Code, w.Header()) } } else if w.Code != 303 || rendered != "" || w.Header().Get("Location") != destination { t.Fatal("local flow changed") } } } func TestOAuthContinuationRefusesUnboundDestinationsWithoutLeakingState(t *testing.T) { for _, destination := range []string{"//keymail.example/oauth/authorize", strings.Replace(oauthTarget(), "https:", "http:", 1), strings.Replace(oauthTarget(), "scope=identify", "scope=other", 1), oauthTarget() + "&client_id=other", oauthTarget() + "#fragment", "https://user@keymail.example/oauth/authorize", "https://keymail.example/other", strings.Replace(oauthTarget(), "S256", "plain", 1)} { w := httptest.NewRecorder() h := OAuthContinuation(configOrigin, func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Set-Cookie", "private-cookie=private") http.Redirect(w, r, destination, 303) }, func(http.ResponseWriter, *http.Request, string) { t.Fatal("invalid redirect rendered") }) h(w, httptest.NewRequest("POST", configOrigin+"/signin", nil)) if w.Code != 502 || w.Header().Get("Location") != "" || len(w.Result().Cookies()) != 0 || strings.Contains(w.Body.String(), "private") { t.Fatal("unsafe destination accepted or leaked", w.Code) } } }