Files
upstream-ory-kratos/x/nosurfx/nosurf_test.go
upstream-mirror c60cc7ca75
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
2026-09-15 21:19:12 +05:00

190 lines
7.2 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package nosurfx_test
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"regexp"
"strings"
"testing"
"github.com/ory/kratos/x/nosurfx"
"github.com/tidwall/gjson"
"github.com/ory/x/assertx"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/pkg"
"github.com/ory/nosurf"
"github.com/ory/x/randx"
)
func TestNosurfBaseCookieHandler(t *testing.T) {
ctx := context.Background()
conf, reg := pkg.NewFastRegistryWithMocks(t)
require.NoError(t, conf.Set(ctx, config.ViperKeyPublicBaseURL, "http://foo.com/bar"))
cookie := nosurfx.NosurfBaseCookieHandler(reg)(httptest.NewRecorder(), httptest.NewRequest("GET", "https://foo/bar", nil))
assert.EqualValues(t, "csrf_token_01c86631efd1537ee34a98e75884a6e21dd8e2d9e944934bca21204106bfd32f", cookie.Name, "base64 representation of http://foo.com/bar")
assert.EqualValues(t, http.SameSiteLaxMode, cookie.SameSite, "is set to lax because https/secure is false - chrome rejects none samesite on non-https")
assert.EqualValues(t, nosurf.MaxAge, cookie.MaxAge)
assert.EqualValues(t, "/", cookie.Path, "cookie path is site root by default")
assert.EqualValues(t, "", cookie.Domain, "domain for the cookie is set to empty per default")
assert.False(t, cookie.Secure, "false because insecure dev mode")
assert.True(t, cookie.HttpOnly)
alNum := regexp.MustCompile("[a-zA-Z_0-9]+")
for i := 0; i < 10; i++ {
require.NoError(t, conf.Set(ctx, config.ViperKeyPublicBaseURL, randx.MustString(16, randx.AlphaNum)))
cookie := nosurfx.NosurfBaseCookieHandler(reg)(httptest.NewRecorder(), httptest.NewRequest("GET", "https://foo/bar", nil))
assert.NotEqual(t, "aHR0cDovL2Zvby5jb20vYmFy_csrf_token", cookie.Name, "should no longer be http://foo.com/bar")
assert.True(t, alNum.MatchString(cookie.Name), "does not have any special chars")
}
require.NoError(t, conf.Set(ctx, config.ViperKeyCookieSameSite, "None"))
require.NoError(t, conf.Set(ctx, "dev", false))
cookie = nosurfx.NosurfBaseCookieHandler(reg)(httptest.NewRecorder(), httptest.NewRequest("GET", "https://foo/bar", nil))
assert.EqualValues(t, http.SameSiteNoneMode, cookie.SameSite, "can be none because https/secure is true")
assert.True(t, cookie.Secure, "true because secure mode")
assert.True(t, cookie.HttpOnly)
}
func TestNosurfBaseCookieHandlerAliasing(t *testing.T) {
ctx := context.Background()
conf, reg := pkg.NewFastRegistryWithMocks(t)
require.NoError(t, conf.Set(ctx, config.ViperKeyPublicBaseURL, "http://foo.com/bar"))
cookie := nosurfx.NosurfBaseCookieHandler(reg)(httptest.NewRecorder(), httptest.NewRequest("GET", "http://foo.com/bar", nil))
assert.EqualValues(t, "", cookie.Domain, "remains unset")
assert.EqualValues(t, "/", cookie.Path, "cookie path is site root by default")
// Check root settings
require.NoError(t, conf.Set(ctx, config.ViperKeyCookieDomain, "bar.com"))
require.NoError(t, conf.Set(ctx, config.ViperKeyCookiePath, "/baz"))
cookie = nosurfx.NosurfBaseCookieHandler(reg)(httptest.NewRecorder(), httptest.NewRequest("GET", "http://foo.com/bar", nil))
assert.EqualValues(t, "bar.com", cookie.Domain, "domain doesn't change when request not from an alias but is overwritten by ViperKeyCookieDomain")
assert.EqualValues(t, "/baz", cookie.Path, "cookie path is site root by default but is overwritten by ViperKeyCookiePath")
}
func TestNosurfBaseCookieErrorHandler(t *testing.T) {
_, reg := pkg.NewFastRegistryWithMocks(t)
h := nosurfx.CSRFFailureHandler(reg)
expectError := func(t *testing.T, err error, req *http.Request) {
t.Helper()
rec := httptest.NewRecorder()
h(rec, req)
assertx.EqualAsJSON(t, err, json.RawMessage(gjson.Get(rec.Body.String(), "error").Raw))
}
newAjaxFormRequest := func() *http.Request {
req := httptest.NewRequest("GET", "https://foo/bar", nil)
req.Header.Set("Sec-Fetch-Mode", "cors")
return req
}
newAjaxJSONRequest := func() *http.Request {
req := httptest.NewRequest("GET", "https://foo/bar", nil)
req.Header.Set("Sec-Fetch-Mode", "cors")
req.Header.Set("Content-Type", "application/json")
return req
}
newBrowserRequest := func() *http.Request {
req := httptest.NewRequest("GET", "https://foo/bar", nil)
return req
}
t.Run("case=without cookie", func(t *testing.T) {
t.Run("source=ajax", func(t *testing.T) {
expectError(t, nosurfx.ErrInvalidCSRFTokenAJAXNoCookies(), newAjaxFormRequest())
})
t.Run("source=browser", func(t *testing.T) {
expectError(t, nosurfx.ErrInvalidCSRFTokenServerNoCookies(), newBrowserRequest())
})
})
t.Run("case=ajax with cookie but without csrf cookie", func(t *testing.T) {
test := func(t *testing.T, req *http.Request, err error) {
req.Header.Set("Cookie", "foo=bar;")
expectError(t, err, req)
}
t.Run("source=ajax", func(t *testing.T) {
test(t, newAjaxFormRequest(), nosurfx.ErrInvalidCSRFTokenAJAXCookieMissing())
})
t.Run("source=browser", func(t *testing.T) {
test(t, newBrowserRequest(), nosurfx.ErrInvalidCSRFTokenServerCookieMissing())
})
})
t.Run("case=with correct cookie but token was not sent in header", func(t *testing.T) {
test := func(t *testing.T, req *http.Request, err error) {
req.Header.Set("Cookie", nosurfx.CSRFCookieName(req.Context(), reg)+"=bar;")
expectError(t, err, req)
}
t.Run("source=ajax", func(t *testing.T) {
test(t, newAjaxFormRequest(), nosurfx.ErrInvalidCSRFTokenAJAXTokenNotSent())
})
t.Run("source=ajax json", func(t *testing.T) {
test(t, newAjaxJSONRequest(), nosurfx.ErrInvalidCSRFTokenAJAXTokenMismatch())
})
t.Run("source=browser", func(t *testing.T) {
test(t, newBrowserRequest(), nosurfx.ErrInvalidCSRFTokenServerTokenNotSent())
})
})
t.Run("case=ajax with correct cookie and token in header but they do not match", func(t *testing.T) {
test := func(t *testing.T, req *http.Request, err error) {
req.Header.Set(nosurf.HeaderName, "bar")
req.Header.Set("Cookie", nosurfx.CSRFCookieName(req.Context(), reg)+"=bar;")
expectError(t, err, req)
}
t.Run("source=ajax", func(t *testing.T) {
test(t, newAjaxFormRequest(), nosurfx.ErrInvalidCSRFTokenAJAXTokenMismatch())
})
t.Run("source=browser", func(t *testing.T) {
test(t, newBrowserRequest(), nosurfx.ErrInvalidCSRFTokenServerTokenMismatch())
})
})
t.Run("case=correct cookie and token in body but they do not match", func(t *testing.T) {
test := func(t *testing.T, req *http.Request, err error) {
req.Header.Set("Accept", "application/x-www-form-urlencoded")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Cookie", nosurfx.CSRFCookieName(req.Context(), reg)+"=bar;")
require.NoError(t, req.ParseForm())
expectError(t, err, req)
}
t.Run("source=ajax", func(t *testing.T) {
req := httptest.NewRequest("POST", "https://foo/bar", strings.NewReader(url.Values{nosurf.FormFieldName: {"bar"}}.Encode()))
req.Header.Set("Sec-Fetch-Mode", "cors")
test(t, req, nosurfx.ErrInvalidCSRFTokenAJAXTokenMismatch())
})
t.Run("source=browser", func(t *testing.T) {
req := httptest.NewRequest("POST", "https://foo/bar", strings.NewReader(url.Values{nosurf.FormFieldName: {"bar"}}.Encode()))
test(t, req, nosurfx.ErrInvalidCSRFTokenServerTokenMismatch())
})
})
}