package webui import ( "io" "net/http" "net/http/cookiejar" "net/http/httptest" "net/url" "regexp" "strings" "testing" ) var csrfTokenInPage = regexp.MustCompile(`window\.__csrfToken\s*=\s*"([0-9a-f]+)"`) // TestCSRFProtectionAppliesAcrossAdminAndWebmail is a live-HTTP test (real // httptest.NewServer wrapped exactly like main.go composes it — // SecurityHeaders(app.CSRFProtect(mux)) — not just httptest.NewRecorder against the // bare mux) confirming: a forged/missing CSRF token on a state-changing POST is // rejected for BOTH an admin route and a webmail route, a real page-driven // submission (token scraped from the actual rendered page, exactly as the injected // csrf_script.html partial would hand it to a real form) succeeds, and every // response carries the new security headers. func TestCSRFProtectionAppliesAcrossAdminAndWebmail(t *testing.T) { app := newTestApp(t) srv := httptest.NewServer(SecurityHeaders(app.CSRFProtect(app.Mux()))) defer srv.Close() // Security headers present on a plain unauthenticated GET too. headResp, err := http.Get(srv.URL + Prefix + "/login") if err != nil { t.Fatal(err) } headResp.Body.Close() if headResp.Header.Get("X-Frame-Options") != "SAMEORIGIN" { t.Fatalf("expected X-Frame-Options on every response, got headers: %v", headResp.Header) } if headResp.Header.Get("Content-Security-Policy") == "" { t.Fatal("expected a Content-Security-Policy header") } adminCookie := loginSession(t, app) domains, _ := app.DB.ListDomains() mailboxID := createTestMailboxWithPassword(t, app, "csrf-mailbox@example.com", domains[0].ID, "csrf-password-1!") mailboxCookie := webmailLoginSession(t, app, mailboxID) jarClient := func(cookie *http.Cookie) *http.Client { jar, _ := cookiejar.New(nil) u, _ := url.Parse(srv.URL) jar.SetCookies(u, []*http.Cookie{cookie}) return &http.Client{Jar: jar} } // Regression check: the compose popup (webmail_compose_widget.html) loads // /webmail/mail/compose in a same-origin