snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
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
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
This commit is contained in:
312
x/nosurfx/nosurf.go
Normal file
312
x/nosurfx/nosurf.go
Normal file
@@ -0,0 +1,312 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package nosurfx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ory/x/httpx"
|
||||
"github.com/ory/x/logrusx"
|
||||
|
||||
"github.com/ory/kratos/text"
|
||||
|
||||
"github.com/ory/kratos/driver/config"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ory/herodot"
|
||||
"github.com/ory/nosurf"
|
||||
"github.com/ory/x/randx"
|
||||
)
|
||||
|
||||
func ErrInvalidCSRFToken() *herodot.DefaultError {
|
||||
return herodot.ErrForbidden().
|
||||
WithID(text.ErrIDCSRF).
|
||||
WithError("the request was rejected to protect you from Cross-Site-Request-Forgery").
|
||||
WithDetail("docs", "https://www.ory.com/docs/troubleshooting/csrf").
|
||||
WithReason("Please retry the flow and optionally clear your cookies. The request was rejected to protect you from Cross-Site-Request-Forgery (CSRF) which could cause account takeover, leaking personal information, and other serious security issues.")
|
||||
}
|
||||
|
||||
func ErrGone() *herodot.DefaultError {
|
||||
return &herodot.DefaultError{
|
||||
CodeField: http.StatusGone,
|
||||
StatusField: http.StatusText(http.StatusGone),
|
||||
ErrorField: "The requested resource is no longer available because it has expired or is otherwise invalid.",
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
noCookie = "The HTTP Cookie Header is empty or not set."
|
||||
cookieMissing = "The HTTP Cookie Header was set but did not include the anti-CSRF cookie."
|
||||
tokenNotSent = "The anti-CSRF cookie was found but the CSRF token was not included in the HTTP request body (" + nosurf.CookieName + ") nor in the HTTP Header (" + nosurf.HeaderName + ")."
|
||||
tokenMismatch = "The HTTP Cookie Header was set and a CSRF token was sent but they do not match. We recommend deleting all cookies for this domain and retrying the flow."
|
||||
)
|
||||
|
||||
func ErrInvalidCSRFTokenAJAX() *herodot.DefaultError {
|
||||
return ErrInvalidCSRFToken().
|
||||
WithDetail("hint", "We detected an AJAX call, please ensure that CORS is enabled and configured correctly, and that your AJAX code sends cookies and has credentials enabled. For further debugging, check your Browser's Network Tab to see what cookies are included or excluded.")
|
||||
}
|
||||
|
||||
func ErrInvalidCSRFTokenAJAXNoCookies() *herodot.DefaultError {
|
||||
return ErrInvalidCSRFTokenAJAX().WithDetail("reject_reason", noCookie)
|
||||
}
|
||||
|
||||
func ErrInvalidCSRFTokenAJAXCookieMissing() *herodot.DefaultError {
|
||||
return ErrInvalidCSRFTokenAJAX().WithDetail("reject_reason", cookieMissing)
|
||||
}
|
||||
|
||||
func ErrInvalidCSRFTokenAJAXTokenNotSent() *herodot.DefaultError {
|
||||
return ErrInvalidCSRFToken().WithDetail("hint", tokenNotSent)
|
||||
}
|
||||
|
||||
func ErrInvalidCSRFTokenAJAXTokenMismatch() *herodot.DefaultError {
|
||||
return ErrInvalidCSRFTokenAJAX().WithDetail("reject_reason", tokenMismatch)
|
||||
}
|
||||
|
||||
func ErrInvalidCSRFTokenServer() *herodot.DefaultError {
|
||||
return ErrInvalidCSRFToken().
|
||||
WithDetail("hint", "We detected a regular browser or server-side call. To debug browser calls check your Browser's Network Tab to see what cookies are included or excluded. If you are calling from a server ensure that the appropriate cookies are being forwarded and that the SDK method is called correctly.")
|
||||
}
|
||||
|
||||
func ErrInvalidCSRFTokenServerNoCookies() *herodot.DefaultError {
|
||||
return ErrInvalidCSRFTokenServer().WithDetail("reject_reason", noCookie)
|
||||
}
|
||||
|
||||
func ErrInvalidCSRFTokenServerCookieMissing() *herodot.DefaultError {
|
||||
return ErrInvalidCSRFTokenServer().WithDetail("reject_reason", cookieMissing)
|
||||
}
|
||||
|
||||
func ErrInvalidCSRFTokenServerTokenNotSent() *herodot.DefaultError {
|
||||
return ErrInvalidCSRFToken().WithDetail("hint", tokenNotSent)
|
||||
}
|
||||
|
||||
func ErrInvalidCSRFTokenServerTokenMismatch() *herodot.DefaultError {
|
||||
return ErrInvalidCSRFTokenServer().WithDetail("reject_reason", tokenMismatch)
|
||||
}
|
||||
|
||||
type CSRFTokenGeneratorProvider interface {
|
||||
GenerateCSRFToken(r *http.Request) string
|
||||
}
|
||||
|
||||
type CSRFToken func(r *http.Request) string
|
||||
|
||||
const CSRFTokenName = "csrf_token"
|
||||
|
||||
func DefaultCSRFToken(r *http.Request) string {
|
||||
return nosurf.Token(r)
|
||||
}
|
||||
|
||||
var FakeCSRFToken = base64.StdEncoding.EncodeToString([]byte(randx.MustString(32, randx.AlphaLowerNum)))
|
||||
|
||||
func FakeCSRFTokenGenerator(r *http.Request) string {
|
||||
return FakeCSRFToken
|
||||
}
|
||||
|
||||
func FakeCSRFTokenGeneratorWithToken(token string) func(r *http.Request) string {
|
||||
return func(r *http.Request) string {
|
||||
return token
|
||||
}
|
||||
}
|
||||
|
||||
var _ nosurf.Handler = new(FakeCSRFHandler)
|
||||
|
||||
type FakeCSRFHandler struct {
|
||||
cookieNameFn func(r *http.Request) string
|
||||
}
|
||||
|
||||
func NewFakeCSRFHandler(reg interface {
|
||||
config.Provider
|
||||
},
|
||||
) *FakeCSRFHandler {
|
||||
return &FakeCSRFHandler{
|
||||
cookieNameFn: func(r *http.Request) string {
|
||||
return CSRFCookieName(r.Context(), reg)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeCSRFHandler) DisablePath(s string) {
|
||||
}
|
||||
|
||||
func (f *FakeCSRFHandler) DisableGlob(s string) {
|
||||
}
|
||||
|
||||
func (f *FakeCSRFHandler) DisableGlobs(s ...string) {
|
||||
}
|
||||
|
||||
func (f *FakeCSRFHandler) ExemptPath(s string) {
|
||||
}
|
||||
|
||||
func (f *FakeCSRFHandler) IgnorePath(s string) {
|
||||
}
|
||||
|
||||
func (f *FakeCSRFHandler) IgnoreGlob(s string) {}
|
||||
|
||||
func (f *FakeCSRFHandler) IgnoreGlobs(s ...string) {}
|
||||
|
||||
func (f *FakeCSRFHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {}
|
||||
|
||||
func (f *FakeCSRFHandler) RegenerateToken(_ http.ResponseWriter, _ *http.Request) string {
|
||||
return FakeCSRFToken
|
||||
}
|
||||
|
||||
func WithFakeCSRFCookie(t *testing.T, reg interface {
|
||||
config.Provider
|
||||
}, urlStr string,
|
||||
) (*url.URL, []*http.Cookie) {
|
||||
u, err := url.Parse(urlStr)
|
||||
require.NoError(t, err)
|
||||
|
||||
cookie := &http.Cookie{
|
||||
Name: CSRFCookieName(t.Context(), reg),
|
||||
Value: FakeCSRFToken,
|
||||
}
|
||||
return u, []*http.Cookie{cookie}
|
||||
}
|
||||
|
||||
type CSRFProvider interface {
|
||||
CSRFHandler() nosurf.Handler
|
||||
}
|
||||
|
||||
func CSRFCookieName(ctx context.Context, reg interface {
|
||||
config.Provider
|
||||
},
|
||||
) string {
|
||||
return "csrf_token_" + fmt.Sprintf("%x", sha256.Sum256([]byte(reg.Config().SelfPublicURL(ctx).String())))
|
||||
}
|
||||
|
||||
func NosurfBaseCookieHandler(reg interface {
|
||||
config.Provider
|
||||
},
|
||||
) func(w http.ResponseWriter, r *http.Request) http.Cookie {
|
||||
return func(w http.ResponseWriter, r *http.Request) http.Cookie {
|
||||
secure := reg.Config().CookieSecure(r.Context())
|
||||
|
||||
sameSite := reg.Config().CookieSameSiteMode(r.Context())
|
||||
if !secure {
|
||||
sameSite = http.SameSiteLaxMode
|
||||
}
|
||||
|
||||
domain := ""
|
||||
if d := reg.Config().CookieDomain(r.Context()); d != "" {
|
||||
domain = d
|
||||
}
|
||||
|
||||
name := CSRFCookieName(r.Context(), reg)
|
||||
cookie := http.Cookie{
|
||||
Name: name,
|
||||
MaxAge: nosurf.MaxAge,
|
||||
Path: reg.Config().CookiePath(r.Context()),
|
||||
Domain: domain,
|
||||
HttpOnly: true,
|
||||
Secure: secure,
|
||||
SameSite: sameSite,
|
||||
}
|
||||
|
||||
if alias := reg.Config().SelfPublicURL(r.Context()); reg.Config().SelfPublicURL(r.Context()).String() != alias.String() {
|
||||
// If a domain alias is detected use that instead.
|
||||
cookie.Domain = alias.Hostname()
|
||||
cookie.Path = alias.Path
|
||||
}
|
||||
|
||||
return cookie
|
||||
}
|
||||
}
|
||||
|
||||
func CSRFErrorReason(r *http.Request, reg interface {
|
||||
config.Provider
|
||||
},
|
||||
) error {
|
||||
// Is it an AJAX request?
|
||||
secFetchMode := r.Header.Get("Sec-Fetch-Mode")
|
||||
isAjax := secFetchMode == "cors" || secFetchMode == "same-origin"
|
||||
|
||||
if len(r.Header.Get("Cookie")) == 0 {
|
||||
if isAjax {
|
||||
return errors.WithStack(ErrInvalidCSRFTokenAJAXNoCookies())
|
||||
}
|
||||
return errors.WithStack(ErrInvalidCSRFTokenServerNoCookies())
|
||||
} else if _, err := r.Cookie(CSRFCookieName(r.Context(), reg)); errors.Is(err, http.ErrNoCookie) {
|
||||
if isAjax {
|
||||
return errors.WithStack(ErrInvalidCSRFTokenAJAXCookieMissing())
|
||||
}
|
||||
return errors.WithStack(ErrInvalidCSRFTokenServerCookieMissing())
|
||||
}
|
||||
|
||||
// We can't parse the request body here, so we check if the content type is JSON,
|
||||
// as we can't return a meaningful error in that case.
|
||||
if !strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") {
|
||||
if len(r.Form.Get("csrf_token")+r.Header.Get(nosurf.HeaderName)) == 0 {
|
||||
if isAjax {
|
||||
return errors.WithStack(ErrInvalidCSRFTokenAJAXTokenNotSent())
|
||||
}
|
||||
return errors.WithStack(ErrInvalidCSRFTokenServerTokenNotSent())
|
||||
}
|
||||
}
|
||||
|
||||
if isAjax {
|
||||
return errors.WithStack(ErrInvalidCSRFTokenAJAXTokenMismatch())
|
||||
}
|
||||
return errors.WithStack(ErrInvalidCSRFTokenServerTokenMismatch())
|
||||
}
|
||||
|
||||
func CSRFFailureHandler(reg interface {
|
||||
config.Provider
|
||||
logrusx.Provider
|
||||
httpx.WriterProvider
|
||||
},
|
||||
) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
err := CSRFErrorReason(r, reg)
|
||||
reg.Logger().
|
||||
WithError(err).
|
||||
WithField("result", nosurf.VerifyToken(nosurf.Token(r), r.Form.Get("csrf_token"))).
|
||||
WithField("expected_token", nosurf.Token(r)).
|
||||
WithField("received_cookies", r.Cookies()).
|
||||
WithField("received_token_form", r.Form.Get(nosurf.FormFieldName)).
|
||||
WithField("received_token_body", r.PostForm.Get(nosurf.FormFieldName)).
|
||||
WithField("received_token_header", r.Header.Get(nosurf.HeaderName)).
|
||||
Warn("A request failed due to a missing or invalid csrf_token value")
|
||||
|
||||
reg.Writer().WriteError(w, r, err)
|
||||
}
|
||||
}
|
||||
|
||||
func NewCSRFHandler(
|
||||
router http.Handler,
|
||||
reg interface {
|
||||
config.Provider
|
||||
logrusx.Provider
|
||||
httpx.WriterProvider
|
||||
},
|
||||
) *nosurf.CSRFHandler {
|
||||
n := nosurf.New(router)
|
||||
|
||||
n.SetBaseCookieFunc(NosurfBaseCookieHandler(reg))
|
||||
n.SetFailureHandler(CSRFFailureHandler(reg))
|
||||
return n
|
||||
}
|
||||
|
||||
func NewTestCSRFHandler(router http.Handler, reg interface {
|
||||
WithCSRFHandler(handler nosurf.Handler)
|
||||
WithCSRFTokenGenerator(CSRFToken)
|
||||
httpx.WriterProvider
|
||||
logrusx.Provider
|
||||
config.Provider
|
||||
},
|
||||
) *nosurf.CSRFHandler {
|
||||
n := NewCSRFHandler(router, reg)
|
||||
reg.WithCSRFHandler(n)
|
||||
reg.WithCSRFTokenGenerator(nosurf.Token)
|
||||
return n
|
||||
}
|
||||
189
x/nosurfx/nosurf_test.go
Normal file
189
x/nosurfx/nosurf_test.go
Normal file
@@ -0,0 +1,189 @@
|
||||
// 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())
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user